Projects
Essentials
pipewire-aptx
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 29
View file
pipewire-aptx.changes
Changed
@@ -1,4 +1,9 @@ ------------------------------------------------------------------- +Thu Jun 29 10:47:58 UTC 2023 - Bjørn Lie <zaitor@opensuse.org> + +- Update to version 0.3.72 + +------------------------------------------------------------------- Sat May 20 12:08:17 UTC 2023 - Bjørn Lie <zaitor@opensuse.org> - Update to version 0.3.71
View file
pipewire-aptx.spec
Changed
@@ -7,7 +7,7 @@ %define soversion 0_2 Name: pipewire-aptx -Version: 0.3.71 +Version: 0.3.72 Release: 0 Summary: PipeWire Bluetooth aptX codec plugin License: MIT
View file
pipewire-0.3.71.tar.gz/.gitlab-ci.yml -> pipewire-0.3.72.tar.gz/.gitlab-ci.yml
Changed
@@ -25,7 +25,7 @@ .fedora: variables: # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2023-04-18.0' + FDO_DISTRIBUTION_TAG: '2023-05-31.0' FDO_DISTRIBUTION_VERSION: '37' FDO_DISTRIBUTION_PACKAGES: >- alsa-lib-devel @@ -46,6 +46,7 @@ jack-audio-connection-kit-devel libasan libcanberra-devel + libffado-devel libldac-devel libmysofa-devel libsndfile-devel @@ -304,6 +305,8 @@ -Droc=disabled -Dlibcamera=disabled -Dsession-managers= + -Dc_args='-UFASTPATH' + -Dcpp_args='-UFASTPATH' parallel: matrix: - CC: gcc, clang
View file
pipewire-0.3.71.tar.gz/NEWS -> pipewire-0.3.72.tar.gz/NEWS
Changed
@@ -1,3 +1,109 @@ +# PipeWire 0.3.72 (2023-06-26) + +This is a bugfix release that is API and ABI compatible with previous +0.3.x releases. + +## Highlights + - Fix a critical bug that would refuse to update the samplerate or + buffersize in JACK clients. (#3226) + - A new module-netjack2-driver and module-netjack2-manager were added + that are compatible with NETJACK2. This allows PipeWire to become + a NETJACK2 manager or a driver between JACK2 or PipeWire servers. + - Support was added for firewire devices with FFADO. This is untested + for now and MIDI is not implemented yet. + - The node scheduling was optimized some more. External drivers are now + as efficient as in-server ones. This should improve performance of + various drivers such as bluetooth and JACK based drivers. + - Many, many bug fixes and a ton of improvements. + + +## PipeWire + - pw-filter can now be used to write sinks and sources. + - The node activation for drivers was changed. The driver now does not + need to go to the server to start the processing cycle. This makes + out-of-server drivers as efficient as in-server drivers. + - Don't try to use drivers with 0 priority as fallback drivers. This + avoids making the screencast driver a driver for audio. (#3219) + - Improve xrun count reporting in pw-top and the profiler. Now each + node has their own xrun counter updated when it fails to complete + processing during the cycle. + - pw-filter now also has support for TRIGGER. + - A potential fd leak was found when fds were send to a zombie client. + (#1840) + - Fix a bug where monitor or capture streams were logged twice in the + profiler. (#3278) + - Remove stream hooks safely. (#3251) + - A bug in serialization of container properties was fixed. This could + result in truncated property values. (#3290) + - The PIPEWIRE_AUTOCONNECT environment variable now always overrides the + autoconnect settings of streams. (#3299) + - Node, port and link destroy now avoids some useless work. + - Port will now try to renegotiate a new format when idle. (#3266) + +## Modules + - The module-sap now is more compatible with AES67. + - A new FFADO driver module was added. This is completely untested because + of lack of hardware. Please test and report issues. + - A new NETJACK2 driver and a NETJACK2 manager module were added. These + should be drop in replacements for the JACK2 parts. + - The RAOP discover module now tries harder to only list devices once. + - The zeroconf discover module now tries harder to only list devices once. + - The RAOP sink module now handles latency better and is compatible with + some more devices. (#3247, #3282) + - The loopback and filter-chain modules now always dequeue the last input + buffer to avoid stuttering in some cases. (#3276) + - The SPA node factory module can now also export nodes. This is used to + export the PTP clock from the AES67 config file. + - A bug in module-jack-tunnel was fixed that would cause stuttering and + corrupted output in some cases. (#3255) + - The resampler is now disabled in module-loopback and filter-chain when + the samplerate is set to follow the graph rate. (#2969) + - The way the mixer peer is sent to clients was improved. It is now also + possible to let a remote node know about mixer port removes, which + can avoid memory leaks and some code simplifications. + +## SPA + - Monitor ports now report latency correctly. + - The ALSA plugin now uses htimestamp to get a more accurate ringbuffer + position to estimate the clock skew. + - The channelmixer now has min/max-volume settings to limit or fix the + volume. + - The ALSA plugin can now control the playback and capture rate of USB + gadgets. This can avoid resampling and instead use the USB feedback + to control the rate. + - The ALSA output to multiple devices has been improved, some lockups + are avoided when the device ringbuffer is full. + - The compress-offload sink has improved negotiation. + +## pulse-server + - Only try to use GSettings when the schema exists. + - @DEFAULT_SOURCE@, @DEFAULT_SINK@ and @DEFAULT_MONITOR@ are now correctly + handled as targets in playback and capture streams. (#3284) + - 2 new quirks are added to disable volume updates on sinks/sources. + (#1517) + - The virtual-sink and virtual-source modules were added. These are really + example modules but actually also work and are useful on PulseAudio so + implement them as well. + - Fix initial stream volumes. (#3306) + +## Bluetooth + - Only register A2DP or BAP when we have codecs. + - Include codec into the media.name + +## JACK + - Fix a critical bug that would refuse to update the samplerate or + buffersize. (#3226) + - Improve updates of samplerate/buffersize, delay the updates until the + client is activated. (#3297) + - Use the new mix-info updates to simplify the mixer setup and peer + detection. + +## GStreamer + - Fill default strides instead of 0 on pipewire video buffers. (#3236) + +Older versions: + + # PipeWire 0.3.71 (2023-05-17) This is a bugfix release that is API and ABI compatible with previous @@ -140,9 +246,6 @@ - Document the SPA Pod serialization. - Document the PipeWire native protocol. - -Older versions: - # PipeWire 0.3.70 (2023-04-20) This is a quick bugfix release that is API and ABI compatible with previous
View file
pipewire-0.3.71.tar.gz/doc/pipewire-modules.dox -> pipewire-0.3.72.tar.gz/doc/pipewire-modules.dox
Changed
@@ -59,12 +59,15 @@ - \subpage page_module_example_sink - \subpage page_module_example_source - \subpage page_module_fallback_sink +- \subpage page_module_ffado_driver - \subpage page_module_filter_chain - \subpage page_module_jackdbus_detect - \subpage page_module_jack_tunnel - \subpage page_module_link_factory - \subpage page_module_loopback - \subpage page_module_metadata +- \subpage page_module_netjack2_driver +- \subpage page_module_netjack2_manager - \subpage page_module_pipe_tunnel - \subpage page_module_portal - \subpage page_module_profiler
View file
pipewire-0.3.71.tar.gz/doc/pipewire-protocol.dox -> pipewire-0.3.72.tar.gz/doc/pipewire-protocol.dox
Changed
@@ -46,6 +46,10 @@ message than can fit in one message, the message is split into multiple parts. +A receiver needs to first read the header to know the size of the +complete message. After collecting the complete message, it can start +processing it. + The payload is a single POD see \ref page_spa_pod for details. After the payload, there is an optional footer POD object. @@ -61,14 +65,28 @@ The client opens the socket and allocates a Core proxy with id 0 and a Client -proxy with id 1. +proxy with id 1. The proxy is nothing more than a way to identify the remote +objects with an id. Usually they also contain the type and version of the +remote object and some helpers (interfaces) to dispatch messages. -The server will allocate a new Core resource with id 0. +The server will allocate a new Core resource with id 0. Resources are the +counterpart of client proxies and also typically contain extra information +and helpers (interfaces) to dispatch messages. The client sends the Core::Hello message on the socket to start the communication. -The server binds the client resource with id 1 to the client. +The server binds the client resource with id 1 to the client. This means +that the client object on the server will now have a resource with a +client side proxy associated with it. + +When the client sends a message for a certain proxy, the server side +resource with the same id for the client is found and the message is +dispatched to the object associated with the resource. + +Similarly when a server sends a message on a resource, the client will +find the proxy with the same id and dispatch the message to it's +associated object. The client then sends client properties to the server. @@ -1235,7 +1253,8 @@ ) ``` - readfd: the eventfd to start processing -- writefd: the eventfd to signal driver start +- writefd: the eventfd to signal when the driver completes and profiling is + enabled. - memfd: the index of the memfd of the activation record - offset: the offset in memfd of the start of the activation record - size: the size of the activation record @@ -1243,6 +1262,11 @@ The activation record is currently an internal data structure that is not yet ABI stable. +The writefd is meant to wake up the server after the driver completes so +that the profiler can collect the information. The profiler is active +when the pw_node_activation::flags fields has PW_NODE_ACTIVATION_FLAG_PROFILER +set. When the profiler is disabled (or when the node is not driving), this +eventfd should not be signaled. ### ClientNode::SetParam (Opcode 1)
View file
pipewire-0.3.71.tar.gz/doc/pipewire-scheduling.dox -> pipewire-0.3.72.tar.gz/doc/pipewire-scheduling.dox
Changed
@@ -17,7 +17,7 @@ - an activation record that lives in shared memory with memfd. ``` - evenfd + eventfd +-^---------+ | | in out @@ -42,7 +42,7 @@ - Information about repositions (seek) and timebase owners. -# links. +# Links When two nodes are linked together, the output node becomes a dependency for the input node. This means the input node can only start processing when the output node is finished. @@ -57,7 +57,7 @@ ``` - evenfd eventfd + eventfd eventfd +-^---------+ +-^---------+ | | link | | in A out ---------------------> in B out @@ -92,7 +92,7 @@ ``` - evenfd eventfd + eventfd eventfd +-^---------+ +-^---------+ | | link | | in A out ---------------------> in B out @@ -108,11 +108,11 @@ | | / / | | / / | | / / - | | / / + | | / / v | /-------------/ / activation { / status:OK, V---------------/ - pending:0, + pending:0, required:2 } +-^---------+ @@ -133,17 +133,16 @@ The driver will then start processing the graph by emitting the ready signal. PipeWire will then: - - Perform some statistics about the previous cycle. Did it complete? compute processing - times, cpu usage etc. + - Check the previous cycle. Did it complete? Mark xrun on unfinished nodes. - Perform reposition requests if any, timebase changes, etc.. - The pending counter of each follower node is set to the required field. - - it then loops over all targets of the driver and atomically decrements the required + - It then loops over all targets of the driver and atomically decrements the required field of the activation record. When the required field is 0, the eventfd is signaled and the node can be scheduled. -In our example above, Node A and be will have their pending state decremented. Node A +In our example above, Node A and B will have their pending state decremented. Node A will be 0 and will be triggered first (node B has 2 pending dependencies to start with and -will not be triggered yet). The driver itself also has 2 dependcies left and will not +will not be triggered yet). The driver itself also has 2 dependencies left and will not be triggered (complete) yet. ## Scheduling node A @@ -169,13 +168,15 @@ The graph always completes after the driver is triggered and scheduled. All required fields from all the nodes in the target list of the driver are now 0. +The driver calculates some stats about cpu time etc. + # Remote nodes. For remote nodes, the eventfd and the activation is transfered from the server to the client. This means that writing to the remote client eventfd will wake the client directly -without going to the server first. +without going to the server first. All remote clients also get the activation and eventfd of the peer and driver they are linked to and can directly trigger peers and drivers without going to the @@ -183,9 +184,10 @@ ## Remote driver nodes. -Currently the graph start cycle is managed by the server. +Remote drivers start the graph cycle directly without going to the server first. -Remote driver nodes therefore have an extra eventfd to wake up the server and signal -the graph start. +After they complete (and only when the profiler is active), they will trigger an +extra eventfd to signal the server that the graph completed. This is used by the +server to generate the profiler info. */
View file
pipewire-0.3.71.tar.gz/doc/spa-pod.dox -> pipewire-0.3.72.tar.gz/doc/spa-pod.dox
Changed
@@ -623,7 +623,7 @@ 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | 4 | + | 8 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 5 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -659,7 +659,7 @@ 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | 4 | + | 8 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 7 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -948,7 +948,7 @@ 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | size | + | 16 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 17 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -971,7 +971,7 @@ 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | size | + | 8 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 18 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
View file
pipewire-0.3.71.tar.gz/doc/tutorial1.c -> pipewire-0.3.72.tar.gz/doc/tutorial1.c
Changed
@@ -11,9 +11,9 @@ pw_init(&argc, &argv); fprintf(stdout, "Compiled with libpipewire %s\n" - "Linked with libpipewire %s\n", - pw_get_headers_version(), - pw_get_library_version()); + "Linked with libpipewire %s\n", + pw_get_headers_version(), + pw_get_library_version()); return 0; } /* code */
View file
pipewire-0.3.71.tar.gz/doc/tutorial2.c -> pipewire-0.3.72.tar.gz/doc/tutorial2.c
Changed
@@ -20,37 +20,37 @@ int main(int argc, char *argv) { - struct pw_main_loop *loop; - struct pw_context *context; - struct pw_core *core; - struct pw_registry *registry; - struct spa_hook registry_listener; + struct pw_main_loop *loop; + struct pw_context *context; + struct pw_core *core; + struct pw_registry *registry; + struct spa_hook registry_listener; - pw_init(&argc, &argv); + pw_init(&argc, &argv); - loop = pw_main_loop_new(NULL /* properties */); - context = pw_context_new(pw_main_loop_get_loop(loop), - NULL /* properties */, - 0 /* user_data size */); + loop = pw_main_loop_new(NULL /* properties */); + context = pw_context_new(pw_main_loop_get_loop(loop), + NULL /* properties */, + 0 /* user_data size */); - core = pw_context_connect(context, - NULL /* properties */, - 0 /* user_data size */); + core = pw_context_connect(context, + NULL /* properties */, + 0 /* user_data size */); - registry = pw_core_get_registry(core, PW_VERSION_REGISTRY, - 0 /* user_data size */); + registry = pw_core_get_registry(core, PW_VERSION_REGISTRY, + 0 /* user_data size */); - spa_zero(registry_listener); - pw_registry_add_listener(registry, ®istry_listener, - ®istry_events, NULL); + spa_zero(registry_listener); + pw_registry_add_listener(registry, ®istry_listener, + ®istry_events, NULL); - pw_main_loop_run(loop); + pw_main_loop_run(loop); - pw_proxy_destroy((struct pw_proxy*)registry); - pw_core_disconnect(core); - pw_context_destroy(context); - pw_main_loop_destroy(loop); + pw_proxy_destroy((struct pw_proxy*)registry); + pw_core_disconnect(core); + pw_context_destroy(context); + pw_main_loop_destroy(loop); - return 0; + return 0; } /* code */
View file
pipewire-0.3.71.tar.gz/doc/tutorial4.c -> pipewire-0.3.72.tar.gz/doc/tutorial4.c
Changed
@@ -43,15 +43,15 @@ stride = sizeof(int16_t) * DEFAULT_CHANNELS; n_frames = buf->datas0.maxsize / stride; - for (i = 0; i < n_frames; i++) { - data->accumulator += M_PI_M2 * 440 / DEFAULT_RATE; - if (data->accumulator >= M_PI_M2) - data->accumulator -= M_PI_M2; - - val = sin(data->accumulator) * DEFAULT_VOLUME * 16767.f; - for (c = 0; c < DEFAULT_CHANNELS; c++) - *dst++ = val; - } + for (i = 0; i < n_frames; i++) { + data->accumulator += M_PI_M2 * 440 / DEFAULT_RATE; + if (data->accumulator >= M_PI_M2) + data->accumulator -= M_PI_M2; + + val = sin(data->accumulator) * DEFAULT_VOLUME * 16767.f; + for (c = 0; c < DEFAULT_CHANNELS; c++) + *dst++ = val; + } buf->datas0.chunk->offset = 0; buf->datas0.chunk->stride = stride;
View file
pipewire-0.3.71.tar.gz/meson.build -> pipewire-0.3.72.tar.gz/meson.build
Changed
@@ -1,5 +1,5 @@ project('pipewire', 'c' , - version : '0.3.71', + version : '0.3.72', license : 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' , meson_version : '>= 0.61.1', default_options : 'warning_level=3', @@ -405,6 +405,9 @@ summary({'lilv (for lv2 plugins)': lilv_lib.found()}, bool_yn: true) cdata.set('HAVE_LILV', lilv_lib.found()) +libffado_dep = dependency('libffado', required: get_option('libffado')) +summary({'ffado': libffado_dep.found()}, bool_yn: true) + check_functions = 'gettid', '#include <unistd.h>', '-D_GNU_SOURCE', , 'memfd_create', '#include <sys/mman.h>', '-D_GNU_SOURCE', ,
View file
pipewire-0.3.71.tar.gz/meson_options.txt -> pipewire-0.3.72.tar.gz/meson_options.txt
Changed
@@ -322,3 +322,7 @@ description: 'Enable code that depends on opus', type: 'feature', value: 'auto') +option('libffado', + description: 'Enable code that depends on libffado', + type: 'feature', + value: 'auto')
View file
pipewire-0.3.71.tar.gz/pipewire-jack/src/pipewire-jack.c -> pipewire-0.3.72.tar.gz/pipewire-jack/src/pipewire-jack.c
Changed
@@ -142,7 +142,6 @@ uint32_t dst_serial; bool src_ours; bool dst_ours; - bool is_complete; struct port *our_input; struct port *our_output; } port_link; @@ -518,10 +517,13 @@ } -static void init_mix(struct mix *mix, uint32_t mix_id, struct port *port) +static void init_mix(struct mix *mix, uint32_t mix_id, struct port *port, uint32_t peer_id) { + pw_log_debug("create %p mix:%d peer:%d", port, mix_id, peer_id); mix->id = mix_id; + mix->peer_id = peer_id; mix->port = port; + mix->peer_port = NULL; mix->io = NULL; mix->n_buffers = 0; spa_list_init(&mix->queue); @@ -538,6 +540,17 @@ return NULL; } +static struct mix *find_port_peer(struct port *port, uint32_t peer_id) +{ + struct mix *mix; + spa_list_for_each(mix, &port->mix, port_link) { + pw_log_info("%p %d %d", port, mix->peer_id, peer_id); + if (mix->peer_id == peer_id) + return mix; + } + return NULL; +} + static struct mix *find_mix(struct client *c, struct port *port, uint32_t mix_id) { struct mix *mix; @@ -549,14 +562,12 @@ return NULL; } -static struct mix *ensure_mix(struct client *c, struct port *port, uint32_t mix_id) +static struct mix *create_mix(struct client *c, struct port *port, + uint32_t mix_id, uint32_t peer_id) { struct mix *mix; uint32_t i; - if ((mix = find_mix(c, port, mix_id)) != NULL) - return mix; - if (spa_list_is_empty(&c->free_mix)) { mix = calloc(OBJECT_CHUNK, sizeof(struct mix)); if (mix == NULL) @@ -570,11 +581,19 @@ spa_list_append(&port->mix, &mix->port_link); - init_mix(mix, mix_id, port); + init_mix(mix, mix_id, port, peer_id); return mix; } +static struct mix *ensure_mix(struct client *c, struct port *port, uint32_t mix_id) +{ + struct mix *mix; + if ((mix = find_mix(c, port, mix_id)) != NULL) + return mix; + return create_mix(c, port, mix_id, SPA_ID_INVALID); +} + static int clear_buffers(struct client *c, struct mix *mix) { struct port *port = mix->port; @@ -926,7 +945,7 @@ notify = SPA_PTROFF(c->notify_buffer, index & NOTIFY_BUFFER_MASK, struct notify); o = notify->object; - pw_log_debug("%p: dequeue notify index:%08x %p type:%d %p arg1:%d\n", c, + pw_log_debug("%p: dequeue notify index:%08x %p type:%d %p arg1:%d", c, index, notify, notify->type, o, notify->arg1); switch (notify->type) { @@ -1025,10 +1044,8 @@ int32_t filled; uint32_t index; struct notify *notify; - bool emit = false;; + bool emit = false; - if ((type & NOTIFY_ACTIVE_FLAG) && !c->active) - return 0; switch (type) { case NOTIFY_TYPE_REGISTRATION: emit = c->registration_callback != NULL && o != NULL; @@ -1060,8 +1077,19 @@ default: break; } - if (!emit) { - pw_log_debug("%p: skip notify %d", c, type); + if (!emit || ((type & NOTIFY_ACTIVE_FLAG) && !c->active)) { + switch (type) { + case NOTIFY_TYPE_BUFFER_FRAMES: + if (!emit) + c->buffer_frames = arg1; + break; + case NOTIFY_TYPE_SAMPLE_RATE: + if (!emit) + c->sample_rate = arg1; + break; + } + pw_log_debug("%p: skip notify %08x active:%d emit:%d", c, type, + c->active, emit); if (o != NULL && arg1 == 0 && o->removing) { o->removing = false; free_object(c, o); @@ -1080,7 +1108,7 @@ notify->object = o; notify->arg1 = arg1; notify->msg = msg; - pw_log_debug("%p: queue notify index:%08x %p type:%d %p arg1:%d msg:%s\n", c, + pw_log_debug("%p: queue notify index:%08x %p type:%d %p arg1:%d msg:%s", c, index, notify, notify->type, o, notify->arg1, notify->msg); index += sizeof(struct notify); spa_ringbuffer_write_update(&c->notify_ring, index); @@ -1597,11 +1625,10 @@ if (SPA_UNLIKELY(sample_rate != c->sample_rate)) { pw_log_info("%p: sample_rate old:%d new:%d cb:%p", c, c->sample_rate, sample_rate, c->srate_callback); - if (c->srate_callback != NULL) { + if (c->sample_rate != (uint32_t)-1) queue_notify(c, NOTIFY_TYPE_SAMPLE_RATE, NULL, sample_rate, NULL); - } else { + else c->sample_rate = sample_rate; - } } return c->sample_rate == sample_rate; } @@ -2679,13 +2706,6 @@ void *ptr; int res = 0; - if (c->node_id == node_id) { - pw_log_debug("%p: our activation %u: %u %u %u", c, node_id, - mem_id, offset, size); - close(signalfd); - return 0; - } - if (mem_id == SPA_ID_INVALID) { mm = ptr = NULL; size = 0; @@ -2701,8 +2721,13 @@ ptr = mm->ptr; } - pw_log_debug("%p: set activation %u: %u %u %u %p", c, node_id, - mem_id, offset, size, ptr); + if (c->node_id == node_id) { + pw_log_debug("%p: our activation %u: %u %u %u %p", c, node_id, + mem_id, offset, size, ptr); + } else { + pw_log_debug("%p: set activation %u: %u %u %u %p", c, node_id, + mem_id, offset, size, ptr); + } if (ptr) { link = calloc(1, sizeof(struct link)); @@ -2751,8 +2776,6 @@ struct client *c = (struct client *) data; struct port *p = GET_PORT(c, direction, port_id); struct mix *mix; - struct object *l; - uint32_t src, dst; int res = 0; if (p == NULL || !p->valid) { @@ -2760,40 +2783,21 @@ goto exit; } - if ((mix = ensure_mix(c, p, mix_id)) == NULL) { - res = -ENOMEM; - goto exit; - } - mix->peer_id = peer_id; + mix = find_mix(c, p, mix_id); - if (direction == SPA_DIRECTION_INPUT) { - src = peer_id; - dst = p->object->id; + if (peer_id == SPA_ID_INVALID) { + if (mix == NULL) { + res = -ENOENT; + goto exit; + } + free_mix(c, mix); } else { - src = p->object->id; - dst = peer_id; - } - - if ((l = find_link(c, src, dst)) != NULL) { - if (direction == SPA_DIRECTION_INPUT) - mix->peer_port = l->port_link.our_output; - else - mix->peer_port = l->port_link.our_input; - - pw_log_debug("peer port %p %p %p", mix->peer_port, - l->port_link.our_output, l->port_link.our_input); - - if (!l->port_link.is_complete) { - l->port_link.is_complete = true; - pw_log_info("%p: our link %u/%u -> %u/%u completed", c, - l->port_link.src, l->port_link.src_serial, - l->port_link.dst, l->port_link.dst_serial); - queue_notify(c, NOTIFY_TYPE_CONNECT, l, 1, NULL); - queue_notify(c, NOTIFY_TYPE_GRAPH, NULL, 0, NULL); - emit_callbacks(c); + if (mix != NULL) { + res = -EEXIST; + goto exit; } + mix = create_mix(c, p, mix_id, peer_id); } - exit: if (res < 0) pw_proxy_error((struct pw_proxy*)c->node, res, spa_strerror(res)); @@ -3351,7 +3355,16 @@ if (o->port_link.dst_ours) o->port_link.our_input = p->port.port; - o->port_link.is_complete = !o->port_link.src_ours && !o->port_link.dst_ours; + if (o->port_link.our_input != NULL && + o->port_link.our_output != NULL) { + struct mix *mix; + mix = find_port_peer(o->port_link.our_output, o->port_link.dst); + if (mix != NULL) + mix->peer_port = o->port_link.our_input; + mix = find_port_peer(o->port_link.our_input, o->port_link.src); + if (mix != NULL) + mix->peer_port = o->port_link.our_output; + } pw_log_debug("%p: add link %d %u/%u->%u/%u", c, id, o->port_link.src, o->port_link.src_serial, o->port_link.dst, o->port_link.dst_serial); @@ -3412,14 +3425,11 @@ break; case INTERFACE_Link: - pw_log_info("%p: link %u %u/%u -> %u/%u added complete:%d", c, + pw_log_info("%p: link %u %u/%u -> %u/%u added", c, o->id, o->port_link.src, o->port_link.src_serial, - o->port_link.dst, o->port_link.dst_serial, - o->port_link.is_complete); - if (o->port_link.is_complete) { - queue_notify(c, NOTIFY_TYPE_CONNECT, o, 1, NULL); - queue_notify(c, NOTIFY_TYPE_GRAPH, NULL, 0, NULL); - } + o->port_link.dst, o->port_link.dst_serial); + queue_notify(c, NOTIFY_TYPE_CONNECT, o, 1, NULL); + queue_notify(c, NOTIFY_TYPE_GRAPH, NULL, 0, NULL); break; } emit_callbacks(c); @@ -3467,13 +3477,11 @@ queue_notify(c, NOTIFY_TYPE_PORTREGISTRATION, o, 0, NULL); break; case INTERFACE_Link: - if (o->port_link.is_complete && - find_type(c, o->port_link.src, INTERFACE_Port, true) != NULL && + if (find_type(c, o->port_link.src, INTERFACE_Port, true) != NULL && find_type(c, o->port_link.dst, INTERFACE_Port, true) != NULL) { pw_log_info("%p: link %u %u/%u -> %u/%u removed", c, o->id, o->port_link.src, o->port_link.src_serial, o->port_link.dst, o->port_link.dst_serial); - o->port_link.is_complete = false; queue_notify(c, NOTIFY_TYPE_CONNECT, o, 0, NULL); queue_notify(c, NOTIFY_TYPE_GRAPH, NULL, 0, NULL); } else { @@ -4539,6 +4547,7 @@ res = c->position->clock.rate.denom; } } + c->sample_rate = res; pw_log_debug("sample_rate: %u", res); return res; }
View file
pipewire-0.3.71.tar.gz/po/ca.po -> pipewire-0.3.72.tar.gz/po/ca.po
Changed
@@ -5,7 +5,7 @@ # Xavier Conde Rueda <xavi.conde@gmail.com>, 2008. # Agustí Grau <fletxa@gmail.com>, 2009. # Judith Pintó Subirada <judithp@gmail.com> -# Jordi Mas i Herǹandez, <jmas@softcatala.org>, 2022 +# Jordi Mas i Herǹandez, <jmas@softcatala.org>, 2022-2023 # # This file is translated according to the glossary and style guide of # Softcatalà. If you plan to modify this file, please read first the page @@ -27,17 +27,20 @@ msgid "" msgstr "" "Project-Id-Version: pipewire\n" -"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/issues/new\n" -"POT-Creation-Date: 2021-04-18 16:54+0800\n" -"PO-Revision-Date: 2022-09-01 19:24+0000\n" +"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" +"issues\n" +"POT-Creation-Date: 2023-06-06 15:28+0000\n" +"PO-Revision-Date: 2023-06-06 22:39+0200\n" "Last-Translator: Jordi Mas i Herǹandez, <jmas@softcatala.org>,\n" "Language-Team: Catalan <fedora@softcatala.net>\n" "Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 2.4.2\n" -#: src/daemon/pipewire.c:43 +#: src/daemon/pipewire.c:26 #, c-format msgid "" "%s options\n" @@ -48,7 +51,8 @@ "%s opcions\n" " -h, --help Mostra aquesta ajuda\n" " --version Mostra la versió\n" -" -c, --config Carrega la configuració (predeterminada %s)\n" +" -c, --config Carrega la configuració " +"(predeterminada %s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" @@ -58,305 +62,347 @@ msgid "Start the PipeWire Media System" msgstr "Inicia el sistema multimèdia PipeWire" -#: src/examples/media-session/alsa-monitor.c:526 -#: spa/plugins/alsa/acp/compat.c:187 -msgid "Built-in Audio" -msgstr "Àudio intern" +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:141 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:141 +#, c-format +msgid "Tunnel to %s%s%s" +msgstr "Túnel cap a %s%s%s" -#: src/examples/media-session/alsa-monitor.c:530 -#: spa/plugins/alsa/acp/compat.c:192 -msgid "Modem" -msgstr "Mòdem" +#: src/modules/module-fallback-sink.c:31 +msgid "Dummy Output" +msgstr "Sortida fictícia" + +#: src/modules/module-pulse-tunnel.c:844 +#, c-format +msgid "Tunnel for %s@%s" +msgstr "Túnel per a %s@%s" -#: src/examples/media-session/alsa-monitor.c:539 +#: src/modules/module-zeroconf-discover.c:315 msgid "Unknown device" msgstr "Dispositiu desconegut" -#: src/tools/pw-cat.c:991 +#: src/modules/module-zeroconf-discover.c:327 +#, c-format +msgid "%s on %s@%s" +msgstr "%s en %s@%s" + +#: src/modules/module-zeroconf-discover.c:331 +#, c-format +msgid "%s on %s" +msgstr "%s en %s" + +#: src/tools/pw-cat.c:974 #, c-format msgid "" -"%s options <file>\n" +"%s options <file>|-\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" -"%s opcions <fitxer>\n" +"%s opcions <fitxer>|-\n" " -h, --help Mostra aquesta ajuda\n" " --version Mostra la versió\n" " -v, --verbose Habilita les operacions detallades\n" -#: src/tools/pw-cat.c:998 -#, c-format, fuzzy +#: src/tools/pw-cat.c:981 +#, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" -" --target Set node target (default %s)\n" +" --target Set node target serial or name " +"(default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" -" the rate is the one of the source file\n" -" --list-targets List available targets for --target\n" +" the rate is the one of the source " +"file\n" +" -P --properties Set node properties\n" "\n" msgstr "" -"-R, --remote Nom del dimoni remot\n" -" --media-type Estableix el tipus de mitjà (per defecte %s)\n" -" --media-category Estableix la categoria dels mitjans (per defecte %s)\n" -" --media-role Estableix el rol del mitjà (per defecte %s)\n" -" --target Estableix l'objectiu del node (per defecte %s)\n" +" -R, --remote Nom del dimoni remot\n" +" --media-type Estableix el tipus de mitjà (per " +"defecte %s)\n" +" --media-category Estableix la categoria del " +"mitjà (per defecte %s)\n" +" --media-role Estableix el rol del mitjà (per " +"defecte %s)\n" +" --target Estableix l'objectiu sèrie o el nom " +"del node (per defecte %s)\n" " 0 vol dir que no enllaça\n" -" --latency Estableix latència del node (per defecte %s)\n" +" --latency Estableix latència del node (per " +"defecte %s)\n" " Xunit (unitat = s, ms, us, ns)\n" " o mostres directes (256)\n" " la taxa és la del fitxer d'origen\n" -" --list-targets Llista d'objectius disponibles per a --target" +" -P --properties Estableix les propietats del " +"node\n" +"\n" -#: src/tools/pw-cat.c:1016 -#, c-format, fuzzy +#: src/tools/pw-cat.c:999 +#, c-format msgid "" -" --rate Sample rate (req. for rec) (default %u)\n" -" --channels Number of channels (req. for rec) (default %u)\n" +" --rate Sample rate (req. for rec) (default " +"%u)\n" +" --channels Number of channels (req. for rec) " +"(default %u)\n" " --channel-map Channel map\n" -" one of: \"stereo\", \"surround-51\",... or\n" -" comma separated list of channel names: eg. \"FL,FR\"\n" -" --format Sample format %s (req. for rec) (default %s)\n" +" one of: \"stereo\", " +"\"surround-51\",... or\n" +" comma separated list of channel " +"names: eg. \"FL,FR\"\n" +" --format Sample format %s (req. for rec) " +"(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" -" -q --quality Resampler quality (0 - 15) (default %d)\n" +" -q --quality Resampler quality (0 - 15) (default " +"%d)\n" "\n" msgstr "" -"--rate Freqüència de mostreig (req. per rec) (predeterminat %u)\n" -" --channels Nombre de canals (req. per rec) (predeterminat %u)\n" -" --channel-map Mapa de canals\n" -" un dels següents: \"estèreo\", \"surround-51\",... o\n" -" Llista separada per comes dels noms dels canals: per exemple. \"FL,FR\"\n" -" --format Format de mostra %s (req. per a rec) (predeterminat %s)\n" -" --volume Volum de flux 0-1.0 (predeterminat %.3f)\n" -" -q --qualitat Remostrador de qualitat (0 - 15) (per defecte %d)" - -#: src/tools/pw-cat.c:1033 -#, fuzzy +" --rate Freqüència de mostreig (req. per a " +"rec) (predeterminat %u)\n" +" --channels Nombre de canals (req. per a rec) " +"(predeterminat %u)\n" +" --channel-map Mapa de canals\n" +" un dels següents: \"stereo\", " +"\"surround-51\",... o\n" +" llista separada per comes dels " +"noms dels canals: per exemple. «FL,FR»\n" +" --format Format de mostra %s (req. per a rec) " +"(predeterminat %s)\n" +" --volume Volum del flux 0-1.0 (predeterminat " +"%.3f)\n" +" -q --quality Qualitat de remostrador (0 - 15) " +"(predeterminal %d)\n" +"\n" + +#: src/tools/pw-cat.c:1016 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" +" -d, --dsd DSD mode\n" +" -o, --encoded Encoded mode\n" "\n" msgstr "" -"-p, --playback Mode de reproducció\n" -" -r, --record mode d'enregistrament\n" -" -m, --midi Mode MIDI" +" -p, --playback Mode de reproducció\n" +" -r, --record Mode d'enregistrament\n" +" -m, --midi Mode MIDI\n" +" -d, --dsd Mode DSD\n" +" -o, --encoded Mode codificat\n" +"\n" -#: src/tools/pw-cli.c:2932 -#, c-format, fuzzy +#: src/tools/pw-cli.c:2220 +#, c-format msgid "" "%s options command\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" +" -m, --monitor Monitor activity\n" "\n" msgstr "" -"%s opcions ordre\n" -" -h, --help Mostra aquesta ajuda\n" -" --version Mostra la versió\n" -" -d, --daemon Inicia com a dimoni (fals predeterminat)\n" -" -r, --remote Nom del dimoni remot" +"%s opcions ordre\n" +" -h, --help Mostra aquesta ajuda\n" +" --version Mostra la versió\n" +" -d, --daemon Inicia com a dimoni (fals per " +"defecte)\n" +" -r, --remote Nom del dimoni remot\n" +" -m, --monitor Monitor d'activitat\n" +"\n" -#: spa/plugins/alsa/acp/acp.c:290 +#: spa/plugins/alsa/acp/acp.c:303 msgid "Pro Audio" msgstr "Pro Audio" -#: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 -#: spa/plugins/bluez5/bluez5-device.c:1000 +#: spa/plugins/alsa/acp/acp.c:427 spa/plugins/alsa/acp/alsa-mixer.c:4648 +#: spa/plugins/bluez5/bluez5-device.c:1586 msgid "Off" msgstr "Inactiu" -#: spa/plugins/alsa/acp/channelmap.h:466 -msgid "(invalid)" -msgstr "(incorrecte)" - -#: spa/plugins/alsa/acp/alsa-mixer.c:2709 +#: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Entrada" -#: spa/plugins/alsa/acp/alsa-mixer.c:2710 +#: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Entrada de l'estació d'acoblament" -#: spa/plugins/alsa/acp/alsa-mixer.c:2711 +#: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Micròfon de l'estació d'acoblament" -#: spa/plugins/alsa/acp/alsa-mixer.c:2712 +#: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Entrada de línia de l'estació d'acoblament" -#: spa/plugins/alsa/acp/alsa-mixer.c:2713 -#: spa/plugins/alsa/acp/alsa-mixer.c:2804 +#: spa/plugins/alsa/acp/alsa-mixer.c:2656 +#: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Entrada de línia" -#: spa/plugins/alsa/acp/alsa-mixer.c:2714 -#: spa/plugins/alsa/acp/alsa-mixer.c:2798 -#: spa/plugins/bluez5/bluez5-device.c:1145 +#: spa/plugins/alsa/acp/alsa-mixer.c:2657 +#: spa/plugins/alsa/acp/alsa-mixer.c:2741 +#: spa/plugins/bluez5/bluez5-device.c:1831 msgid "Microphone" msgstr "Micròfon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2715 -#: spa/plugins/alsa/acp/alsa-mixer.c:2799 +#: spa/plugins/alsa/acp/alsa-mixer.c:2658 +#: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Micròfon frontal" -#: spa/plugins/alsa/acp/alsa-mixer.c:2716 -#: spa/plugins/alsa/acp/alsa-mixer.c:2800 +#: spa/plugins/alsa/acp/alsa-mixer.c:2659 +#: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Micròfon posterior" -#: spa/plugins/alsa/acp/alsa-mixer.c:2717 +#: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Micròfon extern" -#: spa/plugins/alsa/acp/alsa-mixer.c:2718 -#: spa/plugins/alsa/acp/alsa-mixer.c:2802 +#: spa/plugins/alsa/acp/alsa-mixer.c:2661 +#: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Micròfon intern" -#: spa/plugins/alsa/acp/alsa-mixer.c:2719 -#: spa/plugins/alsa/acp/alsa-mixer.c:2805 +#: spa/plugins/alsa/acp/alsa-mixer.c:2662 +#: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Ràdio" -#: spa/plugins/alsa/acp/alsa-mixer.c:2720 -#: spa/plugins/alsa/acp/alsa-mixer.c:2806 +#: spa/plugins/alsa/acp/alsa-mixer.c:2663 +#: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Vídeo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2721 +#: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Control de guany automàtic" -#: spa/plugins/alsa/acp/alsa-mixer.c:2722 +#: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Sense control de guany automàtic" -#: spa/plugins/alsa/acp/alsa-mixer.c:2723 +#: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Accentuació" -#: spa/plugins/alsa/acp/alsa-mixer.c:2724 +#: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Sense accentuació" -#: spa/plugins/alsa/acp/alsa-mixer.c:2725 +#: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Amplificador" -#: spa/plugins/alsa/acp/alsa-mixer.c:2726 +#: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Sense amplificador" -#: spa/plugins/alsa/acp/alsa-mixer.c:2727 +#: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Sense accentuació dels baixos" -#: spa/plugins/alsa/acp/alsa-mixer.c:2728 +#: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Sense accentuació dels baixos" -#: spa/plugins/alsa/acp/alsa-mixer.c:2729 -#: spa/plugins/bluez5/bluez5-device.c:1150 +#: spa/plugins/alsa/acp/alsa-mixer.c:2672 +#: spa/plugins/bluez5/bluez5-device.c:1837 msgid "Speaker" msgstr "Altaveu" -#: spa/plugins/alsa/acp/alsa-mixer.c:2730 -#: spa/plugins/alsa/acp/alsa-mixer.c:2808 +#: spa/plugins/alsa/acp/alsa-mixer.c:2673 +#: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Auriculars" -#: spa/plugins/alsa/acp/alsa-mixer.c:2797 +#: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Entrada analògica" -#: spa/plugins/alsa/acp/alsa-mixer.c:2801 +#: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Micròfon de l'acoblador" -#: spa/plugins/alsa/acp/alsa-mixer.c:2803 +#: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Micròfon d'auriculars" -#: spa/plugins/alsa/acp/alsa-mixer.c:2807 +#: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Sortida analògica" -#: spa/plugins/alsa/acp/alsa-mixer.c:2809 +#: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Auriculars 2" -#: spa/plugins/alsa/acp/alsa-mixer.c:2810 +#: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Sortida mono dels auriculars" -#: spa/plugins/alsa/acp/alsa-mixer.c:2811 +#: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Sortida de línia" -#: spa/plugins/alsa/acp/alsa-mixer.c:2812 +#: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Sortida mono analògica" -#: spa/plugins/alsa/acp/alsa-mixer.c:2813 +#: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Altaveus" -#: spa/plugins/alsa/acp/alsa-mixer.c:2814 +#: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" -#: spa/plugins/alsa/acp/alsa-mixer.c:2815 +#: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Sortida digital (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2816 +#: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Entrada digital (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2817 +#: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Entrada multicanal" -#: spa/plugins/alsa/acp/alsa-mixer.c:2818 +#: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Sortida multicanal" -#: spa/plugins/alsa/acp/alsa-mixer.c:2819 +#: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Sortida del joc" -#: spa/plugins/alsa/acp/alsa-mixer.c:2820 -#: spa/plugins/alsa/acp/alsa-mixer.c:2821 +#: spa/plugins/alsa/acp/alsa-mixer.c:2763 +#: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Sortida del xat" -#: spa/plugins/alsa/acp/alsa-mixer.c:2822 +#: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Entrada del xat" -#: spa/plugins/alsa/acp/alsa-mixer.c:2823 +#: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Envoltant virtual 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4527 +#: spa/plugins/alsa/acp/alsa-mixer.c:4471 msgid "Analog Mono" msgstr "Mono analògic" -#: spa/plugins/alsa/acp/alsa-mixer.c:4528 +#: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Analog Mono (Left)" msgstr "Mono analògic (esquerra)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4529 +#: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Mono (Right)" msgstr "Mono analògic (dreta)" @@ -365,255 +411,338 @@ #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. -#: spa/plugins/alsa/acp/alsa-mixer.c:4530 -#: spa/plugins/alsa/acp/alsa-mixer.c:4538 -#: spa/plugins/alsa/acp/alsa-mixer.c:4539 +#: spa/plugins/alsa/acp/alsa-mixer.c:4474 +#: spa/plugins/alsa/acp/alsa-mixer.c:4482 +#: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Stereo" msgstr "Estèreo analògic" -#: spa/plugins/alsa/acp/alsa-mixer.c:4531 +#: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Mono" msgstr "Mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4532 +#: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Stereo" msgstr "Estèreo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4540 -#: spa/plugins/alsa/acp/alsa-mixer.c:4698 -#: spa/plugins/bluez5/bluez5-device.c:1135 +#: spa/plugins/alsa/acp/alsa-mixer.c:4484 +#: spa/plugins/alsa/acp/alsa-mixer.c:4642 +#: spa/plugins/bluez5/bluez5-device.c:1819 msgid "Headset" msgstr "Auriculars" -#: spa/plugins/alsa/acp/alsa-mixer.c:4541 -#: spa/plugins/alsa/acp/alsa-mixer.c:4699 +#: spa/plugins/alsa/acp/alsa-mixer.c:4485 +#: spa/plugins/alsa/acp/alsa-mixer.c:4643 msgid "Speakerphone" msgstr "Altaveu del telèfon" -#: spa/plugins/alsa/acp/alsa-mixer.c:4542 -#: spa/plugins/alsa/acp/alsa-mixer.c:4543 +#: spa/plugins/alsa/acp/alsa-mixer.c:4486 +#: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Multichannel" msgstr "Multicanal" -#: spa/plugins/alsa/acp/alsa-mixer.c:4544 +#: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Analog Surround 2.1" msgstr "Envoltant analògic 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4545 +#: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Analog Surround 3.0" msgstr "Envoltant analògic 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4546 +#: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Analog Surround 3.1" msgstr "Envoltant analògic 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4547 +#: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Analog Surround 4.0" msgstr "Envoltant analògic 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4548 +#: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Analog Surround 4.1" msgstr "Envoltant analògic 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4549 +#: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Analog Surround 5.0" msgstr "Envoltant analògic 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4550 +#: spa/plugins/alsa/acp/alsa-mixer.c:4494 msgid "Analog Surround 5.1" msgstr "Envoltant analògic 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4551 +#: spa/plugins/alsa/acp/alsa-mixer.c:4495 msgid "Analog Surround 6.0" msgstr "Envoltant analògic 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4552 +#: spa/plugins/alsa/acp/alsa-mixer.c:4496 msgid "Analog Surround 6.1" msgstr "Envoltant analògic 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4553 +#: spa/plugins/alsa/acp/alsa-mixer.c:4497 msgid "Analog Surround 7.0" msgstr "Envoltant analògic 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4554 +#: spa/plugins/alsa/acp/alsa-mixer.c:4498 msgid "Analog Surround 7.1" msgstr "Envoltant analògic 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4555 +#: spa/plugins/alsa/acp/alsa-mixer.c:4499 msgid "Digital Stereo (IEC958)" msgstr "Estèreo digital (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4556 +#: spa/plugins/alsa/acp/alsa-mixer.c:4500 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Envoltant digital 4.0 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4557 +#: spa/plugins/alsa/acp/alsa-mixer.c:4501 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Envoltant digital 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4558 +#: spa/plugins/alsa/acp/alsa-mixer.c:4502 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Envoltant digital 5.1 (IEC958/DTS)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4559 +#: spa/plugins/alsa/acp/alsa-mixer.c:4503 msgid "Digital Stereo (HDMI)" msgstr "Estèreo digital (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4560 +#: spa/plugins/alsa/acp/alsa-mixer.c:4504 msgid "Digital Surround 5.1 (HDMI)" msgstr "Envoltant digital 5.1 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4561 +#: spa/plugins/alsa/acp/alsa-mixer.c:4505 msgid "Chat" msgstr "Xat" -#: spa/plugins/alsa/acp/alsa-mixer.c:4562 +#: spa/plugins/alsa/acp/alsa-mixer.c:4506 msgid "Game" msgstr "Joc" -#: spa/plugins/alsa/acp/alsa-mixer.c:4696 +#: spa/plugins/alsa/acp/alsa-mixer.c:4640 msgid "Analog Mono Duplex" msgstr "Dúplex mono analògic" -#: spa/plugins/alsa/acp/alsa-mixer.c:4697 +#: spa/plugins/alsa/acp/alsa-mixer.c:4641 msgid "Analog Stereo Duplex" msgstr "Dúplex estèreo analògic" -#: spa/plugins/alsa/acp/alsa-mixer.c:4700 +#: spa/plugins/alsa/acp/alsa-mixer.c:4644 msgid "Digital Stereo Duplex (IEC958)" msgstr "Dúplex estèreo digital (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4701 +#: spa/plugins/alsa/acp/alsa-mixer.c:4645 msgid "Multichannel Duplex" msgstr "Dúplex Multicanal" -#: spa/plugins/alsa/acp/alsa-mixer.c:4702 +#: spa/plugins/alsa/acp/alsa-mixer.c:4646 msgid "Stereo Duplex" msgstr "Dúplex estèreo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4703 +#: spa/plugins/alsa/acp/alsa-mixer.c:4647 msgid "Mono Chat + 7.1 Surround" msgstr "Xat mono + 7.1 envoltant" -#: spa/plugins/alsa/acp/alsa-mixer.c:4806 +#: spa/plugins/alsa/acp/alsa-mixer.c:4748 #, c-format msgid "%s Output" msgstr "Sortida %s" -#: spa/plugins/alsa/acp/alsa-mixer.c:4813 +#: spa/plugins/alsa/acp/alsa-mixer.c:4756 #, c-format msgid "%s Input" msgstr "Entrada %s" -#: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 -#, fuzzy, c-format +#: spa/plugins/alsa/acp/alsa-util.c:1187 spa/plugins/alsa/acp/alsa-util.c:1281 +#, c-format msgid "" -"snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu ms).\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." +"snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " +"ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." msgid_plural "" -"snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu ms).\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." +"snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " +"ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." msgstr0 "" -"snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu bytes (%lu ms).\n" -"Probablement es tracta d'un error del controlador de l'ALSA '%s'. Informeu d'aquest incident als desenvolupadors de l'ALSA." +"snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu byte (%lu " +"ms).\n" +"Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu " +"d'aquest incident als desenvolupadors de l'ALSA." msgstr1 "" -"snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu bytes (%lu ms).\n" -"Probablement es tracta d'un error del controlador de l'ALSA '%s'. Informeu d'aquest incident als desenvolupadors de l'ALSA." +"snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu bytes (%lu " +"ms).\n" +"Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu " +"d'aquest incident als desenvolupadors de l'ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1241 -#, fuzzy, c-format +#: spa/plugins/alsa/acp/alsa-util.c:1253 +#, c-format msgid "" -"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s%lu ms).\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." +"snd_pcm_delay() returned a value that is exceptionally large: %li byte " +"(%s%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." msgid_plural "" -"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s%lu ms).\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." +"snd_pcm_delay() returned a value that is exceptionally large: %li bytes " +"(%s%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." msgstr0 "" -"snd_pcm_delay() ha retornat un valor excepcionalment gran: %li bytes (%s%lu ms).\n" -"Probablement es tracta d'un error del controlador de l'ALSA '%s'. Informeu d'aquest incident als desenvolupadors de l'ALSA." +"snd_pcm_delay() ha retornat un valor excepcionalment gran: %li byte (%s%lu " +"ms).\n" +"Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu " +"d'aquest incident als desenvolupadors de l'ALSA." msgstr1 "" -"snd_pcm_delay() ha retornat un valor excepcionalment gran: %li bytes (%s%lu ms).\n" -"Probablement es tracta d'un error del controlador de l'ALSA '%s'. Informeu d'aquest incident als desenvolupadors de l'ALSA." +"snd_pcm_delay() ha retornat un valor excepcionalment gran: %li bytes (%s%lu " +"ms).\n" +"Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu " +"d'aquest incident als desenvolupadors de l'ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1288 +#: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" -"snd_pcm_avail_delay() returned strange values: delay %lu is less than avail %lu.\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." +"snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " +"%lu.\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." msgstr "" -"snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu bytes (%lu ms).\n" -"Probablement es tracta d'un error del controlador d'ALSA «%s». Informeu d'aquest problema als desenvolupadors d'ALSA." +"snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu bytes (%lu " +"ms).\n" +"Probablement es tracta d'un error del controlador d'ALSA «%s». Informeu " +"d'aquest problema als desenvolupadors d'ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1331 -#, fuzzy, c-format +#: spa/plugins/alsa/acp/alsa-util.c:1343 +#, c-format msgid "" -"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte (%lu ms).\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." +"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " +"(%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." msgid_plural "" -"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes (%lu ms).\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." +"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " +"(%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." msgstr0 "" -"snd_pcm_mmap_begin() ha retornat un valor excepcionalment gran: %lu bytes (%lu ms).\n" -"Probablement es tracta d'un error del controlador de l'ALSA '%s'. Informeu d'aquest incident als desenvolupadors de l'ALSA." +"snd_pcm_mmap_begin() ha retornat un valor excepcionalment gran: %lu byte " +"(%lu ms).\n" +"Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu " +"d'aquest incident als desenvolupadors de l'ALSA." msgstr1 "" -"snd_pcm_mmap_begin() ha retornat un valor excepcionalment gran: %lu bytes (%lu ms).\n" -"Probablement es tracta d'un error del controlador de l'ALSA '%s'. Informeu d'aquest incident als desenvolupadors de l'ALSA." +"snd_pcm_mmap_begin() ha retornat un valor excepcionalment gran: %lu bytes " +"(%lu ms).\n" +"Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu " +"d'aquest incident als desenvolupadors de l'ALSA." + +#: spa/plugins/alsa/acp/channelmap.h:457 +msgid "(invalid)" +msgstr "(incorrecte)" + +#: spa/plugins/alsa/acp/compat.c:189 +msgid "Built-in Audio" +msgstr "Àudio intern" + +#: spa/plugins/alsa/acp/compat.c:194 +msgid "Modem" +msgstr "Mòdem" -#: spa/plugins/bluez5/bluez5-device.c:1010 +#: spa/plugins/bluez5/bluez5-device.c:1597 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Passarel·la d'àudio (A2DP Source & HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1033 +#: spa/plugins/bluez5/bluez5-device.c:1622 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Reproducció d'alta fidelitat (Sink A2DP, còdec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1035 +#: spa/plugins/bluez5/bluez5-device.c:1625 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Dúplex d'alta fidelitat (A2DP Source/Sink, còdec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1041 +#: spa/plugins/bluez5/bluez5-device.c:1633 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Reproducció d'alta fidelitat (A2DP Sink)" -#: spa/plugins/bluez5/bluez5-device.c:1043 +#: spa/plugins/bluez5/bluez5-device.c:1635 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Dúplex d'alta fidelitat (A2DP Source/Sink)" -#: spa/plugins/bluez5/bluez5-device.c:1070 +#: spa/plugins/bluez5/bluez5-device.c:1677 +#, c-format +msgid "High Fidelity Playback (BAP Sink, codec %s)" +msgstr "Reproducció d'alta fidelitat (sortida BAP, còdec %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1681 +#, c-format +msgid "High Fidelity Input (BAP Source, codec %s)" +msgstr "Entrada d'alta fidelitat (font A2DP, còdec %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1685 +#, c-format +msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" +msgstr "Dúplex d'alta fidelitat (BAP Source/Sink, còdec %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1693 +msgid "High Fidelity Playback (BAP Sink)" +msgstr "Reproducció d'alta fidelitat (Sortida BAP)" + +#: spa/plugins/bluez5/bluez5-device.c:1696 +msgid "High Fidelity Input (BAP Source)" +msgstr "Entrada d'alta fidelitat (Font BAP)" + +#: spa/plugins/bluez5/bluez5-device.c:1699 +msgid "High Fidelity Duplex (BAP Source/Sink)" +msgstr "Dúplex d'alta fidelitat (BAP Source/Sink)" + +#: spa/plugins/bluez5/bluez5-device.c:1735 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Unitat d'ariculars pel cap (HSP/HFP, còdec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1074 +#: spa/plugins/bluez5/bluez5-device.c:1740 msgid "Headset Head Unit (HSP/HFP)" msgstr "Unitat d'ariculars pel cap (HSP/HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1140 +#: spa/plugins/bluez5/bluez5-device.c:1820 +#: spa/plugins/bluez5/bluez5-device.c:1825 +#: spa/plugins/bluez5/bluez5-device.c:1832 +#: spa/plugins/bluez5/bluez5-device.c:1838 +#: spa/plugins/bluez5/bluez5-device.c:1844 +#: spa/plugins/bluez5/bluez5-device.c:1850 +#: spa/plugins/bluez5/bluez5-device.c:1856 +#: spa/plugins/bluez5/bluez5-device.c:1862 +#: spa/plugins/bluez5/bluez5-device.c:1868 msgid "Handsfree" msgstr "Mans lliures" -#: spa/plugins/bluez5/bluez5-device.c:1155 +#: spa/plugins/bluez5/bluez5-device.c:1826 +msgid "Handsfree (HFP)" +msgstr "Mans lliures (HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:1843 msgid "Headphone" msgstr "Auricular" -#: spa/plugins/bluez5/bluez5-device.c:1160 +#: spa/plugins/bluez5/bluez5-device.c:1849 msgid "Portable" msgstr "Portable" -#: spa/plugins/bluez5/bluez5-device.c:1165 +#: spa/plugins/bluez5/bluez5-device.c:1855 msgid "Car" msgstr "Cotxe" -#: spa/plugins/bluez5/bluez5-device.c:1170 +#: spa/plugins/bluez5/bluez5-device.c:1861 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:1175 +#: spa/plugins/bluez5/bluez5-device.c:1867 msgid "Phone" msgstr "Telèfon" -#: spa/plugins/bluez5/bluez5-device.c:1181 +#: spa/plugins/bluez5/bluez5-device.c:1874 msgid "Bluetooth" msgstr "Bluetooth" + +#: spa/plugins/bluez5/bluez5-device.c:1875 +msgid "Bluetooth (HFP)" +msgstr "Bluetooth (HFP)"
View file
pipewire-0.3.71.tar.gz/po/pl.po -> pipewire-0.3.72.tar.gz/po/pl.po
Changed
@@ -8,8 +8,8 @@ "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" -"POT-Creation-Date: 2023-03-04 12:58+0000\n" -"PO-Revision-Date: 2023-03-04 14:10+0100\n" +"POT-Creation-Date: 2023-05-28 10:45+0000\n" +"PO-Revision-Date: 2023-05-28 12:48+0200\n" "Last-Translator: Piotr Drąg <piotrdrag@gmail.com>\n" "Language-Team: Polish <community-poland@mozilla.org>\n" "Language: pl\n" @@ -41,17 +41,17 @@ msgid "Start the PipeWire Media System" msgstr "Uruchomienie systemu multimediów PipeWire" -#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 -#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:141 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:141 #, c-format -msgid "Tunnel to %s/%s" -msgstr "Tunel do %s/%s" +msgid "Tunnel to %s%s%s" +msgstr "Tunel do %s%s%s" #: src/modules/module-fallback-sink.c:31 msgid "Dummy Output" msgstr "Głuche wyjście" -#: src/modules/module-pulse-tunnel.c:675 +#: src/modules/module-pulse-tunnel.c:844 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunel dla %s@%s" @@ -175,7 +175,7 @@ " -o, --encoded Tryb zakodowany\n" "\n" -#: src/tools/pw-cli.c:2216 +#: src/tools/pw-cli.c:2220 #, c-format msgid "" "%s options command\n" @@ -200,7 +200,7 @@ msgstr "Dźwięk w zastosowaniach profesjonalnych" #: spa/plugins/alsa/acp/acp.c:427 spa/plugins/alsa/acp/alsa-mixer.c:4648 -#: spa/plugins/bluez5/bluez5-device.c:1283 +#: spa/plugins/bluez5/bluez5-device.c:1586 msgid "Off" msgstr "Wyłączone" @@ -227,7 +227,7 @@ #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:1519 +#: spa/plugins/bluez5/bluez5-device.c:1831 msgid "Microphone" msgstr "Mikrofon" @@ -293,7 +293,7 @@ msgstr "Brak podbicia basów" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:1525 +#: spa/plugins/bluez5/bluez5-device.c:1837 msgid "Speaker" msgstr "Głośnik" @@ -408,7 +408,7 @@ #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 -#: spa/plugins/bluez5/bluez5-device.c:1507 +#: spa/plugins/bluez5/bluez5-device.c:1819 msgid "Headset" msgstr "Słuchawki z mikrofonem" @@ -522,12 +522,12 @@ msgid "Mono Chat + 7.1 Surround" msgstr "Rozmowa mono + przestrzenne 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4754 +#: spa/plugins/alsa/acp/alsa-mixer.c:4748 #, c-format msgid "%s Output" msgstr "Wyjście %s" -#: spa/plugins/alsa/acp/alsa-mixer.c:4761 +#: spa/plugins/alsa/acp/alsa-mixer.c:4756 #, c-format msgid "%s Input" msgstr "Wejście %s" @@ -632,104 +632,104 @@ msgid "Modem" msgstr "Modem" -#: spa/plugins/bluez5/bluez5-device.c:1294 +#: spa/plugins/bluez5/bluez5-device.c:1597 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Bramka dźwięku (źródło A2DP i AG HSP/HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1319 +#: spa/plugins/bluez5/bluez5-device.c:1622 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Odtwarzanie o wysokiej dokładności (odpływ A2DP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1322 +#: spa/plugins/bluez5/bluez5-device.c:1625 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Dupleks o wysokiej dokładności (źródło/odpływ A2DP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1330 +#: spa/plugins/bluez5/bluez5-device.c:1633 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Odtwarzanie o wysokiej dokładności (odpływ A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1332 +#: spa/plugins/bluez5/bluez5-device.c:1635 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Dupleks o wysokiej dokładności (źródło/odpływ A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1374 +#: spa/plugins/bluez5/bluez5-device.c:1677 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Odtwarzanie o wysokiej dokładności (odpływ BAP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1378 +#: spa/plugins/bluez5/bluez5-device.c:1681 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Wejście o wysokiej dokładności (źródło BAP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1382 +#: spa/plugins/bluez5/bluez5-device.c:1685 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "Dupleks o wysokiej dokładności (źródło/odpływ BAP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1390= +#: spa/plugins/bluez5/bluez5-device.c:1693 msgid "High Fidelity Playback (BAP Sink)" msgstr "Odtwarzanie o wysokiej dokładności (odpływ BAP)" -#: spa/plugins/bluez5/bluez5-device.c:1393 +#: spa/plugins/bluez5/bluez5-device.c:1696 msgid "High Fidelity Input (BAP Source)" msgstr "Wejście o wysokiej dokładności (źródło BAP)" -#: spa/plugins/bluez5/bluez5-device.c:1396 +#: spa/plugins/bluez5/bluez5-device.c:1699 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "Dupleks o wysokiej dokładności (źródło/odpływ BAP)" -#: spa/plugins/bluez5/bluez5-device.c:1424 +#: spa/plugins/bluez5/bluez5-device.c:1735 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Jednostka główna słuchawek z mikrofonem (HSP/HFP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1429 +#: spa/plugins/bluez5/bluez5-device.c:1740 msgid "Headset Head Unit (HSP/HFP)" msgstr "Jednostka główna słuchawek z mikrofonem (HSP/HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1508 -#: spa/plugins/bluez5/bluez5-device.c:1513 -#: spa/plugins/bluez5/bluez5-device.c:1520 -#: spa/plugins/bluez5/bluez5-device.c:1526 -#: spa/plugins/bluez5/bluez5-device.c:1532 -#: spa/plugins/bluez5/bluez5-device.c:1538 -#: spa/plugins/bluez5/bluez5-device.c:1544 -#: spa/plugins/bluez5/bluez5-device.c:1550 -#: spa/plugins/bluez5/bluez5-device.c:1556 +#: spa/plugins/bluez5/bluez5-device.c:1820 +#: spa/plugins/bluez5/bluez5-device.c:1825 +#: spa/plugins/bluez5/bluez5-device.c:1832 +#: spa/plugins/bluez5/bluez5-device.c:1838 +#: spa/plugins/bluez5/bluez5-device.c:1844 +#: spa/plugins/bluez5/bluez5-device.c:1850 +#: spa/plugins/bluez5/bluez5-device.c:1856 +#: spa/plugins/bluez5/bluez5-device.c:1862 +#: spa/plugins/bluez5/bluez5-device.c:1868 msgid "Handsfree" msgstr "Zestaw głośnomówiący" -#: spa/plugins/bluez5/bluez5-device.c:1514 +#: spa/plugins/bluez5/bluez5-device.c:1826 msgid "Handsfree (HFP)" msgstr "Zestaw głośnomówiący (HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1531 +#: spa/plugins/bluez5/bluez5-device.c:1843 msgid "Headphone" msgstr "Słuchawki" -#: spa/plugins/bluez5/bluez5-device.c:1537 +#: spa/plugins/bluez5/bluez5-device.c:1849 msgid "Portable" msgstr "Przenośne" -#: spa/plugins/bluez5/bluez5-device.c:1543 +#: spa/plugins/bluez5/bluez5-device.c:1855 msgid "Car" msgstr "Samochód" -#: spa/plugins/bluez5/bluez5-device.c:1549 +#: spa/plugins/bluez5/bluez5-device.c:1861 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:1555 +#: spa/plugins/bluez5/bluez5-device.c:1867 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:1562 +#: spa/plugins/bluez5/bluez5-device.c:1874 msgid "Bluetooth" msgstr "Bluetooth" -#: spa/plugins/bluez5/bluez5-device.c:1563 +#: spa/plugins/bluez5/bluez5-device.c:1875 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)"
View file
pipewire-0.3.71.tar.gz/po/tr.po -> pipewire-0.3.72.tar.gz/po/tr.po
Changed
@@ -1,6 +1,7 @@ # Turkish translation for PipeWire. -# Copyright (C) 2014 PipeWire's COPYRIGHT HOLDER +# Copyright (C) 2014-2023 PipeWire's COPYRIGHT HOLDER # This file is distributed under the same license as the PipeWire package. +# # Necdet Yücel <necdetyucel@gmail.com>, 2014. # Kaan Özdinçer <kaanozdincer@gmail.com>, 2014. # Muhammet Kara <muhammetk@gmail.com>, 2015, 2016, 2017. @@ -12,15 +13,15 @@ "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2022-06-30 12:50+0200\n" -"PO-Revision-Date: 2022-10-23 10:40+0300\n" -"Last-Translator: Oğuz Ersen <oguz@ersen.moe>\n" -"Language-Team: Turkish <tr>\n" +"PO-Revision-Date: 2023-05-26 23:39+0300\n" +"Last-Translator: Sabri Ünal <libreajans@gmail.com>\n" +"Language-Team: Türkçe <takim@gnome.org.tr>\n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 4.4.2\n" +"X-Generator: Poedit 3.2.2\n" #: src/daemon/pipewire.c:46 #, c-format @@ -96,7 +97,7 @@ " -P --properties Set node properties\n" "\n" msgstr "" -" -R, --remote Uzak arka plan programı adı\n" +" -R, --remote Uzak art alan hizmeti adı\n" " --media-type Ortam türünü ayarla (öntanımlı %s)\n" " --media-category Ortam kategorisini ayarla (öntanımlı " "%s)\n" @@ -175,7 +176,7 @@ "%s seçenekler komut\n" " -h, --help Bu yardımı göster\n" " --version Sürümü göster\n" -" -d, --daemon Arka plan programı olarak başlat " +" -d, --daemon Art alan hizmeti olarak başlat " "(Öntanımlı olarak yanlış)\n" " -r, --remote Uzak arka plan programı adı\n" "\n" @@ -683,9 +684,3 @@ #: spa/plugins/bluez5/bluez5-device.c:1498 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" - -#~ msgid "PipeWire Media System" -#~ msgstr "PipeWire Ortam Sistemi" - -#~ msgid "Start the PipeWire Media System" -#~ msgstr "PipeWire Ortam Sistemini Başlat"
View file
pipewire-0.3.71.tar.gz/spa/plugins/alsa/alsa-compress-offload-sink.c -> pipewire-0.3.72.tar.gz/spa/plugins/alsa/alsa-compress-offload-sink.c
Changed
@@ -510,6 +510,8 @@ this->device_context = NULL; this->device_started = false; this->device_is_paused = false; + + this->have_format = false; } static int device_start(struct impl *this) @@ -919,11 +921,6 @@ if (this->started) return 0; - if (!this->have_format) - return -EIO; - if (this->n_buffers == 0) - return -EIO; - this->following = is_following(this); spa_log_debug(this->log, "%p: starting output; starting as follower: %d", this, this->following); @@ -1357,8 +1354,14 @@ break; case SPA_NODE_COMMAND_Start: + if (!this->have_format) + return -EIO; + if (this->n_buffers == 0) + return -EIO; + if (SPA_UNLIKELY((res = do_start(this)) < 0)) return res; + break; case SPA_NODE_COMMAND_Suspend: @@ -1403,6 +1406,12 @@ "device opened: %d have configured format: %d device started: %d", this, device_opened, this->have_format, device_started); + if (!this->started && this->have_format) { + spa_log_debug(this->log, "%p: closing device to reset configured format", this); + device_close(this); + device_opened = false; + } + if (!device_opened) { if ((res = device_open(this)) < 0) return res; @@ -1487,11 +1496,18 @@ return port_enum_formats(this, seq, start, num, filter, &b); case SPA_PARAM_Format: - if (!this->have_format) + if (!this->have_format) { + spa_log_debug(this->log, "%p: attempted to enumerate current " + "format, but no current audio info set", this); return -EIO; + } + if (result.index > 0) return 0; + spa_log_debug(this->log, "%p: current audio info is set; " + "enumerating currently set format", this); + param = spa_format_audio_build(&b, id, &this->current_audio_info); break; @@ -1557,8 +1573,6 @@ spa_log_debug(this->log, "%p: clearing format and closing device", this); device_close(this); clear_buffers(this); - - this->have_format = false; } else { struct spa_audio_info info = { 0 }; uint32_t rate;
View file
pipewire-0.3.71.tar.gz/spa/plugins/alsa/alsa-pcm-sink.c -> pipewire-0.3.72.tar.gz/spa/plugins/alsa/alsa-pcm-sink.c
Changed
@@ -771,6 +771,7 @@ break; case SPA_IO_RateMatch: this->rate_match = data; + spa_alsa_update_rate_match(this); break; default: return -ENOENT; @@ -819,6 +820,11 @@ io->status = SPA_STATUS_OK; } + else if (!spa_list_is_empty(&this->ready)) { + spa_alsa_write(this); + + io->status = SPA_STATUS_OK; + } return SPA_STATUS_HAVE_DATA; }
View file
pipewire-0.3.71.tar.gz/spa/plugins/alsa/alsa-pcm-source.c -> pipewire-0.3.72.tar.gz/spa/plugins/alsa/alsa-pcm-source.c
Changed
@@ -709,6 +709,7 @@ break; case SPA_IO_RateMatch: this->rate_match = data; + spa_alsa_update_rate_match(this); break; default: return -ENOENT;
View file
pipewire-0.3.71.tar.gz/spa/plugins/alsa/alsa-pcm.c -> pipewire-0.3.72.tar.gz/spa/plugins/alsa/alsa-pcm.c
Changed
@@ -477,6 +477,11 @@ .write = log_write, }; +static void silence_error_handler(const char *file, int line, + const char *function, int err, const char *fmt, ...) +{ +} + int spa_alsa_init(struct state *state, const struct spa_dict *info) { uint32_t i; @@ -546,6 +551,57 @@ return err; } +static int probe_pitch_ctl(struct state *state, const char* device_name) +{ + snd_ctl_elem_id_t *id; + /* TODO: Add configuration params for the control name and units */ + const char *elem_name = + state->stream == SND_PCM_STREAM_CAPTURE ? + "Capture Pitch 1000000" : + "Playback Pitch 1000000"; + int err; + + snd_lib_error_set_handler(silence_error_handler); + + err = snd_ctl_open(&state->ctl, device_name, SND_CTL_NONBLOCK); + if (err < 0) { + spa_log_info(state->log, "%s could not find ctl device: %s", + state->props.device, snd_strerror(err)); + state->ctl = NULL; + goto error; + } + + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_id_set_name(id, elem_name); + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_PCM); + + snd_ctl_elem_value_malloc(&state->pitch_elem); + snd_ctl_elem_value_set_id(state->pitch_elem, id); + + err = snd_ctl_elem_read(state->ctl, state->pitch_elem); + if (err < 0) { + spa_log_debug(state->log, "%s: did not find ctl %s: %s", + state->props.device, elem_name, snd_strerror(err)); + + snd_ctl_elem_value_free(state->pitch_elem); + state->pitch_elem = NULL; + + snd_ctl_close(state->ctl); + state->ctl = NULL; + goto error; + } + + snd_ctl_elem_value_set_integer(state->pitch_elem, 0, 1000000); + CHECK(snd_ctl_elem_write(state->ctl, state->pitch_elem), "snd_ctl_elem_write"); + state->last_rate = 1.0; + + spa_log_info(state->log, "%s: found ctl %s", state->props.device, elem_name); + err = 0; +error: + snd_lib_error_set_handler(NULL); + return err; +} + int spa_alsa_open(struct state *state, const char *params) { int err; @@ -588,6 +644,8 @@ state->sample_count = 0; state->sample_time = 0; + probe_pitch_ctl(state, device_name); + return 0; error_exit_close: @@ -622,6 +680,14 @@ state->have_format = false; state->opened = false; + if (state->pitch_elem) { + snd_ctl_elem_value_free(state->pitch_elem); + state->pitch_elem = NULL; + + snd_ctl_close(state->ctl); + state->ctl = NULL; + } + return err; } @@ -1665,6 +1731,41 @@ return match ? 0 : 1; } +int spa_alsa_update_rate_match(struct state *state) +{ + uint64_t pitch, last_pitch; + int err; + + if (!state->pitch_elem) + return -ENOENT; + + /* The rate/pitch defines the rate of input to output (if there were a + * resampler, it's the ratio of input samples to output samples). This + * means that to adjust the playback rate, we need to apply the inverse + * of the given rate. */ + if (state->stream == SND_PCM_STREAM_CAPTURE) { + pitch = 1000000 * state->rate_match->rate; + last_pitch = 1000000 * state->last_rate; + } else { + pitch = 1000000 / state->rate_match->rate; + last_pitch = 1000000 / state->last_rate; + } + + /* The pitch adjustment is limited to 1 ppm */ + if (pitch == last_pitch) + return 0; + + snd_ctl_elem_value_set_integer(state->pitch_elem, 0, pitch); + CHECK(snd_ctl_elem_write(state->ctl, state->pitch_elem), "snd_ctl_elem_write"); + + spa_log_trace_fp(state->log, "%s %u set rate to %g", + state->props.device, state->stream, state->rate_match->rate); + + state->last_rate = state->rate_match->rate; + + return 0; +} + static int set_swparams(struct state *state) { snd_pcm_t *hndl = state->hndl; @@ -1856,7 +1957,8 @@ return do_start(state); } -static int get_avail(struct state *state, uint64_t current_time) +#if 0 +static int get_avail(struct state *state, uint64_t current_time, snd_pcm_uframes_t *delay) { int res, missed; snd_pcm_sframes_t avail; @@ -1874,19 +1976,21 @@ } else { state->alsa_recovering = false; } + *delay = avail; return avail; } -#if 0 -static int get_avail_htimestamp(struct state *state, uint64_t current_time) +#else +static int get_avail(struct state *state, uint64_t current_time, snd_pcm_uframes_t *delay) { int res, missed; snd_pcm_uframes_t avail; snd_htimestamp_t tstamp; uint64_t then; + avail = snd_pcm_avail(state->hndl); if ((res = snd_pcm_htimestamp(state->hndl, &avail, &tstamp)) < 0) { - if ((res = alsa_recover(state, avail)) < 0) + if ((res = alsa_recover(state, res)) < 0) return res; if ((res = snd_pcm_htimestamp(state->hndl, &avail, &tstamp)) < 0) { if ((missed = ratelimit_test(&state->rate_limit, current_time)) >= 0) { @@ -1898,28 +2002,34 @@ } else { state->alsa_recovering = false; } + *delay = avail; if ((then = SPA_TIMESPEC_TO_NSEC(&tstamp)) != 0) { + int64_t diff; + if (then < current_time) - avail += (current_time - then) * state->rate / SPA_NSEC_PER_SEC; + diff = ((int64_t)(current_time - then)) * state->rate / SPA_NSEC_PER_SEC; else - avail -= (then - current_time) * state->rate / SPA_NSEC_PER_SEC; + diff = -((int64_t)(then - current_time)) * state->rate / SPA_NSEC_PER_SEC; + + spa_log_trace_fp(state->log, "%"PRIu64" %"PRIu64" %"PRIi64, current_time, then, diff); + + *delay += diff; } return SPA_MIN(avail, state->buffer_frames); } #endif -static int get_status(struct state *state, uint64_t current_time, +static int get_status(struct state *state, uint64_t current_time, snd_pcm_uframes_t *avail, snd_pcm_uframes_t *delay, snd_pcm_uframes_t *target) { - int avail; - - if ((avail = get_avail(state, current_time)) < 0) - return avail; + int res; + snd_pcm_uframes_t a, d; - avail = SPA_MIN(avail, (int)state->buffer_frames); + if ((res = get_avail(state, current_time, &d)) < 0) + return res; - *target = state->threshold + state->headroom; + a = SPA_MIN(res, (int)state->buffer_frames); if (state->resample && state->rate_match) { state->delay = state->rate_match->delay; @@ -1928,12 +2038,14 @@ state->delay = 0; state->read_size = state->threshold; } - if (state->stream == SND_PCM_STREAM_PLAYBACK) { - *delay = state->buffer_frames - avail; + *avail = state->buffer_frames - a; + *delay = state->buffer_frames - SPA_MIN(d, state->buffer_frames); + *target = state->threshold + state->headroom; } else { - *delay = avail; - *target = SPA_MAX(*target, state->read_size + state->headroom); + *avail = a; + *delay = d; + *target = SPA_MAX(state->threshold, state->read_size) + state->headroom; } *target = SPA_CLAMP(*target, state->min_delay, state->max_delay); return 0; @@ -2004,7 +2116,10 @@ else state->rate_match->rate = 1.0/corr; - SPA_FLAG_UPDATE(state->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE, state->matching); + if (state->pitch_elem && state->matching) + spa_alsa_update_rate_match(state); + else + SPA_FLAG_UPDATE(state->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE, state->matching); } state->next_time += state->threshold / corr * 1e9 / state->rate; @@ -2044,7 +2159,7 @@ if (spa_streq(state->position->clock.name, state->clock_name)) state->matching = false; - state->resample = ((uint32_t)state->rate != state->rate_denom) || state->matching; + state->resample = !state->pitch_elem && (((uint32_t)state->rate != state->rate_denom) || state->matching); spa_log_info(state->log, "driver clock:'%s'@%d our clock:'%s'@%d matching:%d resample:%d", state->position->clock.name, state->rate_denom, @@ -2105,11 +2220,11 @@ if (state->following && state->alsa_started) { uint64_t current_time; - snd_pcm_uframes_t delay, target; + snd_pcm_uframes_t avail, delay, target; current_time = state->position->clock.nsec; - if (SPA_UNLIKELY((res = get_status(state, current_time, &delay, &target)) < 0)) + if (SPA_UNLIKELY((res = get_status(state, current_time, &avail, &delay, &target)) < 0)) return res; if (SPA_UNLIKELY((res = update_time(state, current_time, delay, target, true)) < 0)) @@ -2124,16 +2239,17 @@ lev = SPA_LOG_LEVEL_INFO; if ((missed = ratelimit_test(&state->rate_limit, current_time)) >= 0) { - spa_log_lev(state->log, lev, "%s: follower delay:%ld target:%ld thr:%u, " - "resync (%d missed)", state->props.device, delay, + spa_log_lev(state->log, lev, "%s: follower avail:%lu delay:%ld " + "target:%ld thr:%u, resync (%d missed)", + state->props.device, avail, delay, target, state->threshold, missed); } - if (delay > target) - snd_pcm_rewind(state->hndl, delay - target); - else if (delay < target) - spa_alsa_silence(state, target - delay); - delay = target; + if (avail > target) + snd_pcm_rewind(state->hndl, avail - target); + else if (avail < target) + spa_alsa_silence(state, target - avail); + avail = target; state->alsa_sync = false; } else state->alsa_sync_warning = true; @@ -2344,11 +2460,9 @@ current_time = state->position->clock.nsec; - if ((res = get_status(state, current_time, &delay, &target)) < 0) + if ((res = get_status(state, current_time, &avail, &delay, &target)) < 0) return res; - avail = delay; - if (SPA_UNLIKELY((res = update_time(state, current_time, delay, target, true)) < 0)) return res; @@ -2366,11 +2480,11 @@ target, state->threshold, missed); } - if (delay < target) - max_read = target - delay; - else if (delay > target) - snd_pcm_forward(state->hndl, delay - target); - delay = target; + if (avail < target) + max_read = target - avail; + else if (avail > target) + snd_pcm_forward(state->hndl, avail - target); + avail = target; state->alsa_sync = false; } else state->alsa_sync_warning = true; @@ -2458,13 +2572,15 @@ } -static int handle_play(struct state *state, uint64_t current_time, +static int handle_play(struct state *state, uint64_t current_time, snd_pcm_uframes_t avail, snd_pcm_uframes_t delay, snd_pcm_uframes_t target) { + struct spa_io_buffers *io = state->io; int res; if (state->alsa_started && SPA_UNLIKELY(delay > target + state->max_error)) { - spa_log_trace(state->log, "%p: early wakeup %lu %lu", state, delay, target); + spa_log_trace(state->log, "%p: early wakeup %ld %lu %lu", state, + avail, delay, target); if (delay > target * 3) delay = target * 3; state->next_time = current_time + (delay - target) * SPA_NSEC_PER_SEC / state->rate; @@ -2474,32 +2590,24 @@ if (SPA_UNLIKELY((res = update_time(state, current_time, delay, target, false)) < 0)) return res; - if (spa_list_is_empty(&state->ready)) { - struct spa_io_buffers *io = state->io; - - spa_log_trace_fp(state->log, "%p: %d", state, io->status); + spa_log_trace_fp(state->log, "%p: %d", state, io->status); - update_sources(state, false); + update_sources(state, false); - io->status = SPA_STATUS_NEED_DATA; - res = spa_node_call_ready(&state->callbacks, SPA_STATUS_NEED_DATA); - } - else { - res = spa_alsa_write(state); - } - return res; + io->status = SPA_STATUS_NEED_DATA; + return spa_node_call_ready(&state->callbacks, SPA_STATUS_NEED_DATA); } -static int handle_capture(struct state *state, uint64_t current_time, +static int handle_capture(struct state *state, uint64_t current_time, snd_pcm_uframes_t avail, snd_pcm_uframes_t delay, snd_pcm_uframes_t target) { int res; struct spa_io_buffers *io; - if (SPA_UNLIKELY(delay < state->read_size)) { - spa_log_trace(state->log, "%p: early wakeup %ld %ld %d", state, delay, target, - state->read_size); - state->next_time = current_time + (target - delay) * SPA_NSEC_PER_SEC / + if (SPA_UNLIKELY(avail < state->read_size)) { + spa_log_trace(state->log, "%p: early wakeup %ld %ld %ld %d", state, + delay, avail, target, state->read_size); + state->next_time = current_time + (state->read_size - avail) * SPA_NSEC_PER_SEC / state->rate; return -EAGAIN; } @@ -2544,9 +2652,9 @@ static void alsa_wakeup_event(struct spa_source *source) { struct state *state = source->data; - snd_pcm_uframes_t delay, target; + snd_pcm_uframes_t avail, delay, target; uint64_t expire, current_time; - int res; + int res, missed; if (SPA_UNLIKELY(state->disable_tsched)) { /* ALSA poll fds need to be "demangled" to know whether it's a real wakeup */ @@ -2594,7 +2702,7 @@ return; } - if (SPA_UNLIKELY(get_status(state, current_time, &delay, &target) < 0)) { + if (SPA_UNLIKELY(get_status(state, current_time, &avail, &delay, &target) < 0)) { spa_log_error(state->log, "get_status error"); state->next_time += state->threshold * 1e9 / state->rate; goto done; @@ -2603,24 +2711,28 @@ #ifndef FASTPATH if (SPA_UNLIKELY(spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) { uint64_t nsec = get_time_ns(state); - spa_log_trace_fp(state->log, "%p: wakeup %lu %lu %"PRIu64" %"PRIu64" %"PRIi64 - " %d %"PRIi64, state, delay, target, nsec, nsec, + spa_log_trace_fp(state->log, "%p: wakeup %lu %lu %lu %"PRIu64" %"PRIu64" %"PRIi64 + " %d %"PRIi64, state, avail, delay, target, nsec, nsec, nsec - current_time, state->threshold, state->sample_count); } #endif if (state->stream == SND_PCM_STREAM_PLAYBACK) - handle_play(state, current_time, delay, target); + handle_play(state, current_time, avail, delay, target); else - handle_capture(state, current_time, delay, target); + handle_capture(state, current_time, avail, delay, target); done: if (!state->disable_tsched && (state->next_time > current_time + SPA_NSEC_PER_SEC || current_time > state->next_time + SPA_NSEC_PER_SEC)) { - spa_log_error(state->log, "%s: impossible timeout %lu %lu %"PRIu64" %"PRIu64" %"PRIi64 - " %d %"PRIi64, state->props.device, delay, target, current_time, state->next_time, - state->next_time - current_time, state->threshold, state->sample_count); + if ((missed = ratelimit_test(&state->rate_limit, current_time)) >= 0) { + spa_log_error(state->log, "%s: impossible timeout %lu %lu %lu %" + PRIu64" %"PRIu64" %"PRIi64" %d %"PRIi64" (%d missed)", + state->props.device, avail, delay, target, + current_time, state->next_time, state->next_time - current_time, + state->threshold, state->sample_count, missed); + } state->next_time = current_time + state->threshold * 1e9 / state->rate; } set_timeout(state, state->next_time);
View file
pipewire-0.3.71.tar.gz/spa/plugins/alsa/alsa-pcm.h -> pipewire-0.3.72.tar.gz/spa/plugins/alsa/alsa-pcm.h
Changed
@@ -218,6 +218,11 @@ struct spa_latency_info latency2; struct spa_process_latency_info process_latency; + + /* Rate match via an ALSA ctl */ + snd_ctl_t *ctl; + snd_ctl_elem_value_t *pitch_elem; + double last_rate; }; struct spa_pod *spa_alsa_enum_propinfo(struct state *state, @@ -230,6 +235,7 @@ const struct spa_pod *filter); int spa_alsa_set_format(struct state *state, struct spa_audio_info *info, uint32_t flags); +int spa_alsa_update_rate_match(struct state *state); int spa_alsa_init(struct state *state, const struct spa_dict *info); int spa_alsa_clear(struct state *state);
View file
pipewire-0.3.71.tar.gz/spa/plugins/audioconvert/audioadapter.c -> pipewire-0.3.72.tar.gz/spa/plugins/audioconvert/audioadapter.c
Changed
@@ -87,6 +87,7 @@ unsigned int add_listener:1; unsigned int have_format:1; unsigned int started:1; + unsigned int warned:1; unsigned int ready:1; unsigned int driver:1; unsigned int async:1; @@ -844,15 +845,18 @@ if ((res = negotiate_buffers(this)) < 0) return res; this->ready = true; + this->warned = false; break; case SPA_NODE_COMMAND_Suspend: this->started = false; this->ready = false; + this->warned = false; spa_log_debug(this->log, "%p: suspending", this); break; case SPA_NODE_COMMAND_Pause: this->started = false; this->ready = false; + this->warned = false; spa_log_debug(this->log, "%p: pausing", this); break; case SPA_NODE_COMMAND_Flush: @@ -1470,7 +1474,9 @@ int status = 0, fstatus, retry = 8; if (!this->started) { - spa_log_warn(this->log, "%p: scheduling stopped node", this); + if (!this->warned) + spa_log_warn(this->log, "%p: scheduling stopped node", this); + this->warned = true; return -EIO; }
View file
pipewire-0.3.71.tar.gz/spa/plugins/audioconvert/audioconvert.c -> pipewire-0.3.72.tar.gz/spa/plugins/audioconvert/audioconvert.c
Changed
@@ -44,8 +44,10 @@ #define MAX_DATAS SPA_AUDIO_MAX_CHANNELS #define MAX_PORTS (SPA_AUDIO_MAX_CHANNELS+1) -#define DEFAULT_MUTE false -#define DEFAULT_VOLUME VOLUME_NORM +#define DEFAULT_MUTE false +#define DEFAULT_VOLUME VOLUME_NORM +#define DEFAULT_MIN_VOLUME 0.0 +#define DEFAULT_MAX_VOLUME 10.0 struct volumes { bool mute; @@ -72,6 +74,8 @@ struct props { float volume; + float min_volume; + float max_volume; float prev_volume; uint32_t n_channels; uint32_t channel_mapSPA_AUDIO_MAX_CHANNELS; @@ -91,6 +95,8 @@ { uint32_t i; props->volume = DEFAULT_VOLUME; + props->min_volume = DEFAULT_MIN_VOLUME; + props->max_volume = DEFAULT_MAX_VOLUME; props->n_channels = 0; for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) props->channel_mapi = SPA_AUDIO_CHANNEL_UNKNOWN; @@ -446,7 +452,8 @@ SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_volume), SPA_PROP_INFO_description, SPA_POD_String("Volume"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0)); + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, + DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME)); break; case 1: param = spa_pod_builder_add_object(&b, @@ -460,7 +467,8 @@ SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_channelVolumes), SPA_PROP_INFO_description, SPA_POD_String("Channel Volumes"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, + DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); break; case 3: @@ -483,7 +491,8 @@ SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_monitorVolumes), SPA_PROP_INFO_description, SPA_POD_String("Monitor Volumes"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, + DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); break; case 6: @@ -498,7 +507,8 @@ SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_softVolumes), SPA_PROP_INFO_description, SPA_POD_String("Soft Volumes"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, + DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); break; case 8: @@ -521,13 +531,31 @@ case 10: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.min-volume"), + SPA_PROP_INFO_description, SPA_POD_String("Minimum volume level"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->min_volume, + DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 11: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.max-volume"), + SPA_PROP_INFO_description, SPA_POD_String("Maximum volume level"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->max_volume, + DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 12: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.normalize"), SPA_PROP_INFO_description, SPA_POD_String("Normalize Volumes"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_NORMALIZE)), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 11: + case 13: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.mix-lfe"), @@ -536,7 +564,7 @@ SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_MIX_LFE)), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 12: + case 14: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.upmix"), @@ -545,7 +573,7 @@ SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_UPMIX)), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 13: + case 15: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.lfe-cutoff"), @@ -554,7 +582,7 @@ this->mix.lfe_cutoff, 0.0, 1000.0), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 14: + case 16: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.fc-cutoff"), @@ -563,7 +591,7 @@ this->mix.fc_cutoff, 0.0, 48000.0), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 15: + case 17: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.rear-delay"), @@ -572,7 +600,7 @@ this->mix.rear_delay, 0.0, 1000.0), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 16: + case 18: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.stereo-widen"), @@ -581,7 +609,7 @@ this->mix.widen, 0.0, 1.0), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 17: + case 19: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.hilbert-taps"), @@ -590,7 +618,7 @@ this->mix.hilbert_taps, 0, MAX_TAPS), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 18: + case 20: spa_pod_builder_push_object(&b, &f0, SPA_TYPE_OBJECT_PropInfo, id); spa_pod_builder_add(&b, SPA_PROP_INFO_name, SPA_POD_String("channelmix.upmix-method"), @@ -609,14 +637,14 @@ spa_pod_builder_pop(&b, &f1); param = spa_pod_builder_pop(&b, &f0); break; - case 19: + case 21: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_rate), SPA_PROP_INFO_description, SPA_POD_String("Rate scaler"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Double(p->rate, 0.0, 10.0)); break; - case 20: + case 22: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_quality), @@ -625,7 +653,7 @@ SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->resample_quality, 0, 14), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 21: + case 23: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("resample.disable"), @@ -633,7 +661,7 @@ SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->resample_disabled), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 22: + case 24: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("dither.noise"), @@ -641,7 +669,7 @@ SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(this->dir1.conv.noise_bits, 0, 16), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 23: + case 25: spa_pod_builder_push_object(&b, &f0, SPA_TYPE_OBJECT_PropInfo, id); spa_pod_builder_add(&b, SPA_PROP_INFO_name, SPA_POD_String("dither.method"), @@ -659,7 +687,7 @@ spa_pod_builder_pop(&b, &f1); param = spa_pod_builder_pop(&b, &f0); break; - case 24: + case 26: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("debug.wav-path"), @@ -710,6 +738,10 @@ spa_pod_builder_bool(&b, this->monitor_channel_volumes); spa_pod_builder_string(&b, "channelmix.disable"); spa_pod_builder_bool(&b, this->props.mix_disabled); + spa_pod_builder_string(&b, "channelmix.min-volume"); + spa_pod_builder_float(&b, this->props.min_volume); + spa_pod_builder_string(&b, "channelmix.max-volume"); + spa_pod_builder_float(&b, this->props.max_volume); spa_pod_builder_string(&b, "channelmix.normalize"); spa_pod_builder_bool(&b, SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_NORMALIZE)); @@ -788,6 +820,10 @@ this->monitor_channel_volumes = spa_atob(s); else if (spa_streq(k, "channelmix.disable")) this->props.mix_disabled = spa_atob(s); + else if (spa_streq(k, "channelmix.min-volume")) + spa_atof(s, &this->props.min_volume); + else if (spa_streq(k, "channelmix.max-volume")) + spa_atof(s, &this->props.max_volume); else if (spa_streq(k, "channelmix.normalize")) SPA_FLAG_UPDATE(this->mix.options, CHANNELMIX_OPTION_NORMALIZE, spa_atob(s)); else if (spa_streq(k, "channelmix.mix-lfe")) @@ -1476,10 +1512,12 @@ vol = &this->props.channel; for (i = 0; i < vol->n_volumes; i++) - volumesi = vol->volumesdir->remapi; + volumesi = SPA_CLAMPF(vol->volumesdir->remapi, + this->props.min_volume, this->props.max_volume); - channelmix_set_volume(&this->mix, this->props.volume, vol->mute, - vol->n_volumes, volumes); + channelmix_set_volume(&this->mix, + SPA_CLAMPF(this->props.volume, this->props.min_volume, this->props.max_volume), + vol->mute, vol->n_volumes, volumes); this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; this->paramsIDX_Props.user++; @@ -1910,7 +1948,7 @@ struct port *port; struct spa_pod *param; struct spa_pod_builder b = { 0 }; - uint8_t buffer2048; + uint8_t buffer4096; struct spa_result_node_params result; uint32_t count = 0; int res; @@ -2026,8 +2064,13 @@ case SPA_PARAM_Latency: switch (result.index) { case 0: case 1: - param = spa_latency_build(&b, id, &this->dirresult.index.latency); + { + uint32_t idx = result.index; + if (port->is_monitor) + idx = idx ^ 1; + param = spa_latency_build(&b, id, &this->diridx.latency); break; + } default: return 0; } @@ -2227,7 +2270,7 @@ } } -static void queue_buffer(struct impl *this, struct port *port, uint32_t id) +static inline void queue_buffer(struct impl *this, struct port *port, uint32_t id) { struct buffer *b = &port->buffersid; @@ -2240,7 +2283,7 @@ SPA_FLAG_SET(b->flags, BUFFER_FLAG_QUEUED); } -static struct buffer *peek_buffer(struct impl *this, struct port *port) +static inline struct buffer *peek_buffer(struct impl *this, struct port *port) { struct buffer *b; @@ -2256,7 +2299,7 @@ return b; } -static void dequeue_buffer(struct impl *this, struct port *port, struct buffer *b) +static inline void dequeue_buffer(struct impl *this, struct port *port, struct buffer *b) { spa_list_remove(&b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_QUEUED); @@ -2491,7 +2534,7 @@ return end ? 1 : 0; } -static uint32_t resample_get_in_size(struct impl *this, bool passthrough, uint32_t out_size) +static inline uint32_t resample_get_in_size(struct impl *this, bool passthrough, uint32_t out_size) { uint32_t match_size = passthrough ? out_size : resample_in_len(&this->resample, out_size); spa_log_trace_fp(this->log, "%p: current match %u", this, match_size); @@ -2579,7 +2622,10 @@ r = 1.0; } if (this->rate_scale != r) { - spa_log_info(this->log, "scale %f->%f", this->rate_scale, r); + spa_log_info(this->log, "scale graph:%u in:%u out:%u scale:%f->%f", + this->io_position->clock.rate.denom, + this->resample.i_rate, this->resample.o_rate, + this->rate_scale, r); this->rate_scale = r; } } @@ -2742,7 +2788,8 @@ uint32_t mon_max; remap = n_mon_datas++; - volume = this->props.monitor.mute ? 0.0f : this->props.monitor.volumesremap; + volume = this->props.monitor.mute ? + 0.0f : this->props.monitor.volumesremap; if (this->monitor_channel_volumes) volume *= this->props.channel.mute ? 0.0f : this->props.channel.volumesremap;
View file
pipewire-0.3.71.tar.gz/spa/plugins/audioconvert/resample-native.c -> pipewire-0.3.72.tar.gz/spa/plugins/audioconvert/resample-native.c
Changed
@@ -169,8 +169,8 @@ r->func_name = data->info->inter_name; } - spa_log_trace_fp(r->log, "native %p: rate:%f in:%d out:%d phase:%d inc:%d frac:%d", r, - rate, data->in_rate, data->out_rate, data->phase, data->inc, data->frac); + spa_log_trace_fp(r->log, "native %p: rate:%f in:%d out:%d gcd:%d phase:%d inc:%d frac:%d", r, + rate, r->i_rate, r->o_rate, gcd, data->phase, data->inc, data->frac); } @@ -367,8 +367,8 @@ return -ENOTSUP; } - spa_log_debug(r->log, "native %p: q:%d in:%d out:%d n_taps:%d n_phases:%d features:%08x:%08x", - r, r->quality, in_rate, out_rate, n_taps, n_phases, + spa_log_debug(r->log, "native %p: q:%d in:%d out:%d gcd:%d n_taps:%d n_phases:%d features:%08x:%08x", + r, r->quality, r->i_rate, r->o_rate, gcd, n_taps, n_phases, r->cpu_flags, d->info->cpu_flags); r->cpu_flags = d->info->cpu_flags;
View file
pipewire-0.3.71.tar.gz/spa/plugins/bluez5/bluez-hardware.conf -> pipewire-0.3.72.tar.gz/spa/plugins/bluez5/bluez-hardware.conf
Changed
@@ -30,6 +30,7 @@ { name = "Air 1 Plus", no-features = hw-volume-mic }, { name = "AirPods", no-features = msbc-alt1, msbc-alt1-rtl }, { name = "AirPods Pro", no-features = msbc-alt1, msbc-alt1-rtl }, + { name = "Audio Pro_A26", address = "~^7c:96:d2:", no-features = hw-volume }, # doesn't remember volume, #pipewire-3225 { name = "AXLOIE Goin", no-features = msbc-alt1, msbc-alt1-rtl }, { name = "BAA 100", no-features = hw-volume }, # Buxton BAA 100, doesn't remember volume, #pipewire-1449 { name = "D50s", address = "~^00:13:ef:", no-features = hw-volume }, # volume has no effect, #pipewire-1562
View file
pipewire-0.3.71.tar.gz/spa/plugins/bluez5/bluez5-dbus.c -> pipewire-0.3.72.tar.gz/spa/plugins/bluez5/bluez5-dbus.c
Changed
@@ -203,6 +203,7 @@ static int spa_bt_transport_start_volume_timer(struct spa_bt_transport *transport); static int spa_bt_transport_stop_release_timer(struct spa_bt_transport *transport); static int spa_bt_transport_start_release_timer(struct spa_bt_transport *transport); +static void spa_bt_transport_commit_release_timer(struct spa_bt_transport *transport); static int device_start_timer(struct spa_bt_device *device); static int device_stop_timer(struct spa_bt_device *device); @@ -2589,6 +2590,13 @@ if (state >= SPA_BT_TRANSPORT_STATE_PENDING && old < SPA_BT_TRANSPORT_STATE_PENDING) transport_sync_volume(transport); + if (state < SPA_BT_TRANSPORT_STATE_ACTIVE) { + /* If transport becomes inactive, do any pending releases + * immediately, since the fd is not usable any more. + */ + spa_bt_transport_commit_release_timer(transport); + } + if (state == SPA_BT_TRANSPORT_STATE_ERROR) { uint64_t now = get_time_now(monitor); @@ -2719,6 +2727,27 @@ return res; } +static void spa_bt_transport_do_release(struct spa_bt_transport *transport) +{ + struct spa_bt_monitor *monitor = transport->monitor; + + spa_assert(transport->acquire_refcount >= 1); + spa_assert(transport->acquired); + + if (transport->acquire_refcount == 1) { + if (!transport->keepalive) { + spa_bt_transport_impl(transport, release, 0); + transport->acquired = false; + } else { + spa_log_debug(monitor->log, "transport %p: keepalive %s on release", + transport, transport->path); + } + } else { + spa_log_debug(monitor->log, "transport %p: delayed decref %s", transport, transport->path); + } + transport->acquire_refcount -= 1; +} + int spa_bt_transport_release(struct spa_bt_transport *transport) { struct spa_bt_monitor *monitor = transport->monitor; @@ -2736,8 +2765,15 @@ spa_assert(transport->acquire_refcount == 1); spa_assert(transport->acquired); - /* Postpone transport releases, since we might need it again soon */ - return spa_bt_transport_start_release_timer(transport); + /* Postpone active transport releases, since we might need it again soon. + * If not active, release now since it has to be reacquired before using again. + */ + if (transport->state == SPA_BT_TRANSPORT_STATE_ACTIVE) { + return spa_bt_transport_start_release_timer(transport); + } else { + spa_bt_transport_do_release(transport); + return 0; + } } static int spa_bt_transport_release_now(struct spa_bt_transport *transport) @@ -2808,25 +2844,9 @@ static void spa_bt_transport_release_timer_event(struct spa_source *source) { struct spa_bt_transport *transport = source->data; - struct spa_bt_monitor *monitor = transport->monitor; - - spa_assert(transport->acquire_refcount >= 1); - spa_assert(transport->acquired); spa_bt_transport_stop_release_timer(transport); - - if (transport->acquire_refcount == 1) { - if (!transport->keepalive) { - spa_bt_transport_impl(transport, release, 0); - transport->acquired = false; - } else { - spa_log_debug(monitor->log, "transport %p: keepalive %s on release", - transport, transport->path); - } - } else { - spa_log_debug(monitor->log, "transport %p: delayed decref %s", transport, transport->path); - } - transport->acquire_refcount -= 1; + spa_bt_transport_do_release(transport); } static int spa_bt_transport_start_release_timer(struct spa_bt_transport *transport) @@ -2842,6 +2862,17 @@ return stop_timeout_timer(transport->monitor, &transport->release_timer); } +static void spa_bt_transport_commit_release_timer(struct spa_bt_transport *transport) +{ + struct spa_bt_monitor *monitor = transport->monitor; + + /* Do release now if it is pending */ + if (transport->release_timer.data) { + spa_log_debug(monitor->log, "transport %p: commit pending release", transport); + spa_bt_transport_release_timer_event(&transport->release_timer); + } +} + static void spa_bt_transport_volume_changed(struct spa_bt_transport *transport) { struct spa_bt_monitor *monitor = transport->monitor; @@ -4906,10 +4937,28 @@ dbus_connection_unregister_object_path(monitor->conn, A2DP_OBJECT_MANAGER_PATH); } +static bool have_codec_endpoints(struct spa_bt_monitor *monitor, bool bap) +{ + const struct media_codec * const * const media_codecs = monitor->media_codecs; + int i; + + for (i = 0; media_codecsi; i++) { + const struct media_codec *codec = media_codecsi; + + if (codec->bap != bap) + continue; + if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SINK) || + endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SOURCE)) + return true; + } + return false; +} + static int adapter_register_application(struct spa_bt_adapter *a, bool bap) { const char *object_manager_path = bap ? BAP_OBJECT_MANAGER_PATH : A2DP_OBJECT_MANAGER_PATH; struct spa_bt_monitor *monitor = a->monitor; + const char *ep_type_name = (bap ? "LE Audio" : "A2DP"); DBusMessage *m; DBusMessageIter i, d; DBusPendingCall *call; @@ -4925,8 +4974,14 @@ return -ENOTSUP; } + if (!have_codec_endpoints(monitor, bap)) { + spa_log_warn(monitor->log, "No available %s codecs to register on adapter %s", + ep_type_name, a->path); + return -ENOENT; + } + spa_log_debug(monitor->log, "Registering bluez5 %s media application on adapter %s", - (bap ? "LE Audio" : "A2DP"), a->path); + ep_type_name, a->path); m = dbus_message_new_method_call(BLUEZ_SERVICE, a->path,
View file
pipewire-0.3.71.tar.gz/spa/plugins/bluez5/media-source.c -> pipewire-0.3.72.tar.gz/spa/plugins/bluez5/media-source.c
Changed
@@ -845,14 +845,23 @@ { uint64_t old = full ? this->info.change_mask : 0; char latency64; + char media_name256; + + spa_scnprintf( + media_name, + sizeof(media_name), + "%s (codec %s)", + ((this->transport && this->transport->device->name) ? + this->transport->device->name : this->codec->bap ? "BAP" : "A2DP"), + this->codec->description + ); struct spa_dict_item node_info_items = { { SPA_KEY_DEVICE_API, "bluez5" }, { SPA_KEY_MEDIA_CLASS, this->is_internal ? "Audio/Source/Internal" : this->is_input ? "Audio/Source" : "Stream/Output/Audio" }, { SPA_KEY_NODE_LATENCY, this->is_input ? "" : latency }, - { "media.name", ((this->transport && this->transport->device->name) ? - this->transport->device->name : this->codec->bap ? "BAP" : "A2DP") }, + { "media.name", media_name }, { SPA_KEY_NODE_DRIVER, this->is_input ? "true" : "false" }, };
View file
pipewire-0.3.71.tar.gz/spa/plugins/control/mixer.c -> pipewire-0.3.72.tar.gz/spa/plugins/control/mixer.c
Changed
@@ -656,10 +656,16 @@ d = inport->buffersinio->buffer_id.buffer->datas; if ((pod = spa_pod_from_data(d->data, d->maxsize, - d->chunk->offset, d->chunk->size)) == NULL) + d->chunk->offset, d->chunk->size)) == NULL) { + spa_log_trace_fp(this->log, NAME " %p: skip input idx:%d max:%u " + "offset:%u size:%u", this, i, + d->maxsize, d->chunk->offset, d->chunk->size); continue; - if (!spa_pod_is_sequence(pod)) + } + if (!spa_pod_is_sequence(pod)) { + spa_log_trace_fp(this->log, NAME " %p: skip input idx:%d", this, i); continue; + } seqn_seq = pod; ctrln_seq = spa_pod_control_first(&seqn_seq->body);
View file
pipewire-0.3.71.tar.gz/spa/plugins/support/node-driver.c -> pipewire-0.3.72.tar.gz/spa/plugins/support/node-driver.c
Changed
@@ -539,10 +539,10 @@ const char *s = info->itemsi.value; if (spa_streq(k, "node.freewheel")) { this->props.freewheel = spa_atob(s); - } else if (spa_streq(k, "clock.name")) { + } else if (spa_streq(k, "clock.name") && this->clock_fd < 0) { spa_scnprintf(this->props.clock_name, sizeof(this->props.clock_name), "%s", s); - } else if (spa_streq(k, "clock.id")) { + } else if (spa_streq(k, "clock.id") && this->clock_fd < 0) { this->props.clock_id = clock_name_to_id(s); if (this->props.clock_id == -1) { spa_log_warn(this->log, "unknown clock id '%s'", s); @@ -551,7 +551,7 @@ } else if (spa_streq(k, "clock.device")) { this->clock_fd = open(s, O_RDWR); if (this->clock_fd == -1) { - spa_log_warn(this->log, "failed to open clock device '%s'", s); + spa_log_info(this->log, "failed to open clock device '%s'", s); } else { this->props.clock_id = FD_TO_CLOCKID(this->clock_fd); }
View file
pipewire-0.3.71.tar.gz/src/daemon/pipewire-aes67.conf.in -> pipewire-0.3.72.tar.gz/src/daemon/pipewire-aes67.conf.in
Changed
@@ -18,10 +18,28 @@ #default.clock.quantum-limit = 8192 } -#context.spa-libs = { -# audio.convert.* = audioconvert/libspa-audioconvert -# support.* = support/libspa-support -#} +context.spa-libs = { + support.* = support/libspa-support +} + +context.objects = + # An example clock reading from /dev/ptp0. Another option is to sync the + # ptp clock to CLOCK_TAI and then set clock.id = tai. + # If both device and ID are given and available, device takes precedence + { factory = spa-node-factory + args = { + factory.name = support.node.driver + node.name = PTP0-Driver + node.group = pipewire.ptp0 + # This driver should only be used for network nodes marked with group + priority.driver = 0 + clock.name = "clock.system.ptp0" + clock.device = "/dev/ptp0" + clock.id = tai + object.export = true + } + } + context.modules = { name = libpipewire-module-rt @@ -35,12 +53,15 @@ } { name = libpipewire-module-protocol-native } { name = libpipewire-module-client-node } + { name = libpipewire-module-spa-node-factory } { name = libpipewire-module-adapter } { name = libpipewire-module-rtp-sap args = { local.ifname = eth0 sap.ip = 239.255.255.255 sap.port = 9875 + net.ttl = 32 + net.loop = true stream.rules = { @@ -55,10 +76,54 @@ media.class = "Audio/Source" device.api = aes67 sess.latency.msec = 10 + node.group = pipewire.ptp0 + } + } + }, + { + matches = + { + sess.sap.announce = true } + + actions = { + announce-stream = {} } - } + } } - } + }, + { name = libpipewire-module-rtp-sink + args = { + local.ifname = eth0 + destination.ip = 239.69.150.243 + destination.port = 5004 + net.mtu = 1280 + net.ttl = 32 + net.loop = true + sess.min-ptime = 1 + sess.max-ptime = 1 + sess.name = "PipeWire RTP stream" + sess.media = "audio" + sess.ts-refclk = "ptp=traceable" + sess.ts-offset = 0 + sess.ptime = 1 + sess.latency.msec = 1 + sess.announce = true + audio.format = "S24BE" + audio.rate = 48000 + audio.channels = 2 + audio.position = FL FR + + stream.props = { + node.name = "rtp-sink" + media.class = "Audio/Sink" + node.virtual = false + device.api = aes67 + sess.sap.announce = true + node.always-process = true + node.group = pipewire.ptp0 + } + } + },
View file
pipewire-0.3.71.tar.gz/src/daemon/pipewire-pulse.conf.in -> pipewire-0.3.72.tar.gz/src/daemon/pipewire-pulse.conf.in
Changed
@@ -129,6 +129,8 @@ # Possible quirks:" # force-s16-info forces sink and source info as S16 format # remove-capture-dont-move removes the capture DONT_MOVE flag + # block-source-volume blocks updates to source volume + # block-sink-volume blocks updates to sink volume #quirks = } } @@ -158,4 +160,8 @@ } } } + #{ + # matches = { application.process.binary = "Discord" } + # actions = { quirks = block-source-volume } + #}
View file
pipewire-0.3.71.tar.gz/src/daemon/pipewire.conf.in -> pipewire-0.3.72.tar.gz/src/daemon/pipewire.conf.in
Changed
@@ -221,19 +221,6 @@ node.freewheel = true } } - # An example clock reading from /dev/ptp0. Another option is to sync the - # ptp clock to CLOCK_TAI and then set clock.id = tai. - #{ factory = spa-node-factory - # args = { - # factory.name = support.node.driver - # node.name = PTP0-Driver - # node.group = pipewire.ptp0 - # priority.driver = 30000 - # clock.name = "clock.system.ptp0" - # #clock.id = tai - # clock.device = "/dev/ptp0" - # } - #} # This creates a new Source node. It will have input ports # that you can link, to provide audio for this source.
View file
pipewire-0.3.72.tar.gz/src/examples/internal.c
Added
@@ -0,0 +1,126 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +/* + title + In process pipewire graph + title + */ + +#include <stdio.h> +#include <errno.h> +#include <signal.h> +#include <math.h> + +#include <spa/utils/names.h> + +#include <pipewire/pipewire.h> +#include <pipewire/impl.h> + +struct data { + struct pw_main_loop *loop; + + struct pw_context *context; + struct pw_core *core; + + struct pw_proxy *source; + struct pw_proxy *sink; + struct pw_proxy *link; + + int res; +}; + +static void do_quit(void *userdata, int signal_number) +{ + struct data *data = userdata; + pw_main_loop_quit(data->loop); +} + +int main(int argc, char *argv) +{ + struct data data = { 0, }; + struct pw_properties *props; + const char *dev = "hw:0"; + + pw_init(&argc, &argv); + + data.loop = pw_main_loop_new(NULL); + + if (argc > 1) + dev = argv1; + + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); + + data.context = pw_context_new(pw_main_loop_get_loop(data.loop), + pw_properties_new( + PW_KEY_CONFIG_NAME, "client-rt.conf", + NULL), 0); + + pw_context_load_module(data.context, "libpipewire-module-spa-node-factory", NULL, NULL); + pw_context_load_module(data.context, "libpipewire-module-link-factory", NULL, NULL); + + data.core = pw_context_connect_self(data.context, NULL, 0); + if (data.core == NULL) { + fprintf(stderr, "can't connect: %m\n"); + data.res = -errno; + goto cleanup; + } + + props = pw_properties_new( + SPA_KEY_LIBRARY_NAME, "audiotestsrc/libspa-audiotestsrc", + SPA_KEY_FACTORY_NAME, "audiotestsrc", + PW_KEY_NODE_NAME, "test_source", + "Spa:Pod:Object:Param:Props:live", "false", + NULL); + data.source = pw_core_create_object(data.core, + "spa-node-factory", + PW_TYPE_INTERFACE_Node, + PW_VERSION_NODE, + &props->dict, 0); + pw_properties_free(props); + + props = pw_properties_new( + SPA_KEY_LIBRARY_NAME, "alsa/libspa-alsa", + SPA_KEY_FACTORY_NAME, SPA_NAME_API_ALSA_PCM_SINK, + PW_KEY_NODE_NAME, "alsa_sink", + "api.alsa.path", dev, + "priority.driver", "1000", + NULL); + data.sink = pw_core_create_object(data.core, + "spa-node-factory", + PW_TYPE_INTERFACE_Node, + PW_VERSION_NODE, + &props->dict, 0); + + while (true) { + if (pw_proxy_get_bound_id(data.source) != SPA_ID_INVALID && + pw_proxy_get_bound_id(data.sink) != SPA_ID_INVALID) + break; + + pw_loop_iterate(pw_main_loop_get_loop(data.loop), -1); + } + + pw_properties_clear(props); + pw_properties_setf(props, + PW_KEY_LINK_OUTPUT_NODE, "%d", pw_proxy_get_bound_id(data.source)); + pw_properties_setf(props, + PW_KEY_LINK_INPUT_NODE, "%d", pw_proxy_get_bound_id(data.sink)); + + data.link = pw_core_create_object(data.core, + "link-factory", + PW_TYPE_INTERFACE_Link, + PW_VERSION_LINK, + &props->dict, 0); + pw_properties_free(props); + + pw_main_loop_run(data.loop); + +cleanup: + pw_context_destroy(data.context); + pw_main_loop_destroy(data.loop); + pw_deinit(); + + return data.res; +}
View file
pipewire-0.3.71.tar.gz/src/examples/meson.build -> pipewire-0.3.72.tar.gz/src/examples/meson.build
Changed
@@ -13,6 +13,7 @@ 'video-src-reneg', 'video-src-fixate', 'video-play-fixate', + 'internal', 'export-sink', 'export-source', 'export-spa',
View file
pipewire-0.3.71.tar.gz/src/gst/gstpipewiresink.c -> pipewire-0.3.72.tar.gz/src/gst/gstpipewiresink.c
Changed
@@ -482,7 +482,7 @@ GstMemory *mem = gst_buffer_peek_memory (buffer, i); d->chunk->offset = mem->offset; d->chunk->size = mem->size; - d->chunk->stride = 0; + d->chunk->stride = pwsink->pool->video_info.stridei; } GstVideoMeta *meta = gst_buffer_get_video_meta (buffer);
View file
pipewire-0.3.71.tar.gz/src/modules/meson.build -> pipewire-0.3.72.tar.gz/src/modules/meson.build
Changed
@@ -14,12 +14,15 @@ 'module-example-sink.c', 'module-example-source.c', 'module-fallback-sink.c', + 'module-ffado-driver.c', 'module-filter-chain.c', 'module-jack-tunnel.c', 'module-jackdbus-detect.c', 'module-link-factory.c', 'module-loopback.c', 'module-metadata.c', + 'module-netjack2-driver.c', + 'module-netjack2-manager.c', 'module-pipe-tunnel.c', 'module-portal.c', 'module-profiler.c', @@ -185,7 +188,45 @@ summary({'jack-tunnel': build_module_jack_tunnel}, bool_yn: true, section: 'Optional Modules') +build_module_ffado_driver = libffado_dep.found() +if build_module_ffado_driver + pipewire_module_jack_tunnel = shared_library('pipewire-module-ffado-driver', + 'module-ffado-driver.c' , + include_directories : configinc, + install : true, + install_dir : modules_install_dir, + install_rpath: modules_install_dir, + dependencies : mathlib, dl_lib, pipewire_dep, libffado_dep, + ) +endif + +summary({'ffado-driver': build_module_ffado_driver}, bool_yn: true, section: 'Optional Modules') + +opus_custom_h = cc.has_header('opus/opus_custom.h', dependencies: opus_dep) +if opus_custom_h + opus_custom_dep = declare_dependency(compile_args: '-DHAVE_OPUS_CUSTOM', dependencies: opus_dep) +else + opus_custom_dep = dependency('', required: false) +endif +summary({'Opus with custom modes for NetJack2': opus_custom_dep}, bool_yn: true, section: 'Streaming between daemons') + +pipewire_module_netjack2_driver = shared_library('pipewire-module-netjack2-driver', + 'module-netjack2-driver.c' , + include_directories : configinc, + install : true, + install_dir : modules_install_dir, + install_rpath: modules_install_dir, + dependencies : spa_dep, mathlib, dl_lib, pipewire_dep, opus_custom_dep, +) +pipewire_module_netjack2_manager = shared_library('pipewire-module-netjack2-manager', + 'module-netjack2-manager.c' , + include_directories : configinc, + install : true, + install_dir : modules_install_dir, + install_rpath: modules_install_dir, + dependencies : spa_dep, mathlib, dl_lib, pipewire_dep, opus_custom_dep, +) pipewire_module_profiler = shared_library('pipewire-module-profiler', 'module-profiler.c', @@ -322,6 +363,8 @@ 'module-protocol-pulse/modules/module-switch-on-connect.c', 'module-protocol-pulse/modules/module-tunnel-sink.c', 'module-protocol-pulse/modules/module-tunnel-source.c', + 'module-protocol-pulse/modules/module-virtual-sink.c', + 'module-protocol-pulse/modules/module-virtual-source.c', 'module-protocol-pulse/modules/module-x11-bell.c', 'module-protocol-pulse/modules/module-zeroconf-discover.c',
View file
pipewire-0.3.71.tar.gz/src/modules/module-client-node/client-node.c -> pipewire-0.3.72.tar.gz/src/modules/module-client-node/client-node.c
Changed
@@ -50,7 +50,7 @@ struct mix { unsigned int valid:1; - uint32_t id; + uint32_t mix_id; struct port *port; uint32_t peer_id; uint32_t n_buffers; @@ -209,10 +209,10 @@ return mix; } -static void mix_init(struct mix *mix, struct port *p, uint32_t id) +static void mix_init(struct mix *mix, struct port *p, uint32_t mix_id) { mix->valid = true; - mix->id = id; + mix->mix_id = mix_id; mix->port = p; mix->n_buffers = 0; } @@ -281,7 +281,7 @@ if (!mix->valid) return; do_port_use_buffers(impl, port->direction, port->id, - mix->id, 0, NULL, 0); + mix->mix_id, 0, NULL, 0); mix->valid = false; } @@ -890,8 +890,8 @@ * directly */ spa_log_warn(impl->log, "exported node activation"); spa_system_clock_gettime(impl->data_system, CLOCK_MONOTONIC, &ts); - n->rt.activation->status = PW_NODE_ACTIVATION_TRIGGERED; - n->rt.activation->signal_time = SPA_TIMESPEC_TO_NSEC(&ts); + n->rt.target.activation->status = PW_NODE_ACTIVATION_TRIGGERED; + n->rt.target.activation->signal_time = SPA_TIMESPEC_TO_NSEC(&ts); if (SPA_UNLIKELY(spa_system_eventfd_write(n->rt.target.system, n->rt.target.fd, 1) < 0)) pw_log_warn("%p: write failed %m", impl); @@ -1080,8 +1080,6 @@ if (SPA_LIKELY(source->rmask & SPA_IO_IN)) { uint64_t cmd; struct pw_impl_node *node = impl->this.node; - struct pw_node_activation *a = node->rt.activation; - int status; if (SPA_UNLIKELY(spa_system_eventfd_read(impl->data_system, impl->data_source.fd, &cmd) < 0)) @@ -1090,9 +1088,15 @@ pw_log_info("(%s-%u) client missed %"PRIu64" wakeups", node->name, node->info.id, cmd - 1); - status = a->state0.status; - spa_log_trace_fp(impl->log, "%p: got ready %d", impl, status); - spa_node_call_ready(&impl->callbacks, status); + if (impl->resource && impl->resource->version < 5) { + struct pw_node_activation *a = node->rt.target.activation; + int status = a->state0.status; + spa_log_trace_fp(impl->log, "%p: got ready %d", impl, status); + spa_node_call_ready(&impl->callbacks, status); + } else { + spa_log_trace_fp(impl->log, "%p: got complete", impl); + pw_context_driver_emit_complete(node->context, node); + } } } @@ -1197,6 +1201,57 @@ spa_node_emit_result(&impl->hooks, seq, 0, 0, NULL); } +static void node_peer_added(void *data, struct pw_impl_node *peer) +{ + struct impl *impl = data; + struct pw_memblock *m; + + m = pw_mempool_import_block(impl->client->pool, peer->activation); + if (m == NULL) { + pw_log_warn("%p: can't ensure mem: %m", impl); + return; + } + + pw_log_debug("%p: peer %p/%p id:%u added mem_id:%u", impl, peer, + impl->this.node, peer->info.id, m->id); + + if (impl->resource == NULL) + return; + + pw_client_node_resource_set_activation(impl->resource, + peer->info.id, + peer->source.fd, + m->id, + 0, + sizeof(struct pw_node_activation)); +} + +static void node_peer_removed(void *data, struct pw_impl_node *peer) +{ + struct impl *impl = data; + struct pw_memblock *m; + + m = pw_mempool_find_fd(impl->client->pool, peer->activation->fd); + if (m == NULL) { + pw_log_warn("%p: unknown peer %p fd:%d", impl, peer, + peer->source.fd); + return; + } + + pw_log_debug("%p: peer %p/%p id:%u removed mem_id:%u", impl, peer, + impl->this.node, peer->info.id, m->id); + + if (impl->resource != NULL) { + pw_client_node_resource_set_activation(impl->resource, + peer->info.id, + -1, + SPA_ID_INVALID, + 0, + 0); + } + pw_memblock_unref(m); +} + void pw_impl_client_node_registered(struct pw_impl_client_node *this, struct pw_global *global) { struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); @@ -1225,6 +1280,8 @@ 0, sizeof(struct pw_node_activation)); + node_peer_added(impl, node); + if (impl->bind_node_id) { pw_global_bind(global, client, PW_PERM_ALL, impl->bind_node_version, impl->bind_node_id); @@ -1298,7 +1355,7 @@ pw_resource_destroy(impl->resource); if (impl->activation) - pw_memblock_unref(impl->activation); + pw_memblock_free(impl->activation); pw_array_for_each(area, &impl->io_areas) { if (*area) @@ -1351,6 +1408,11 @@ m->peer_id = mix->peer_id; + if (impl->resource && impl->resource->version >= 4) + pw_client_node_resource_port_set_mix_info(impl->resource, + mix->port.direction, mix->p->port_id, + mix->port.port_id, mix->peer_id, NULL); + pw_log_debug("%p: init mix id:%d io:%p base:%p", impl, mix->id, mix->io, area->map->ptr); @@ -1373,6 +1435,11 @@ if ((m = find_mix(port, mix->port.port_id)) == NULL || !m->valid) return -EINVAL; + if (impl->resource && impl->resource->version >= 4) + pw_client_node_resource_port_set_mix_info(impl->resource, + mix->port.direction, mix->p->port_id, + mix->port.port_id, SPA_ID_INVALID, NULL); + pw_map_remove(&impl->io_map, mix->id); m->valid = false; @@ -1458,13 +1525,7 @@ mix->io = data; else mix->io = NULL; - - if (mix->io != NULL && impl->resource && impl->resource->version >= 4) - pw_client_node_resource_port_set_mix_info(impl->resource, - direction, port->port_id, - mix->port.port_id, mix->peer_id, NULL); } - return do_port_set_io(impl, direction, port->port_id, mix->port.port_id, id, data, size); @@ -1544,62 +1605,6 @@ clear_port(impl, p); } -static void node_peer_added(void *data, struct pw_impl_node *peer) -{ - struct impl *impl = data; - struct pw_memblock *m; - - if (peer == impl->this.node) - return; - - m = pw_mempool_import_block(impl->client->pool, peer->activation); - if (m == NULL) { - pw_log_debug("%p: can't ensure mem: %m", impl); - return; - } - pw_log_debug("%p: peer %p id:%u added mem_id:%u", &impl->this, peer, - peer->info.id, m->id); - - if (impl->resource == NULL) - return; - - pw_client_node_resource_set_activation(impl->resource, - peer->info.id, - peer->source.fd, - m->id, - 0, - sizeof(struct pw_node_activation)); -} - -static void node_peer_removed(void *data, struct pw_impl_node *peer) -{ - struct impl *impl = data; - struct pw_memblock *m; - - if (peer == impl->this.node) - return; - - m = pw_mempool_find_fd(impl->client->pool, peer->activation->fd); - if (m == NULL) { - pw_log_warn("%p: unknown peer %p fd:%d", impl, peer, - peer->source.fd); - return; - } - pw_log_debug("%p: peer %p %u removed", impl, peer, - peer->info.id); - - if (impl->resource != NULL) { - pw_client_node_resource_set_activation(impl->resource, - peer->info.id, - -1, - SPA_ID_INVALID, - 0, - 0); - } - - pw_memblock_unref(m); -} - static void node_driver_changed(void *data, struct pw_impl_node *old, struct pw_impl_node *driver) { struct impl *impl = data;
View file
pipewire-0.3.71.tar.gz/src/modules/module-client-node/remote-node.c -> pipewire-0.3.72.tar.gz/src/modules/module-client-node/remote-node.c
Changed
@@ -40,13 +40,15 @@ struct spa_list link; struct pw_impl_port *port; uint32_t mix_id; + uint32_t peer_id; struct pw_impl_port_mix mix; struct pw_array buffers; - bool active; }; struct node_data { struct pw_context *context; + struct spa_hook context_listener; + struct pw_loop *data_loop; struct spa_system *data_system; @@ -91,7 +93,7 @@ struct link *l; spa_list_for_each(l, links, link) { - if (l->node_id == node_id) + if (l->target.id == node_id) return l; } return NULL; @@ -138,66 +140,24 @@ } pw_memmap_free(data->activation); - data->node->rt.activation = data->node->activation->map->ptr; + data->node->rt.target.activation = data->node->activation->map->ptr; spa_system_close(data->data_system, data->rtwritefd); data->have_transport = false; } -static void mix_init(struct mix *mix, struct pw_impl_port *port, uint32_t mix_id) +static void mix_init(struct mix *mix, struct pw_impl_port *port, + uint32_t mix_id, uint32_t peer_id) { pw_log_debug("port %p: mix init %d.%d", port, port->port_id, mix_id); mix->port = port; mix->mix_id = mix_id; + mix->peer_id = peer_id; pw_impl_port_init_mix(port, &mix->mix); - mix->active = false; pw_array_init(&mix->buffers, 32); pw_array_ensure_size(&mix->buffers, sizeof(struct buffer) * 64); } -static int -do_deactivate_mix(struct spa_loop *loop, - bool async, uint32_t seq, const void *data, size_t size, void *user_data) -{ - struct mix *mix = user_data; - spa_list_remove(&mix->mix.rt_link); - return 0; -} - -static int -deactivate_mix(struct node_data *data, struct mix *mix) -{ - if (mix->active) { - pw_log_debug("node %p: mix %p deactivate", data, mix); - pw_loop_invoke(data->data_loop, - do_deactivate_mix, SPA_ID_INVALID, NULL, 0, true, mix); - mix->active = false; - } - return 0; -} - -static int -do_activate_mix(struct spa_loop *loop, - bool async, uint32_t seq, const void *data, size_t size, void *user_data) -{ - struct mix *mix = user_data; - - spa_list_append(&mix->port->rt.mix_list, &mix->mix.rt_link); - return 0; -} - -static int -activate_mix(struct node_data *data, struct mix *mix) -{ - if (!mix->active) { - pw_log_debug("node %p: mix %p activate", data, mix); - pw_loop_invoke(data->data_loop, - do_activate_mix, SPA_ID_INVALID, NULL, 0, false, mix); - mix->active = true; - } - return 0; -} - static struct mix *find_mix(struct node_data *data, enum spa_direction direction, uint32_t port_id, uint32_t mix_id) { @@ -214,15 +174,13 @@ return NULL; } -static struct mix *ensure_mix(struct node_data *data, - enum spa_direction direction, uint32_t port_id, uint32_t mix_id) +static struct mix *create_mix(struct node_data *data, + enum spa_direction direction, uint32_t port_id, + uint32_t mix_id, uint32_t peer_id) { struct mix *mix; struct pw_impl_port *port; - if ((mix = find_mix(data, direction, port_id, mix_id))) - return mix; - port = pw_impl_node_find_port(data->node, direction, port_id); if (port == NULL) return NULL; @@ -234,13 +192,21 @@ mix = spa_list_first(&data->free_mix, struct mix, link); spa_list_remove(&mix->link); } - - mix_init(mix, port, mix_id); + mix_init(mix, port, mix_id, peer_id); spa_list_append(&data->mixdirection, &mix->link); return mix; } +static struct mix *ensure_mix(struct node_data *data, + enum spa_direction direction, uint32_t port_id, + uint32_t mix_id) +{ + struct mix *mix; + if ((mix = find_mix(data, direction, port_id, mix_id))) + return mix; + return create_mix(data, direction, port_id, mix_id, SPA_ID_INVALID); +} static int client_node_transport(void *_data, int readfd, int writefd, uint32_t mem_id, uint32_t offset, uint32_t size) @@ -258,7 +224,10 @@ return -errno; } - node->rt.activation = data->activation->ptr; + node->rt.target.activation = data->activation->ptr; + node->rt.position = &node->rt.target.activation->position; + node->info.id = node->rt.target.activation->position.clock.id; + node->rt.target.id = node->info.id; pw_log_debug("remote-node %p: fds:%d %d node:%u activation:%p", proxy, readfd, writefd, data->remote_id, data->activation->ptr); @@ -824,11 +793,6 @@ pw_log_debug("port %p: set io:%s new:%p old:%p", mix->port, spa_debug_type_find_name(spa_type_io, id), ptr, mix->mix.io); - if (id == SPA_IO_Buffers) { - if (ptr == NULL && mix->mix.io) - deactivate_mix(data, mix); - } - if ((res = spa_node_port_set_io(mix->port->mix, direction, mix->mix.port.port_id, id, ptr, size)) < 0) { if (res == -ENOTSUP) @@ -836,11 +800,6 @@ else goto exit_free; } - if (id == SPA_IO_Buffers) { - mix->mix.io = ptr; - if (ptr) - activate_mix(data, mix); - } exit_free: pw_memmap_free(old); exit: @@ -878,13 +837,6 @@ struct link *link; int res = 0; - if (data->remote_id == node_id) { - pw_log_debug("node %p: our activation %u: %u %u %u", node, node_id, - memid, offset, size); - spa_system_close(data->data_system, signalfd); - return 0; - } - if (memid == SPA_ID_INVALID) { mm = ptr = NULL; size = 0; @@ -897,7 +849,13 @@ } ptr = mm->ptr; } - pw_log_debug("node %p: set activation %d %p %u %u", node, node_id, ptr, offset, size); + if (data->remote_id == node_id) { + pw_log_debug("node %p: our activation %u: %u %p %u %u", node, node_id, + memid, ptr, offset, size); + } else { + pw_log_debug("node %p: set activation %u: %u %p %u %u", node, node_id, + memid, ptr, offset, size); + } if (ptr) { link = calloc(1, sizeof(struct link)); @@ -906,8 +864,8 @@ goto error_exit; } link->data = data; - link->node_id = node_id; link->map = mm; + link->target.id = node_id; link->target.activation = ptr; link->target.system = data->data_system; link->target.fd = signalfd; @@ -938,6 +896,47 @@ return res; } +static void clear_mix(struct node_data *data, struct mix *mix) +{ + pw_log_debug("port %p: mix clear %d.%d", mix->port, mix->port->port_id, mix->mix_id); + + spa_node_port_set_io(mix->port->mix, mix->mix.port.direction, + mix->mix.port.port_id, SPA_IO_Buffers, NULL, 0); + + spa_list_remove(&mix->link); + + clear_buffers(data, mix); + pw_array_clear(&mix->buffers); + + spa_list_append(&data->free_mix, &mix->link); + pw_impl_port_release_mix(mix->port, &mix->mix); +} + +static int client_node_port_set_mix_info(void *_data, + enum spa_direction direction, uint32_t port_id, + uint32_t mix_id, uint32_t peer_id, const struct spa_dict *props) +{ + struct node_data *data = _data; + struct mix *mix; + + pw_log_debug("%p: %d:%d:%d peer:%d", data, direction, port_id, mix_id, peer_id); + + mix = find_mix(data, direction, port_id, mix_id); + + if (peer_id == SPA_ID_INVALID) { + if (mix == NULL) + return -EINVAL; + clear_mix(data, mix); + } else { + if (mix != NULL) + return -EEXIST; + mix = create_mix(data, direction, port_id, mix_id, peer_id); + if (mix == NULL) + return -errno; + } + return 0; +} + static const struct pw_client_node_events client_node_events = { PW_VERSION_CLIENT_NODE_EVENTS, .transport = client_node_transport, @@ -951,6 +950,7 @@ .port_use_buffers = client_node_port_use_buffers, .port_set_io = client_node_port_set_io, .set_activation = client_node_set_activation, + .port_set_mix_info = client_node_port_set_mix_info, }; static void do_node_init(struct node_data *data) @@ -976,21 +976,6 @@ } } -static void clear_mix(struct node_data *data, struct mix *mix) -{ - pw_log_debug("port %p: mix clear %d.%d", mix->port, mix->port->port_id, mix->mix_id); - - deactivate_mix(data, mix); - - spa_list_remove(&mix->link); - - clear_buffers(data, mix); - pw_array_clear(&mix->buffers); - - spa_list_append(&data->free_mix, &mix->link); - pw_impl_port_release_mix(mix->port, &mix->mix); -} - static void clean_node(struct node_data *d) { struct mix *mix; @@ -1127,6 +1112,9 @@ spa_hook_remove(&data->proxy_client_node_listener); spa_hook_remove(&data->client_node_listener); + pw_context_driver_remove_listener(data->context, + &data->context_listener); + if (data->node) { spa_hook_remove(&data->node_listener); pw_impl_node_set_state(data->node, PW_NODE_STATE_SUSPENDED); @@ -1163,66 +1151,22 @@ .bound_props = client_node_bound_props, }; -static inline uint64_t get_time_ns(struct spa_system *system) +static void context_complete(void *data, struct pw_impl_node *node) { - struct timespec ts; - spa_system_clock_gettime(system, CLOCK_MONOTONIC, &ts); - return SPA_TIMESPEC_TO_NSEC(&ts); -} -static int node_ready(void *d, int status) -{ - struct node_data *data = d; - struct pw_impl_node *node = data->node; - struct pw_node_activation *a = node->rt.activation; - struct spa_system *data_system = data->data_system; - struct pw_impl_port *p; - - pw_log_trace_fp("node %p: ready driver:%d exported:%d status:%d", node, - node->driver, node->exported, status); - - if (status & SPA_STATUS_HAVE_DATA) { - spa_list_for_each(p, &node->rt.output_mix, rt.node_link) - spa_node_process_fast(p->mix); - } + struct node_data *d = data; + struct spa_system *data_system = d->data_system; - a->state0.status = status; - a->signal_time = get_time_ns(data_system); + if (node != d->node || !node->driving || + !SPA_FLAG_IS_SET(node->rt.target.activation->flags, PW_NODE_ACTIVATION_FLAG_PROFILER)) + return; - if (SPA_UNLIKELY(spa_system_eventfd_write(data_system, data->rtwritefd, 1) < 0)) + if (SPA_UNLIKELY(spa_system_eventfd_write(data_system, d->rtwritefd, 1) < 0)) pw_log_warn("node %p: write failed %m", node); - - return 0; } -static int node_reuse_buffer(void *data, uint32_t port_id, uint32_t buffer_id) -{ - return 0; -} - -static int node_xrun(void *d, uint64_t trigger, uint64_t delay, struct spa_pod *info) -{ - struct node_data *data = d; - struct pw_impl_node *node = data->node; - struct pw_node_activation *a = node->rt.activation; - - a->xrun_count++; - a->xrun_time = trigger; - a->xrun_delay = delay; - a->max_delay = SPA_MAX(a->max_delay, delay); - - pw_log_debug("node %p: XRun! count:%u time:%"PRIu64" delay:%"PRIu64" max:%"PRIu64, - node, a->xrun_count, trigger, delay, a->max_delay); - - pw_context_driver_emit_xrun(data->context, node); - - return 0; -} - -static const struct spa_node_callbacks node_callbacks = { - SPA_VERSION_NODE_CALLBACKS, - .ready = node_ready, - .reuse_buffer = node_reuse_buffer, - .xrun = node_xrun +static const struct pw_context_driver_events context_events = { + PW_VERSION_CONTEXT_DRIVER_EVENTS, + .complete = context_complete, }; static struct pw_proxy *node_export(struct pw_core *core, void *object, bool do_free, @@ -1273,13 +1217,16 @@ &data->proxy_client_node_listener, &proxy_client_node_events, data); - spa_node_set_callbacks(node->node, &node_callbacks, data); pw_impl_node_add_listener(node, &data->node_listener, &node_events, data); pw_client_node_add_listener(data->client_node, &data->client_node_listener, &client_node_events, data); + pw_context_driver_add_listener(data->context, + &data->context_listener, + &context_events, data); + do_node_init(data); return client_node;
View file
pipewire-0.3.71.tar.gz/src/modules/module-combine-stream.c -> pipewire-0.3.72.tar.gz/src/modules/module-combine-stream.c
Changed
@@ -965,8 +965,17 @@ struct stream *s; bool delay_changed = false; - if ((in = pw_stream_dequeue_buffer(impl->combine)) == NULL) { - pw_log_debug("out of buffers: %m"); + in = NULL; + while (true) { + struct pw_buffer *t; + if ((t = pw_stream_dequeue_buffer(impl->combine)) == NULL) + break; + if (in) + pw_stream_queue_buffer(impl->combine, in); + in = t; + } + if (in == NULL) { + pw_log_debug("%p: out of input buffers: %m", impl); return; } @@ -980,7 +989,7 @@ delay_changed = true; if ((out = pw_stream_dequeue_buffer(s->stream)) == NULL) { - pw_log_warn("out of playback buffers: %m"); + pw_log_warn("%p: out of playback buffers: %m", s); goto do_trigger; } @@ -1033,7 +1042,7 @@ bool delay_changed = false; if ((out = pw_stream_dequeue_buffer(impl->combine)) == NULL) { - pw_log_debug("out of buffers: %m"); + pw_log_debug("%p: out of output buffers: %m", impl); return; } @@ -1046,8 +1055,17 @@ if (check_stream_delay(s)) delay_changed = true; - if ((in = pw_stream_dequeue_buffer(s->stream)) == NULL) { - pw_log_warn("%p: out of capture buffers: %m", s); + in = NULL; + while (true) { + struct pw_buffer *t; + if ((t = pw_stream_dequeue_buffer(s->stream)) == NULL) + break; + if (in) + pw_stream_queue_buffer(s->stream, in); + in = t; + } + if (in == NULL) { + pw_log_debug("%p: out of input buffers: %m", s); continue; } s->ready = false; @@ -1357,6 +1375,8 @@ pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); if (pw_properties_get(props, "resample.prefill") == NULL) pw_properties_set(props, "resample.prefill", "true"); + if (pw_properties_get(props, "resample.disable") == NULL) + pw_properties_set(props, "resample.disable", "true"); if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL) { if (impl->mode == MODE_SINK)
View file
pipewire-0.3.72.tar.gz/src/modules/module-ffado-driver.c
Added
@@ -0,0 +1,1148 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <signal.h> +#include <limits.h> +#include <math.h> + +#include "config.h" + +#include <spa/utils/result.h> +#include <spa/utils/string.h> +#include <spa/utils/json.h> +#include <spa/debug/types.h> +#include <spa/pod/builder.h> +#include <spa/param/audio/format-utils.h> +#include <spa/param/latency-utils.h> +#include <spa/param/audio/raw.h> + +#include <pipewire/impl.h> +#include <pipewire/i18n.h> +#include <pipewire/private.h> +#include <pipewire/thread.h> + +#include <libffado/ffado.h> + +/** \page page_module_ffado_driver PipeWire Module: FFADO firewire audio driver + * + * The ffado-driver module provides a source or sink using the libffado library for + * reading and writing to firewire audio devices. + * + * ## Module Options + * + * - `driver.mode`: the driver mode, sink|source|duplex, default duplex + * - `ffado.devices`: array of devices to open, default hw:0 + * - `ffado.period-size`: period size,default 1024 + * - `ffado.period-num`: period number,default 3 + * - `ffado.sample-rate`: sample-rate, default 48000 + * - `ffado.slave-mode`: slave mode + * - `ffado.snoop-mode`: snoop mode + * - `ffado.verbose`: ffado verbose level + * - `latency.internal.input`: extra input latency in frames + * - `latency.internal.output`: extra output latency in frames + * - `source.props`: Extra properties for the source filter. + * - `sink.props`: Extra properties for the sink filter. + * + * ## General options + * + * Options with well-known behavior. + * + * - \ref PW_KEY_REMOTE_NAME + * - \ref SPA_KEY_AUDIO_POSITION + * - \ref PW_KEY_NODE_NAME + * - \ref PW_KEY_NODE_DESCRIPTION + * - \ref PW_KEY_NODE_GROUP + * - \ref PW_KEY_NODE_VIRTUAL + * - \ref PW_KEY_MEDIA_CLASS + * - \ref PW_KEY_TARGET_OBJECT to specify the remote node.name or serial.id to link to + * + * ## Example configuration of a duplex sink/source + * + *\code{.unparsed} + * context.modules = + * { name = libpipewire-module-ffado-driver + * args = { + * #driver.mode = duplex + * #ffado.devices = hw:0 + * #ffado.period-size = 1024 + * #ffado.period-num = 3 + * #ffado.sample-rate = 48000 + * #ffado.slave-mode = false + * #ffado.snoop-mode = false + * #ffado.verbose = 0 + * #latency.internal.input = 0 + * #latency.internal.output = 0 + * #audio.position = FL FR + * source.props = { + * # extra sink properties + * } + * sink.props = { + * # extra sink properties + * } + * } + * } + * + *\endcode + */ + +#define NAME "ffado-driver" + +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); +#define PW_LOG_TOPIC_DEFAULT mod_topic + +#define MAX_PORTS 128 + +#define DEFAULT_DEVICES " hw:0 " +#define DEFAULT_PERIOD_SIZE 1024 +#define DEFAULT_PERIOD_NUM 3 +#define DEFAULT_SAMPLE_RATE 48000 +#define DEFAULT_SLAVE_MODE false +#define DEFAULT_SNOOP_MODE false +#define DEFAULT_VERBOSE 0 + +#define DEFAULT_POSITION " FL FR " +#define DEFAULT_MIDI_PORTS 1 + +#define MODULE_USAGE "( remote.name=<remote> ) " \ + "( driver.mode=<sink|source|duplex> ) " \ + "( ffado.devices=<devices array size, default hw:0> ) " \ + "( ffado.period-size=<period size, default 1024> ) " \ + "( ffado.period-num=<period num, default 3> ) " \ + "( ffado.sample-rate=<sampe rate, default 48000> ) " \ + "( ffado.slave-mode=<slave mode, default false> ) " \ + "( ffado.snoop-mode=<snoop mode, default false> ) " \ + "( ffado.verbose=<verbose level, default 0> ) " \ + "( audio.position=<channel map> ) " \ + "( source.props=<properties> ) " \ + "( sink.props=<properties> ) " + + +static const struct spa_dict_item module_props = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { PW_KEY_MODULE_DESCRIPTION, "Create an FFADO based driver" }, + { PW_KEY_MODULE_USAGE, MODULE_USAGE }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + +struct port { + enum spa_direction direction; + struct spa_latency_info latency2; + bool latency_changed2; + unsigned int is_midi:1; + void *buffer; +}; + +struct volume { + bool mute; + uint32_t n_volumes; + float volumesSPA_AUDIO_MAX_CHANNELS; +}; + +struct stream { + struct impl *impl; + + enum spa_direction direction; + struct pw_properties *props; + struct pw_filter *filter; + struct spa_hook listener; + struct spa_audio_info_raw info; + uint32_t n_ports; + struct port *portsMAX_PORTS; + struct volume volume; + + unsigned int running:1; +}; + +struct impl { + struct pw_context *context; + struct pw_loop *main_loop; + struct spa_system *system; + struct spa_thread_utils *utils; + + ffado_device_info_t device_info; + ffado_options_t device_options; + ffado_device_t *dev; + +#define MODE_SINK (1<<0) +#define MODE_SOURCE (1<<1) +#define MODE_DUPLEX (MODE_SINK|MODE_SOURCE) + uint32_t mode; + struct pw_properties *props; + + struct pw_impl_module *module; + + struct spa_hook module_listener; + + struct pw_core *core; + struct spa_hook core_proxy_listener; + struct spa_hook core_listener; + + struct spa_io_position *position; + + uint32_t latency2; + + struct stream source; + struct stream sink; + + char *devicesFFADO_MAX_SPECSTRINGS; + uint32_t n_devices; + int32_t sample_rate; + int32_t period_size; + int32_t n_periods; + bool slave_mode; + bool snoop_mode; + uint32_t verbose; + + uint32_t input_latency; + uint32_t output_latency; + uint32_t quantum_limit; + + uint32_t pw_xrun; + uint32_t ffado_xrun; + uint32_t frame_time; + + pthread_t thread; + + unsigned int do_disconnect:1; + unsigned int done:1; + unsigned int triggered:1; + unsigned int new_xrun:1; + unsigned int fix_midi:1; +}; + +static void reset_volume(struct volume *vol, uint32_t n_volumes) +{ + uint32_t i; + vol->mute = false; + vol->n_volumes = n_volumes; + for (i = 0; i < n_volumes; i++) + vol->volumesi = 1.0f; +} + +static inline void do_volume(float *dst, const float *src, struct volume *vol, uint32_t ch, uint32_t n_samples) +{ + float v = vol->mute ? 0.0f : vol->volumesch; + + if (v == 0.0f || src == NULL) + memset(dst, 0, n_samples * sizeof(float)); + else if (v == 1.0f) + memcpy(dst, src, n_samples * sizeof(float)); + else { + uint32_t i; + for (i = 0; i < n_samples; i++) + dsti = srci * v; + } +} + +static inline void fix_midi_event(uint8_t *data, size_t size) +{ + /* fixup NoteOn with vel 0 */ + if (size > 2 && (data0 & 0xF0) == 0x90 && data2 == 0x00) { + data0 = 0x80 + (data0 & 0x0F); + data2 = 0x40; + } +} + +static void midi_to_ffado(struct impl *impl, float *dst, float *src, uint32_t n_samples) +{ + struct spa_pod *pod; + struct spa_pod_sequence *seq; + struct spa_pod_control *c; + + if (src == NULL) + return; + + if ((pod = spa_pod_from_data(src, n_samples * sizeof(float), 0, n_samples * sizeof(float))) == NULL) + return; + if (!spa_pod_is_sequence(pod)) + return; + + seq = (struct spa_pod_sequence*)pod; + + SPA_POD_SEQUENCE_FOREACH(seq, c) { + switch(c->type) { + case SPA_CONTROL_Midi: + { + uint8_t *data = SPA_POD_BODY(&c->value); + size_t size = SPA_POD_BODY_SIZE(&c->value); + + if (impl->fix_midi) + fix_midi_event(data, size); + + break; + } + default: + break; + } + } +} + +static void ffado_to_midi(float *dst, float *src, uint32_t size) +{ + struct spa_pod_builder b = { 0, }; + uint32_t i, count; + struct spa_pod_frame f; + + count = src ? 0 : 0; + + spa_pod_builder_init(&b, dst, size); + spa_pod_builder_push_sequence(&b, &f, 0); + for (i = 0; i < count; i++) { + } + spa_pod_builder_pop(&b, &f); +} + +static void stream_destroy(void *d) +{ + struct stream *s = d; + spa_hook_remove(&s->listener); + s->filter = NULL; +} + +static void stream_state_changed(void *d, enum pw_filter_state old, + enum pw_filter_state state, const char *error) +{ + struct stream *s = d; + struct impl *impl = s->impl; + switch (state) { + case PW_FILTER_STATE_ERROR: + case PW_FILTER_STATE_UNCONNECTED: + pw_impl_module_schedule_destroy(impl->module); + break; + case PW_FILTER_STATE_PAUSED: + s->running = false; + break; + case PW_FILTER_STATE_STREAMING: + s->running = true; + break; + default: + break; + } +} + +static void sink_process(void *d, struct spa_io_position *position) +{ + struct stream *s = d; + struct impl *impl = s->impl; + uint32_t i, n_samples = position->clock.duration; + + if (impl->mode & MODE_SINK && impl->triggered) { + impl->triggered = false; + return; + } + + for (i = 0; i < s->n_ports; i++) { + struct port *p = s->portsi; + float *src; + if (p == NULL) + continue; + + src = pw_filter_get_dsp_buffer(p, n_samples); + if (src == NULL) + continue; + + if (SPA_UNLIKELY(p->is_midi)) + midi_to_ffado(impl, p->buffer, src, n_samples); + else + do_volume(p->buffer, src, &s->volume, i, n_samples); + } + ffado_streaming_transfer_playback_buffers(impl->dev); + + pw_log_trace_fp("done %u", impl->frame_time); + if (impl->mode & MODE_SINK) { + impl->done = true; + } +} + +static void source_process(void *d, struct spa_io_position *position) +{ + struct stream *s = d; + struct impl *impl = s->impl; + uint32_t i, n_samples = position->clock.duration; + + if (impl->mode == MODE_SOURCE && !impl->triggered) { + pw_log_trace_fp("done %u", impl->frame_time); + impl->done = true; + return; + } + impl->triggered = false; + + ffado_streaming_transfer_capture_buffers(impl->dev); + + for (i = 0; i < s->n_ports; i++) { + struct port *p = s->portsi; + float *dst; + + if (p == NULL || p->buffer == NULL) + continue; + + dst = pw_filter_get_dsp_buffer(p, n_samples); + if (dst == NULL) + continue; + + if (SPA_UNLIKELY(p->is_midi)) + ffado_to_midi(dst, p->buffer, n_samples); + else + do_volume(dst, p->buffer, &s->volume, i, n_samples); + } +} + +static void stream_io_changed(void *data, void *port_data, uint32_t id, void *area, uint32_t size) +{ + struct stream *s = data; + struct impl *impl = s->impl; + if (port_data == NULL) { + switch (id) { + case SPA_IO_Position: + impl->position = area; + break; + default: + break; + } + } +} + +static void param_latency_changed(struct stream *s, const struct spa_pod *param, + struct port *port) +{ + struct spa_latency_info latency; + bool update = false; + enum spa_direction direction = port->direction; + + if (spa_latency_parse(param, &latency) < 0) + return; + + if (spa_latency_info_compare(&port->latencydirection, &latency)) { + port->latencydirection = latency; + port->latency_changeddirection = update = true; + } +} + +static void make_stream_ports(struct stream *s) +{ + struct impl *impl = s->impl; + struct pw_properties *props; + char name512; + uint8_t buffer1024; + struct spa_pod_builder b; + struct spa_latency_info latency; + const struct spa_pod *params2; + uint32_t i, n_params = 0; + bool is_midi; + + for (i = 0; i < s->n_ports; i++) { + struct port *port = s->portsi; + ffado_streaming_stream_type stream_type; + char portname256; + + if (port != NULL) { + s->portsi = NULL; + free(port->buffer); + pw_filter_remove_port(port); + } + + if (s->direction == PW_DIRECTION_INPUT) { + ffado_streaming_get_playback_stream_name(impl->dev, i, portname, sizeof(portname)); + stream_type = ffado_streaming_get_playback_stream_type(impl->dev, i); + snprintf(name, sizeof(name), "%s_out", portname); + } else { + ffado_streaming_get_capture_stream_name(impl->dev, i, portname, sizeof(portname)); + stream_type = ffado_streaming_get_capture_stream_type(impl->dev, i); + snprintf(name, sizeof(name), "%s_in", portname); + } + + switch (stream_type) { + case ffado_stream_type_audio: + props = pw_properties_new( + PW_KEY_FORMAT_DSP, "32 bit float mono audio", + PW_KEY_PORT_PHYSICAL, "true", + PW_KEY_PORT_NAME, name, + NULL); + is_midi = false; + break; + case ffado_stream_type_midi: + props = pw_properties_new( + PW_KEY_FORMAT_DSP, "8 bit raw midi", + PW_KEY_PORT_NAME, name, + PW_KEY_PORT_PHYSICAL, "true", + NULL); + + is_midi = true; + break; + default: + pw_log_info("not registering unknown stream %d %s (type %d)", i, + name, stream_type); + continue; + + } + latency = SPA_LATENCY_INFO(s->direction, + .min_quantum = 1, + .max_quantum = 1, + .min_rate = impl->latencys->direction, + .max_rate = impl->latencys->direction); + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + n_params = 0; + paramsn_params++ = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + + port = pw_filter_add_port(s->filter, + s->direction, + PW_FILTER_PORT_FLAG_MAP_BUFFERS, + sizeof(struct port), + props, params, n_params); + if (port == NULL) { + pw_log_error("Can't create port: %m"); + return; + } + + port->latencys->direction = latency; + port->is_midi = is_midi; + port->buffer = calloc(sizeof(float), impl->quantum_limit); + if (port->buffer == NULL) { + pw_log_error("Can't create port buffer: %m"); + return; + } + if (s->direction == PW_DIRECTION_INPUT) { + if (ffado_streaming_set_playback_stream_buffer(impl->dev, i, port->buffer)) + pw_log_error("cannot configure port buffer for %s", name); + + if (ffado_streaming_playback_stream_onoff(impl->dev, i, 1)) + pw_log_error("cannot enable port %s", name); + } else { + if (ffado_streaming_set_capture_stream_buffer(impl->dev, i, port->buffer)) + pw_log_error("cannot configure port buffer for %s", name); + + if (ffado_streaming_capture_stream_onoff(impl->dev, i, 1)) + pw_log_error("cannot enable port %s", name); + } + + s->portsi = port; + } +} + +static struct spa_pod *make_props_param(struct spa_pod_builder *b, + struct volume *vol) +{ + return spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, + SPA_PROP_mute, SPA_POD_Bool(vol->mute), + SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), + SPA_TYPE_Float, vol->n_volumes, vol->volumes)); +} + +static void parse_props(struct stream *s, const struct spa_pod *param) +{ + struct spa_pod_object *obj = (struct spa_pod_object *) param; + struct spa_pod_prop *prop; + uint8_t buffer1024; + struct spa_pod_builder b; + const struct spa_pod *params1; + + SPA_POD_OBJECT_FOREACH(obj, prop) { + switch (prop->key) { + case SPA_PROP_mute: + { + bool mute; + if (spa_pod_get_bool(&prop->value, &mute) == 0) + s->volume.mute = mute; + break; + } + case SPA_PROP_channelVolumes: + { + uint32_t n; + float volsSPA_AUDIO_MAX_CHANNELS; + if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, + vols, SPA_AUDIO_MAX_CHANNELS)) > 0) { + s->volume.n_volumes = n; + for (n = 0; n < s->volume.n_volumes; n++) + s->volume.volumesn = volsn; + } + break; + } + default: + break; + } + } + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + params0 = make_props_param(&b, &s->volume); + + pw_filter_update_params(s->filter, NULL, params, 1); +} + +static void stream_param_changed(void *data, void *port_data, uint32_t id, + const struct spa_pod *param) +{ + struct stream *s = data; + if (port_data != NULL) { + switch (id) { + case SPA_PARAM_Latency: + param_latency_changed(s, param, port_data); + break; + } + } else { + switch (id) { + case SPA_PARAM_PortConfig: + pw_log_debug("PortConfig"); + make_stream_ports(s); + break; + case SPA_PARAM_Props: + pw_log_debug("Props"); + parse_props(s, param); + break; + } + } +} + +static const struct pw_filter_events sink_events = { + PW_VERSION_FILTER_EVENTS, + .destroy = stream_destroy, + .state_changed = stream_state_changed, + .param_changed = stream_param_changed, + .io_changed = stream_io_changed, + .process = sink_process +}; + +static const struct pw_filter_events source_events = { + PW_VERSION_FILTER_EVENTS, + .destroy = stream_destroy, + .state_changed = stream_state_changed, + .param_changed = stream_param_changed, + .io_changed = stream_io_changed, + .process = source_process, +}; + +static int make_stream(struct stream *s, const char *name) +{ + struct impl *impl = s->impl; + uint32_t n_params; + const struct spa_pod *params4; + uint8_t buffer1024; + struct spa_pod_builder b; + struct spa_latency_info latency; + + spa_zero(latency); + n_params = 0; + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + s->filter = pw_filter_new(impl->core, name, s->props); + s->props = NULL; + if (s->filter == NULL) + return -errno; + + if (s->direction == PW_DIRECTION_INPUT) { + pw_filter_add_listener(s->filter, &s->listener, + &sink_events, s); + } else { + pw_filter_add_listener(s->filter, &s->listener, + &source_events, s); + } + + reset_volume(&s->volume, s->info.channels); + + n_params = 0; + paramsn_params++ = spa_format_audio_raw_build(&b, + SPA_PARAM_EnumFormat, &s->info); + paramsn_params++ = spa_format_audio_raw_build(&b, + SPA_PARAM_Format, &s->info); + paramsn_params++ = make_props_param(&b, &s->volume); + + return pw_filter_connect(s->filter, + PW_FILTER_FLAG_DRIVER | + PW_FILTER_FLAG_RT_PROCESS | + PW_FILTER_FLAG_CUSTOM_LATENCY, + params, n_params); +} + +static int create_filters(struct impl *impl) +{ + int res = 0; + + if (impl->mode & MODE_SINK) + res = make_stream(&impl->sink, "FFADO Sink"); + + if (impl->mode & MODE_SOURCE) + res = make_stream(&impl->source, "FFADO Source"); + + return res; +} + +static inline uint64_t get_time_ns(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return SPA_TIMESPEC_TO_NSEC(&ts); +} + +static void *ffado_process_thread(void *arg) +{ + struct impl *impl = arg; + bool source_running, sink_running; + uint64_t nsec; + + while (true) { + ffado_wait_response response; + + response = ffado_streaming_wait(impl->dev); + nsec = get_time_ns(); + + switch (response) { + case ffado_wait_ok: + break; + case ffado_wait_xrun: + pw_log_warn("FFADO xrun"); + break; + case ffado_wait_shutdown: + pw_log_info("FFADO shutdown"); + return NULL; + case ffado_wait_error: + default: + pw_log_error("FFADO error"); + return NULL; + } + source_running = impl->source.running; + sink_running = impl->sink.running; + + pw_log_trace_fp("process %d %u %u %p %d", impl->period_size, source_running, + sink_running, impl->position, impl->frame_time); + + if (impl->new_xrun) { + pw_log_warn("Xrun FFADO:%u PipeWire:%u", impl->ffado_xrun, impl->pw_xrun); + impl->new_xrun = false; + } + + if (impl->position) { + struct spa_io_clock *c = &impl->position->clock; + + c->nsec = nsec; + c->rate = SPA_FRACTION(1, impl->sample_rate); + c->position += impl->period_size; + c->duration = impl->period_size; + c->delay = 0; + c->rate_diff = 1.0; + c->next_nsec = nsec; + + c->target_rate = c->rate; + c->target_duration = c->duration; + } + if (impl->mode & MODE_SINK && sink_running) { + impl->done = false; + impl->triggered = true; + pw_filter_trigger_process(impl->sink.filter); + } else if (impl->mode == MODE_SOURCE && source_running) { + impl->done = false; + impl->triggered = true; + pw_filter_trigger_process(impl->source.filter); + } + } + return NULL; +} + +static int +do_schedule_destroy(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct impl *impl = user_data; + pw_impl_module_schedule_destroy(impl->module); + return 0; +} + +void module_schedule_destroy(struct impl *impl) +{ + pw_loop_invoke(impl->main_loop, do_schedule_destroy, 1, NULL, 0, false, impl); +} + +static int open_ffado_device(struct impl *impl) +{ + ffado_streaming_stream_type stream_type; + uint32_t i; + + spa_zero(impl->device_info); + impl->device_info.device_spec_strings = impl->devices; + impl->device_info.nb_device_spec_strings = impl->n_devices; + + spa_zero(impl->device_options); + impl->device_options.sample_rate = impl->sample_rate; + impl->device_options.period_size = impl->period_size; + impl->device_options.nb_buffers = impl->n_periods; + impl->device_options.realtime = 1; + impl->device_options.packetizer_priority = 88; + impl->device_options.verbose = impl->verbose; + impl->device_options.slave_mode = impl->slave_mode; + impl->device_options.snoop_mode = impl->snoop_mode; + + impl->dev = ffado_streaming_init(impl->device_info, impl->device_options); + if (impl->dev == NULL) { + pw_log_error("can't open FFADO device %s", impl->devices0); + return -EIO; + } + + if (impl->device_options.realtime) { + pw_log_info("Streaming thread running with Realtime scheduling, priority %d", + impl->device_options.packetizer_priority); + } else { + pw_log_info("Streaming thread running without Realtime scheduling"); + } + + ffado_streaming_set_audio_datatype(impl->dev, ffado_audio_datatype_float); + + impl->sample_rate = impl->device_options.sample_rate; + impl->source.info.rate = impl->sample_rate; + impl->sink.info.rate = impl->sample_rate; + + impl->source.info.channels = 0; + impl->source.n_ports = ffado_streaming_get_nb_capture_streams(impl->dev); + for (i = 0; i < impl->source.n_ports; i++) { + stream_type = ffado_streaming_get_capture_stream_type(impl->dev, i); + switch (stream_type) { + case ffado_stream_type_audio: + impl->source.info.channels++; + break; + default: + break; + } + } + impl->sink.info.channels = 0; + impl->sink.n_ports = ffado_streaming_get_nb_playback_streams(impl->dev); + for (i = 0; i < impl->sink.n_ports; i++) { + stream_type = ffado_streaming_get_playback_stream_type(impl->dev, i); + switch (stream_type) { + case ffado_stream_type_audio: + impl->sink.info.channels++; + break; + default: + break; + } + } + if (ffado_streaming_prepare(impl->dev)) { + pw_log_error("Could not prepare streaming"); + return -EIO; + } + return 0; +} + +static int start_ffado_device(struct impl *impl) +{ + struct spa_thread *thr; + + if (ffado_streaming_start(impl->dev)) { + pw_log_error("Could not start streaming"); + return -EIO; + } + + thr = spa_thread_utils_create(impl->utils, NULL, ffado_process_thread, impl); + impl->thread = (pthread_t)thr; + if (thr == NULL) { + pw_log_error("%p: can't create thread: %m", impl); + return -errno; + } + spa_thread_utils_acquire_rt(impl->utils, thr, -1); + + return 0; +} + +static int stop_ffado_device(struct impl *impl) +{ + if (ffado_streaming_stop(impl->dev)) { + pw_log_error("Could not stop streaming"); + } + spa_thread_utils_join(impl->utils, (struct spa_thread*)impl->thread, NULL); + + return 0; +} + +static void core_error(void *data, uint32_t id, int seq, int res, const char *message) +{ + struct impl *impl = data; + + pw_log_error("error id:%u seq:%d res:%d (%s): %s", + id, seq, res, spa_strerror(res), message); + + if (id == PW_ID_CORE && res == -EPIPE) + pw_impl_module_schedule_destroy(impl->module); +} + +static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .error = core_error, +}; + +static void core_destroy(void *d) +{ + struct impl *impl = d; + spa_hook_remove(&impl->core_listener); + impl->core = NULL; + pw_impl_module_schedule_destroy(impl->module); +} + +static const struct pw_proxy_events core_proxy_events = { + .destroy = core_destroy, +}; + +static void impl_destroy(struct impl *impl) +{ + uint32_t i; + + if (impl->dev) { + stop_ffado_device(impl); + ffado_streaming_finish(impl->dev); + impl->dev = NULL; + } + if (impl->source.filter) + pw_filter_destroy(impl->source.filter); + if (impl->sink.filter) + pw_filter_destroy(impl->sink.filter); + if (impl->core && impl->do_disconnect) + pw_core_disconnect(impl->core); + + pw_properties_free(impl->sink.props); + pw_properties_free(impl->source.props); + pw_properties_free(impl->props); + + for (i = 0; i < impl->n_devices; i++) + free(impl->devicesi); + free(impl); +} + +static void module_destroy(void *data) +{ + struct impl *impl = data; + spa_hook_remove(&impl->module_listener); + impl_destroy(impl); +} + +static const struct pw_impl_module_events module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = module_destroy, +}; + +static uint32_t channel_from_name(const char *name) +{ + int i; + for (i = 0; spa_type_audio_channeli.name; i++) { + if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_channeli.name))) + return spa_type_audio_channeli.type; + } + return SPA_AUDIO_CHANNEL_UNKNOWN; +} + +static void parse_devices(struct impl *impl, const char *val, size_t len) +{ + struct spa_json it2; + char vFFADO_MAX_SPECSTRING_LENGTH; + + spa_json_init(&it0, val, len); + if (spa_json_enter_array(&it0, &it1) <= 0) + spa_json_init(&it1, val, len); + + impl->n_devices = 0; + while (spa_json_get_string(&it1, v, sizeof(v)) > 0 && + impl->n_devices < FFADO_MAX_SPECSTRINGS) { + impl->devicesimpl->n_devices++ = strdup(v); + } +} + +static void parse_position(struct spa_audio_info_raw *info, const char *val, size_t len) +{ + struct spa_json it2; + char v256; + + spa_json_init(&it0, val, len); + if (spa_json_enter_array(&it0, &it1) <= 0) + spa_json_init(&it1, val, len); + + info->channels = 0; + while (spa_json_get_string(&it1, v, sizeof(v)) > 0 && + info->channels < SPA_AUDIO_MAX_CHANNELS) { + info->positioninfo->channels++ = channel_from_name(v); + } +} + +static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +{ + const char *str; + + spa_zero(*info); + info->format = SPA_AUDIO_FORMAT_F32P; + info->rate = 0; + info->channels = pw_properties_get_uint32(props, PW_KEY_AUDIO_CHANNELS, info->channels); + info->channels = SPA_MIN(info->channels, SPA_AUDIO_MAX_CHANNELS); + if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) != NULL) + parse_position(info, str, strlen(str)); + if (info->channels == 0) + parse_position(info, DEFAULT_POSITION, strlen(DEFAULT_POSITION)); +} + +static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) +{ + const char *str; + if ((str = pw_properties_get(props, key)) != NULL) { + if (pw_properties_get(impl->sink.props, key) == NULL) + pw_properties_set(impl->sink.props, key, str); + if (pw_properties_get(impl->source.props, key) == NULL) + pw_properties_set(impl->source.props, key, str); + } +} + +SPA_EXPORT +int pipewire__module_init(struct pw_impl_module *module, const char *args) +{ + struct pw_context *context = pw_impl_module_get_context(module); + struct pw_properties *props = NULL; + struct impl *impl; + const char *str; + int res; + + PW_LOG_TOPIC_INIT(mod_topic); + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + return -errno; + + pw_log_debug("module %p: new %s", impl, args); + + if (args == NULL) + args = ""; + + props = pw_properties_new_string(args); + if (props == NULL) { + res = -errno; + pw_log_error( "can't create properties: %m"); + goto error; + } + impl->props = props; + str = pw_properties_get(props, "ffado.devices"); + if (str == NULL) + str = DEFAULT_DEVICES; + parse_devices(impl, str, strlen(str)); + + impl->period_size = pw_properties_get_int32(props, + "ffado.period-size", DEFAULT_PERIOD_SIZE); + impl->n_periods = pw_properties_get_int32(props, + "ffado.period-num", DEFAULT_PERIOD_NUM); + impl->sample_rate = pw_properties_get_int32(props, + "ffado.sample-rate", DEFAULT_SAMPLE_RATE); + impl->slave_mode = pw_properties_get_bool(props, + "ffado.slave-mode", DEFAULT_SLAVE_MODE); + impl->snoop_mode = pw_properties_get_bool(props, + "ffado.snoop-mode", DEFAULT_SNOOP_MODE); + impl->verbose = pw_properties_get_uint32(props, + "ffado.verbose", DEFAULT_VERBOSE); + impl->input_latency = pw_properties_get_uint32(props, + "latency.internal.input", 0); + impl->output_latency = pw_properties_get_uint32(props, + "latency.internal.output", 0); + impl->quantum_limit = 8192; + impl->utils = pw_thread_utils_get(); + + impl->sink.props = pw_properties_new(NULL, NULL); + impl->source.props = pw_properties_new(NULL, NULL); + if (impl->source.props == NULL || impl->sink.props == NULL) { + res = -errno; + pw_log_error( "can't create properties: %m"); + goto error; + } + + impl->module = module; + impl->context = context; + impl->main_loop = pw_context_get_main_loop(context); + impl->system = impl->main_loop->system; + + impl->source.impl = impl; + impl->source.direction = PW_DIRECTION_OUTPUT; + impl->sink.impl = impl; + impl->sink.direction = PW_DIRECTION_INPUT; + + impl->mode = MODE_DUPLEX; + if ((str = pw_properties_get(props, "driver.mode")) != NULL) { + if (spa_streq(str, "source")) { + impl->mode = MODE_SOURCE; + } else if (spa_streq(str, "sink")) { + impl->mode = MODE_SINK; + } else if (spa_streq(str, "duplex")) { + impl->mode = MODE_DUPLEX; + } else { + pw_log_error("invalid driver.mode '%s'", str); + res = -EINVAL; + goto error; + } + } + + if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) + pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); + if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL) + pw_properties_set(props, PW_KEY_NODE_GROUP, "ffado-group"); + if (pw_properties_get(props, PW_KEY_NODE_ALWAYS_PROCESS) == NULL) + pw_properties_set(props, PW_KEY_NODE_ALWAYS_PROCESS, "true"); + + pw_properties_set(impl->sink.props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); + pw_properties_set(impl->sink.props, PW_KEY_PRIORITY_DRIVER, "35001"); + pw_properties_set(impl->sink.props, PW_KEY_NODE_NAME, "ffado_sink"); + pw_properties_set(impl->sink.props, PW_KEY_NODE_DESCRIPTION, "FFADO Sink"); + + pw_properties_set(impl->source.props, PW_KEY_MEDIA_CLASS, "Audio/Source"); + pw_properties_set(impl->source.props, PW_KEY_PRIORITY_DRIVER, "35000"); + pw_properties_set(impl->source.props, PW_KEY_NODE_NAME, "ffado_source"); + pw_properties_set(impl->source.props, PW_KEY_NODE_DESCRIPTION, "FFADO Source"); + + if ((str = pw_properties_get(props, "sink.props")) != NULL) + pw_properties_update_string(impl->sink.props, str, strlen(str)); + if ((str = pw_properties_get(props, "source.props")) != NULL) + pw_properties_update_string(impl->source.props, str, strlen(str)); + + copy_props(impl, props, PW_KEY_NODE_ALWAYS_PROCESS); + copy_props(impl, props, PW_KEY_NODE_GROUP); + copy_props(impl, props, PW_KEY_NODE_VIRTUAL); + + parse_audio_info(impl->source.props, &impl->source.info); + parse_audio_info(impl->sink.props, &impl->sink.info); + + impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); + if (impl->core == NULL) { + str = pw_properties_get(props, PW_KEY_REMOTE_NAME); + impl->core = pw_context_connect(impl->context, + pw_properties_new( + PW_KEY_REMOTE_NAME, str, + NULL), + 0); + impl->do_disconnect = true; + } + if (impl->core == NULL) { + res = -errno; + pw_log_error("can't connect: %m"); + goto error; + } + + pw_proxy_add_listener((struct pw_proxy*)impl->core, + &impl->core_proxy_listener, + &core_proxy_events, impl); + pw_core_add_listener(impl->core, + &impl->core_listener, + &core_events, impl); + + if ((res = open_ffado_device(impl)) < 0) + goto error; + + if ((res = create_filters(impl)) < 0) + goto error; + + if ((res = start_ffado_device(impl)) < 0) + goto error; + + pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); + + pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); + + return 0; + +error: + impl_destroy(impl); + return res; +}
View file
pipewire-0.3.71.tar.gz/src/modules/module-filter-chain.c -> pipewire-0.3.72.tar.gz/src/modules/module-filter-chain.c
Changed
@@ -166,6 +166,9 @@ * are read-only except for the bq_raw biquad, which can configure default values * depending on the graph rate and change those at runtime. * + * We refer to https://arachnoid.com/BiQuadDesigner/index.html for an explanation of + * the controls. + * * The following labels can be used: * * - `bq_lowpass` a lowpass filter. @@ -685,7 +688,16 @@ struct graph_port *port; struct spa_data *bd; - if ((in = pw_stream_dequeue_buffer(impl->capture)) == NULL) + 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) @@ -2454,7 +2466,12 @@ parse_audio_info(impl->capture_props, &impl->capture_info); parse_audio_info(impl->playback_props, &impl->playback_info); - if (impl->capture_info.rate && !impl->playback_info.rate) + if (!impl->capture_info.rate && !impl->playback_info.rate) { + if (pw_properties_get(impl->playback_props, "resample.disable") == NULL) + pw_properties_set(impl->playback_props, "resample.disable", "true"); + if (pw_properties_get(impl->capture_props, "resample.disable") == NULL) + pw_properties_set(impl->capture_props, "resample.disable", "true"); + } else if (impl->capture_info.rate && !impl->playback_info.rate) impl->playback_info.rate = impl->capture_info.rate; else if (impl->playback_info.rate && !impl->capture_info.rate) impl->capture_info.rate = !impl->playback_info.rate;
View file
pipewire-0.3.71.tar.gz/src/modules/module-jack-tunnel.c -> pipewire-0.3.72.tar.gz/src/modules/module-jack-tunnel.c
Changed
@@ -193,6 +193,7 @@ uint32_t jack_xrun; unsigned int do_disconnect:1; + unsigned int triggered:1; unsigned int done:1; unsigned int new_xrun:1; unsigned int fix_midi:1; @@ -323,6 +324,11 @@ struct impl *impl = s->impl; uint32_t i, n_samples = position->clock.duration; + if (impl->mode & MODE_SINK && impl->triggered) { + impl->triggered = false; + return; + } + for (i = 0; i < s->n_ports; i++) { struct port *p = s->portsi; float *src, *dst; @@ -342,7 +348,7 @@ else do_volume(dst, src, &s->volume, i, n_samples); } - pw_log_trace_fp("done %u", impl->frame_time); + pw_log_trace_fp("done %u %u", impl->frame_time, n_samples); if (impl->mode & MODE_SINK) { impl->done = true; jack.cycle_signal(impl->client, 0); @@ -355,6 +361,14 @@ struct impl *impl = s->impl; uint32_t i, n_samples = position->clock.duration; + if (impl->mode == MODE_SOURCE && !impl->triggered) { + pw_log_trace_fp("done %u", impl->frame_time); + impl->done = true; + jack.cycle_signal(impl->client, 0); + return; + } + impl->triggered = false; + for (i = 0; i < s->n_ports; i++) { struct port *p = s->portsi; float *src, *dst; @@ -373,11 +387,6 @@ else do_volume(dst, src, &s->volume, i, n_samples); } - pw_log_trace_fp("done %u", impl->frame_time); - if (impl->mode == MODE_SOURCE) { - impl->done = true; - jack.cycle_signal(impl->client, 0); - } } static void stream_io_changed(void *data, void *port_data, uint32_t id, void *area, uint32_t size) @@ -606,9 +615,7 @@ const struct spa_pod *params4; uint8_t buffer1024; struct spa_pod_builder b; - struct spa_latency_info latency; - spa_zero(latency); n_params = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); @@ -703,11 +710,14 @@ } if (impl->mode & MODE_SINK && sink_running) { impl->done = false; + impl->triggered = true; pw_filter_trigger_process(impl->sink.filter); } else if (impl->mode == MODE_SOURCE && source_running) { impl->done = false; + impl->triggered = true; pw_filter_trigger_process(impl->source.filter); } else { + pw_log_trace_fp("done %d", nframes); jack.cycle_signal(impl->client, 0); } } @@ -801,9 +811,9 @@ jack.port_get_latency_range(port->jack_port, mode, &range); - latency.direction = s->direction; - latency.min_rate = range.min; - latency.max_rate = range.max; + latency = SPA_LATENCY_INFO(s->direction, + .min_rate = range.min, + .max_rate = range.max); pw_log_debug("port latency %d %d %d", mode, range.min, range.max); if (spa_latency_info_compare(&latency, &port->latencys->direction)) {
View file
pipewire-0.3.71.tar.gz/src/modules/module-jackdbus-detect.c -> pipewire-0.3.72.tar.gz/src/modules/module-jackdbus-detect.c
Changed
@@ -38,7 +38,7 @@ * ## Example configuration *\code{.unparsed} * context.modules = - * { name = libpipewire-jackdbus-detect + * { name = libpipewire-module-jackdbus-detect * args { * #jack.server = null * #tunnel.mode = duplex
View file
pipewire-0.3.71.tar.gz/src/modules/module-loopback.c -> pipewire-0.3.72.tar.gz/src/modules/module-loopback.c
Changed
@@ -180,6 +180,7 @@ unsigned int do_disconnect:1; unsigned int recalc_delay:1; + struct spa_audio_info_raw delay_info; float target_delay; struct spa_ringbuffer buffer; uint8_t *buffer_data; @@ -195,7 +196,7 @@ static void recalculate_delay(struct impl *impl) { - uint32_t target = impl->capture_info.rate * impl->target_delay, cdelay, pdelay; + uint32_t target = impl->delay_info.rate * impl->target_delay, cdelay, pdelay; uint32_t delay, w; struct pw_time pwt; @@ -232,11 +233,20 @@ impl->recalc_delay = false; } - if ((in = pw_stream_dequeue_buffer(impl->capture)) == NULL) - pw_log_debug("out of capture buffers: %m"); + 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("out of playback buffers: %m"); + pw_log_debug("%p: out of playback buffers: %m", impl); if (in != NULL && out != NULL) { uint32_t outsize = UINT32_MAX; @@ -347,11 +357,11 @@ static void recalculate_buffer(struct impl *impl) { if (impl->target_delay > 0.0f) { - uint32_t delay = impl->capture_info.rate * impl->target_delay; + uint32_t delay = impl->delay_info.rate * impl->target_delay; void *data; impl->buffer_size = (delay + (1u<<15)) * 4; - data = realloc(impl->buffer_data, impl->buffer_size * impl->capture_info.channels); + data = realloc(impl->buffer_data, impl->buffer_size * impl->delay_info.channels); if (data == NULL) { pw_log_warn("can't allocate delay buffer, delay disabled: %m"); impl->buffer_size = 0; @@ -385,7 +395,7 @@ info.channels > SPA_AUDIO_MAX_CHANNELS) return; - impl->capture_info = info; + impl->delay_info = info; recalculate_buffer(impl); break; } @@ -696,6 +706,23 @@ parse_audio_info(impl->capture_props, &impl->capture_info); parse_audio_info(impl->playback_props, &impl->playback_info); + if (!impl->capture_info.rate && !impl->playback_info.rate) { + if (pw_properties_get(impl->playback_props, "resample.disable") == NULL) + pw_properties_set(impl->playback_props, "resample.disable", "true"); + if (pw_properties_get(impl->capture_props, "resample.disable") == NULL) + pw_properties_set(impl->capture_props, "resample.disable", "true"); + } else if (impl->capture_info.rate && !impl->playback_info.rate) + impl->playback_info.rate = impl->capture_info.rate; + else if (impl->playback_info.rate && !impl->capture_info.rate) + impl->capture_info.rate = !impl->playback_info.rate; + else if (impl->capture_info.rate != impl->playback_info.rate) { + pw_log_warn("Both capture and playback rate are set, but" + " they are different. Using the highest of two. This behaviour" + " is deprecated, please use equal rates in the module config"); + impl->playback_info.rate = impl->capture_info.rate = + SPA_MAX(impl->playback_info.rate, impl->capture_info.rate); + } + if (pw_properties_get(impl->capture_props, PW_KEY_MEDIA_NAME) == NULL) pw_properties_setf(impl->capture_props, PW_KEY_MEDIA_NAME, "%s input", pw_properties_get(impl->capture_props, PW_KEY_NODE_DESCRIPTION));
View file
pipewire-0.3.72.tar.gz/src/modules/module-netjack2
Added
+(directory)
View file
pipewire-0.3.72.tar.gz/src/modules/module-netjack2-driver.c
Added
@@ -0,0 +1,1374 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <signal.h> +#include <limits.h> +#include <arpa/inet.h> +#include <netinet/ip.h> +#include <netinet/in.h> +#include <net/if.h> +#include <math.h> + +#include "config.h" + +#include <spa/utils/result.h> +#include <spa/utils/string.h> +#include <spa/utils/json.h> +#include <spa/debug/types.h> +#include <spa/pod/builder.h> +#include <spa/param/audio/format-utils.h> +#include <spa/param/latency-utils.h> +#include <spa/param/audio/raw.h> + +#include <pipewire/impl.h> +#include <pipewire/i18n.h> +#include <pipewire/private.h> + +#include "module-netjack2/packets.h" +#include "module-netjack2/peer.c" + +#ifndef IPTOS_DSCP +#define IPTOS_DSCP_MASK 0xfc +#define IPTOS_DSCP(x) ((x) & IPTOS_DSCP_MASK) +#endif + +/** \page page_module_netjack2_driver PipeWire Module: Netjack2 driver + * + * The netjack2-driver module provides a source or sink that is following a + * netjack2 driver. + * + * ## Module Options + * + * - `driver.mode`: the driver mode, sink|source|duplex, default duplex + * - `local.ifname = <str>`: interface name to use + * - `net.ip =<str>`: multicast IP address, default "225.3.19.154" + * - `net.port =<int>`: control port, default "19000" + * - `net.mtu = <int>`: MTU to use, default 1500 + * - `net.ttl = <int>`: TTL to use, default 1 + * - `net.loop = <bool>`: loopback multicast, default false + * - `netjack2.client-name`: the name of the NETJACK2 client. + * - `netjack2.save`: if jack port connections should be save automatically. Can also be + * placed per stream. + * - `netjack2.latency`: the latency in cycles, default 2 + * - `audio.channels`: the number of audio ports. Can also be added to the stream props. + * - `midi.ports`: the number of midi ports. Can also be added to the stream props. + * - `source.props`: Extra properties for the source filter. + * - `sink.props`: Extra properties for the sink filter. + * + * ## General options + * + * Options with well-known behavior. + * + * - \ref PW_KEY_REMOTE_NAME + * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_POSITION + * - \ref PW_KEY_NODE_NAME + * - \ref PW_KEY_NODE_DESCRIPTION + * - \ref PW_KEY_NODE_GROUP + * - \ref PW_KEY_NODE_VIRTUAL + * - \ref PW_KEY_MEDIA_CLASS + * - \ref PW_KEY_TARGET_OBJECT to specify the remote node.name or serial.id to link to + * + * ## Example configuration of a duplex sink/source + * + *\code{.unparsed} + * context.modules = + * { name = libpipewire-module-netjack2-driver + * args = { + * #driver.mode = duplex + * #netjack2.client-name = PipeWire + * #netjack2.save = false + * #netjack2.latency = 2 + * #midi.ports = 0 + * #audio.channels = 2 + * #audio.position = FL FR + * source.props = { + * # extra sink properties + * } + * sink.props = { + * # extra sink properties + * } + * } + * } + * + *\endcode + */ + +#define NAME "netjack2-driver" + +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); +#define PW_LOG_TOPIC_DEFAULT mod_topic + +#define MAX_PORTS 128 + +#define DEFAULT_NET_IP "225.3.19.154" +#define DEFAULT_NET_PORT 19000 +#define DEFAULT_NET_TTL 1 +#define DEFAULT_NET_MTU 1500 +#define DEFAULT_NET_LOOP false +#define DEFAULT_NET_DSCP 34 /* Default to AES-67 AF41 (34) */ +#define MAX_MTU 9000 + +#define DEFAULT_NETWORK_LATENCY 2 +#define NETWORK_MAX_LATENCY 30 + +#define DEFAULT_CLIENT_NAME "PipeWire" +#define DEFAULT_CHANNELS 2 +#define DEFAULT_POSITION " FL FR " +#define DEFAULT_MIDI_PORTS 1 + +#define FOLLOWER_INIT_TIMEOUT 1 +#define FOLLOWER_INIT_RETRY -1 + +#define MODULE_USAGE "( remote.name=<remote> ) " \ + "( driver.mode=<sink|source|duplex> ) " \ + "( local.ifname=<interface name> ) " \ + "( net.ip=<ip address to use, default 225.3.19.154> ) " \ + "( net.port=<port to use, default 19000> ) " \ + "( net.mtu=<MTU to use, default 1500> ) " \ + "( net.ttl=<TTL to use, default 1> ) " \ + "( net.loop=<loopback, default false> ) " \ + "( netjack2.client-name=<name of the NETJACK2 client> ) " \ + "( netjack2.save=<bool, save ports> ) " \ + "( netjack2.latency=<latency in cycles, default 2> ) " \ + "( midi.ports=<number of midi ports> ) " \ + "( audio.channels=<number of channels> ) " \ + "( audio.position=<channel map> ) " \ + "( source.props=<properties> ) " \ + "( sink.props=<properties> ) " + + +static const struct spa_dict_item module_props = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { PW_KEY_MODULE_DESCRIPTION, "Create a netjack2 driver" }, + { PW_KEY_MODULE_USAGE, MODULE_USAGE }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + +struct port { + enum spa_direction direction; + struct spa_latency_info latency2; + bool latency_changed2; + unsigned int is_midi:1; +}; + +struct stream { + struct impl *impl; + + enum spa_direction direction; + struct pw_properties *props; + struct pw_filter *filter; + struct spa_hook listener; + + struct spa_audio_info_raw info; + + uint32_t n_midi; + uint32_t n_ports; + struct port *portsMAX_PORTS; + + struct volume volume; + + uint32_t active_audio_ports; + uint32_t active_midi_ports; + + unsigned int running:1; +}; + +struct impl { + struct pw_context *context; + struct pw_loop *main_loop; + struct pw_data_loop *data_loop; + struct spa_system *system; + +#define MODE_SINK (1<<0) +#define MODE_SOURCE (1<<1) +#define MODE_DUPLEX (MODE_SINK|MODE_SOURCE) + uint32_t mode; + struct pw_properties *props; + + bool loop; + int ttl; + int dscp; + int mtu; + uint32_t latency; + + struct pw_impl_module *module; + struct spa_hook module_listener; + + struct pw_core *core; + struct spa_hook core_proxy_listener; + struct spa_hook core_listener; + + struct spa_io_position *position; + + struct stream source; + struct stream sink; + + uint32_t period_size; + uint32_t samplerate; + uint64_t frame_time; + + uint32_t pw_xrun; + uint32_t nj2_xrun; + + struct sockaddr_storage dst_addr; + socklen_t dst_len; + struct sockaddr_storage src_addr; + socklen_t src_len; + + struct spa_source *setup_socket; + struct spa_source *socket; + struct spa_source *timer; + int32_t init_retry; + + struct netjack2_peer peer; + + uint32_t driving; + uint32_t received; + + unsigned int triggered:1; + unsigned int do_disconnect:1; + unsigned int done:1; + unsigned int new_xrun:1; + unsigned int started:1; +}; + +static void reset_volume(struct volume *vol, uint32_t n_volumes) +{ + uint32_t i; + vol->mute = false; + vol->n_volumes = n_volumes; + for (i = 0; i < n_volumes; i++) + vol->volumesi = 1.0f; +} + +static void stream_destroy(void *d) +{ + struct stream *s = d; + uint32_t i; + + spa_hook_remove(&s->listener); + for (i = 0; i < s->n_ports; i++) + s->portsi = NULL; + s->filter = NULL; +} + +static void stream_state_changed(void *d, enum pw_filter_state old, + enum pw_filter_state state, const char *error) +{ + struct stream *s = d; + struct impl *impl = s->impl; + switch (state) { + case PW_FILTER_STATE_ERROR: + case PW_FILTER_STATE_UNCONNECTED: + pw_impl_module_schedule_destroy(impl->module); + break; + case PW_FILTER_STATE_PAUSED: + s->running = false; + break; + case PW_FILTER_STATE_STREAMING: + s->running = true; + break; + default: + break; + } +} + +static inline void set_info(struct stream *s, uint32_t nframes, + struct data_info *midi, uint32_t *n_midi, + struct data_info *audio, uint32_t *n_audio) +{ + uint32_t i, n_m, n_a; + n_m = n_a = 0; + for (i = 0; i < s->n_ports; i++) { + struct port *p = s->portsi; + void *data = p ? pw_filter_get_dsp_buffer(p, nframes) : NULL; + if (p && p->is_midi) { + midin_m.data = data; + midin_m.id = i; + midin_m++.filled = false; + } else if (data != NULL) { + audion_a.data = data; + audion_a.id = i; + audion_a++.filled = false; + } + } + *n_midi = n_m; + *n_audio = n_a; +} + +static void sink_process(void *d, struct spa_io_position *position) +{ + struct stream *s = d; + struct impl *impl = s->impl; + uint32_t nframes = position->clock.duration; + struct data_info midis->n_ports; + struct data_info audios->n_ports; + uint32_t n_midi, n_audio; + + if (impl->driving == MODE_SINK && impl->triggered) { + impl->triggered = false; + return; + } + + set_info(s, nframes, midi, &n_midi, audio, &n_audio); + + netjack2_send_data(&impl->peer, nframes, midi, n_midi, audio, n_audio); + + pw_log_trace_fp("done %"PRIu64, impl->frame_time); + if (impl->driving == MODE_SINK) + impl->done = true; +} + +static void source_process(void *d, struct spa_io_position *position) +{ + struct stream *s = d; + struct impl *impl = s->impl; + uint32_t nframes = position->clock.duration; + struct data_info midis->n_ports; + struct data_info audios->n_ports; + uint32_t n_midi, n_audio; + + if (impl->driving == MODE_SOURCE && !impl->triggered) { + pw_log_trace_fp("done %"PRIu64, impl->frame_time); + impl->done = true; + return; + } + impl->triggered = false; + + set_info(s, nframes, midi, &n_midi, audio, &n_audio); + + netjack2_recv_data(&impl->peer, midi, n_midi, audio, n_audio); +} + +static void stream_io_changed(void *data, void *port_data, uint32_t id, void *area, uint32_t size) +{ + struct stream *s = data; + struct impl *impl = s->impl; + if (port_data == NULL) { + switch (id) { + case SPA_IO_Position: + impl->position = area; + break; + default: + break; + } + } +} + +static void param_latency_changed(struct stream *s, const struct spa_pod *param, + struct port *port) +{ + struct spa_latency_info latency; + bool update = false; + enum spa_direction direction = port->direction; + + if (spa_latency_parse(param, &latency) < 0) + return; + + if (spa_latency_info_compare(&port->latencydirection, &latency)) { + port->latencydirection = latency; + port->latency_changeddirection = update = true; + } +} + +static void make_stream_ports(struct stream *s) +{ + struct impl *impl = s->impl; + uint32_t i; + struct pw_properties *props; + const char *str, *prefix; + char name256; + bool is_midi; + uint8_t buffer512; + struct spa_pod_builder b; + struct spa_latency_info latency; + const struct spa_pod *params1; + + if (s->direction == PW_DIRECTION_INPUT) { + /* sink */ + prefix = "playback"; + } else { + /* source */ + prefix = "capture"; + } + + for (i = 0; i < s->n_ports; i++) { + struct port *port = s->portsi; + + if (port != NULL) { + s->portsi = NULL; + pw_filter_remove_port(port); + } + + if (i < s->info.channels) { + str = spa_debug_type_find_short_name(spa_type_audio_channel, + s->info.positioni); + if (str) + snprintf(name, sizeof(name), "%s_%s", prefix, str); + else + snprintf(name, sizeof(name), "%s_%d", prefix, i); + + props = pw_properties_new( + PW_KEY_FORMAT_DSP, "32 bit float mono audio", + PW_KEY_AUDIO_CHANNEL, str ? str : "UNK", + PW_KEY_PORT_PHYSICAL, "true", + PW_KEY_PORT_NAME, name, + NULL); + + is_midi = false; + } else { + snprintf(name, sizeof(name), "%s_%d", prefix, i - s->info.channels); + props = pw_properties_new( + PW_KEY_FORMAT_DSP, "8 bit raw midi", + PW_KEY_PORT_NAME, name, + PW_KEY_PORT_PHYSICAL, "true", + NULL); + + is_midi = true; + } + latency = SPA_LATENCY_INFO(s->direction, + .min_quantum = impl->latency, + .max_quantum = impl->latency); + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + params0 = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + + port = pw_filter_add_port(s->filter, + s->direction, + PW_FILTER_PORT_FLAG_MAP_BUFFERS, + sizeof(struct port), + props, params, 1); + if (port == NULL) { + pw_log_error("Can't create port: %m"); + return; + } + port->latencys->direction = latency; + port->is_midi = is_midi; + + s->portsi = port; + } +} + +static struct spa_pod *make_props_param(struct spa_pod_builder *b, + struct volume *vol) +{ + return spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, + SPA_PROP_mute, SPA_POD_Bool(vol->mute), + SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), + SPA_TYPE_Float, vol->n_volumes, vol->volumes)); +} + +static void parse_props(struct stream *s, const struct spa_pod *param) +{ + struct spa_pod_object *obj = (struct spa_pod_object *) param; + struct spa_pod_prop *prop; + uint8_t buffer1024; + struct spa_pod_builder b; + const struct spa_pod *params1; + + SPA_POD_OBJECT_FOREACH(obj, prop) { + switch (prop->key) { + case SPA_PROP_mute: + { + bool mute; + if (spa_pod_get_bool(&prop->value, &mute) == 0) + s->volume.mute = mute; + break; + } + case SPA_PROP_channelVolumes: + { + uint32_t n; + float volsSPA_AUDIO_MAX_CHANNELS; + if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, + vols, SPA_AUDIO_MAX_CHANNELS)) > 0) { + s->volume.n_volumes = n; + for (n = 0; n < s->volume.n_volumes; n++) + s->volume.volumesn = volsn; + } + break; + } + default: + break; + } + } + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + params0 = make_props_param(&b, &s->volume); + + pw_filter_update_params(s->filter, NULL, params, 1); +} + +static void stream_param_changed(void *data, void *port_data, uint32_t id, + const struct spa_pod *param) +{ + struct stream *s = data; + if (port_data != NULL) { + switch (id) { + case SPA_PARAM_Latency: + param_latency_changed(s, param, port_data); + break; + } + } else { + switch (id) { + case SPA_PARAM_PortConfig: + pw_log_debug("PortConfig"); + make_stream_ports(s); + pw_filter_set_active(s->filter, true); + break; + case SPA_PARAM_Props: + pw_log_debug("Props"); + parse_props(s, param); + break; + } + } +} + +static const struct pw_filter_events sink_events = { + PW_VERSION_FILTER_EVENTS, + .destroy = stream_destroy, + .state_changed = stream_state_changed, + .param_changed = stream_param_changed, + .io_changed = stream_io_changed, + .process = sink_process +}; + +static const struct pw_filter_events source_events = { + PW_VERSION_FILTER_EVENTS, + .destroy = stream_destroy, + .state_changed = stream_state_changed, + .param_changed = stream_param_changed, + .io_changed = stream_io_changed, + .process = source_process, +}; + +static int make_stream(struct stream *s, const char *name) +{ + struct impl *impl = s->impl; + uint32_t n_params; + const struct spa_pod *params4; + uint8_t buffer1024; + struct spa_pod_builder b; + + n_params = 0; + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + s->filter = pw_filter_new(impl->core, name, pw_properties_copy(s->props)); + if (s->filter == NULL) + return -errno; + + if (s->direction == PW_DIRECTION_INPUT) { + pw_filter_add_listener(s->filter, &s->listener, + &sink_events, s); + } else { + pw_filter_add_listener(s->filter, &s->listener, + &source_events, s); + } + + reset_volume(&s->volume, s->info.channels); + + n_params = 0; + paramsn_params++ = spa_format_audio_raw_build(&b, + SPA_PARAM_EnumFormat, &s->info); + paramsn_params++ = spa_format_audio_raw_build(&b, + SPA_PARAM_Format, &s->info); + paramsn_params++ = make_props_param(&b, &s->volume); + + return pw_filter_connect(s->filter, + PW_FILTER_FLAG_INACTIVE | + PW_FILTER_FLAG_DRIVER | + PW_FILTER_FLAG_RT_PROCESS | + PW_FILTER_FLAG_CUSTOM_LATENCY, + params, n_params); +} + +static int create_filters(struct impl *impl) +{ + int res = 0; + + if (impl->mode & MODE_SINK) + res = make_stream(&impl->sink, "NETJACK2 Sink"); + + if (impl->mode & MODE_SOURCE) + res = make_stream(&impl->source, "NETJACK2 Source"); + + return res; +} + + +static inline uint64_t get_time_ns(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return SPA_TIMESPEC_TO_NSEC(&ts); +} + +static void +on_data_io(void *data, int fd, uint32_t mask) +{ + struct impl *impl = data; + + if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { + pw_log_warn("error:%08x", mask); + pw_loop_update_io(impl->data_loop->loop, impl->socket, 0); + return; + } + if (mask & SPA_IO_IN) { + bool source_running, sink_running; + uint32_t nframes; + uint64_t nsec; + + nframes = netjack2_driver_sync_wait(&impl->peer); + if (nframes == 0) + return; + + nsec = get_time_ns(); + + if (!impl->done) { + impl->pw_xrun++; + impl->new_xrun = true; + } + impl->received++; + + source_running = impl->source.running; + sink_running = impl->sink.running; + + impl->frame_time += nframes; + + pw_log_trace_fp("process %d %u %u %p %"PRIu64, nframes, source_running, + sink_running, impl->position, impl->frame_time); + + if (impl->new_xrun) { + pw_log_warn("Xrun netjack2:%u PipeWire:%u", impl->nj2_xrun, impl->pw_xrun); + impl->new_xrun = false; + } + if (impl->position) { + struct spa_io_clock *c = &impl->position->clock; + + c->nsec = nsec; + c->rate = SPA_FRACTION(1, impl->samplerate); + c->position = impl->frame_time; + c->duration = nframes; + c->delay = 0; + c->rate_diff = 1.0; + c->next_nsec = nsec; + + c->target_rate = c->rate; + c->target_duration = c->duration; + } + if (!source_running) + netjack2_recv_data(&impl->peer, NULL, 0, NULL, 0); + + if (impl->mode & MODE_SOURCE && source_running) { + impl->done = false; + impl->triggered = true; + impl->driving = MODE_SOURCE; + pw_filter_trigger_process(impl->source.filter); + } else if (impl->mode == MODE_SINK && sink_running) { + impl->done = false; + impl->triggered = true; + impl->driving = MODE_SINK; + pw_filter_trigger_process(impl->sink.filter); + } else { + sink_running = false; + impl->done = true; + } + if (!sink_running) + netjack2_send_data(&impl->peer, nframes, NULL, 0, NULL, 0); + } +} + +static int +do_schedule_destroy(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct impl *impl = user_data; + pw_impl_module_schedule_destroy(impl->module); + return 0; +} + +void module_schedule_destroy(struct impl *impl) +{ + pw_loop_invoke(impl->main_loop, do_schedule_destroy, 1, NULL, 0, false, impl); +} + +static int parse_address(const char *address, uint16_t port, + struct sockaddr_storage *addr, socklen_t *len) +{ + struct sockaddr_in *sa4 = (struct sockaddr_in*)addr; + struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)addr; + + if (inet_pton(AF_INET, address, &sa4->sin_addr) > 0) { + sa4->sin_family = AF_INET; + sa4->sin_port = htons(port); + *len = sizeof(*sa4); + } else if (inet_pton(AF_INET6, address, &sa6->sin6_addr) > 0) { + sa6->sin6_family = AF_INET6; + sa6->sin6_port = htons(port); + *len = sizeof(*sa6); + } else + return -EINVAL; + + return 0; +} + +static bool is_multicast(struct sockaddr *sa, socklen_t salen) +{ + if (sa->sa_family == AF_INET) { + static const uint32_t ipv4_mcast_mask = 0xe0000000; + struct sockaddr_in *sa4 = (struct sockaddr_in*)sa; + return (ntohl(sa4->sin_addr.s_addr) & ipv4_mcast_mask) == ipv4_mcast_mask; + } else if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)sa; + return sa6->sin6_addr.s6_addr0 == 0xff; + } + return false; +} + +static int make_socket(struct sockaddr_storage *src, socklen_t src_len, + struct sockaddr_storage *dst, socklen_t dst_len, + bool loop, int ttl, int dscp) +{ + int af, fd, val, res; + struct timeval timeout; + + af = src->ss_family; + if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC, 0)) < 0) { + pw_log_error("socket failed: %m"); + return -errno; + } + val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) { + res = -errno; + pw_log_error("setsockopt failed: %m"); + goto error; + } + +#ifdef SO_PRIORITY + val = 6; + if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0) + pw_log_warn("setsockopt(SO_PRIORITY) failed: %m"); +#endif + timeout.tv_sec = 2; + timeout.tv_usec = 0; + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) + pw_log_warn("setsockopt(SO_RCVTIMEO) failed: %m"); + + if (dscp > 0) { + val = IPTOS_DSCP(dscp << 2); + if (setsockopt(fd, IPPROTO_IP, IP_TOS, &val, sizeof(val)) < 0) + pw_log_warn("setsockopt(IP_TOS) failed: %m"); + } + if (bind(fd, (struct sockaddr*)src, src_len) < 0) { + res = -errno; + pw_log_error("bind() failed: %m"); + goto error; + } + if (is_multicast((struct sockaddr*)dst, dst_len)) { + val = loop; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &val, sizeof(val)) < 0) + pw_log_warn("setsockopt(IP_MULTICAST_LOOP) failed: %m"); + + val = ttl; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &val, sizeof(val)) < 0) + pw_log_warn("setsockopt(IP_MULTICAST_TTL) failed: %m"); + } + + return fd; +error: + close(fd); + return res; +} + +static const char *get_ip(const struct sockaddr_storage *sa, char *ip, size_t len) +{ + if (sa->ss_family == AF_INET) { + struct sockaddr_in *in = (struct sockaddr_in*)sa; + inet_ntop(sa->ss_family, &in->sin_addr, ip, len); + } else if (sa->ss_family == AF_INET6) { + struct sockaddr_in6 *in = (struct sockaddr_in6*)sa; + inet_ntop(sa->ss_family, &in->sin6_addr, ip, len); + } else + snprintf(ip, len, "invalid ip"); + return ip; +} + +static void update_timer(struct impl *impl, uint64_t timeout) +{ + struct timespec value, interval; + value.tv_sec = 0; + value.tv_nsec = timeout ? 1 : 0; + interval.tv_sec = timeout; + interval.tv_nsec = 0; + pw_loop_update_timer(impl->main_loop, impl->timer, &value, &interval, false); +} + +static bool encoding_supported(uint32_t encoder) +{ + switch (encoder) { + case NJ2_ENCODER_FLOAT: + case NJ2_ENCODER_INT: + return true; +#ifdef HAVE_OPUS + case NJ2_ENCODER_OPUS: + return true; +#endif + } + return false; +} + +static int handle_follower_setup(struct impl *impl, struct nj2_session_params *params, + struct sockaddr_storage *addr, socklen_t addr_len) +{ + int res; + struct netjack2_peer *peer = &impl->peer; + + pw_log_info("got follower setup"); + nj2_dump_session_params(params); + + nj2_session_params_ntoh(&peer->params, params); + SPA_SWAP(peer->params.send_audio_channels, peer->params.recv_audio_channels); + SPA_SWAP(peer->params.send_midi_channels, peer->params.recv_midi_channels); + + if (peer->params.send_audio_channels < 0 || + peer->params.recv_audio_channels < 0 || + peer->params.send_midi_channels < 0 || + peer->params.recv_midi_channels < 0 || + peer->params.sample_rate == 0 || + peer->params.period_size == 0 || + !encoding_supported(peer->params.sample_encoder)) { + pw_log_warn("invalid follower setup"); + return -EINVAL; + } + + pw_loop_update_io(impl->main_loop, impl->setup_socket, 0); + + impl->source.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels; + impl->source.info.rate = peer->params.sample_rate; + impl->source.info.channels = peer->params.send_audio_channels; + impl->sink.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels; + impl->sink.info.rate = peer->params.sample_rate; + impl->sink.info.channels = peer->params.recv_audio_channels; + impl->samplerate = peer->params.sample_rate; + impl->period_size = peer->params.period_size; + + pw_properties_setf(impl->sink.props, PW_KEY_NODE_DESCRIPTION, "NETJACK2 to %s", + peer->params.driver_name); + pw_properties_setf(impl->source.props, PW_KEY_NODE_DESCRIPTION, "NETJACK2 from %s", + peer->params.driver_name); + + pw_properties_setf(impl->sink.props, PW_KEY_NODE_RATE, + "1/%u", impl->samplerate); + pw_properties_set(impl->sink.props, PW_KEY_NODE_FORCE_RATE, "0"); + pw_properties_setf(impl->sink.props, PW_KEY_NODE_FORCE_QUANTUM, + "%u", impl->period_size); + pw_properties_setf(impl->source.props, PW_KEY_NODE_RATE, + "1/%u", impl->samplerate); + pw_properties_set(impl->source.props, PW_KEY_NODE_FORCE_RATE, "0"); + pw_properties_setf(impl->source.props, PW_KEY_NODE_FORCE_QUANTUM, + "%u", impl->period_size); + + if ((res = create_filters(impl)) < 0) + return res; + + peer->fd = impl->socket->fd; + peer->our_stream = 'r'; + peer->other_stream = 's'; + peer->send_volume = &impl->sink.volume; + peer->recv_volume = &impl->source.volume; + netjack2_init(peer); + + int bufsize = NETWORK_MAX_LATENCY * (peer->params.mtu + + peer->params.period_size * sizeof(float) * + SPA_MAX(impl->source.n_ports, impl->sink.n_ports)); + + pw_log_info("send/recv buffer %d", bufsize); + if (setsockopt(impl->socket->fd, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)) < 0) + pw_log_warn("setsockopt(SO_SNDBUF) failed: %m"); + if (setsockopt(impl->socket->fd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) < 0) + pw_log_warn("setsockopt(SO_SNDBUF) failed: %m"); + + if (connect(impl->socket->fd, (struct sockaddr*)addr, addr_len) < 0) + goto connect_error; + + impl->started = true; + params->packet_id = htonl(NJ2_ID_START_DRIVER); + send(impl->socket->fd, params, sizeof(*params), 0); + + impl->done = true; + pw_loop_update_io(impl->data_loop->loop, impl->socket, SPA_IO_IN); + + return 0; +connect_error: + pw_log_error("connect() failed: %m"); + return -errno; +} + +static void +on_socket_io(void *data, int fd, uint32_t mask) +{ + struct impl *impl = data; + + if (mask & SPA_IO_IN) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + ssize_t len; + struct nj2_session_params params; + + if ((len = recvfrom(fd, ¶ms, sizeof(params), 0, + (struct sockaddr *)&addr, &addr_len)) < 0) + goto receive_error; + + if (len < (int)sizeof(struct nj2_session_params)) + goto short_packet; + + if (strcmp(params.type, "params") != 0) + goto wrong_type; + + switch(ntohl(params.packet_id)) { + case NJ2_ID_FOLLOWER_SETUP: + handle_follower_setup(impl, ¶ms, &addr, addr_len); + break; + } + } + return; + +receive_error: + pw_log_warn("recv error: %m"); + return; +short_packet: + pw_log_warn("short packet received"); + return; +wrong_type: + pw_log_warn("wrong packet type received"); + return; +} + +static int send_follower_available(struct impl *impl) +{ + char buffer256; + struct nj2_session_params params; + const char *client_name; + + pw_loop_update_io(impl->main_loop, impl->setup_socket, SPA_IO_IN); + + pw_log_info("sending AVAILABLE to %s", get_ip(&impl->dst_addr, buffer, sizeof(buffer))); + + client_name = pw_properties_get(impl->props, "netjack2.client-name"); + if (client_name == NULL) + client_name = DEFAULT_CLIENT_NAME; + + spa_zero(params); + strcpy(params.type, "params"); + params.version = htonl(NJ2_NETWORK_PROTOCOL); + params.packet_id = htonl(NJ2_ID_FOLLOWER_AVAILABLE); + snprintf(params.name, sizeof(params.name), "%s", client_name); + snprintf(params.follower_name, sizeof(params.follower_name), "%s", pw_get_host_name()); + params.mtu = htonl(impl->mtu); + params.transport_sync = htonl(0); + params.send_audio_channels = htonl(-1); + params.recv_audio_channels = htonl(-1); + params.send_midi_channels = htonl(-1); + params.recv_midi_channels = htonl(-1); + params.sample_encoder = htonl(NJ2_ENCODER_FLOAT); + params.follower_sync_mode = htonl(1); + params.network_latency = htonl(impl->latency); + sendto(impl->setup_socket->fd, ¶ms, sizeof(params), 0, + (struct sockaddr*)&impl->dst_addr, impl->dst_len); + return 0; +} + +static int create_netjack2_socket(struct impl *impl) +{ + const char *str; + uint32_t port; + int fd, res; + + port = pw_properties_get_uint32(impl->props, "net.port", 0); + if (port == 0) + port = DEFAULT_NET_PORT; + if ((str = pw_properties_get(impl->props, "net.ip")) == NULL) + str = DEFAULT_NET_IP; + if ((res = parse_address(str, port, &impl->dst_addr, &impl->dst_len)) < 0) { + pw_log_error("invalid net.ip %s: %s", str, spa_strerror(res)); + goto out; + } + if ((res = parse_address("0.0.0.0", 0, &impl->src_addr, &impl->src_len)) < 0) { + pw_log_error("invalid source.ip: %s", spa_strerror(res)); + goto out; + } + + impl->mtu = pw_properties_get_uint32(impl->props, "net.mtu", DEFAULT_NET_MTU); + impl->ttl = pw_properties_get_uint32(impl->props, "net.ttl", DEFAULT_NET_TTL); + impl->loop = pw_properties_get_bool(impl->props, "net.loop", DEFAULT_NET_LOOP); + impl->dscp = pw_properties_get_uint32(impl->props, "net.dscp", DEFAULT_NET_DSCP); + + fd = make_socket(&impl->src_addr, impl->src_len, + &impl->dst_addr, impl->dst_len, impl->loop, impl->ttl, impl->dscp); + if (fd < 0) { + res = -errno; + pw_log_error("can't create socket: %s", spa_strerror(res)); + goto out; + } + + impl->setup_socket = pw_loop_add_io(impl->main_loop, fd, + 0, true, on_socket_io, impl); + if (impl->setup_socket == NULL) { + res = -errno; + pw_log_error("can't create setup source: %m"); + close(fd); + goto out; + } + + impl->socket = pw_loop_add_io(impl->data_loop->loop, fd, + 0, false, on_data_io, impl); + if (impl->socket == NULL) { + res = -errno; + pw_log_error("can't create data source: %m"); + goto out; + } + + impl->init_retry = -1; + update_timer(impl, FOLLOWER_INIT_TIMEOUT); + + return 0; +out: + return res; +} + +static int send_stop_driver(struct impl *impl) +{ + struct nj2_session_params params; + + impl->started = false; + if (impl->socket) + pw_loop_update_io(impl->data_loop->loop, impl->socket, 0); + + pw_log_info("sending STOP_DRIVER"); + nj2_session_params_hton(¶ms, &impl->peer.params); + params.packet_id = htonl(NJ2_ID_STOP_DRIVER); + sendto(impl->setup_socket->fd, ¶ms, sizeof(params), 0, + (struct sockaddr*)&impl->dst_addr, impl->dst_len); + + if (impl->source.filter) + pw_filter_destroy(impl->source.filter); + if (impl->sink.filter) + pw_filter_destroy(impl->sink.filter); + + netjack2_cleanup(&impl->peer); + return 0; +} + +static int destroy_netjack2_socket(struct impl *impl) +{ + update_timer(impl, 0); + + if (impl->socket) { + pw_loop_destroy_source(impl->data_loop->loop, impl->socket); + impl->socket = NULL; + } + if (impl->setup_socket) { + send_stop_driver(impl); + pw_loop_destroy_source(impl->main_loop, impl->setup_socket); + impl->setup_socket = NULL; + } + return 0; +} + +static void restart_netjack2_socket(struct impl *impl) +{ + destroy_netjack2_socket(impl); + create_netjack2_socket(impl); +} + +static void on_timer_event(void *data, uint64_t expirations) +{ + struct impl *impl = data; + + if (impl->started) { + if (impl->received == 0) { + pw_log_warn("receive timeout, restarting"); + restart_netjack2_socket(impl); + } + impl->received = 0; + } + if (!impl->started) { + if (impl->init_retry > 0 && --impl->init_retry == 0) { + pw_log_error("timeout in connect"); + update_timer(impl, 0); + pw_impl_module_schedule_destroy(impl->module); + return; + } + send_follower_available(impl); + } +} + +static void core_error(void *data, uint32_t id, int seq, int res, const char *message) +{ + struct impl *impl = data; + + pw_log_error("error id:%u seq:%d res:%d (%s): %s", + id, seq, res, spa_strerror(res), message); + + if (id == PW_ID_CORE && res == -EPIPE) + pw_impl_module_schedule_destroy(impl->module); +} + +static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .error = core_error, +}; + +static void core_destroy(void *d) +{ + struct impl *impl = d; + spa_hook_remove(&impl->core_listener); + impl->core = NULL; + pw_impl_module_schedule_destroy(impl->module); +} + +static const struct pw_proxy_events core_proxy_events = { + .destroy = core_destroy, +}; + +static void impl_destroy(struct impl *impl) +{ + destroy_netjack2_socket(impl); + + if (impl->source.filter) + pw_filter_destroy(impl->source.filter); + if (impl->sink.filter) + pw_filter_destroy(impl->sink.filter); + if (impl->core && impl->do_disconnect) + pw_core_disconnect(impl->core); + + if (impl->timer) + pw_loop_destroy_source(impl->main_loop, impl->timer); + + pw_properties_free(impl->sink.props); + pw_properties_free(impl->source.props); + pw_properties_free(impl->props); + + free(impl); +} + +static void module_destroy(void *data) +{ + struct impl *impl = data; + spa_hook_remove(&impl->module_listener); + impl_destroy(impl); +} + +static const struct pw_impl_module_events module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = module_destroy, +}; + +static uint32_t channel_from_name(const char *name) +{ + int i; + for (i = 0; spa_type_audio_channeli.name; i++) { + if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_channeli.name))) + return spa_type_audio_channeli.type; + } + return SPA_AUDIO_CHANNEL_UNKNOWN; +} + +static void parse_position(struct spa_audio_info_raw *info, const char *val, size_t len) +{ + struct spa_json it2; + char v256; + + spa_json_init(&it0, val, len); + if (spa_json_enter_array(&it0, &it1) <= 0) + spa_json_init(&it1, val, len); + + info->channels = 0; + while (spa_json_get_string(&it1, v, sizeof(v)) > 0 && + info->channels < SPA_AUDIO_MAX_CHANNELS) { + info->positioninfo->channels++ = channel_from_name(v); + } +} + +static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +{ + const char *str; + + spa_zero(*info); + info->format = SPA_AUDIO_FORMAT_F32P; + info->rate = 0; + info->channels = pw_properties_get_uint32(props, PW_KEY_AUDIO_CHANNELS, info->channels); + info->channels = SPA_MIN(info->channels, SPA_AUDIO_MAX_CHANNELS); + if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) != NULL) + parse_position(info, str, strlen(str)); + if (info->channels == 0) + parse_position(info, DEFAULT_POSITION, strlen(DEFAULT_POSITION)); +} + +static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) +{ + const char *str; + if ((str = pw_properties_get(props, key)) != NULL) { + if (pw_properties_get(impl->sink.props, key) == NULL) + pw_properties_set(impl->sink.props, key, str); + if (pw_properties_get(impl->source.props, key) == NULL) + pw_properties_set(impl->source.props, key, str); + } +} + +SPA_EXPORT +int pipewire__module_init(struct pw_impl_module *module, const char *args) +{ + struct pw_context *context = pw_impl_module_get_context(module); + struct pw_properties *props = NULL; + struct impl *impl; + const char *str; + int res; + + PW_LOG_TOPIC_INIT(mod_topic); + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + return -errno; + + pw_log_debug("module %p: new %s", impl, args); + + if (args == NULL) + args = ""; + + props = pw_properties_new_string(args); + if (props == NULL) { + res = -errno; + pw_log_error( "can't create properties: %m"); + goto error; + } + impl->props = props; + impl->data_loop = pw_context_get_data_loop(context); + + impl->sink.props = pw_properties_new(NULL, NULL); + impl->source.props = pw_properties_new(NULL, NULL); + if (impl->source.props == NULL || impl->sink.props == NULL) { + res = -errno; + pw_log_error( "can't create properties: %m"); + goto error; + } + + impl->module = module; + impl->context = context; + impl->main_loop = pw_context_get_main_loop(context); + impl->system = impl->main_loop->system; + + impl->source.impl = impl; + impl->source.direction = PW_DIRECTION_OUTPUT; + impl->sink.impl = impl; + impl->sink.direction = PW_DIRECTION_INPUT; + + impl->mode = MODE_DUPLEX; + if ((str = pw_properties_get(props, "driver.mode")) != NULL) { + if (spa_streq(str, "source")) { + impl->mode = MODE_SOURCE; + } else if (spa_streq(str, "sink")) { + impl->mode = MODE_SINK; + } else if (spa_streq(str, "duplex")) { + impl->mode = MODE_DUPLEX; + } else { + pw_log_error("invalid driver.mode '%s'", str); + res = -EINVAL; + goto error; + } + } + impl->latency = pw_properties_get_uint32(impl->props, "netjack2.latency", + DEFAULT_NETWORK_LATENCY); + + if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) + pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); + if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL) + pw_properties_set(props, PW_KEY_NODE_GROUP, "jack-group"); + if (pw_properties_get(props, PW_KEY_NODE_ALWAYS_PROCESS) == NULL) + pw_properties_set(props, PW_KEY_NODE_ALWAYS_PROCESS, "true"); + + pw_properties_set(impl->sink.props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); + pw_properties_set(impl->sink.props, PW_KEY_PRIORITY_DRIVER, "40000"); + pw_properties_set(impl->sink.props, PW_KEY_NODE_NAME, "netjack2_driver_send"); + + pw_properties_set(impl->source.props, PW_KEY_MEDIA_CLASS, "Audio/Source"); + pw_properties_set(impl->source.props, PW_KEY_PRIORITY_DRIVER, "40001"); + pw_properties_set(impl->source.props, PW_KEY_NODE_NAME, "netjack2_driver_receive"); + + if ((str = pw_properties_get(props, "sink.props")) != NULL) + pw_properties_update_string(impl->sink.props, str, strlen(str)); + if ((str = pw_properties_get(props, "source.props")) != NULL) + pw_properties_update_string(impl->source.props, str, strlen(str)); + + copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_POSITION); + copy_props(impl, props, PW_KEY_NODE_ALWAYS_PROCESS); + copy_props(impl, props, PW_KEY_NODE_GROUP); + copy_props(impl, props, PW_KEY_NODE_VIRTUAL); + + parse_audio_info(impl->source.props, &impl->source.info); + parse_audio_info(impl->sink.props, &impl->sink.info); + + impl->source.n_midi = pw_properties_get_uint32(impl->source.props, + "midi.ports", DEFAULT_MIDI_PORTS); + impl->sink.n_midi = pw_properties_get_uint32(impl->sink.props, + "midi.ports", DEFAULT_MIDI_PORTS); + + impl->source.n_ports = impl->source.n_midi + impl->source.info.channels; + impl->sink.n_ports = impl->sink.n_midi + impl->sink.info.channels; + if (impl->source.n_ports > MAX_PORTS || impl->sink.n_ports > MAX_PORTS) { + pw_log_error("too many ports"); + res = -EINVAL; + goto error; + } + + impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); + if (impl->core == NULL) { + str = pw_properties_get(props, PW_KEY_REMOTE_NAME); + impl->core = pw_context_connect(impl->context, + pw_properties_new( + PW_KEY_REMOTE_NAME, str, + NULL), + 0); + impl->do_disconnect = true; + } + if (impl->core == NULL) { + res = -errno; + pw_log_error("can't connect: %m"); + goto error; + } + + pw_proxy_add_listener((struct pw_proxy*)impl->core, + &impl->core_proxy_listener, + &core_proxy_events, impl); + pw_core_add_listener(impl->core, + &impl->core_listener, + &core_events, impl); + + impl->timer = pw_loop_add_timer(impl->main_loop, on_timer_event, impl); + if (impl->timer == NULL) { + res = -errno; + pw_log_error("can't create timer source: %m"); + goto error; + } + + if ((res = create_netjack2_socket(impl)) < 0) + goto error; + + pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); + + pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); + + return 0; + +error: + impl_destroy(impl); + return res; +}
View file
pipewire-0.3.72.tar.gz/src/modules/module-netjack2-manager.c
Added
@@ -0,0 +1,1418 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <signal.h> +#include <limits.h> +#include <arpa/inet.h> +#include <netinet/ip.h> +#include <netinet/in.h> +#include <net/if.h> +#include <math.h> + +#include "config.h" + +#include <spa/utils/result.h> +#include <spa/utils/string.h> +#include <spa/utils/json.h> +#include <spa/debug/types.h> +#include <spa/pod/builder.h> +#include <spa/param/audio/format-utils.h> +#include <spa/param/latency-utils.h> +#include <spa/param/audio/raw.h> + +#include <pipewire/impl.h> +#include <pipewire/i18n.h> +#include <pipewire/private.h> + +#include "module-netjack2/packets.h" + +#include "module-netjack2/peer.c" + +#ifndef IPTOS_DSCP +#define IPTOS_DSCP_MASK 0xfc +#define IPTOS_DSCP(x) ((x) & IPTOS_DSCP_MASK) +#endif + +/** \page page_module_netjack2_manager PipeWire Module: Netjack2 manager + * + * The netjack2 manager module listens for new netjack2 driver messages and will + * start a communication channel with them. + * + * ## Module Options + * + * - `local.ifname = <str>`: interface name to use + * - `net.ip =<str>`: multicast IP address, default "225.3.19.154" + * - `net.port =<int>`: control port, default "19000" + * - `net.mtu = <int>`: MTU to use, default 1500 + * - `net.ttl = <int>`: TTL to use, default 1 + * - `net.loop = <bool>`: loopback multicast, default false + * - `netjack2.connect`: if jack ports should be connected automatically. Can also be + * placed per stream. + * - `netjack2.sample-rate`: the sample rate to use, default 48000 + * - `netjack2.period-size`: the buffer size to use, default 1024 + * - `netjack2.encoding`: the encoding, float|opus|int, default float + * - `netjack2.kbps`: the number of kilobits per second when encoding, default 64 + * - `audio.channels`: the number of audio ports. Can also be added to the stream props. + * - `midi.ports`: the number of midi ports. Can also be added to the stream props. + * - `source.props`: Extra properties for the source filter. + * - `sink.props`: Extra properties for the sink filter. + * + * ## General options + * + * Options with well-known behavior. + * + * - \ref PW_KEY_REMOTE_NAME + * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_POSITION + * - \ref PW_KEY_NODE_NAME + * - \ref PW_KEY_NODE_DESCRIPTION + * - \ref PW_KEY_NODE_GROUP + * - \ref PW_KEY_NODE_VIRTUAL + * - \ref PW_KEY_MEDIA_CLASS + * - \ref PW_KEY_TARGET_OBJECT to specify the remote node.name or serial.id to link to + * + * ## Example configuration of a duplex sink/source + * + *\code{.unparsed} + * context.modules = + * { name = libpipewire-module-netjack2-manager + * args = { + * #netjack2.connect = true + * #netjack2.sample-rate = 48000 + * #netjack2.period-size = 1024 + * #netjack2.encoding = float # float|opus + * #netjack2.kbps = 64 + * #midi.ports = 0 + * #audio.channels = 2 + * #audio.position = FL FR + * source.props = { + * # extra sink properties + * } + * sink.props = { + * # extra sink properties + * } + * } + * } + * + *\endcode + */ + +#define NAME "netjack2-manager" + +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); +#define PW_LOG_TOPIC_DEFAULT mod_topic + +#define MAX_PORTS 128 + +#define DEFAULT_NET_IP "225.3.19.154" +#define DEFAULT_NET_PORT 19000 +#define DEFAULT_NET_TTL 1 +#define DEFAULT_NET_MTU 1500 +#define DEFAULT_NET_LOOP false +#define DEFAULT_NET_DSCP 34 /* Default to AES-67 AF41 (34) */ +#define MAX_MTU 9000 + + +#define NETWORK_MAX_LATENCY 30 + +#define DEFAULT_SAMPLE_RATE 48000 +#define DEFAULT_PERIOD_SIZE 1024 +#define DEFAULT_ENCODING "float" +#define DEFAULT_KBPS 64 +#define DEFAULT_CHANNELS 2 +#define DEFAULT_POSITION " FL FR " +#define DEFAULT_MIDI_PORTS 1 + +#define MODULE_USAGE "( remote.name=<remote> ) " \ + "( local.ifname=<interface name> ) " \ + "( net.ip=<ip address to use, default 225.3.19.154> ) " \ + "( net.port=<port to use, default 19000> ) " \ + "( net.mtu=<MTU to use, default 1500> ) " \ + "( net.ttl=<TTL to use, default 1> ) " \ + "( net.loop=<loopback, default false> ) " \ + "( netjack2.connect=<bool, autoconnect ports> ) " \ + "( netjack2.sample-rate=<sampl erate, default 48000> ) "\ + "( netjack2.period-size=<period size, default 1024> ) " \ + "( midi.ports=<number of midi ports> ) " \ + "( audio.channels=<number of channels> ) " \ + "( audio.position=<channel map> ) " \ + "( source.props=<properties> ) " \ + "( sink.props=<properties> ) " + + +static const struct spa_dict_item module_props = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { PW_KEY_MODULE_DESCRIPTION, "Create a netjack2 manager" }, + { PW_KEY_MODULE_USAGE, MODULE_USAGE }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + +static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info); + +struct port { + enum spa_direction direction; + struct spa_latency_info latency2; + bool latency_changed2; + unsigned int is_midi:1; +}; + +struct stream { + struct impl *impl; + struct follower *follower; + + enum spa_direction direction; + struct pw_properties *props; + struct pw_filter *filter; + struct spa_hook listener; + + struct spa_audio_info_raw info; + + uint32_t n_midi; + uint32_t n_ports; + struct port *portsMAX_PORTS; + + struct volume volume; + + uint32_t active_audio_ports; + uint32_t active_midi_ports; + + unsigned int running:1; + unsigned int ready:1; +}; + +struct follower { + struct spa_list link; + struct impl *impl; + + struct spa_io_position *position; + + struct stream source; + struct stream sink; + + uint32_t id; + struct sockaddr_storage dst_addr; + socklen_t dst_len; + + uint32_t period_size; + uint32_t samplerate; + uint64_t frame_time; + uint32_t cycle; + + uint32_t pw_xrun; + uint32_t nj2_xrun; + + struct spa_source *setup_socket; + struct spa_source *socket; + + struct netjack2_peer peer; + + unsigned int done:1; + unsigned int new_xrun:1; + unsigned int started:1; +}; + +struct impl { + struct pw_context *context; + struct pw_loop *main_loop; + struct pw_data_loop *data_loop; + struct spa_system *system; + +#define MODE_SINK (1<<0) +#define MODE_SOURCE (1<<1) +#define MODE_DUPLEX (MODE_SINK|MODE_SOURCE) + uint32_t mode; + struct pw_properties *props; + struct pw_properties *sink_props; + struct pw_properties *source_props; + + uint32_t mtu; + uint32_t ttl; + bool loop; + uint32_t dscp; + uint32_t period_size; + uint32_t samplerate; + uint32_t encoding; + uint32_t kbps; + + struct pw_impl_module *module; + struct spa_hook module_listener; + + struct pw_core *core; + struct spa_hook core_proxy_listener; + struct spa_hook core_listener; + + struct sockaddr_storage src_addr; + socklen_t src_len; + + struct spa_source *setup_socket; + struct spa_list follower_list; + uint32_t follower_id; + + unsigned int do_disconnect:1; +}; + +static void reset_volume(struct volume *vol, uint32_t n_volumes) +{ + uint32_t i; + vol->mute = false; + vol->n_volumes = n_volumes; + for (i = 0; i < n_volumes; i++) + vol->volumesi = 1.0f; +} + +static void stream_destroy(void *d) +{ + struct stream *s = d; + uint32_t i; + + spa_hook_remove(&s->listener); + for (i = 0; i < s->n_ports; i++) + s->portsi = NULL; + s->filter = NULL; +} + +static void stream_state_changed(void *d, enum pw_filter_state old, + enum pw_filter_state state, const char *error) +{ + struct stream *s = d; + struct impl *impl = s->impl; + switch (state) { + case PW_FILTER_STATE_ERROR: + case PW_FILTER_STATE_UNCONNECTED: + pw_impl_module_schedule_destroy(impl->module); + break; + case PW_FILTER_STATE_PAUSED: + s->running = false; + break; + case PW_FILTER_STATE_STREAMING: + s->running = true; + break; + default: + break; + } +} + +static inline void set_info(struct stream *s, uint32_t nframes, + struct data_info *midi, uint32_t *n_midi, + struct data_info *audio, uint32_t *n_audio) +{ + uint32_t i, n_m, n_a; + n_m = n_a = 0; + for (i = 0; i < s->n_ports; i++) { + struct port *p = s->portsi; + void *data = p ? pw_filter_get_dsp_buffer(p, nframes) : NULL; + if (p && p->is_midi) { + midin_m.data = data; + midin_m.id = i; + midin_m++.filled = false; + } else if (data != NULL) { + audion_a.data = data; + audion_a.id = i; + audion_a++.filled = false; + } + } + *n_midi = n_m; + *n_audio = n_a; +} + +static void sink_process(void *d, struct spa_io_position *position) +{ + struct stream *s = d; + struct follower *follower = s->follower; + uint32_t nframes = position->clock.duration; + struct data_info midis->n_ports; + struct data_info audios->n_ports; + uint32_t n_midi, n_audio; + + set_info(s, nframes, midi, &n_midi, audio, &n_audio); + + follower->peer.cycle++; + netjack2_send_data(&follower->peer, nframes, midi, n_midi, audio, n_audio); + + if (follower->socket) + pw_loop_update_io(s->impl->data_loop->loop, follower->socket, SPA_IO_IN); +} + +static void source_process(void *d, struct spa_io_position *position) +{ + struct stream *s = d; + struct follower *follower = s->follower; + uint32_t nframes = position->clock.duration; + struct data_info midis->n_ports; + struct data_info audios->n_ports; + uint32_t n_midi, n_audio; + + set_info(s, nframes, midi, &n_midi, audio, &n_audio); + + netjack2_manager_sync_wait(&follower->peer); + netjack2_recv_data(&follower->peer, midi, n_midi, audio, n_audio); +} + +static void follower_free(struct follower *follower) +{ + struct impl *impl = follower->impl; + + spa_list_remove(&follower->link); + + if (follower->source.filter) + pw_filter_destroy(follower->source.filter); + if (follower->sink.filter) + pw_filter_destroy(follower->sink.filter); + + pw_properties_free(follower->source.props); + pw_properties_free(follower->sink.props); + + if (follower->socket) + pw_loop_destroy_source(impl->data_loop->loop, follower->socket); + if (follower->setup_socket) + pw_loop_destroy_source(impl->main_loop, follower->setup_socket); + + netjack2_cleanup(&follower->peer); + free(follower); +} + +static int +do_stop_follower(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct follower *follower = user_data; + follower->started = false; + if (follower->source.filter) + pw_filter_set_active(follower->source.filter, false); + if (follower->sink.filter) + pw_filter_set_active(follower->sink.filter, false); + follower_free(follower); + return 0; +} + +static int start_follower(struct follower *follower) +{ + struct impl *impl = follower->impl; + pw_log_info("start follower %s", follower->peer.params.name); + follower->started = true; + if (follower->source.filter && follower->source.ready) + pw_filter_set_active(follower->source.filter, true); + if (follower->sink.filter && follower->sink.ready) + pw_filter_set_active(follower->sink.filter, true); + pw_loop_update_io(impl->main_loop, follower->setup_socket, 0); + return 0; +} + +static void +on_setup_io(void *data, int fd, uint32_t mask) +{ + struct follower *follower = data; + struct impl *impl = follower->impl; + + if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { + pw_log_warn("error:%08x", mask); + pw_loop_destroy_source(impl->main_loop, follower->setup_socket); + follower->setup_socket = NULL; + return; + } + if (mask & SPA_IO_IN) { + ssize_t len; + struct nj2_session_params params; + + if ((len = recv(fd, ¶ms, sizeof(params), 0)) < 0) + goto receive_error; + + if (len < (int)sizeof(params)) + goto short_packet; + + if (strcmp(params.type, "params") != 0) + goto wrong_type; + + switch(ntohl(params.packet_id)) { + case NJ2_ID_START_DRIVER: + start_follower(follower); + break; + } + } + return; + +receive_error: + pw_log_warn("recv error: %m"); + return; +short_packet: + pw_log_warn("short packet received"); + return; +wrong_type: + pw_log_warn("wrong packet type received"); + return; +} + +static void +on_data_io(void *data, int fd, uint32_t mask) +{ + struct follower *follower = data; + struct impl *impl = follower->impl; + + if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { + pw_log_warn("error:%08x", mask); + pw_loop_destroy_source(impl->data_loop->loop, follower->socket); + follower->socket = NULL; + pw_loop_invoke(impl->main_loop, do_stop_follower, 1, NULL, 0, false, follower); + return; + } + if (mask & SPA_IO_IN) { + pw_loop_update_io(impl->data_loop->loop, follower->socket, 0); + + pw_filter_trigger_process(follower->source.filter); + } +} + +static void stream_io_changed(void *data, void *port_data, uint32_t id, void *area, uint32_t size) +{ + struct stream *s = data; + struct follower *follower = s->follower; + if (port_data == NULL) { + switch (id) { + case SPA_IO_Position: + follower->position = area; + break; + default: + break; + } + } +} + +static void param_latency_changed(struct stream *s, const struct spa_pod *param, + struct port *port) +{ + struct spa_latency_info latency; + bool update = false; + enum spa_direction direction = port->direction; + + if (spa_latency_parse(param, &latency) < 0) + return; + + if (spa_latency_info_compare(&port->latencydirection, &latency)) { + port->latencydirection = latency; + port->latency_changeddirection = update = true; + } +} + +static void make_stream_ports(struct stream *s) +{ + struct follower *follower = s->follower; + uint32_t i; + struct pw_properties *props; + const char *str, *prefix; + char name256; + bool is_midi; + uint8_t buffer512; + struct spa_pod_builder b; + struct spa_latency_info latency; + const struct spa_pod *params1; + + if (s->direction == PW_DIRECTION_INPUT) { + /* sink */ + prefix = "playback"; + } else { + /* source */ + prefix = "capture"; + } + + for (i = 0; i < s->n_ports; i++) { + struct port *port = s->portsi; + if (port != NULL) { + s->portsi = NULL; + pw_filter_remove_port(port); + } + + if (i < s->info.channels) { + str = spa_debug_type_find_short_name(spa_type_audio_channel, + s->info.positioni); + if (str) + snprintf(name, sizeof(name), "%s_%s", prefix, str); + else + snprintf(name, sizeof(name), "%s_%d", prefix, i); + + props = pw_properties_new( + PW_KEY_FORMAT_DSP, "32 bit float mono audio", + PW_KEY_AUDIO_CHANNEL, str ? str : "UNK", + PW_KEY_PORT_PHYSICAL, "true", + PW_KEY_PORT_NAME, name, + NULL); + + is_midi = false; + } else { + snprintf(name, sizeof(name), "%s_%d", prefix, i - s->info.channels); + props = pw_properties_new( + PW_KEY_FORMAT_DSP, "8 bit raw midi", + PW_KEY_PORT_NAME, name, + PW_KEY_PORT_PHYSICAL, "true", + NULL); + + is_midi = true; + } + spa_zero(latency); + latency = SPA_LATENCY_INFO(s->direction, + .min_quantum = follower->peer.params.network_latency, + .max_quantum = follower->peer.params.network_latency); + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + params0 = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + + port = pw_filter_add_port(s->filter, + s->direction, + PW_FILTER_PORT_FLAG_MAP_BUFFERS, + sizeof(struct port), + props, params, 1); + if (port == NULL) { + pw_log_error("Can't create port: %m"); + return; + } + + port->latencys->direction = latency; + port->is_midi = is_midi; + + s->portsi = port; + } +} + +static struct spa_pod *make_props_param(struct spa_pod_builder *b, + struct volume *vol) +{ + return spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, + SPA_PROP_mute, SPA_POD_Bool(vol->mute), + SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), + SPA_TYPE_Float, vol->n_volumes, vol->volumes)); +} + +static void parse_props(struct stream *s, const struct spa_pod *param) +{ + struct spa_pod_object *obj = (struct spa_pod_object *) param; + struct spa_pod_prop *prop; + uint8_t buffer1024; + struct spa_pod_builder b; + const struct spa_pod *params1; + + SPA_POD_OBJECT_FOREACH(obj, prop) { + switch (prop->key) { + case SPA_PROP_mute: + { + bool mute; + if (spa_pod_get_bool(&prop->value, &mute) == 0) + s->volume.mute = mute; + break; + } + case SPA_PROP_channelVolumes: + { + uint32_t n; + float volsSPA_AUDIO_MAX_CHANNELS; + if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, + vols, SPA_AUDIO_MAX_CHANNELS)) > 0) { + s->volume.n_volumes = n; + for (n = 0; n < s->volume.n_volumes; n++) + s->volume.volumesn = volsn; + } + break; + } + default: + break; + } + } + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + params0 = make_props_param(&b, &s->volume); + + pw_filter_update_params(s->filter, NULL, params, 1); +} + +static void stream_param_changed(void *data, void *port_data, uint32_t id, + const struct spa_pod *param) +{ + struct stream *s = data; + if (port_data != NULL) { + switch (id) { + case SPA_PARAM_Latency: + param_latency_changed(s, param, port_data); + break; + } + } else { + switch (id) { + case SPA_PARAM_PortConfig: + pw_log_debug("PortConfig"); + make_stream_ports(s); + s->ready = true; + if (s->follower->started) + pw_filter_set_active(s->filter, true); + break; + case SPA_PARAM_Props: + pw_log_debug("Props"); + parse_props(s, param); + break; + } + } +} + +static const struct pw_filter_events sink_events = { + PW_VERSION_FILTER_EVENTS, + .destroy = stream_destroy, + .state_changed = stream_state_changed, + .param_changed = stream_param_changed, + .io_changed = stream_io_changed, + .process = sink_process +}; + +static const struct pw_filter_events source_events = { + PW_VERSION_FILTER_EVENTS, + .destroy = stream_destroy, + .state_changed = stream_state_changed, + .param_changed = stream_param_changed, + .io_changed = stream_io_changed, + .process = source_process, +}; + +static int make_stream(struct stream *s, const char *name) +{ + struct impl *impl = s->impl; + uint32_t n_params; + const struct spa_pod *params4; + uint8_t buffer1024; + struct spa_pod_builder b; + uint32_t flags; + + n_params = 0; + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + s->filter = pw_filter_new(impl->core, name, s->props); + s->props = NULL; + if (s->filter == NULL) + return -errno; + + flags = PW_FILTER_FLAG_INACTIVE | + PW_FILTER_FLAG_RT_PROCESS | + PW_FILTER_FLAG_CUSTOM_LATENCY; + + if (s->direction == PW_DIRECTION_INPUT) { + pw_filter_add_listener(s->filter, &s->listener, + &sink_events, s); + } else { + pw_filter_add_listener(s->filter, &s->listener, + &source_events, s); + flags |= PW_FILTER_FLAG_TRIGGER; + } + + reset_volume(&s->volume, s->info.channels); + + n_params = 0; + paramsn_params++ = spa_format_audio_raw_build(&b, + SPA_PARAM_EnumFormat, &s->info); + paramsn_params++ = spa_format_audio_raw_build(&b, + SPA_PARAM_Format, &s->info); + paramsn_params++ = make_props_param(&b, &s->volume); + + return pw_filter_connect(s->filter, flags, params, n_params); +} + +static int create_filters(struct follower *follower) +{ + struct impl *impl = follower->impl; + int res = 0; + + if (impl->mode & MODE_SINK) + res = make_stream(&follower->sink, "NETJACK2 Send"); + + if (impl->mode & MODE_SOURCE) + res = make_stream(&follower->source, "NETJACK2 Receive"); + + return res; +} + + +static int +do_schedule_destroy(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct impl *impl = user_data; + pw_impl_module_schedule_destroy(impl->module); + return 0; +} + +void module_schedule_destroy(struct impl *impl) +{ + pw_loop_invoke(impl->main_loop, do_schedule_destroy, 1, NULL, 0, false, impl); +} + +static int parse_address(const char *address, uint16_t port, + struct sockaddr_storage *addr, socklen_t *len) +{ + struct sockaddr_in *sa4 = (struct sockaddr_in*)addr; + struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)addr; + + if (inet_pton(AF_INET, address, &sa4->sin_addr) > 0) { + sa4->sin_family = AF_INET; + sa4->sin_port = htons(port); + *len = sizeof(*sa4); + } else if (inet_pton(AF_INET6, address, &sa6->sin6_addr) > 0) { + sa6->sin6_family = AF_INET6; + sa6->sin6_port = htons(port); + *len = sizeof(*sa6); + } else + return -EINVAL; + + return 0; +} + +static bool is_multicast(struct sockaddr *sa, socklen_t salen) +{ + if (sa->sa_family == AF_INET) { + static const uint32_t ipv4_mcast_mask = 0xe0000000; + struct sockaddr_in *sa4 = (struct sockaddr_in*)sa; + return (ntohl(sa4->sin_addr.s_addr) & ipv4_mcast_mask) == ipv4_mcast_mask; + } else if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)sa; + return sa6->sin6_addr.s6_addr0 == 0xff; + } + return false; +} + +static int make_data_socket(struct sockaddr_storage *sa, socklen_t salen, + bool loop, int ttl, int dscp, char *ifname) +{ + int af, fd, val, res; + struct timeval timeout; + + af = sa->ss_family; + if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC, 0)) < 0) { + pw_log_error("socket failed: %m"); + return -errno; + } + if (connect(fd, (struct sockaddr*)sa, salen) < 0) { + res = -errno; + pw_log_error("connect() failed: %m"); + goto error; + } + + timeout.tv_sec = 2; + timeout.tv_usec = 0; + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) + pw_log_warn("setsockopt(SO_RCVTIMEO) failed: %m"); + + if (dscp > 0) { + val = IPTOS_DSCP(dscp << 2); + if (setsockopt(fd, IPPROTO_IP, IP_TOS, &val, sizeof(val)) < 0) + pw_log_warn("setsockopt(IP_TOS) failed: %m"); + } + if (is_multicast((struct sockaddr*)sa, salen)) { + val = loop; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &val, sizeof(val)) < 0) + pw_log_warn("setsockopt(IP_MULTICAST_LOOP) failed: %m"); + + val = ttl; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &val, sizeof(val)) < 0) + pw_log_warn("setsockopt(IP_MULTICAST_TTL) failed: %m"); + } + return fd; +error: + close(fd); + return res; +} + +static int make_announce_socket(struct sockaddr_storage *sa, socklen_t salen, + char *ifname) +{ + int af, fd, val, res; + struct ifreq req; + + af = sa->ss_family; + if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC, 0)) < 0) { + pw_log_error("socket failed: %m"); + return -errno; + } + val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) { + res = -errno; + pw_log_error("setsockopt failed: %m"); + goto error; + } + spa_zero(req); + if (ifname) { + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", ifname); + res = ioctl(fd, SIOCGIFINDEX, &req); + if (res < 0) + pw_log_warn("SIOCGIFINDEX %s failed: %m", ifname); + } + res = 0; + if (af == AF_INET) { + static const uint32_t ipv4_mcast_mask = 0xe0000000; + struct sockaddr_in *sa4 = (struct sockaddr_in*)sa; + if ((ntohl(sa4->sin_addr.s_addr) & ipv4_mcast_mask) == ipv4_mcast_mask) { + struct ip_mreqn mr4; + memset(&mr4, 0, sizeof(mr4)); + mr4.imr_multiaddr = sa4->sin_addr; + mr4.imr_ifindex = req.ifr_ifindex; + res = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mr4, sizeof(mr4)); + } else { + sa4->sin_addr.s_addr = INADDR_ANY; + } + } else if (af == AF_INET6) { + struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)sa; + if (sa6->sin6_addr.s6_addr0 == 0xff) { + struct ipv6_mreq mr6; + memset(&mr6, 0, sizeof(mr6)); + mr6.ipv6mr_multiaddr = sa6->sin6_addr; + mr6.ipv6mr_interface = req.ifr_ifindex; + res = setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mr6, sizeof(mr6)); + } else { + sa6->sin6_addr = in6addr_any; + } + } else { + res = -EINVAL; + goto error; + } + + if (res < 0) { + res = -errno; + pw_log_error("join mcast failed: %m"); + goto error; + } + if (bind(fd, (struct sockaddr*)sa, salen) < 0) { + res = -errno; + pw_log_error("bind() failed: %m"); + goto error; + } + return fd; +error: + close(fd); + return res; +} + +static const char *get_ip(const struct sockaddr_storage *sa, char *ip, size_t len) +{ + if (sa->ss_family == AF_INET) { + struct sockaddr_in *in = (struct sockaddr_in*)sa; + inet_ntop(sa->ss_family, &in->sin_addr, ip, len); + } else if (sa->ss_family == AF_INET6) { + struct sockaddr_in6 *in = (struct sockaddr_in6*)sa; + inet_ntop(sa->ss_family, &in->sin6_addr, ip, len); + } else { + snprintf(ip, len, "invalid address"); + } + return ip; +} + +static int handle_follower_available(struct impl *impl, struct nj2_session_params *params, + struct sockaddr_storage *addr, socklen_t addr_len) +{ + int res, fd; + struct follower *follower; + char buffer256; + struct netjack2_peer *peer; + + pw_log_info("got follower available"); + nj2_dump_session_params(params); + + if (ntohl(params->version) != NJ2_NETWORK_PROTOCOL) { + pw_log_warn("invalid version"); + return -EINVAL; + } + + follower = calloc(1, sizeof(*follower)); + if (follower == NULL) + return -errno; + + follower->impl = impl; + follower->id = impl->follower_id; + spa_list_append(&impl->follower_list, &follower->link); + + peer = &follower->peer; + + follower->source.impl = impl; + follower->source.follower = follower; + follower->source.direction = PW_DIRECTION_OUTPUT; + follower->source.props = pw_properties_copy(impl->source_props); + follower->sink.impl = impl; + follower->sink.follower = follower; + follower->sink.direction = PW_DIRECTION_INPUT; + follower->sink.props = pw_properties_copy(impl->sink_props); + + parse_audio_info(follower->source.props, &follower->source.info); + parse_audio_info(follower->sink.props, &follower->sink.info); + + follower->source.n_midi = pw_properties_get_uint32(follower->source.props, + "midi.ports", DEFAULT_MIDI_PORTS); + follower->sink.n_midi = pw_properties_get_uint32(follower->sink.props, + "midi.ports", DEFAULT_MIDI_PORTS); + + follower->samplerate = impl->samplerate; + follower->period_size = impl->period_size; + + pw_properties_setf(follower->sink.props, PW_KEY_NODE_RATE, + "1/%u", follower->samplerate); + pw_properties_set(follower->sink.props, PW_KEY_NODE_FORCE_RATE, "0"); + pw_properties_setf(follower->sink.props, PW_KEY_NODE_FORCE_QUANTUM, + "%u", follower->period_size); + pw_properties_setf(follower->source.props, PW_KEY_NODE_RATE, + "1/%u", follower->samplerate); + pw_properties_set(follower->source.props, PW_KEY_NODE_FORCE_RATE, "0"); + pw_properties_setf(follower->source.props, PW_KEY_NODE_FORCE_QUANTUM, + "%u", follower->period_size); + + nj2_session_params_ntoh(&peer->params, params); + + pw_properties_setf(follower->source.props, PW_KEY_NODE_DESCRIPTION, "%s NETJACK2 from %s", + params->name, params->follower_name); + pw_properties_setf(follower->sink.props, PW_KEY_NODE_DESCRIPTION, "%s NETJACK2 to %s", + params->name, params->follower_name); + + peer->params.mtu = impl->mtu; + peer->params.id = follower->id; + snprintf(peer->params.driver_name, sizeof(peer->params.driver_name), "%s", pw_get_host_name()); + peer->params.sample_rate = follower->samplerate; + peer->params.period_size = follower->period_size; + peer->params.sample_encoder = impl->encoding; + peer->params.kbps = impl->kbps; + + if (peer->params.send_audio_channels < 0) + peer->params.send_audio_channels = follower->sink.info.channels; + if (peer->params.recv_audio_channels < 0) + peer->params.recv_audio_channels = follower->source.info.channels; + if (peer->params.send_midi_channels < 0) + peer->params.send_midi_channels = follower->sink.n_midi; + if (peer->params.recv_midi_channels < 0) + peer->params.recv_midi_channels = follower->source.n_midi; + + follower->source.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels; + follower->source.info.rate = peer->params.sample_rate; + follower->source.info.channels = peer->params.send_audio_channels; + follower->sink.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels; + follower->sink.info.rate = peer->params.sample_rate; + follower->sink.info.channels = peer->params.recv_audio_channels; + + follower->source.n_ports = follower->source.n_midi + follower->source.info.channels; + follower->sink.n_ports = follower->sink.n_midi + follower->sink.info.channels; + if (follower->source.n_ports > MAX_PORTS || follower->sink.n_ports > MAX_PORTS) { + pw_log_error("too many ports"); + res = -EINVAL; + goto cleanup; + } + + if ((res = create_filters(follower)) < 0) + goto create_failed; + + fd = make_data_socket(addr, addr_len, impl->loop, + impl->ttl, impl->dscp, NULL); + if (fd < 0) + goto socket_failed; + + follower->setup_socket = pw_loop_add_io(impl->main_loop, fd, + 0, true, on_setup_io, follower); + if (follower->setup_socket == NULL) { + res = -errno; + pw_log_error("can't create setup source: %m"); + goto socket_failed; + } + + follower->socket = pw_loop_add_io(impl->data_loop->loop, fd, + 0, false, on_data_io, follower); + if (follower->socket == NULL) { + res = -errno; + pw_log_error("can't create data source: %m"); + goto socket_failed; + } + peer->fd = fd; + peer->our_stream = 's'; + peer->other_stream = 'r'; + peer->send_volume = &follower->sink.volume; + peer->recv_volume = &follower->source.volume; + netjack2_init(peer); + + int bufsize = NETWORK_MAX_LATENCY * (peer->params.mtu + + follower->period_size * sizeof(float) * + SPA_MAX(follower->source.n_ports, follower->sink.n_ports)); + + pw_log_info("send/recv buffer %d", bufsize); + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)) < 0) + pw_log_warn("setsockopt(SO_SNDBUF) failed: %m"); + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) < 0) + pw_log_warn("setsockopt(SO_SNDBUF) failed: %m"); + + impl->follower_id++; + + pw_loop_update_io(impl->main_loop, follower->setup_socket, SPA_IO_IN); + + nj2_session_params_hton(params, &peer->params); + params->packet_id = htonl(NJ2_ID_FOLLOWER_SETUP); + + pw_log_info("sending follower setup to %s", get_ip(addr, buffer, sizeof(buffer))); + nj2_dump_session_params(params); + send(follower->socket->fd, params, sizeof(*params), 0); + + return 0; + +create_failed: + pw_log_error("can't create streams: %s", spa_strerror(res)); + goto cleanup; +socket_failed: + res = fd; + pw_log_error("can't create socket: %s", spa_strerror(res)); + goto cleanup; +cleanup: + follower_free(follower); + return res; +} + +static void +on_socket_io(void *data, int fd, uint32_t mask) +{ + struct impl *impl = data; + + if (mask & SPA_IO_IN) { + ssize_t len; + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + struct nj2_session_params params; + + if ((len = recvfrom(fd, ¶ms, sizeof(params), 0, + (struct sockaddr *)&addr, &addr_len)) < 0) + goto receive_error; + + if (len < (int)sizeof(params)) + goto short_packet; + + if (strcmp(params.type, "params") != 0) + goto wrong_type; + + switch(ntohl(params.packet_id)) { + case NJ2_ID_FOLLOWER_AVAILABLE: + handle_follower_available(impl, ¶ms, &addr, addr_len); + break; + case NJ2_ID_STOP_DRIVER: + break; + } + } + return; + +receive_error: + pw_log_warn("recv error: %m"); + return; +short_packet: + pw_log_warn("short packet received"); + return; +wrong_type: + pw_log_warn("wrong packet type received"); + return; +} + +static int create_netjack2_socket(struct impl *impl) +{ + const char *str; + uint32_t port; + int fd, res; + char buffer256; + + port = pw_properties_get_uint32(impl->props, "net.port", 0); + if (port == 0) + port = DEFAULT_NET_PORT; + if ((str = pw_properties_get(impl->props, "net.ip")) == NULL) + str = DEFAULT_NET_IP; + + if ((res = parse_address(str, port, &impl->src_addr, &impl->src_len)) < 0) { + pw_log_error("invalid net.ip %s: %s", str, spa_strerror(res)); + goto out; + } + + impl->mtu = pw_properties_get_uint32(impl->props, "net.mtu", DEFAULT_NET_MTU); + impl->ttl = pw_properties_get_uint32(impl->props, "net.ttl", DEFAULT_NET_TTL); + impl->loop = pw_properties_get_bool(impl->props, "net.loop", DEFAULT_NET_LOOP); + impl->dscp = pw_properties_get_uint32(impl->props, "net.dscp", DEFAULT_NET_DSCP); + + fd = make_announce_socket(&impl->src_addr, impl->src_len, NULL); + if (fd < 0) { + res = fd; + pw_log_error("can't create socket: %s", spa_strerror(res)); + goto out; + } + + impl->setup_socket = pw_loop_add_io(impl->main_loop, fd, + SPA_IO_IN, true, on_socket_io, impl); + if (impl->setup_socket == NULL) { + res = -errno; + pw_log_error("can't create setup source: %m"); + close(fd); + goto out; + } + pw_log_info("listening for AVAILABLE on %s", + get_ip(&impl->src_addr, buffer, sizeof(buffer))); + return 0; +out: + return res; +} + +static void core_error(void *data, uint32_t id, int seq, int res, const char *message) +{ + struct impl *impl = data; + + pw_log_error("error id:%u seq:%d res:%d (%s): %s", + id, seq, res, spa_strerror(res), message); + + if (id == PW_ID_CORE && res == -EPIPE) + pw_impl_module_schedule_destroy(impl->module); +} + +static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .error = core_error, +}; + +static void core_destroy(void *d) +{ + struct impl *impl = d; + spa_hook_remove(&impl->core_listener); + impl->core = NULL; + pw_impl_module_schedule_destroy(impl->module); +} + +static const struct pw_proxy_events core_proxy_events = { + .destroy = core_destroy, +}; + +static void impl_destroy(struct impl *impl) +{ + struct follower *f; + + if (impl->setup_socket) { + pw_loop_destroy_source(impl->main_loop, impl->setup_socket); + impl->setup_socket = NULL; + } + spa_list_consume(f, &impl->follower_list, link) + follower_free(f); + + if (impl->core && impl->do_disconnect) + pw_core_disconnect(impl->core); + + pw_properties_free(impl->sink_props); + pw_properties_free(impl->source_props); + pw_properties_free(impl->props); + + free(impl); +} + +static void module_destroy(void *data) +{ + struct impl *impl = data; + spa_hook_remove(&impl->module_listener); + impl_destroy(impl); +} + +static const struct pw_impl_module_events module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = module_destroy, +}; + +static uint32_t channel_from_name(const char *name) +{ + int i; + for (i = 0; spa_type_audio_channeli.name; i++) { + if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_channeli.name))) + return spa_type_audio_channeli.type; + } + return SPA_AUDIO_CHANNEL_UNKNOWN; +} + +static void parse_position(struct spa_audio_info_raw *info, const char *val, size_t len) +{ + struct spa_json it2; + char v256; + + spa_json_init(&it0, val, len); + if (spa_json_enter_array(&it0, &it1) <= 0) + spa_json_init(&it1, val, len); + + info->channels = 0; + while (spa_json_get_string(&it1, v, sizeof(v)) > 0 && + info->channels < SPA_AUDIO_MAX_CHANNELS) { + info->positioninfo->channels++ = channel_from_name(v); + } +} + +static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +{ + const char *str; + + spa_zero(*info); + info->format = SPA_AUDIO_FORMAT_F32P; + info->rate = 0; + info->channels = pw_properties_get_uint32(props, PW_KEY_AUDIO_CHANNELS, info->channels); + info->channels = SPA_MIN(info->channels, SPA_AUDIO_MAX_CHANNELS); + if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) != NULL) + parse_position(info, str, strlen(str)); + if (info->channels == 0) + parse_position(info, DEFAULT_POSITION, strlen(DEFAULT_POSITION)); +} + +static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) +{ + const char *str; + if ((str = pw_properties_get(props, key)) != NULL) { + if (pw_properties_get(impl->sink_props, key) == NULL) + pw_properties_set(impl->sink_props, key, str); + if (pw_properties_get(impl->source_props, key) == NULL) + pw_properties_set(impl->source_props, key, str); + } +} + +SPA_EXPORT +int pipewire__module_init(struct pw_impl_module *module, const char *args) +{ + struct pw_context *context = pw_impl_module_get_context(module); + struct pw_properties *props = NULL; + struct impl *impl; + const char *str; + int res; + + PW_LOG_TOPIC_INIT(mod_topic); + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + return -errno; + + pw_log_debug("module %p: new %s", impl, args); + spa_list_init(&impl->follower_list); + + if (args == NULL) + args = ""; + + props = pw_properties_new_string(args); + if (props == NULL) { + res = -errno; + pw_log_error( "can't create properties: %m"); + goto error; + } + impl->props = props; + impl->data_loop = pw_context_get_data_loop(context); + + impl->sink_props = pw_properties_new(NULL, NULL); + impl->source_props = pw_properties_new(NULL, NULL); + if (impl->source_props == NULL || impl->sink_props == NULL) { + res = -errno; + pw_log_error( "can't create properties: %m"); + goto error; + } + + impl->module = module; + impl->context = context; + impl->main_loop = pw_context_get_main_loop(context); + impl->system = impl->main_loop->system; + + impl->mode = MODE_DUPLEX; + if ((str = pw_properties_get(props, "tunnel.mode")) != NULL) { + if (spa_streq(str, "source")) { + impl->mode = MODE_SOURCE; + } else if (spa_streq(str, "sink")) { + impl->mode = MODE_SINK; + } else if (spa_streq(str, "duplex")) { + impl->mode = MODE_DUPLEX; + } else { + pw_log_error("invalid tunnel.mode '%s'", str); + res = -EINVAL; + goto error; + } + } + impl->samplerate = pw_properties_get_uint32(impl->props, "netjack2.sample-rate", + DEFAULT_SAMPLE_RATE); + impl->period_size = pw_properties_get_uint32(impl->props, "netjack2.period-size", + DEFAULT_PERIOD_SIZE); + if ((str = pw_properties_get(impl->props, "netjack2.encoding")) == NULL) + str = DEFAULT_ENCODING; + if (spa_streq(str, "float")) { + impl->encoding = NJ2_ENCODER_FLOAT; + } else if (spa_streq(str, "opus")) { +#ifdef HAVE_OPUS + impl->encoding = NJ2_ENCODER_OPUS; +#else + pw_log_error("OPUS support is disabled"); + res = -EINVAL; + goto error; +#endif + } else if (spa_streq(str, "int")) { + impl->encoding = NJ2_ENCODER_INT; + } else { + pw_log_error("invalid netjack2.encoding '%s'", str); + res = -EINVAL; + goto error; + } + impl->kbps = pw_properties_get_uint32(impl->props, "netjack2.kbps", + DEFAULT_KBPS); + + if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) + pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); + if (pw_properties_get(props, PW_KEY_NODE_NETWORK) == NULL) + pw_properties_set(props, PW_KEY_NODE_NETWORK, "true"); + if (pw_properties_get(props, PW_KEY_NODE_LINK_GROUP) == NULL) + pw_properties_set(props, PW_KEY_NODE_LINK_GROUP, "jack-group"); + if (pw_properties_get(props, PW_KEY_NODE_ALWAYS_PROCESS) == NULL) + pw_properties_set(props, PW_KEY_NODE_ALWAYS_PROCESS, "true"); + if (pw_properties_get(props, PW_KEY_NODE_LOCK_QUANTUM) == NULL) + pw_properties_set(props, PW_KEY_NODE_LOCK_QUANTUM, "true"); + if (pw_properties_get(props, PW_KEY_NODE_LOCK_RATE) == NULL) + pw_properties_set(props, PW_KEY_NODE_LOCK_RATE, "true"); + + pw_properties_set(impl->sink_props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); + pw_properties_set(impl->sink_props, PW_KEY_NODE_NAME, "netjack2_manager_send"); + + pw_properties_set(impl->source_props, PW_KEY_MEDIA_CLASS, "Audio/Source"); + pw_properties_set(impl->source_props, PW_KEY_NODE_NAME, "netjack2_manager_recv"); + + if ((str = pw_properties_get(props, "sink.props")) != NULL) + pw_properties_update_string(impl->sink_props, str, strlen(str)); + if ((str = pw_properties_get(props, "source.props")) != NULL) + pw_properties_update_string(impl->source_props, str, strlen(str)); + + copy_props(impl, props, PW_KEY_NODE_VIRTUAL); + copy_props(impl, props, PW_KEY_NODE_NETWORK); + copy_props(impl, props, PW_KEY_NODE_LINK_GROUP); + copy_props(impl, props, PW_KEY_NODE_ALWAYS_PROCESS); + copy_props(impl, props, PW_KEY_NODE_LOCK_QUANTUM); + copy_props(impl, props, PW_KEY_NODE_LOCK_RATE); + copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_POSITION); + + impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); + if (impl->core == NULL) { + str = pw_properties_get(props, PW_KEY_REMOTE_NAME); + impl->core = pw_context_connect(impl->context, + pw_properties_new( + PW_KEY_REMOTE_NAME, str, + NULL), + 0); + impl->do_disconnect = true; + } + if (impl->core == NULL) { + res = -errno; + pw_log_error("can't connect: %m"); + goto error; + } + + pw_proxy_add_listener((struct pw_proxy*)impl->core, + &impl->core_proxy_listener, + &core_proxy_events, impl); + pw_core_add_listener(impl->core, + &impl->core_listener, + &core_events, impl); + + if ((res = create_netjack2_socket(impl)) < 0) + goto error; + + pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); + + pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); + + return 0; + +error: + impl_destroy(impl); + return res; +}
View file
pipewire-0.3.72.tar.gz/src/modules/module-netjack2/packets.h
Added
@@ -0,0 +1,194 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans <wim.taymans@gmail.com> */ +/* SPDX-License-Identifier: MIT */ + +#ifndef PIPEWIRE_NETJACK2_H +#define PIPEWIRE_NETJACK2_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define JACK_CLIENT_NAME_SIZE 64 +#define JACK_SERVER_NAME_SIZE 256 + +struct nj2_session_params { + char type8; /* packet type ('param') */ +#define NJ2_NETWORK_PROTOCOL 8 + uint32_t version; /* version */ +#define NJ2_ID_FOLLOWER_AVAILABLE 0 /* a follower is available */ +#define NJ2_ID_FOLLOWER_SETUP 1 /* follower configuration */ +#define NJ2_ID_START_DRIVER 2 /* follower is ready, start driver */ +#define NJ2_ID_START_FOLLOWER 3 /* driver is ready, activate follower */ +#define NJ2_ID_STOP_DRIVER 4 /* driver must stop */ + int32_t packet_id; /* indicates the packet type */ + char nameJACK_CLIENT_NAME_SIZE; /* follower's name */ + char driver_nameJACK_SERVER_NAME_SIZE; /* driver hostname (network) */ + char follower_nameJACK_SERVER_NAME_SIZE; /* follower hostname (network) */ + uint32_t mtu; /* connection mtu */ + uint32_t id; /* follower's ID */ + uint32_t transport_sync; /* is the transport synced ? */ + int32_t send_audio_channels; /* number of driver->follower channels */ + int32_t recv_audio_channels; /* number of follower->driver channels */ + int32_t send_midi_channels; /* number of driver->follower midi channels */ + int32_t recv_midi_channels; /* number of follower->driver midi channels */ + uint32_t sample_rate; /* session sample rate */ + uint32_t period_size; /* period size */ +#define NJ2_ENCODER_FLOAT 0 +#define NJ2_ENCODER_INT 1 +#define NJ2_ENCODER_CELT 2 +#define NJ2_ENCODER_OPUS 3 + uint32_t sample_encoder; /* samples encoder */ + uint32_t kbps; /* KB per second for CELT encoder */ + uint32_t follower_sync_mode; /* is the follower in sync mode ? */ + uint32_t network_latency; /* network latency */ +} __attribute__ ((packed)); + +static inline void nj2_dump_session_params(struct nj2_session_params *params) +{ + pw_log_info("Type: '%s'", params->type); + pw_log_info("Version: %u", ntohl(params->version)); + pw_log_info("packet ID: %d", ntohl(params->packet_id)); + pw_log_info("Name: '%s'", params->name); + pw_log_info("Driver Name: '%s'", params->driver_name); + pw_log_info("Follower Name: '%s'", params->follower_name); + pw_log_info("MTU: %u", ntohl(params->mtu)); + pw_log_info("ID: %u", ntohl(params->id)); + pw_log_info("TransportSync: %u", ntohl(params->transport_sync)); + pw_log_info("Audio Send: %d", ntohl(params->send_audio_channels)); + pw_log_info("Audio Recv: %d", ntohl(params->recv_audio_channels)); + pw_log_info("MIDI Send: %d", ntohl(params->send_midi_channels)); + pw_log_info("MIDI Recv: %d", ntohl(params->recv_midi_channels)); + pw_log_info("Sample Rate: %u", ntohl(params->sample_rate)); + pw_log_info("Period Size: %u", ntohl(params->period_size)); + pw_log_info("Encoder: %u", ntohl(params->sample_encoder)); + pw_log_info("KBps: %u", ntohl(params->kbps)); + pw_log_info("Follower Sync: %u", ntohl(params->follower_sync_mode)); + pw_log_info("Latency: %u", ntohl(params->network_latency)); +} + +static inline void nj2_session_params_ntoh(struct nj2_session_params *host, + const struct nj2_session_params *net) +{ + memcpy(host, net, sizeof(*host)); + host->version = ntohl(net->version); + host->packet_id = ntohl(net->packet_id); + host->mtu = ntohl(net->mtu); + host->id = ntohl(net->id); + host->transport_sync = ntohl(net->transport_sync); + host->send_audio_channels = ntohl(net->send_audio_channels); + host->recv_audio_channels = ntohl(net->recv_audio_channels); + host->send_midi_channels = ntohl(net->send_midi_channels); + host->recv_midi_channels = ntohl(net->recv_midi_channels); + host->sample_rate = ntohl(net->sample_rate); + host->period_size = ntohl(net->period_size); + host->sample_encoder = ntohl(net->sample_encoder); + host->kbps = ntohl(net->kbps); + host->follower_sync_mode = ntohl(net->follower_sync_mode); + host->network_latency = ntohl(net->network_latency); +} + +static inline void nj2_session_params_hton(struct nj2_session_params *net, + const struct nj2_session_params *host) +{ + memcpy(net, host, sizeof(*net)); + net->version = htonl(host->version); + net->packet_id = htonl(host->packet_id); + net->mtu = htonl(host->mtu); + net->id = htonl(host->id); + net->transport_sync = htonl(host->transport_sync); + net->send_audio_channels = htonl(host->send_audio_channels); + net->recv_audio_channels = htonl(host->recv_audio_channels); + net->send_midi_channels = htonl(host->send_midi_channels); + net->recv_midi_channels = htonl(host->recv_midi_channels); + net->sample_rate = htonl(host->sample_rate); + net->period_size = htonl(host->period_size); + net->sample_encoder = htonl(host->sample_encoder); + net->kbps = htonl(host->kbps); + net->follower_sync_mode = htonl(host->follower_sync_mode); + net->network_latency = htonl(host->network_latency); +} + +struct nj2_packet_header { + char type8; /* packet type ('headr') */ + uint32_t data_type; /* 'a' for audio, 'm' for midi and 's' for sync */ + uint32_t data_stream; /* 's' for send, 'r' for return */ + uint32_t id; /* unique ID of the follower */ + uint32_t num_packets; /* number of data packets of the cycle */ + uint32_t packet_size; /* packet size in bytes */ + uint32_t active_ports; /* number of active ports */ + uint32_t cycle; /* process cycle counter */ + uint32_t sub_cycle; /* midi/audio subcycle counter */ + int32_t frames; /* process cycle size in frames (can be -1 to indicate entire buffer) */ + uint32_t is_last; /* is it the last packet of a given cycle ('y' or 'n') */ +} __attribute__ ((packed)); + +#define UDP_HEADER_SIZE 64 /* 40 bytes for IP header in IPV6, 20 in IPV4, 8 for UDP, so take 64 */ + +#define PACKET_AVAILABLE_SIZE(mtu) (mtu - UDP_HEADER_SIZE - sizeof(struct nj2_packet_header)) + +static inline void nj2_dump_packet_header(struct nj2_packet_header *header) +{ + pw_log_info("Type: %s", header->type); + pw_log_info("Data Type: %c", ntohl(header->data_type)); + pw_log_info("Data Stream: %c", ntohl(header->data_stream)); + pw_log_info("ID: %u", ntohl(header->id)); + pw_log_info("Num Packets: %u", ntohl(header->num_packets)); + pw_log_info("Packet Size: %u", ntohl(header->packet_size)); + pw_log_info("Active Ports: %u", ntohl(header->active_ports)); + pw_log_info("Cycle: %u", ntohl(header->cycle)); + pw_log_info("Sub Cycle: %u", ntohl(header->sub_cycle)); + pw_log_info("Frames %d", ntohl(header->frames)); + pw_log_info("Is Last: %u", ntohl(header->is_last)); +} + +#define MIDI_INLINE_MAX 4 + +struct nj2_midi_event { + uint32_t time; /**< Sample index at which event is valid */ + uint32_t size; /**< Number of bytes of data in the event */ + union { + uint32_t offset; /**< offset in buffer */ + uint8_t bufferMIDI_INLINE_MAX; /**< Raw inline data */ + }; +}; + +struct nj2_midi_buffer { +#define MIDI_BUFFER_MAGIC 0x900df00d + uint32_t magic; + uint32_t buffer_size; + uint32_t nframes; + uint32_t write_pos; + uint32_t event_count; + uint32_t lost_events; + + struct nj2_midi_event event1; +}; + +static inline void nj2_midi_buffer_hton(struct nj2_midi_buffer *net, + const struct nj2_midi_buffer *host) +{ + net->magic = htonl(host->magic); + net->buffer_size = htonl(host->buffer_size); + net->nframes = htonl(host->nframes); + net->write_pos = htonl(host->write_pos); + net->event_count = htonl(host->event_count); + net->lost_events = htonl(host->lost_events); +} + +static inline void nj2_midi_buffer_ntoh(struct nj2_midi_buffer *host, + const struct nj2_midi_buffer *net) +{ + host->magic = ntohl(net->magic); + host->buffer_size = ntohl(net->buffer_size); + host->nframes = ntohl(net->nframes); + host->write_pos = ntohl(net->write_pos); + host->event_count = ntohl(net->event_count); + host->lost_events = ntohl(net->lost_events); +} + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_NETJACK2_H */
View file
pipewire-0.3.72.tar.gz/src/modules/module-netjack2/peer.c
Added
@@ -0,0 +1,1031 @@ + +#include <byteswap.h> + +#ifdef HAVE_OPUS_CUSTOM +#include <opus/opus.h> +#include <opus/opus_custom.h> +#endif + +#define MAX_BUFFER_FRAMES 8192 + +struct volume { + bool mute; + uint32_t n_volumes; + float volumesSPA_AUDIO_MAX_CHANNELS; +}; + +static inline float bswap_f32(float f) +{ + union { + float f; + uint32_t u; + } v; + v.f = f; + v.u = bswap_32(v.u); + return v.f; +} + +static inline void do_volume(float *dst, const float *src, struct volume *vol, + uint32_t ch, uint32_t n_samples, bool recv) +{ + float v = vol->mute ? 0.0f : vol->volumesch; + uint32_t i; + + if (v == 0.0f || src == NULL) + memset(dst, 0, n_samples * sizeof(float)); + else if (v == 1.0f) { +#if __BYTE_ORDER == __BIG_ENDIAN + for (i = 0; i < n_samples; i++) + dsti = bswap_f32(srci); +#else + memcpy(dst, src, n_samples * sizeof(float)); +#endif + + } else { +#if __BYTE_ORDER == __BIG_ENDIAN + if (recv) { + for (i = 0; i < n_samples; i++) + dsti = bswap_f32(srci) * v; + } else { + for (i = 0; i < n_samples; i++) + dsti = bswap_f32(srci * v); + } +#else + for (i = 0; i < n_samples; i++) + dsti = srci * v; +#endif + } +} + +#define ITOF(type,v,scale) \ + (((type)(v)) * (1.0f / (scale))) +#define FTOI(type,v,scale,min,max) \ + (type)(SPA_CLAMPF((v) * (scale), min, max)) + +#define S16_MIN -32768 +#define S16_MAX 32767 +#define S16_SCALE 32768.0f +#define S16_TO_F32(v) ITOF(int16_t, v, S16_SCALE) +#define F32_TO_S16(v) FTOI(int16_t, v, S16_SCALE, S16_MIN, S16_MAX) + +static inline void do_volume_to_s16(int16_t *dst, const float *src, struct volume *vol, + uint32_t ch, uint32_t n_samples) +{ + float v = vol->mute ? 0.0f : vol->volumesch; + uint32_t i; + + if (v == 0.0f || src == NULL) + memset(dst, 0, n_samples * sizeof(int16_t)); + else if (v == 1.0f) { + for (i = 0; i < n_samples; i++) + dsti = F32_TO_S16(srci); + } else { + for (i = 0; i < n_samples; i++) + dsti = F32_TO_S16(srci * v); + } +} + +static inline void do_volume_from_s16(float *dst, const int16_t *src, struct volume *vol, + uint32_t ch, uint32_t n_samples) +{ + float v = vol->mute ? 0.0f : vol->volumesch; + uint32_t i; + + if (v == 0.0f || src == NULL) + memset(dst, 0, n_samples * sizeof(float)); + else if (v == 1.0f) { + for (i = 0; i < n_samples; i++) + dsti = S16_TO_F32(srci); + } else { + for (i = 0; i < n_samples; i++) + dsti = S16_TO_F32(srci) * v; + } +} + +struct netjack2_peer { + int fd; + + uint32_t our_stream; + uint32_t other_stream; + struct nj2_session_params params; + struct nj2_packet_header sync; + uint32_t cycle; + + struct volume *send_volume; + struct volume *recv_volume; + + void *midi_data; + uint32_t midi_size; + + float *empty; + void *encoded_data; + uint32_t encoded_size; + uint32_t max_encoded_size; +#ifdef HAVE_OPUS_CUSTOM + OpusCustomMode *opus_config; + OpusCustomEncoder **opus_enc; + OpusCustomDecoder **opus_dec; +#endif + + unsigned fix_midi:1; +}; + +static int netjack2_init(struct netjack2_peer *peer) +{ + int res = 0; + + peer->empty = calloc(MAX_BUFFER_FRAMES, sizeof(float)); + + peer->midi_size = peer->params.period_size * sizeof(float) * + SPA_MAX(peer->params.send_midi_channels, peer->params.recv_midi_channels); + peer->midi_data = calloc(1, peer->midi_size); + + if (peer->params.sample_encoder == NJ2_ENCODER_INT) { + peer->max_encoded_size = peer->params.period_size * sizeof(int16_t); + peer->encoded_size = peer->max_encoded_size * + SPA_MAX(peer->params.send_audio_channels, peer->params.recv_audio_channels); + if ((peer->encoded_data = calloc(1, peer->encoded_size)) == NULL) + goto error_errno; + } else if (peer->params.sample_encoder == NJ2_ENCODER_OPUS) { +#ifdef HAVE_OPUS_CUSTOM + int32_t i; + peer->max_encoded_size = (peer->params.kbps * peer->params.period_size * 1024) / + (peer->params.sample_rate * 8) + sizeof(uint16_t); + peer->encoded_size = peer->max_encoded_size * + SPA_MAX(peer->params.send_audio_channels, peer->params.recv_audio_channels); + if ((peer->encoded_data = calloc(1, peer->encoded_size)) == NULL) + goto error_errno; + if ((peer->opus_config = opus_custom_mode_create(peer->params.sample_rate, + peer->params.period_size, &res)) == NULL) + goto error_opus; + if ((peer->opus_enc = calloc(peer->params.send_audio_channels, + sizeof(OpusCustomEncoder*))) == NULL) + goto error_errno; + + for (i = 0; i < peer->params.send_audio_channels; i++) { + if ((peer->opus_enci = opus_custom_encoder_create(peer->opus_config, + 1, &res)) == NULL) + goto error_opus; + opus_custom_encoder_ctl(peer->opus_enci, + OPUS_SET_BITRATE(peer->params.kbps*1024)); // bits per second + opus_custom_encoder_ctl(peer->opus_enci, + OPUS_SET_COMPLEXITY(10)); + opus_custom_encoder_ctl(peer->opus_enci, + OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC)); + opus_custom_encoder_ctl(peer->opus_enci, + OPUS_SET_SIGNAL(OPUS_APPLICATION_RESTRICTED_LOWDELAY)); + } + if ((peer->opus_dec = calloc(peer->params.recv_audio_channels, + sizeof(OpusCustomDecoder*))) == NULL) + goto error_errno; + for (i = 0; i < peer->params.recv_audio_channels; i++) { + if ((peer->opus_deci = opus_custom_decoder_create(peer->opus_config, + 1, &res)) == NULL) + goto error_opus; + } +#else + return -ENOTSUP; +#endif + + } + return res; +error_errno: + pw_log_warn("error: %m"); + return -errno; +#ifdef HAVE_OPUS_CUSTOM +error_opus: + pw_log_warn("error: %d", res); + return -EINVAL; +#endif +} + +static void netjack2_cleanup(struct netjack2_peer *peer) +{ + + free(peer->empty); + free(peer->midi_data); +#ifdef HAVE_OPUS_CUSTOM + int32_t i; + if (peer->opus_enc != NULL) { + for (i = 0; i < peer->params.send_audio_channels; i++) { + if (peer->opus_enci) + opus_custom_encoder_destroy(peer->opus_enci); + } + free(peer->opus_enc); + } + if (peer->opus_dec != NULL) { + for (i = 0; i < peer->params.recv_audio_channels; i++) { + if (peer->opus_deci) + opus_custom_decoder_destroy(peer->opus_deci); + } + free(peer->opus_dec); + } + if (peer->opus_config) + opus_custom_mode_destroy(peer->opus_config); + free(peer->encoded_data); +#endif + spa_zero(*peer); +} + +struct data_info { + uint32_t id; + void *data; + bool filled; +}; + +static inline void fix_midi_event(uint8_t *data, size_t size) +{ + /* fixup NoteOn with vel 0 */ + if (size > 2 && (data0 & 0xF0) == 0x90 && data2 == 0x00) { + data0 = 0x80 + (data0 & 0x0F); + data2 = 0x40; + } +} + +static void midi_to_netjack2(struct netjack2_peer *peer, + struct nj2_midi_buffer *buf, float *src, uint32_t n_samples) +{ + struct spa_pod *pod; + struct spa_pod_sequence *seq; + struct spa_pod_control *c; + struct nj2_midi_event *ev; + uint32_t free_size; + + buf->magic = MIDI_BUFFER_MAGIC; + buf->buffer_size = MAX_BUFFER_FRAMES * sizeof(float); + buf->nframes = n_samples; + buf->write_pos = 0; + buf->event_count = 0; + buf->lost_events = 0; + + if (src == NULL) + return; + + if ((pod = spa_pod_from_data(src, n_samples * sizeof(float), + 0, n_samples * sizeof(float))) == NULL) + return; + if (!spa_pod_is_sequence(pod)) + return; + + seq = (struct spa_pod_sequence*)pod; + + free_size = buf->buffer_size - sizeof(*buf); + + SPA_POD_SEQUENCE_FOREACH(seq, c) { + switch(c->type) { + case SPA_CONTROL_Midi: + { + uint8_t *data = SPA_POD_BODY(&c->value); + size_t size = SPA_POD_BODY_SIZE(&c->value); + void *ptr; + + if (c->offset >= n_samples || + size >= free_size) { + buf->lost_events++; + continue; + } + if (peer->fix_midi) + fix_midi_event(data, size); + + ev = &buf->eventbuf->event_count; + ev->time = c->offset; + ev->size = size; + if (size <= MIDI_INLINE_MAX) { + ptr = ev->buffer; + } else { + buf->write_pos += size; + ev->offset = buf->buffer_size - 1 - buf->write_pos; + free_size -= size; + ptr = SPA_PTROFF(buf, ev->offset, void); + } + memcpy(ptr, data, size); + buf->event_count++; + free_size -= sizeof(*ev); + break; + } + default: + break; + } + } + if (buf->write_pos > 0) + memmove(SPA_PTROFF(buf, sizeof(*buf) + buf->event_count * sizeof(struct nj2_midi_event), void), + SPA_PTROFF(buf, buf->buffer_size - buf->write_pos, void), + buf->write_pos); +} + +static inline void netjack2_to_midi(float *dst, uint32_t size, struct nj2_midi_buffer *buf) +{ + struct spa_pod_builder b = { 0, }; + uint32_t i; + struct spa_pod_frame f; + + spa_pod_builder_init(&b, dst, size); + spa_pod_builder_push_sequence(&b, &f, 0); + for (i = 0; buf != NULL && i < buf->event_count; i++) { + struct nj2_midi_event *ev = &buf->eventi; + void *data; + + if (ev->size <= MIDI_INLINE_MAX) + data = ev->buffer; + else if (ev->offset > buf->write_pos) + data = SPA_PTROFF(buf, ev->offset - buf->write_pos, void); + else + continue; + + spa_pod_builder_control(&b, ev->time, SPA_CONTROL_Midi); + spa_pod_builder_bytes(&b, data, ev->size); + } + spa_pod_builder_pop(&b, &f); +} + +static int netjack2_send_sync(struct netjack2_peer *peer, uint32_t nframes) +{ + struct nj2_packet_header header; + uint8_t bufferpeer->params.mtu; + uint32_t i, packet_size, active_ports, is_last; + int32_t *p; + + /* we always listen on all ports */ + active_ports = peer->params.recv_audio_channels; + packet_size = sizeof(header) + active_ports * sizeof(int32_t); + is_last = peer->params.send_midi_channels == 0 && + peer->params.send_audio_channels == 0 ? 1 : 0; + + strcpy(header.type, "header"); + header.data_type = htonl('s'); + header.data_stream = htonl(peer->our_stream); + header.id = htonl(peer->params.id); + header.num_packets = 0; + header.packet_size = htonl(packet_size); + header.active_ports = htonl(active_ports); + header.cycle = htonl(peer->cycle); + header.sub_cycle = 0; + header.frames = htonl(nframes); + header.is_last = htonl(is_last); + + memcpy(buffer, &header, sizeof(header)); + p = SPA_PTROFF(buffer, sizeof(header), int32_t); + for (i = 0; i < active_ports; i++) + pi = htonl(i); + send(peer->fd, buffer, packet_size, 0); + return 0; +} + +static int netjack2_send_midi(struct netjack2_peer *peer, uint32_t nframes, + struct data_info *info, uint32_t n_info) +{ + struct nj2_packet_header header; + uint8_t bufferpeer->params.mtu, *midi_data; + uint32_t i, num_packets, active_ports, midi_size; + uint32_t max_size; + + active_ports = peer->params.send_midi_channels; + if (active_ports <= 0) + return 0; + + midi_size = 0; + midi_data = peer->midi_data; + + for (i = 0; i < active_ports; i++) { + struct nj2_midi_buffer *mbuf; + void *data = (i < n_info && info) ? infoi.data : NULL; + + mbuf = SPA_PTROFF(midi_data, midi_size, struct nj2_midi_buffer); + midi_to_netjack2(peer, mbuf, data, nframes); + + midi_size += sizeof(*mbuf) + + mbuf->event_count * sizeof(struct nj2_midi_event) + + mbuf->write_pos; + + nj2_midi_buffer_hton(mbuf, mbuf); + } + + /* Note: jack2 calculates the packet max_size and num packets with + * different values... */ + max_size = peer->params.mtu - sizeof(header); + num_packets = (midi_size + max_size-1) / max_size; + + strcpy(header.type, "header"); + header.data_type = htonl('m'); + header.data_stream = htonl(peer->our_stream); + header.id = htonl(peer->params.id); + header.cycle = htonl(peer->cycle); + header.active_ports = htonl(active_ports); + header.num_packets = htonl(num_packets); + header.frames = htonl(nframes); + + for (i = 0; i < num_packets; i++) { + uint32_t is_last = ((i == num_packets - 1) && peer->params.send_audio_channels == 0) ? 1 : 0; + uint32_t size = midi_size - i * max_size; + uint32_t copy_size = SPA_MIN(size, max_size); + uint32_t packet_size = sizeof(header) + copy_size; + + header.sub_cycle = htonl(i); + header.is_last = htonl(is_last); + header.packet_size = htonl(packet_size); + memcpy(buffer, &header, sizeof(header)); + memcpy(SPA_PTROFF(buffer, sizeof(header), void), + SPA_PTROFF(midi_data, i * max_size, void), + copy_size); + send(peer->fd, buffer, packet_size, 0); + //nj2_dump_packet_header(&header); + } + return 0; +} + +static int netjack2_send_float(struct netjack2_peer *peer, uint32_t nframes, + struct data_info *info, uint32_t n_info) +{ + struct nj2_packet_header header; + uint8_t bufferpeer->params.mtu; + uint32_t i, j, active_ports, num_packets; + uint32_t sub_period_size, sub_period_bytes; + + if (peer->params.send_audio_channels <= 0) + return 0; + + active_ports = n_info; + + if (active_ports == 0) { + sub_period_size = nframes; + } else { + uint32_t max_size = PACKET_AVAILABLE_SIZE(peer->params.mtu); + uint32_t period = (uint32_t) powf(2.f, (uint32_t) (logf((float)max_size / + (active_ports * sizeof(float))) / logf(2.f))); + sub_period_size = SPA_MIN(period, nframes); + } + sub_period_bytes = sub_period_size * sizeof(float) + sizeof(int32_t); + num_packets = nframes / sub_period_size; + + strcpy(header.type, "header"); + header.data_type = htonl('a'); + header.data_stream = htonl(peer->our_stream); + header.id = htonl(peer->params.id); + header.cycle = htonl(peer->cycle); + header.active_ports = htonl(active_ports); + header.num_packets = htonl(num_packets); + header.frames = htonl(nframes); + + for (i = 0; i < num_packets; i++) { + uint32_t is_last = (i == num_packets - 1) ? 1 : 0; + uint32_t packet_size = sizeof(header) + active_ports * sub_period_bytes; + int32_t *ap = SPA_PTROFF(buffer, sizeof(header), int32_t); + float *src; + + for (j = 0; j < active_ports; j++) { + ap0 = htonl(infoj.id); + + src = SPA_PTROFF(infoj.data, i * sub_period_size * sizeof(float), float); + do_volume((float*)&ap1, src, peer->send_volume, infoj.id, sub_period_size, false); + + ap = SPA_PTROFF(ap, sub_period_bytes, int32_t); + } + header.sub_cycle = htonl(i); + header.is_last = htonl(is_last); + header.packet_size = htonl(packet_size); + memcpy(buffer, &header, sizeof(header)); + send(peer->fd, buffer, packet_size, 0); + //nj2_dump_packet_header(&header); + } + return 0; +} + +static int netjack2_send_opus(struct netjack2_peer *peer, uint32_t nframes, + struct data_info *info, uint32_t n_info) +{ +#ifdef HAVE_OPUS_CUSTOM + struct nj2_packet_header header; + uint8_t bufferpeer->params.mtu, *encoded_data; + uint32_t i, j, active_ports, num_packets, max_size, max_encoded; + uint32_t sub_period_bytes, last_period_bytes; + + active_ports = peer->params.send_audio_channels; + if (active_ports <= 0) + return 0; + + max_encoded = peer->max_encoded_size; + + max_size = PACKET_AVAILABLE_SIZE(peer->params.mtu); + num_packets = ((active_ports * max_encoded) + max_size-1) / max_size; + + sub_period_bytes = max_encoded / num_packets; + last_period_bytes = sub_period_bytes + max_encoded % num_packets; + + encoded_data = peer->encoded_data; + + for (i = 0; i < active_ports; i++) { + uint16_t *ap = SPA_PTROFF(encoded_data, i * max_encoded, uint16_t); + void *pcm; + int res; + + if (i >= n_info || (pcm = infoi.data) == NULL) + pcm = peer->empty; + + res = opus_custom_encode_float(peer->opus_enci, + pcm, nframes, (unsigned char*)&ap1, max_encoded - 2); + + if (res < 0 || res > 0xffff) { + pw_log_warn("encoding error %d", res); + ap0 = 0; + } else { + ap0 = htons(res); + } + } + + strcpy(header.type, "header"); + header.data_type = htonl('a'); + header.data_stream = htonl(peer->our_stream); + header.id = htonl(peer->params.id); + header.cycle = htonl(peer->cycle); + header.active_ports = htonl(active_ports); + header.num_packets = htonl(num_packets); + header.frames = htonl(nframes); + + for (i = 0; i < num_packets; i++) { + uint32_t is_last = (i == num_packets - 1) ? 1 : 0; + uint32_t data_size, packet_size; + + data_size = is_last ? last_period_bytes : sub_period_bytes; + packet_size = sizeof(header) + active_ports * data_size; + + header.sub_cycle = htonl(i); + header.is_last = htonl(is_last); + header.packet_size = htonl(packet_size); + memcpy(buffer, &header, sizeof(header)); + for (j = 0; j < active_ports; j++) { + memcpy(SPA_PTROFF(buffer, sizeof(header) + j * data_size, void), + SPA_PTROFF(encoded_data, + j * max_encoded + i * sub_period_bytes, void), + data_size); + } + send(peer->fd, buffer, packet_size, 0); + //nj2_dump_packet_header(&header); + } + return 0; +#else + return -ENOTSUP; +#endif +} + + +static int netjack2_send_int(struct netjack2_peer *peer, uint32_t nframes, + struct data_info *info, uint32_t n_info) +{ + struct nj2_packet_header header; + uint8_t bufferpeer->params.mtu, *encoded_data; + uint32_t i, j, active_ports, num_packets, max_size, max_encoded; + uint32_t sub_period_bytes, last_period_bytes; + + active_ports = peer->params.send_audio_channels; + if (active_ports <= 0) + return 0; + + max_encoded = peer->max_encoded_size; + + max_size = PACKET_AVAILABLE_SIZE(peer->params.mtu); + num_packets = ((active_ports * max_encoded) + max_size-1) / max_size; + + sub_period_bytes = max_encoded / num_packets; + last_period_bytes = sub_period_bytes + max_encoded % num_packets; + + encoded_data = peer->encoded_data; + + for (i = 0; i < active_ports; i++) { + int16_t *ap = SPA_PTROFF(encoded_data, i * max_encoded, int16_t); + void *pcm; + + if (i < n_info && (pcm = infoi.data) != NULL) + do_volume_to_s16(ap, pcm, peer->send_volume, i, nframes); + else + memset(ap, 0, max_encoded); + } + + strcpy(header.type, "header"); + header.data_type = htonl('a'); + header.data_stream = htonl(peer->our_stream); + header.id = htonl(peer->params.id); + header.cycle = htonl(peer->cycle); + header.active_ports = htonl(active_ports); + header.num_packets = htonl(num_packets); + header.frames = htonl(nframes); + + for (i = 0; i < num_packets; i++) { + uint32_t is_last = (i == num_packets - 1) ? 1 : 0; + uint32_t data_size, packet_size; + + data_size = is_last ? last_period_bytes : sub_period_bytes; + packet_size = sizeof(header) + active_ports * data_size; + + header.sub_cycle = htonl(i); + header.is_last = htonl(is_last); + header.packet_size = htonl(packet_size); + memcpy(buffer, &header, sizeof(header)); + for (j = 0; j < active_ports; j++) { + memcpy(SPA_PTROFF(buffer, sizeof(header) + j * data_size, void), + SPA_PTROFF(encoded_data, + j * max_encoded + i * sub_period_bytes, void), + data_size); + } + send(peer->fd, buffer, packet_size, 0); + //nj2_dump_packet_header(&header); + } + return 0; +} + +static int netjack2_send_data(struct netjack2_peer *peer, uint32_t nframes, + struct data_info *midi, uint32_t n_midi, + struct data_info *audio, uint32_t n_audio) +{ + netjack2_send_sync(peer, nframes); + netjack2_send_midi(peer, nframes, midi, n_midi); + switch (peer->params.sample_encoder) { + case NJ2_ENCODER_INT: + netjack2_send_int(peer, nframes, audio, n_audio); + break; + case NJ2_ENCODER_FLOAT: + netjack2_send_float(peer, nframes, audio, n_audio); + break; + case NJ2_ENCODER_OPUS: + netjack2_send_opus(peer, nframes, audio, n_audio); + break; + } + return 0; +} + +static inline int32_t netjack2_driver_sync_wait(struct netjack2_peer *peer) +{ + struct nj2_packet_header sync; + ssize_t len; + + while (true) { + if ((len = recv(peer->fd, &sync, sizeof(sync), 0)) < 0) + goto receive_error; + + if (len >= (ssize_t)sizeof(sync)) { + //nj2_dump_packet_header(&sync); + + if (strcmp(sync.type, "header") == 0 && + ntohl(sync.data_type) == 's' && + ntohl(sync.data_stream) == peer->other_stream && + ntohl(sync.id) == peer->params.id) + break; + } + } + peer->sync.is_last = ntohl(sync.is_last); + peer->sync.frames = ntohl(sync.frames); + if (peer->sync.frames == -1) + peer->sync.frames = peer->params.period_size; + + return peer->sync.frames; + +receive_error: + pw_log_warn("recv error: %m"); + return 0; +} + +static inline int32_t netjack2_manager_sync_wait(struct netjack2_peer *peer) +{ + struct nj2_packet_header sync; + ssize_t len; + int32_t offset; + + while (true) { + if ((len = recv(peer->fd, &sync, sizeof(sync), MSG_PEEK)) < 0) + goto receive_error; + + if (len >= (ssize_t)sizeof(sync)) { + //nj2_dump_packet_header(sync); + + if (strcmp(sync.type, "header") == 0 && + ntohl(sync.data_type) == 's' && + ntohl(sync.data_stream) == peer->other_stream && + ntohl(sync.id) == peer->params.id) + break; + } + if ((len = recv(peer->fd, &sync, sizeof(sync), 0)) < 0) + goto receive_error; + } + peer->sync.cycle = ntohl(sync.cycle); + peer->sync.is_last = ntohl(sync.is_last); + peer->sync.frames = ntohl(sync.frames); + if (peer->sync.frames == -1) + peer->sync.frames = peer->params.period_size; + + offset = peer->cycle - peer->sync.cycle; + if (offset < (int32_t)peer->params.network_latency) { + pw_log_info("sync offset %d %d %d", peer->cycle, peer->sync.cycle, offset); + peer->sync.is_last = true; + return 0; + } else { + if ((len = recv(peer->fd, &sync, sizeof(sync), 0)) < 0) + goto receive_error; + } + return peer->sync.frames; + +receive_error: + pw_log_warn("recv error: %m"); + return 0; +} + +static int netjack2_recv_midi(struct netjack2_peer *peer, struct nj2_packet_header *header, uint32_t *count, + struct data_info *info, uint32_t n_info) +{ + ssize_t len; + uint32_t i, active_ports, sub_cycle, max_size, offset, midi_size; + uint32_t packet_size = SPA_MIN(ntohl(header->packet_size), peer->params.mtu); + uint8_t bufferpacket_size, *data = buffer, *midi_data; + + if ((len = recv(peer->fd, buffer, packet_size, 0)) < 0) + return -errno; + + active_ports = peer->params.recv_midi_channels; + if (active_ports == 0) + return 0; + + sub_cycle = ntohl(header->sub_cycle); + peer->sync.num_packets = ntohl(header->num_packets); + max_size = peer->params.mtu - sizeof(*header); + offset = max_size * sub_cycle; + + data += sizeof(*header); + len -= sizeof(*header); + + midi_data = peer->midi_data; + midi_size = peer->midi_size; + + if (offset + len < midi_size) + memcpy(SPA_PTROFF(midi_data, offset, void), data, len); + + if (++(*count) < peer->sync.num_packets) + return 0; + + for (i = 0; i < active_ports; i++) { + struct nj2_midi_buffer *mbuf = (struct nj2_midi_buffer *)midi_data; + + nj2_midi_buffer_ntoh(mbuf, mbuf); + + size_t used = sizeof(*mbuf) + + mbuf->event_count * sizeof(struct nj2_midi_event) + + mbuf->write_pos; + if (used > midi_size) + break; + + if (i < n_info && infoi.data != NULL) { + netjack2_to_midi(infoi.data, peer->params.period_size * sizeof(float), mbuf); + infoi.filled = true; + } + midi_data += used; + midi_size -= used; + } + return 0; +} + +static int netjack2_recv_float(struct netjack2_peer *peer, struct nj2_packet_header *header, uint32_t *count, + struct data_info *info, uint32_t n_info) +{ + ssize_t len; + uint32_t i, sub_cycle, sub_period_size, sub_period_bytes, active_ports; + uint32_t packet_size = SPA_MIN(ntohl(header->packet_size), peer->params.mtu); + uint8_t bufferpacket_size; + + if ((len = recv(peer->fd, buffer, packet_size, 0)) < 0) + return -errno; + + active_ports = ntohl(header->active_ports); + if (active_ports == 0) + return 0; + + uint32_t max_size = PACKET_AVAILABLE_SIZE(peer->params.mtu); + uint32_t period = (uint32_t) powf(2.f, (uint32_t) (logf((float)max_size / + (active_ports * sizeof(float))) / logf(2.f))); + sub_period_size = SPA_MIN(period, (uint32_t)peer->sync.frames); + sub_period_bytes = sub_period_size * sizeof(float) + sizeof(int32_t); + + if ((size_t)len < active_ports * sub_period_bytes + sizeof(*header)) + return 0; + + sub_cycle = ntohl(header->sub_cycle); + if (sub_cycle * sub_period_size > MAX_BUFFER_FRAMES) + return 0; + + for (i = 0; i < active_ports; i++) { + int32_t *ap = SPA_PTROFF(buffer, sizeof(*header) + i * sub_period_bytes, int32_t); + uint32_t active_port = ntohl(ap0); + void *data; + + pw_log_trace_fp("%u/%u %u %u", active_port, n_info, + sub_cycle, sub_period_size); + if (active_port >= n_info) + continue; + + if ((data = infoactive_port.data) != NULL) { + float *dst = SPA_PTROFF(data, + sub_cycle * sub_period_size * sizeof(float), + float); + do_volume(dst, (float*)&ap1, peer->recv_volume, active_port, sub_period_size, true); + infoactive_port.filled = true; + } + } + return 0; +} + +static int netjack2_recv_opus(struct netjack2_peer *peer, struct nj2_packet_header *header, + uint32_t *count, struct data_info *info, uint32_t n_info) +{ +#ifdef HAVE_OPUS_CUSTOM + ssize_t len; + uint32_t i, active_ports, sub_cycle, max_size, encoded_size, max_encoded; + uint32_t packet_size = SPA_MIN(ntohl(header->packet_size), peer->params.mtu); + uint8_t bufferpacket_size, *data = buffer, *encoded_data; + uint32_t sub_period_bytes, last_period_bytes, data_size, num_packets; + + if ((len = recv(peer->fd, buffer, packet_size, 0)) < 0) + return -errno; + + active_ports = peer->params.recv_audio_channels; + if (active_ports == 0) + return 0; + + sub_cycle = ntohl(header->sub_cycle); + peer->sync.num_packets = ntohl(header->num_packets); + + max_encoded = peer->max_encoded_size; + + max_size = PACKET_AVAILABLE_SIZE(peer->params.mtu); + num_packets = ((active_ports * max_encoded) + max_size-1) / max_size; + + sub_period_bytes = max_encoded / num_packets; + last_period_bytes = sub_period_bytes + max_encoded % num_packets; + + data += sizeof(*header); + len -= sizeof(*header); + + if (sub_cycle == peer->sync.num_packets-1) + data_size = last_period_bytes; + else + data_size = sub_period_bytes; + + encoded_data = peer->encoded_data; + encoded_size = peer->encoded_size; + + if ((active_ports-1) * max_encoded + sub_cycle * sub_period_bytes + data_size > encoded_size) + return -ENOSPC; + + for (i = 0; i < active_ports; i++) { + memcpy(SPA_PTROFF(encoded_data, + i * max_encoded + sub_cycle * sub_period_bytes, void), + SPA_PTROFF(data, i * data_size, void), + data_size); + } + if (++(*count) < peer->sync.num_packets) + return 0; + + for (i = 0; i < active_ports; i++) { + uint16_t *ap = SPA_PTROFF(encoded_data, i * max_encoded, uint16_t); + void *pcm; + int res; + + if (i >= n_info || (pcm = infoi.data) == NULL) + continue; + + res = opus_custom_decode_float(peer->opus_deci, + (unsigned char*)&ap1, ntohs(ap0), + pcm, peer->sync.frames); + + if (res < 0 || res > 0xffff || res != peer->sync.frames) + pw_log_warn("decoding error %d", res); + else + infoi.filled = true; + } + return 0; +#else + return -ENOTSUP; +#endif +} + +static int netjack2_recv_int(struct netjack2_peer *peer, struct nj2_packet_header *header, + uint32_t *count, struct data_info *info, uint32_t n_info) +{ + ssize_t len; + uint32_t i, active_ports, sub_cycle, max_size, encoded_size, max_encoded; + uint32_t packet_size = SPA_MIN(ntohl(header->packet_size), peer->params.mtu); + uint8_t bufferpacket_size, *data = buffer, *encoded_data; + uint32_t sub_period_bytes, last_period_bytes, data_size, num_packets; + + if ((len = recv(peer->fd, buffer, packet_size, 0)) < 0) + return -errno; + + active_ports = peer->params.recv_audio_channels; + if (active_ports == 0) + return 0; + + sub_cycle = ntohl(header->sub_cycle); + peer->sync.num_packets = ntohl(header->num_packets); + + max_encoded = peer->max_encoded_size; + + max_size = PACKET_AVAILABLE_SIZE(peer->params.mtu); + num_packets = ((active_ports * max_encoded) + max_size-1) / max_size; + + sub_period_bytes = max_encoded / num_packets; + last_period_bytes = sub_period_bytes + max_encoded % num_packets; + + data += sizeof(*header); + len -= sizeof(*header); + + if (sub_cycle == peer->sync.num_packets-1) + data_size = last_period_bytes; + else + data_size = sub_period_bytes; + + encoded_data = peer->encoded_data; + encoded_size = peer->encoded_size; + + if ((active_ports-1) * max_encoded + sub_cycle * sub_period_bytes + data_size > encoded_size) + return -ENOSPC; + + for (i = 0; i < active_ports; i++) { + memcpy(SPA_PTROFF(encoded_data, + i * max_encoded + sub_cycle * sub_period_bytes, void), + SPA_PTROFF(data, i * data_size, void), + data_size); + } + if (++(*count) < peer->sync.num_packets) + return 0; + + for (i = 0; i < active_ports; i++) { + int16_t *ap = SPA_PTROFF(encoded_data, i * max_encoded, int16_t); + void *pcm; + + if (i >= n_info || (pcm = infoi.data) == NULL) + continue; + + do_volume_from_s16(pcm, ap, peer->recv_volume, i, peer->sync.frames); + infoi.filled = true; + } + return 0; +} + +static int netjack2_recv_data(struct netjack2_peer *peer, + struct data_info *midi, uint32_t n_midi, + struct data_info *audio, uint32_t n_audio) +{ + ssize_t len; + uint32_t i, audio_count = 0, midi_count = 0; + struct nj2_packet_header header; + + while (!peer->sync.is_last) { + if ((len = recv(peer->fd, &header, sizeof(header), MSG_PEEK)) < 0) + goto receive_error; + + if (len < (ssize_t)sizeof(header)) + goto receive_error; + + //nj2_dump_packet_header(&header); + + if (ntohl(header.data_stream) != peer->other_stream || + ntohl(header.id) != peer->params.id) { + pw_log_debug("not our packet"); + continue; + } + + peer->sync.is_last = ntohl(header.is_last); + + switch (ntohl(header.data_type)) { + case 'm': + netjack2_recv_midi(peer, &header, &midi_count, midi, n_midi); + break; + case 'a': + switch (peer->params.sample_encoder) { + case NJ2_ENCODER_FLOAT: + netjack2_recv_float(peer, &header, &audio_count, audio, n_audio); + break; + case NJ2_ENCODER_OPUS: + netjack2_recv_opus(peer, &header, &audio_count, audio, n_audio); + break; + case NJ2_ENCODER_INT: + netjack2_recv_int(peer, &header, &audio_count, audio, n_audio); + break; + } + break; + case 's': + pw_log_info("missing last data packet"); + peer->sync.is_last = true; + break; + } + } + for (i = 0; i < n_audio; i++) { + if (!audioi.filled && audioi.data != NULL) + memset(audioi.data, 0, peer->sync.frames * sizeof(float)); + } + for (i = 0; i < n_midi; i++) { + if (!midii.filled && midii.data != NULL) + netjack2_to_midi(midii.data, peer->params.period_size * sizeof(float), NULL); + } + peer->sync.cycle = ntohl(header.cycle); + return 0; + +receive_error: + pw_log_warn("recv error: %m"); + return -errno; +}
View file
pipewire-0.3.71.tar.gz/src/modules/module-profiler.c -> pipewire-0.3.72.tar.gz/src/modules/module-profiler.c
Changed
@@ -168,7 +168,8 @@ struct impl *impl = data; struct spa_pod_builder b; struct spa_pod_frame f2; - struct pw_node_activation *a = node->rt.activation; + uint32_t id = node->info.id; + struct pw_node_activation *a = node->rt.target.activation; struct spa_io_position *pos = &a->position; struct pw_node_target *t; int32_t filled; @@ -205,42 +206,48 @@ spa_pod_builder_prop(&b, SPA_PROFILER_driverBlock, 0); spa_pod_builder_add_struct(&b, - SPA_POD_Int(node->info.id), + SPA_POD_Int(id), SPA_POD_String(node->name), SPA_POD_Long(a->prev_signal_time), SPA_POD_Long(a->signal_time), SPA_POD_Long(a->awake_time), SPA_POD_Long(a->finish_time), SPA_POD_Int(a->status), - SPA_POD_Fraction(&node->latency)); + SPA_POD_Fraction(&node->latency), + SPA_POD_Int(a->xrun_count)); spa_list_for_each(t, &node->rt.target_list, link) { struct pw_impl_node *n = t->node; struct pw_node_activation *na; struct spa_fraction latency; - if (n == NULL || n == node) + if (t->id == id || t->flags & PW_NODE_TARGET_PEER) continue; - latency = n->latency; - if (n->force_quantum != 0) - latency.num = n->force_quantum; - if (n->force_rate != 0) - latency.denom = n->force_rate; - else if (n->rate.denom != 0) - latency.denom = n->rate.denom; - - na = n->rt.activation; + if (n != NULL) { + latency = n->latency; + if (n->force_quantum != 0) + latency.num = n->force_quantum; + if (n->force_rate != 0) + latency.denom = n->force_rate; + else if (n->rate.denom != 0) + latency.denom = n->rate.denom; + } else { + spa_zero(latency); + } + + na = t->activation; spa_pod_builder_prop(&b, SPA_PROFILER_followerBlock, 0); spa_pod_builder_add_struct(&b, - SPA_POD_Int(n->info.id), - SPA_POD_String(n->name), + SPA_POD_Int(t->id), + SPA_POD_String(t->name), SPA_POD_Long(a->signal_time), SPA_POD_Long(na->signal_time), SPA_POD_Long(na->awake_time), SPA_POD_Long(na->finish_time), SPA_POD_Int(na->status), - SPA_POD_Fraction(&latency)); + SPA_POD_Fraction(&latency), + SPA_POD_Int(na->xrun_count)); } spa_pod_builder_pop(&b, &f0); @@ -275,19 +282,11 @@ .complete = context_do_profile, }; -static int do_stop(struct spa_loop *loop, - bool async, uint32_t seq, const void *data, size_t size, void *user_data) -{ - struct impl *impl = user_data; - spa_hook_remove(&impl->context_listener); - return 0; -} - static void stop_listener(struct impl *impl) { if (impl->listening) { - pw_loop_invoke(impl->data_loop, - do_stop, SPA_ID_INVALID, NULL, 0, true, impl); + pw_context_driver_remove_listener(impl->context, + &impl->context_listener); impl->listening = false; } } @@ -307,16 +306,6 @@ }; static int -do_start(struct spa_loop *loop, - bool async, uint32_t seq, const void *data, size_t size, void *user_data) -{ - struct impl *impl = user_data; - spa_hook_list_append(&impl->context->driver_listener_list, - &impl->context_listener, - &context_events, impl); - return 0; -} -static int global_bind(void *object, struct pw_impl_client *client, uint32_t permissions, uint32_t version, uint32_t id) { @@ -340,8 +329,9 @@ if (++impl->busy == 1) { pw_log_info("%p: starting profiler", impl); - pw_loop_invoke(impl->data_loop, - do_start, SPA_ID_INVALID, NULL, 0, false, impl); + pw_context_driver_add_listener(impl->context, + &impl->context_listener, + &context_events, impl); impl->listening = true; } return 0;
View file
pipewire-0.3.71.tar.gz/src/modules/module-protocol-native.c -> pipewire-0.3.72.tar.gz/src/modules/module-protocol-native.c
Changed
@@ -919,12 +919,18 @@ proxy = pw_core_find_proxy(this, msg->id); if (proxy == NULL || proxy->zombie) { + uint32_t i; + if (proxy == NULL) pw_log_error("%p: could not find proxy %u", this, msg->id); else pw_log_debug("%p: zombie proxy %u", this, msg->id); - /* FIXME close fds */ + /* close fds */ + for (i = 0; i < msg->n_fds; i++) { + pw_log_debug("%p: close fd:%d", conn, msg->fdsi); + close(msg->fdsi); + } continue; }
View file
pipewire-0.3.71.tar.gz/src/modules/module-protocol-native/connection.c -> pipewire-0.3.72.tar.gz/src/modules/module-protocol-native/connection.c
Changed
@@ -205,7 +205,7 @@ char cmsgbufCMSG_SPACE(MAX_FDS_MSG * sizeof(int)); struct cmsghdr align; } cmsgbuf; - int n_fds = 0; + int i, n_fds = 0, *fds; size_t avail; avail = buf->buffer_maxsize - buf->buffer_size; @@ -242,10 +242,13 @@ continue; n_fds = cmsg_data_length(cmsg) / sizeof(int); + fds = (int*)CMSG_DATA(cmsg); if (n_fds + buf->n_fds > MAX_FDS) goto too_many_fds; - memcpy(&buf->fdsbuf->n_fds, CMSG_DATA(cmsg), n_fds * sizeof(int)); - buf->n_fds += n_fds; + for (i = 0; i < n_fds; i++) { + pw_log_debug("connection %p: buffer:%p got fd:%d", conn, buf, fdsi); + buf->fdsbuf->n_fds++ = fdsi; + } } pw_log_trace("connection %p: %d read %zd bytes and %d fds", conn, conn->fd, len, n_fds); @@ -272,7 +275,7 @@ { uint32_t i; - pw_log_debug("clear fds:%d", fds); + pw_log_debug("%p clear fds:%d n_fds:%d", buf, fds, buf->n_fds); if (fds) { for (i = 0; i < buf->n_fds; i++) { pw_log_debug("%p: close fd:%d", buf, buf->fdsi); @@ -848,6 +851,8 @@ { struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this); + pw_log_debug("%p: clear", conn); + clear_buffer(&impl->out, true); clear_buffer(&impl->in, true);
View file
pipewire-0.3.71.tar.gz/src/modules/module-protocol-pulse/modules/module-gsettings.c -> pipewire-0.3.72.tar.gz/src/modules/module-protocol-pulse/modules/module-gsettings.c
Changed
@@ -137,6 +137,27 @@ return 0; } +static bool schema_exists(const char *schema_id) +{ + GSettingsSchemaSource *source; + GSettingsSchema *schema; + + source = g_settings_schema_source_get_default(); + if (!source) { + pw_log_error("gsettings schema source not found"); + return false; + } + + schema = g_settings_schema_source_lookup(source, schema_id, TRUE); + if (!schema) { + pw_log_error("required gsettings schema %s does not exist", schema_id); + return false; + } + + g_settings_schema_unref(schema); + return true; +} + static void handle_module_group(struct module_gsettings_data *d, gchar *name) { struct impl *impl = d->module->impl; @@ -147,6 +168,9 @@ snprintf(p, sizeof(p), PA_GSETTINGS_MODULE_GROUPS_PATH"%s/", name); + if (!schema_exists(PA_GSETTINGS_MODULE_GROUP_SCHEMA)) + return; + settings = g_settings_new_with_path(PA_GSETTINGS_MODULE_GROUP_SCHEMA, p); if (settings == NULL) return; @@ -198,12 +222,20 @@ struct module_gsettings_data *data = module->user_data; gchar **name; + /* Check the required schema files are installed. If not, Glib will + * abort in g_settings_new */ + if (!schema_exists(PA_GSETTINGS_MODULE_GROUPS_SCHEMA) || + !schema_exists(PA_GSETTINGS_MODULE_GROUP_SCHEMA)) + return -EIO; + data->context = g_main_context_new(); g_main_context_push_thread_default(data->context); data->settings = g_settings_new(PA_GSETTINGS_MODULE_GROUPS_SCHEMA); - if (data->settings == NULL) + if (data->settings == NULL) { + g_main_context_pop_thread_default(data->context); return -EIO; + } data->group_names = g_settings_list_children(data->settings); @@ -238,15 +270,19 @@ struct module_gsettings_data *d = module->user_data; struct group *g; - g_main_context_invoke(d->context, do_stop, d); - pw_thread_utils_join(d->thr, NULL); - g_main_context_unref(d->context); + if (d->context) { + g_main_context_invoke(d->context, do_stop, d); + if (d->thr) + pw_thread_utils_join(d->thr, NULL); + g_main_context_unref(d->context); + } spa_list_consume(g, &d->groups, link) unload_module(d, g); g_strfreev(d->group_names); - g_object_unref(G_OBJECT(d->settings)); + if (d->settings) + g_object_unref(G_OBJECT(d->settings)); return 0; }
View file
pipewire-0.3.72.tar.gz/src/modules/module-protocol-pulse/modules/module-virtual-sink.c
Added
@@ -0,0 +1,179 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans <wim.taymans@gmail.com> */ +/* SPDX-License-Identifier: MIT */ + +#include <spa/param/audio/format-utils.h> +#include <spa/utils/hook.h> +#include <spa/utils/json.h> +#include <pipewire/pipewire.h> + +#include "../defs.h" +#include "../module.h" + +#define NAME "virtual-sink" + +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); +#define PW_LOG_TOPIC_DEFAULT mod_topic + +struct module_virtual_sink_data { + struct module *module; + + struct pw_impl_module *mod; + struct spa_hook mod_listener; + + struct pw_properties *global_props; + struct pw_properties *capture_props; + struct pw_properties *playback_props; +}; + +static void module_destroy(void *data) +{ + struct module_virtual_sink_data *d = data; + spa_hook_remove(&d->mod_listener); + d->mod = NULL; + module_schedule_unload(d->module); +} + +static const struct pw_impl_module_events module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = module_destroy +}; + +static int module_virtual_sink_load(struct module *module) +{ + struct module_virtual_sink_data *data = module->user_data; + FILE *f; + char *args; + size_t size; + + pw_properties_setf(data->capture_props, PW_KEY_NODE_GROUP, "virtual-sink-%u", module->index); + pw_properties_setf(data->playback_props, PW_KEY_NODE_GROUP, "virtual-sink-%u", module->index); + pw_properties_setf(data->capture_props, "pulse.module.id", "%u", module->index); + pw_properties_setf(data->playback_props, "pulse.module.id", "%u", module->index); + + if ((f = open_memstream(&args, &size)) == NULL) + return -errno; + + fprintf(f, "{"); + pw_properties_serialize_dict(f, &data->global_props->dict, 0); + fprintf(f, " capture.props = {"); + pw_properties_serialize_dict(f, &data->capture_props->dict, 0); + fprintf(f, " } playback.props = {"); + pw_properties_serialize_dict(f, &data->playback_props->dict, 0); + fprintf(f, " } }"); + fclose(f); + + data->mod = pw_context_load_module(module->impl->context, + "libpipewire-module-loopback", + args, NULL); + free(args); + + if (data->mod == NULL) + return -errno; + + pw_impl_module_add_listener(data->mod, + &data->mod_listener, + &module_events, data); + + return 0; +} + +static int module_virtual_sink_unload(struct module *module) +{ + struct module_virtual_sink_data *d = module->user_data; + + if (d->mod) { + spa_hook_remove(&d->mod_listener); + pw_impl_module_destroy(d->mod); + d->mod = NULL; + } + + pw_properties_free(d->capture_props); + pw_properties_free(d->playback_props); + pw_properties_free(d->global_props); + + return 0; +} + +static const struct spa_dict_item module_virtual_sink_info = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { PW_KEY_MODULE_DESCRIPTION, "Virtual sink" }, + { PW_KEY_MODULE_USAGE, "sink_name=<name for the sink> " + "sink_properties=<properties for the sink> " + "master=<name of sink to filter> " + "channels=<number of channels> " + "channel_map=<channel map> " + "use_volume_sharing=<yes or no> " + "force_flat_volume=<yes or no> " }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + +static int module_virtual_sink_prepare(struct module * const module) +{ + struct module_virtual_sink_data * const d = module->user_data; + struct pw_properties * const props = module->props; + struct pw_properties *global_props = NULL, *playback_props = NULL, *capture_props = NULL; + const char *str; + struct spa_audio_info_raw info = { 0 }; + int res; + + PW_LOG_TOPIC_INIT(mod_topic); + + global_props = pw_properties_new(NULL, NULL); + capture_props = pw_properties_new(NULL, NULL); + playback_props = pw_properties_new(NULL, NULL); + if (!global_props || !capture_props || !playback_props) { + res = -EINVAL; + goto out; + } + + if ((str = pw_properties_get(props, "sink_name")) != NULL) { + pw_properties_set(global_props, PW_KEY_NODE_NAME, str); + pw_properties_set(global_props, PW_KEY_NODE_DESCRIPTION, str); + pw_properties_set(props, "sink_name", NULL); + } else { + pw_properties_set(global_props, PW_KEY_NODE_NAME, "vsink"); + pw_properties_set(global_props, PW_KEY_NODE_DESCRIPTION, "Virtual Sink"); + } + if ((str = pw_properties_get(props, "sink_properties")) != NULL) { + module_args_add_props(capture_props, str); + pw_properties_set(props, "sink_properties", NULL); + } + pw_properties_set(playback_props, PW_KEY_NODE_PASSIVE, "true"); + if (pw_properties_get(capture_props, PW_KEY_MEDIA_CLASS) == NULL) + pw_properties_set(capture_props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); + + if ((str = pw_properties_get(props, "master")) != NULL) { + pw_properties_set(playback_props, PW_KEY_TARGET_OBJECT, str); + pw_properties_set(props, "master", NULL); + } + + if (module_args_to_audioinfo_keys(module->impl, props, + NULL, NULL, "channels", "channel_map", &info) < 0) { + res = -EINVAL; + goto out; + } + audioinfo_to_properties(&info, global_props); + + d->module = module; + d->global_props = global_props; + d->capture_props = capture_props; + d->playback_props = playback_props; + + return 0; +out: + pw_properties_free(global_props); + pw_properties_free(playback_props); + pw_properties_free(capture_props); + + return res; +} + +DEFINE_MODULE_INFO(module_virtual_sink) = { + .name = "module-virtual-sink", + .prepare = module_virtual_sink_prepare, + .load = module_virtual_sink_load, + .unload = module_virtual_sink_unload, + .properties = &SPA_DICT_INIT_ARRAY(module_virtual_sink_info), + .data_size = sizeof(struct module_virtual_sink_data), +};
View file
pipewire-0.3.72.tar.gz/src/modules/module-protocol-pulse/modules/module-virtual-source.c
Added
@@ -0,0 +1,188 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans <wim.taymans@gmail.com> */ +/* SPDX-FileCopyrightText: Copyright © 2021 Arun Raghavan <arun@asymptotic.io> */ +/* SPDX-License-Identifier: MIT */ + +#include <spa/param/audio/format-utils.h> +#include <spa/utils/hook.h> +#include <spa/utils/json.h> +#include <pipewire/pipewire.h> + +#include "../defs.h" +#include "../module.h" + +#define NAME "virtual-source" + +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); +#define PW_LOG_TOPIC_DEFAULT mod_topic + +struct module_virtual_source_data { + struct module *module; + + struct pw_impl_module *mod; + struct spa_hook mod_listener; + + struct pw_properties *global_props; + struct pw_properties *capture_props; + struct pw_properties *playback_props; +}; + +static void module_destroy(void *data) +{ + struct module_virtual_source_data *d = data; + spa_hook_remove(&d->mod_listener); + d->mod = NULL; + module_schedule_unload(d->module); +} + +static const struct pw_impl_module_events module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = module_destroy +}; + +static int module_virtual_source_load(struct module *module) +{ + struct module_virtual_source_data *data = module->user_data; + FILE *f; + char *args; + size_t size; + + pw_properties_setf(data->capture_props, PW_KEY_NODE_GROUP, "virtual-source-%u", module->index); + pw_properties_setf(data->playback_props, PW_KEY_NODE_GROUP, "virtual-source-%u", module->index); + pw_properties_setf(data->capture_props, "pulse.module.id", "%u", module->index); + pw_properties_setf(data->playback_props, "pulse.module.id", "%u", module->index); + + if ((f = open_memstream(&args, &size)) == NULL) + return -errno; + + fprintf(f, "{"); + pw_properties_serialize_dict(f, &data->global_props->dict, 0); + fprintf(f, " capture.props = {"); + pw_properties_serialize_dict(f, &data->capture_props->dict, 0); + fprintf(f, " } playback.props = {"); + pw_properties_serialize_dict(f, &data->playback_props->dict, 0); + fprintf(f, " } }"); + fclose(f); + + data->mod = pw_context_load_module(module->impl->context, + "libpipewire-module-loopback", + args, NULL); + free(args); + + if (data->mod == NULL) + return -errno; + + pw_impl_module_add_listener(data->mod, + &data->mod_listener, + &module_events, data); + + return 0; +} + +static int module_virtual_source_unload(struct module *module) +{ + struct module_virtual_source_data *d = module->user_data; + + if (d->mod) { + spa_hook_remove(&d->mod_listener); + pw_impl_module_destroy(d->mod); + d->mod = NULL; + } + + pw_properties_free(d->capture_props); + pw_properties_free(d->playback_props); + pw_properties_free(d->global_props); + + return 0; +} + +static const struct spa_dict_item module_virtual_source_info = { + { PW_KEY_MODULE_AUTHOR, "Arun Raghavan <arun@asymptotic.io>" }, + { PW_KEY_MODULE_DESCRIPTION, "Loopback from source to sink" }, + { PW_KEY_MODULE_USAGE, "source_name=<name for the source> " + "source_properties=<properties for the source> " + "master=<name of source to filter> " + "uplink_sink=<name> (optional)" + "channels=<number of channels> " + "channel_map=<channel map> " + "use_volume_sharing=<yes or no> " + "force_flat_volume=<yes or no> " }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + +static int module_virtual_source_prepare(struct module * const module) +{ + struct module_virtual_source_data * const d = module->user_data; + struct pw_properties * const props = module->props; + struct pw_properties *global_props = NULL, *playback_props = NULL, *capture_props = NULL; + const char *str; + struct spa_audio_info_raw info = { 0 }; + int res; + + PW_LOG_TOPIC_INIT(mod_topic); + + global_props = pw_properties_new(NULL, NULL); + capture_props = pw_properties_new(NULL, NULL); + playback_props = pw_properties_new(NULL, NULL); + if (!global_props || !capture_props || !playback_props) { + res = -EINVAL; + goto out; + } + + if ((str = pw_properties_get(props, "source_name")) != NULL) { + pw_properties_set(global_props, PW_KEY_NODE_NAME, str); + pw_properties_set(global_props, PW_KEY_NODE_DESCRIPTION, str); + pw_properties_set(props, "source_name", NULL); + } else { + pw_properties_set(global_props, PW_KEY_NODE_NAME, "vsource"); + pw_properties_set(global_props, PW_KEY_NODE_DESCRIPTION, "Virtual Source"); + } + if ((str = pw_properties_get(props, "source_properties")) != NULL) { + module_args_add_props(playback_props, str); + pw_properties_set(props, "source_properties", NULL); + } + pw_properties_set(capture_props, PW_KEY_NODE_PASSIVE, "true"); + if (pw_properties_get(playback_props, PW_KEY_MEDIA_CLASS) == NULL) + pw_properties_set(playback_props, PW_KEY_MEDIA_CLASS, "Audio/Source"); + + if ((str = pw_properties_get(props, "master")) != NULL) { + if (spa_strendswith(str, ".monitor")) { + pw_properties_setf(capture_props, PW_KEY_TARGET_OBJECT, + "%.*s", (int)strlen(str)-8, str); + pw_properties_set(capture_props, PW_KEY_STREAM_CAPTURE_SINK, + "true"); + } else { + pw_properties_set(capture_props, PW_KEY_TARGET_OBJECT, str); + } + pw_properties_set(props, "master", NULL); + } + + if (module_args_to_audioinfo_keys(module->impl, props, + NULL, NULL, "channels", "channel_map", &info) < 0) { + res = -EINVAL; + goto out; + } + audioinfo_to_properties(&info, global_props); + + d->module = module; + d->global_props = global_props; + d->capture_props = capture_props; + d->playback_props = playback_props; + + return 0; +out: + pw_properties_free(global_props); + pw_properties_free(playback_props); + pw_properties_free(capture_props); + + return res; +} + +DEFINE_MODULE_INFO(module_virtual_source) = { + .name = "module-virtual-source", + .prepare = module_virtual_source_prepare, + .load = module_virtual_source_load, + .unload = module_virtual_source_unload, + .properties = &SPA_DICT_INIT_ARRAY(module_virtual_source_info), + .data_size = sizeof(struct module_virtual_source_data), +};
View file
pipewire-0.3.71.tar.gz/src/modules/module-protocol-pulse/pulse-server.c -> pipewire-0.3.72.tar.gz/src/modules/module-protocol-pulse/pulse-server.c
Changed
@@ -1097,13 +1097,17 @@ switch (id) { case SPA_PROP_channelVolumes: - stream->volume.channels = control->n_values; - memcpy(stream->volume.values, control->values, control->n_values * sizeof(float)); - pw_log_info("stream %p: volume changed %f", stream, stream->volume.values0); + if (!stream->volume_set) { + stream->volume.channels = control->n_values; + memcpy(stream->volume.values, control->values, control->n_values * sizeof(float)); + pw_log_info("stream %p: volume changed %f", stream, stream->volume.values0); + } break; case SPA_PROP_mute: - stream->muted = control->values0 >= 0.5; - pw_log_info("stream %p: mute changed %d", stream, stream->muted); + if (!stream->muted_set) { + stream->muted = control->values0 >= 0.5; + pw_log_info("stream %p: mute changed %d", stream, stream->muted); + } break; } } @@ -1208,11 +1212,13 @@ struct pw_manager_object *peer; if (stream->volume_set) { + stream->volume_set = false; pw_stream_set_control(stream->stream, SPA_PROP_channelVolumes, stream->volume.channels, stream->volume.values, 0); } if (stream->muted_set) { float val = stream->muted ? 1.0f : 0.0f; + stream->muted_set = false; pw_stream_set_control(stream->stream, SPA_PROP_mute, 1, &val, 0); } @@ -1586,6 +1592,8 @@ const struct spa_pod *paramsMAX_FORMATS; uint8_t buffer4096; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + struct pw_manager_object *o; + bool is_monitor; props = pw_properties_copy(client->props); if (props == NULL) @@ -1632,22 +1640,17 @@ TAG_INVALID) < 0) goto error_protocol; } + o = find_device(client, sink_index, sink_name, true, &is_monitor); spa_zero(fix_ss); spa_zero(fix_map); - if (fix_format || fix_rate || fix_channels) { - struct pw_manager_object *o; - bool is_monitor; - - o = find_device(client, sink_index, sink_name, true, &is_monitor); - if (o != NULL) { - struct device_info dev_info = DEVICE_INFO_INIT(PW_DIRECTION_OUTPUT); - collect_device_info(o, NULL, &dev_info, is_monitor, &impl->defs); - fix_ss.format = fix_format ? dev_info.ss.format : 0; - fix_ss.rate = fix_rate ? dev_info.ss.rate : 0; - fix_ss.channels = fix_channels ? dev_info.ss.channels : 0; - fix_map = dev_info.map; - } + if ((fix_format || fix_rate || fix_channels) && o != NULL) { + struct device_info dev_info = DEVICE_INFO_INIT(PW_DIRECTION_OUTPUT); + collect_device_info(o, NULL, &dev_info, is_monitor, &impl->defs); + fix_ss.format = fix_format ? dev_info.ss.format : 0; + fix_ss.rate = fix_rate ? dev_info.ss.rate : 0; + fix_ss.channels = fix_channels ? dev_info.ss.channels : 0; + fix_map = dev_info.map; } if (client->version >= 13) { @@ -1772,6 +1775,9 @@ flags |= PW_STREAM_FLAG_DONT_RECONNECT; if (sink_name != NULL) { + if (o != NULL) + sink_name = pw_properties_get(o->props, + PW_KEY_NODE_NAME); pw_properties_set(props, PW_KEY_TARGET_OBJECT, sink_name); } else if (sink_index != SPA_ID_INVALID && sink_index != 0) { @@ -1858,6 +1864,8 @@ const struct spa_pod *paramsMAX_FORMATS; uint8_t buffer4096; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + struct pw_manager_object *o; + bool is_monitor = false; props = pw_properties_copy(client->props); if (props == NULL) @@ -1922,22 +1930,17 @@ TAG_INVALID) < 0) goto error_protocol; } + o = find_device(client, source_index, source_name, false, &is_monitor); spa_zero(fix_ss); spa_zero(fix_map); - if (fix_format || fix_rate || fix_channels) { - struct pw_manager_object *o; - bool is_monitor; - - o = find_device(client, source_index, source_name, false, &is_monitor); - if (o != NULL) { - struct device_info dev_info = DEVICE_INFO_INIT(PW_DIRECTION_INPUT); - collect_device_info(o, NULL, &dev_info, is_monitor, &impl->defs); - fix_ss.format = fix_format ? dev_info.ss.format : 0; - fix_ss.rate = fix_rate ? dev_info.ss.rate : 0; - fix_ss.channels = fix_channels ? dev_info.ss.channels : 0; - fix_map = dev_info.map; - } + if ((fix_format || fix_rate || fix_channels) && o != NULL) { + struct device_info dev_info = DEVICE_INFO_INIT(PW_DIRECTION_INPUT); + collect_device_info(o, NULL, &dev_info, is_monitor, &impl->defs); + fix_ss.format = fix_format ? dev_info.ss.format : 0; + fix_ss.rate = fix_rate ? dev_info.ss.rate : 0; + fix_ss.channels = fix_channels ? dev_info.ss.channels : 0; + fix_map = dev_info.map; } if (client->version >= 22) { @@ -2048,16 +2051,21 @@ pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%u", source_index); } else if (source_name != NULL) { + if (o != NULL) + source_name = pw_properties_get(o->props, + PW_KEY_NODE_NAME); if (spa_strendswith(source_name, ".monitor")) { + is_monitor = true; pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%.*s", (int)strlen(source_name)-8, source_name); - pw_properties_set(props, - PW_KEY_STREAM_CAPTURE_SINK, "true"); } else { pw_properties_set(props, PW_KEY_TARGET_OBJECT, source_name); } + if (is_monitor) + pw_properties_set(props, + PW_KEY_STREAM_CAPTURE_SINK, "true"); } stream->stream = pw_stream_new(client->core, name, props); @@ -2453,6 +2461,7 @@ return NULL; sink = true; find_default = true; + monitor = true; } else if (spa_streq(name, DEFAULT_SOURCE)) { if (sink) return NULL; @@ -2908,10 +2917,15 @@ (index != SPA_ID_INVALID && name != NULL)) return -EINVAL; - if (command == COMMAND_SET_SINK_VOLUME) + if (command == COMMAND_SET_SINK_VOLUME) { + if (client->quirks & QUIRK_BLOCK_SINK_VOLUME) + return -EPERM; direction = PW_DIRECTION_OUTPUT; - else + } else { + if (client->quirks & QUIRK_BLOCK_SOURCE_VOLUME) + return -EPERM; direction = PW_DIRECTION_INPUT; + } o = find_device(client, index, name, direction == PW_DIRECTION_OUTPUT, &is_monitor); if (o == NULL || (info = o->info) == NULL || info->props == NULL)
View file
pipewire-0.3.71.tar.gz/src/modules/module-protocol-pulse/quirks.c -> pipewire-0.3.72.tar.gz/src/modules/module-protocol-pulse/quirks.c
Changed
@@ -17,6 +17,8 @@ static const struct { const char *key; uint64_t value; } quirk_keys = { { "force-s16-info", QUIRK_FORCE_S16_FORMAT }, { "remove-capture-dont-move", QUIRK_REMOVE_CAPTURE_DONT_MOVE }, + { "block-source-volume", QUIRK_BLOCK_SOURCE_VOLUME }, + { "block-sink-volume", QUIRK_BLOCK_SINK_VOLUME }, }; SPA_FOR_EACH_ELEMENT_VAR(quirk_keys, i) { if (spa_streq(str, i->key))
View file
pipewire-0.3.71.tar.gz/src/modules/module-protocol-pulse/quirks.h -> pipewire-0.3.72.tar.gz/src/modules/module-protocol-pulse/quirks.h
Changed
@@ -10,6 +10,8 @@ #define QUIRK_FORCE_S16_FORMAT (1ull<<0) /** forces S16 sample format in sink and source * info */ #define QUIRK_REMOVE_CAPTURE_DONT_MOVE (1ull<<1) /** removes the capture stream DONT_MOVE flag */ +#define QUIRK_BLOCK_SOURCE_VOLUME (1ull<<2) /** block volume changes to sources */ +#define QUIRK_BLOCK_SINK_VOLUME (1ull<<3) /** block volume changes to sinks */ int client_update_quirks(struct client *client);
View file
pipewire-0.3.71.tar.gz/src/modules/module-protocol-pulse/stream.c -> pipewire-0.3.72.tar.gz/src/modules/module-protocol-pulse/stream.c
Changed
@@ -179,11 +179,18 @@ missing -= stream->requested; missing -= avail; - if (missing <= 0) + if (missing <= 0) { + pw_log_debug("stream %p: (tlen:%u - req:%"PRIi64" - avail:%"PRIi64") <= 0", + stream, stream->attr.tlength, stream->requested, avail); return 0; + } - if (missing < stream->attr.minreq && !stream_prebuf_active(stream, avail)) + if (missing < stream->attr.minreq && !stream_prebuf_active(stream, avail)) { + pw_log_debug("stream %p: (tlen:%u - req:%"PRIi64" - avail:%"PRIi64") <= minreq:%u", + stream, stream->attr.tlength, stream->requested, avail, + stream->attr.minreq); return 0; + } stream->requested += missing; @@ -304,11 +311,12 @@ uint32_t size; size = stream_pop_missing(stream); - pw_log_debug("stream %p: REQUEST channel:%d %u", stream, stream->channel, size); if (size == 0) return 0; + pw_log_debug("stream %p: REQUEST channel:%d %u", stream, stream->channel, size); + msg = message_alloc(impl, -1, 0); message_put(msg, TAG_U32, COMMAND_REQUEST,
View file
pipewire-0.3.71.tar.gz/src/modules/module-raop-discover.c -> pipewire-0.3.72.tar.gz/src/modules/module-raop-discover.c
Changed
@@ -126,9 +126,6 @@ struct tunnel_info { const char *name; - const char *host_name; - const char *ip; - const char *port; }; #define TUNNEL_INFO(...) ((struct tunnel_info){ __VA_ARGS__ }) @@ -151,9 +148,6 @@ return NULL; t->info.name = strdup(info->name); - t->info.host_name = strdup(info->host_name); - t->info.ip = strdup(info->ip); - t->info.port = strdup(info->port); spa_list_append(&impl->tunnel_list, &t->link); return t; @@ -171,7 +165,11 @@ static void free_tunnel(struct tunnel *t) { - pw_impl_module_destroy(t->module); + spa_list_remove(&t->link); + if (t->module) + pw_impl_module_destroy(t->module); + free((char *) t->info.name); + free(t); } static void impl_free(struct impl *impl) @@ -284,16 +282,8 @@ static void submodule_destroy(void *data) { struct tunnel *t = data; - - spa_list_remove(&t->link); spa_hook_remove(&t->module_listener); - - free((char *) t->info.name); - free((char *) t->info.host_name); - free((char *) t->info.ip); - free((char *) t->info.port); - - free(t); + t->module = NULL; } static const struct pw_impl_module_events submodule_events = { @@ -304,19 +294,18 @@ struct match_info { struct impl *impl; struct pw_properties *props; - struct tunnel_info *tinfo; + struct tunnel *tunnel; bool matched; }; static int create_stream(struct impl *impl, struct pw_properties *props, - struct tunnel_info *tinfo) + struct tunnel *t) { FILE *f; char *args; size_t size; int res = 0; struct pw_impl_module *mod; - struct tunnel *t; if ((f = open_memstream(&args, &size)) == NULL) { res = -errno; @@ -341,16 +330,7 @@ goto done; } - t = make_tunnel(impl, tinfo); - if (t == NULL) { - res = -errno; - pw_log_error("Can't make tunnel: %m"); - pw_impl_module_destroy(mod); - goto done; - } - pw_impl_module_add_listener(mod, &t->module_listener, &submodule_events, t); - t->module = mod; done: return res; @@ -365,7 +345,7 @@ i->matched = true; if (spa_streq(action, "create-stream")) { pw_properties_update_string(i->props, str, len); - create_stream(i->impl, i->props, i->tinfo); + create_stream(i->impl, i->props, i->tunnel); } return res; } @@ -377,10 +357,12 @@ { struct impl *impl = userdata; struct tunnel_info tinfo; - const char *str, *port_str; + struct tunnel *t; + const char *str; AvahiStringList *l; struct pw_properties *props = NULL; char atAVAHI_ADDRESS_STR_MAX; + int ipv; if (event != AVAHI_RESOLVER_FOUND) { pw_log_error("Resolving of '%s' failed: %s", name, @@ -388,7 +370,19 @@ goto done; } - avahi_address_snprint(at, sizeof(at), a); + tinfo = TUNNEL_INFO(.name = name); + + t = find_tunnel(impl, &tinfo); + if (t == NULL) + t = make_tunnel(impl, &tinfo); + if (t == NULL) { + pw_log_error("Can't make tunnel: %m"); + goto done; + } + if (t->module != NULL) { + pw_log_info("found duplicate mdns entry - skipping tunnel creation"); + goto done; + } props = pw_properties_new(NULL, NULL); if (props == NULL) { @@ -396,7 +390,10 @@ goto done; } + avahi_address_snprint(at, sizeof(at), a); + ipv = protocol == AVAHI_PROTO_INET ? 4 : 6; pw_properties_setf(props, "raop.ip", "%s", at); + pw_properties_setf(props, "raop.ip.version", "%d", ipv); pw_properties_setf(props, "raop.port", "%u", port); pw_properties_setf(props, "raop.name", "%s", name); pw_properties_setf(props, "raop.hostname", "%s", host_name); @@ -413,20 +410,13 @@ avahi_free(value); } - port_str = pw_properties_get(props, "raop.port"); - - tinfo = TUNNEL_INFO(.name = name, - .host_name = host_name, - .ip = at, - .port = port_str); - if ((str = pw_properties_get(impl->properties, "stream.rules")) == NULL) str = DEFAULT_CREATE_RULES; if (str != NULL) { struct match_info minfo = { .impl = impl, .props = props, - .tinfo = &tinfo, + .tunnel = t, }; pw_conf_match_rules(str, strlen(str), NAME, &props->dict, rule_matched, &minfo); @@ -459,7 +449,7 @@ switch (event) { case AVAHI_BROWSER_NEW: if (t != NULL) { - pw_log_debug("found duplicate mdns entry - skipping tunnel creation"); + pw_log_info("found duplicate mdns entry - skipping tunnel creation"); return; } if (!(avahi_service_resolver_new(impl->client,
View file
pipewire-0.3.71.tar.gz/src/modules/module-raop-sink.c -> pipewire-0.3.72.tar.gz/src/modules/module-raop-sink.c
Changed
@@ -122,6 +122,9 @@ #define FRAMES_PER_TCP_PACKET 4096 #define FRAMES_PER_UDP_PACKET 352 +#define RAOP_LATENCY_MIN 11025u +#define DEFAULT_LATENCY_MS "1000" + #define DEFAULT_TCP_AUDIO_PORT 6000 #define DEFAULT_UDP_AUDIO_PORT 6000 #define DEFAULT_UDP_CONTROL_PORT 6001 @@ -143,8 +146,6 @@ #define DEFAULT_CHANNELS 2 #define DEFAULT_POSITION " FL FR " -#define DEFAULT_LATENCY 22050 - #define VOLUME_MAX 0.0 #define VOLUME_DEF -30.0 #define VOLUME_MIN -144.0 @@ -243,7 +244,6 @@ struct spa_source *server_source; uint32_t block_size; - uint32_t delay; uint32_t latency; uint16_t seq; @@ -297,10 +297,10 @@ return ntp | (uint64_t) (ts->tv_sec + 0x83aa7e80) << 32; } -static inline uint64_t ntp_now(int clockid) +static inline uint64_t ntp_now(void) { struct timespec now; - clock_gettime(clockid, &now); + clock_gettime(CLOCK_REALTIME, &now); return timespec_to_ntp(&now); } @@ -309,22 +309,20 @@ { uint32_t pkt5; uint32_t rtptime = impl->rtptime; - uint32_t delay = impl->delay; + uint32_t latency = impl->latency; uint64_t transmitted; pkt0 = htonl(0x80d40007); if (impl->first) pkt0 |= htonl(0x10000000); - rtptime -= delay; - pkt1 = htonl(rtptime); - transmitted = ntp_now(CLOCK_MONOTONIC); + pkt1 = htonl(rtptime - latency); + transmitted = ntp_now(); pkt2 = htonl(transmitted >> 32); pkt3 = htonl(transmitted & 0xffffffff); - rtptime += delay; pkt4 = htonl(rtptime); - pw_log_debug("sync: delayed:%u now:%"PRIu64" rtptime:%u", - rtptime - delay, transmitted, rtptime); + pw_log_debug("sync: first:%d latency:%u now:%"PRIx64" rtptime:%u", + impl->first, latency, transmitted, rtptime); return sendto(impl->control_fd, pkt, sizeof(pkt), 0, dest_addr, addrlen); } @@ -341,11 +339,11 @@ pkt3 = htonl(remote & 0xffffffff); pkt4 = htonl(received >> 32); pkt5 = htonl(received & 0xffffffff); - transmitted = ntp_now(CLOCK_MONOTONIC); + transmitted = ntp_now(); pkt6 = htonl(transmitted >> 32); pkt7 = htonl(transmitted & 0xffffffff); - pw_log_debug("sync: remote:%"PRIu64" received:%"PRIu64" transmitted:%"PRIu64, + pw_log_debug("sync: remote:%"PRIx64" received:%"PRIx64" transmitted:%"PRIx64, remote, received, transmitted); return sendto(impl->timing_fd, pkt, sizeof(pkt), 0, dest_addr, addrlen); @@ -391,8 +389,7 @@ if (!impl->recording) return 0; - impl->sync++; - if (impl->first || impl->sync == impl->sync_period) { + if (impl->first || ++impl->sync == impl->sync_period) { impl->sync = 0; send_udp_sync_packet(impl, NULL, 0); } @@ -642,12 +639,17 @@ uint32_t packet8; ssize_t bytes; + if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { + pw_log_warn("error on timing socket: %08x", mask); + pw_loop_update_io(impl->loop, impl->timing_source, 0); + return; + } if (mask & SPA_IO_IN) { uint64_t remote, received; struct sockaddr_storage sender; socklen_t sender_size = sizeof(sender); - received = ntp_now(CLOCK_MONOTONIC); + received = ntp_now(); bytes = recvfrom(impl->timing_fd, packet, sizeof(packet), 0, (struct sockaddr*)&sender, &sender_size); if (bytes < 0) { @@ -679,13 +681,18 @@ uint32_t packet2; ssize_t bytes; + if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { + pw_log_warn("error on control socket: %08x", mask); + pw_loop_update_io(impl->loop, impl->control_source, 0); + return; + } if (mask & SPA_IO_IN) { uint32_t hdr; uint16_t seq, num; bytes = read(impl->control_fd, packet, sizeof(packet)); if (bytes < 0) { - pw_log_debug("error reading control packet: %m"); + pw_log_warn("error reading control packet: %m"); return; } if (bytes != sizeof(packet)) { @@ -882,15 +889,14 @@ pw_log_info("reply %d", status); if ((str = spa_dict_lookup(headers, "Audio-Latency")) != NULL) { - if (!spa_atou32(str, &impl->latency, 0)) - impl->latency = DEFAULT_LATENCY; - } else { - impl->latency = DEFAULT_LATENCY; + uint32_t l; + if (spa_atou32(str, &l, 0)) + impl->latency = SPA_MAX(l, impl->latency); } spa_zero(latency); latency.direction = PW_DIRECTION_INPUT; - latency.min_rate = latency.max_rate = impl->latency; + latency.min_rate = latency.max_rate = impl->latency + RAOP_LATENCY_MIN; n_params = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); @@ -1033,7 +1039,7 @@ if ((impl->timing_fd = connect_socket(impl, SOCK_DGRAM, impl->timing_fd, timing_port)) < 0) return impl->timing_fd; - ntp = ntp_now(CLOCK_MONOTONIC); + ntp = ntp_now(); send_udp_timing_packet(impl, ntp, ntp, NULL, 0); } @@ -1206,8 +1212,6 @@ int res, frames, rsa_len, ip_version; char *sdp; char local_ip256; - int min_latency; - min_latency = DEFAULT_LATENCY; host = pw_properties_get(impl->props, "raop.ip"); if (impl->protocol == PROTO_TCP) @@ -1222,7 +1226,7 @@ switch (impl->encryption) { case CRYPTO_NONE: - asprintf(&sdp, "v=0\r\n" + sdp = spa_aprintf("v=0\r\n" "o=iTunes %s 0 IN IP%d %s\r\n" "s=iTunes\r\n" "c=IN IP%d %s\r\n" @@ -1232,10 +1236,12 @@ "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 %u\r\n", impl->session_id, ip_version, local_ip, ip_version, host, frames, impl->info.rate); + if (!sdp) + return -errno; break; case CRYPTO_AUTH_SETUP: - asprintf(&sdp, "v=0\r\n" + sdp = spa_aprintf("v=0\r\n" "o=iTunes %s 0 IN IP%d %s\r\n" "s=iTunes\r\n" "c=IN IP%d %s\r\n" @@ -1246,7 +1252,9 @@ "a=min-latency:%d", impl->session_id, ip_version, local_ip, ip_version, host, frames, impl->info.rate, - min_latency); + RAOP_LATENCY_MIN); + if (!sdp) + return -errno; break; case CRYPTO_RSA: @@ -1261,7 +1269,7 @@ base64_encode(rsakey, rsa_len, key, '='); base64_encode(impl->iv, 16, iv, '='); - asprintf(&sdp, "v=0\r\n" + sdp = spa_aprintf("v=0\r\n" "o=iTunes %s 0 IN IP%d %s\r\n" "s=iTunes\r\n" "c=IN IP%d %s\r\n" @@ -1274,6 +1282,8 @@ impl->session_id, ip_version, local_ip, ip_version, host, frames, impl->info.rate, key, iv); + if (!sdp) + return -errno; break; default: return -ENOTSUP; @@ -2009,6 +2019,10 @@ str = pw_properties_get(props, "raop.password"); impl->password = str ? strdup(str) : NULL; + if ((str = pw_properties_get(props, "raop.latency.ms")) == NULL) + str = DEFAULT_LATENCY_MS; + impl->latency = SPA_MAX(atoi(str) * impl->info.rate / 1000u, RAOP_LATENCY_MIN); + impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { str = pw_properties_get(props, PW_KEY_REMOTE_NAME);
View file
pipewire-0.3.71.tar.gz/src/modules/module-raop/rtsp-client.c -> pipewire-0.3.72.tar.gz/src/modules/module-raop/rtsp-client.c
Changed
@@ -490,6 +490,7 @@ pw_log_error("getaddrinfo: %s", gai_strerror(res)); return -EINVAL; } + res = -ENOENT; for (rp = result; rp != NULL; rp = rp->ai_next) { fd = socket(rp->ai_family, rp->ai_socktype | SOCK_CLOEXEC | SOCK_NONBLOCK, @@ -501,12 +502,14 @@ if (res == 0 || (res < 0 && errno == EINPROGRESS)) break; + res = -errno; close(fd); } freeaddrinfo(result); if (rp == NULL) { - pw_log_error("Could not connect to %s:%u", hostname, port); + pw_log_error("Could not connect to %s:%u: %s", hostname, port, + spa_strerror(res)); return -EINVAL; }
View file
pipewire-0.3.71.tar.gz/src/modules/module-rtp-sap.c -> pipewire-0.3.72.tar.gz/src/modules/module-rtp-sap.c
Changed
@@ -355,8 +355,10 @@ return false; } -static int make_send_socket(struct sockaddr_storage *sa, socklen_t salen, - bool loop, int ttl, char *ifname) +static int make_send_socket( + struct sockaddr_storage *src, socklen_t src_len, + struct sockaddr_storage *sa, socklen_t salen, + bool loop, int ttl) { int af, fd, val, res; @@ -365,6 +367,11 @@ pw_log_error("socket failed: %m"); return -errno; } + if (bind(fd, (struct sockaddr*)src, src_len) < 0) { + res = -errno; + pw_log_error("bind() failed: %m"); + goto error; + } if (connect(fd, (struct sockaddr*)sa, salen) < 0) { res = -errno; pw_log_error("connect() failed: %m"); @@ -520,43 +527,42 @@ snprintf(dst_ttl, sizeof(dst_ttl), "/%d", sdp->ttl); spa_strbuf_init(&buf, buffer, sizeof(buffer)); + /* Don't add any sdp records in between this definition or change the order + it will break compatibility with Dante/AES67 devices. Add new records to + the end. */ spa_strbuf_append(&buf, "v=0\n" "o=%s %u 0 IN %s %s\n" "s=%s\n" "c=IN %s %s%s\n" "t=%u 0\n" - "a=recvonly\n" - "a=tool:PipeWire %s\n" - "a=type:broadcast\n", + "m=%s %u RTP/AVP %i\n", user_name, sdp->ntp, src_ip4 ? "IP4" : "IP6", src_addr, sdp->session_name, dst_ip4 ? "IP4" : "IP6", dst_addr, dst_ttl, sdp->ntp, - pw_get_library_version()); - spa_strbuf_append(&buf, - "m=%s %u RTP/AVP %i\n", - sdp->media_type, - sdp->dst_port, sdp->payload); + sdp->media_type, sdp->dst_port, sdp->payload); if (sdp->channels) { - spa_strbuf_append(&buf, - "a=rtpmap:%i %s/%u/%u\n", - sdp->payload, sdp->mime_type, - sdp->rate, sdp->channels); if (sdp->channelmap0 != 0) { spa_strbuf_append(&buf, "i=%d channels: %s\n", sdp->channels, sdp->channelmap); } + spa_strbuf_append(&buf, + "a=recvonly\n" + "a=rtpmap:%i %s/%u/%u\n", + sdp->payload, sdp->mime_type, + sdp->rate, sdp->channels); } else { spa_strbuf_append(&buf, "a=rtpmap:%i %s/%u\n", sdp->payload, sdp->mime_type, sdp->rate); } - if (sdp->ptime != 0) + + if (sdp->ptime > 0) spa_strbuf_append(&buf, - "a=ptime:%f\n", sdp->ptime); + "a=ptime:%.6g\n", sdp->ptime); if (sdp->ts_refclk != NULL) { spa_strbuf_append(&buf, @@ -568,6 +574,11 @@ spa_strbuf_append(&buf, "a=mediaclk:sender\n"); } + spa_strbuf_append(&buf, + "a=tool:PipeWire %s\n" + "a=type:broadcast\n", + pw_get_library_version()); + pw_log_debug("sending SAP for %u %s", sess->node->id, buffer); iov3.iov_base = buffer; @@ -672,6 +683,10 @@ sdp->ttl = pw_properties_get_int32(props, "rtp.ttl", DEFAULT_TTL); sdp->payload = pw_properties_get_int32(props, "rtp.payload", 127); + if ((str = pw_properties_get(props, "rtp.ptime")) != NULL) + if (!spa_atof(str, &sdp->ptime)) + sdp->ptime = 0.0; + if ((str = pw_properties_get(props, "rtp.media")) != NULL) sdp->media_type = strdup(str); if ((str = pw_properties_get(props, "rtp.mime")) != NULL) @@ -684,7 +699,7 @@ sdp->ts_offset = atoi(str); if ((str = pw_properties_get(props, "rtp.ts-refclk")) != NULL) sdp->ts_refclk = strdup(str); - if ((str = pw_properties_get(props, "rtp.channel-names")) != NULL) + if ((str = pw_properties_get(props, PW_KEY_NODE_CHANNELNAMES)) != NULL) snprintf(sdp->channelmap, sizeof(sdp->channelmap), "%s", str); pw_log_info("created new session for node:%u", node->id); @@ -884,6 +899,7 @@ pw_properties_setf(props, "rtp.destination.ip", "%s", dst_addr); pw_properties_setf(props, "rtp.destination.port", "%u", info->dst_port); pw_properties_setf(props, "rtp.payload", "%u", info->payload); + pw_properties_setf(props, "rtp.ptime", "%f", info->ptime); pw_properties_setf(props, "rtp.media", "%s", info->media_type); pw_properties_setf(props, "rtp.mime", "%s", info->mime_type); pw_properties_setf(props, "rtp.rate", "%u", info->rate); @@ -1209,9 +1225,9 @@ int fd, res; struct timespec value, interval; - if ((fd = make_send_socket(&impl->sap_addr, impl->sap_len, - impl->mcast_loop, impl->ttl, - impl->ifname)) < 0) + if ((fd = make_send_socket(&impl->src_addr, impl->src_len, + &impl->sap_addr, impl->sap_len, + impl->mcast_loop, impl->ttl)) < 0) return fd; impl->sap_fd = fd; @@ -1456,8 +1472,23 @@ impl->cleanup_interval = pw_properties_get_uint32(impl->props, "sap.cleanup.sec", DEFAULT_CLEANUP_SEC); - if ((str = pw_properties_get(props, "source.ip")) == NULL) + if ((str = pw_properties_get(props, "source.ip")) == NULL) { str = DEFAULT_SOURCE_IP; + if (impl->ifname) { + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd >= 0) { + struct ifreq req; + spa_zero(req); + req.ifr_addr.sa_family = AF_INET; + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", impl->ifname); + res = ioctl(fd, SIOCGIFADDR, &req); + if (res < 0) + pw_log_warn("SIOCGIFADDR %s failed: %m", impl->ifname); + str = inet_ntoa(((struct sockaddr_in *)&req.ifr_addr)->sin_addr); + close(fd); + } + } + } if ((res = parse_address(str, port, &impl->src_addr, &impl->src_len)) < 0) { pw_log_error("invalid source.ip %s: %s", str, spa_strerror(res)); goto out;
View file
pipewire-0.3.71.tar.gz/src/modules/module-rtp-source.c -> pipewire-0.3.72.tar.gz/src/modules/module-rtp-source.c
Changed
@@ -18,6 +18,7 @@ #include <spa/utils/hook.h> #include <spa/utils/result.h> #include <spa/utils/ringbuffer.h> +#include <spa/utils/defs.h> #include <spa/utils/dll.h> #include <spa/utils/json.h> #include <spa/param/audio/format-utils.h> @@ -164,7 +165,8 @@ if (len < 12) goto short_packet; - rtp_stream_receive_packet(impl->stream, buffer, len); + if (SPA_LIKELY(impl->stream)) + rtp_stream_receive_packet(impl->stream, buffer, len); impl->receiving = true; }
View file
pipewire-0.3.71.tar.gz/src/modules/module-rtp/stream.c -> pipewire-0.3.72.tar.gz/src/modules/module-rtp/stream.c
Changed
@@ -389,9 +389,20 @@ min_samples = min_ptime * impl->rate / 1000; max_samples = max_ptime * impl->rate / 1000; - impl->psamples = impl->mtu / impl->stride; - impl->psamples = SPA_CLAMP(impl->psamples, min_samples, max_samples); + float ptime = 0; + if ((str = pw_properties_get(props, "rtp.ptime")) != NULL) + if (!spa_atof(str, &ptime)) + ptime = 0.0; + if (ptime) { + impl->psamples = ptime * impl->rate / 1000; + } else { + impl->psamples = impl->mtu / impl->stride; + impl->psamples = SPA_CLAMP(impl->psamples, min_samples, max_samples); + if (direction == PW_DIRECTION_OUTPUT) + pw_properties_setf(props, "rtp.ptime", "%f", + impl->psamples * 1000.0 / impl->rate); + } latency_msec = pw_properties_get_uint32(props, "sess.latency.msec", DEFAULT_SESS_LATENCY); impl->target_buffer = msec_to_samples(impl, latency_msec); @@ -407,8 +418,6 @@ } pw_properties_setf(props, "net.mtu", "%u", impl->mtu); - pw_properties_setf(props, "rtp.ptime", "%u", - impl->psamples * 1000 / impl->rate); pw_properties_setf(props, "rtp.media", "%s", impl->format_info->media_type); pw_properties_setf(props, "rtp.mime", "%s", impl->format_info->mime); pw_properties_setf(props, "rtp.payload", "%u", impl->payload);
View file
pipewire-0.3.71.tar.gz/src/modules/module-session-manager/client-endpoint/endpoint-stream.c -> pipewire-0.3.72.tar.gz/src/modules/module-session-manager/client-endpoint/endpoint-stream.c
Changed
@@ -10,6 +10,7 @@ #include <pipewire/extensions/session-manager.h> #include <spa/pod/filter.h> +#include <spa/pod/dynamic.h> #include "endpoint-stream.h" #include "client-endpoint.h" @@ -39,8 +40,8 @@ struct endpoint_stream *this = data->stream; struct spa_pod *result; struct spa_pod *param; - uint8_t buffer1024; - struct spa_pod_builder b = { 0 }; + uint8_t buffer2048; + struct spa_pod_dynamic_builder b = { 0 }; uint32_t index; uint32_t next = start; uint32_t count = 0; @@ -55,15 +56,15 @@ if (param == NULL || !spa_pod_is_object_id(param, id)) continue; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - if (spa_pod_filter(&b, &result, param, filter) != 0) - continue; - - pw_log_debug(NAME" %p: %d param %u", this, seq, index); - - pw_endpoint_stream_resource_param(resource, seq, id, index, next, result); + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + if (spa_pod_filter(&b.b, &result, param, filter) == 0) { + pw_log_debug(NAME" %p: %d param %u", this, seq, index); + pw_endpoint_stream_resource_param(resource, seq, id, index, next, result); + count++; + } + spa_pod_dynamic_builder_clean(&b); - if (++count == num) + if (count == num) break; } return 0;
View file
pipewire-0.3.71.tar.gz/src/modules/module-session-manager/client-endpoint/endpoint.c -> pipewire-0.3.72.tar.gz/src/modules/module-session-manager/client-endpoint/endpoint.c
Changed
@@ -10,6 +10,7 @@ #include <pipewire/extensions/session-manager.h> #include <spa/pod/filter.h> +#include <spa/pod/dynamic.h> #include "endpoint.h" #include "client-endpoint.h" @@ -39,8 +40,8 @@ struct endpoint *this = data->endpoint; struct spa_pod *result; struct spa_pod *param; - uint8_t buffer1024; - struct spa_pod_builder b = { 0 }; + uint8_t buffer2048; + struct spa_pod_dynamic_builder b = { 0 }; uint32_t index; uint32_t next = start; uint32_t count = 0; @@ -57,15 +58,15 @@ if (param == NULL || !spa_pod_is_object_id(param, id)) continue; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - if (spa_pod_filter(&b, &result, param, filter) != 0) - continue; - - pw_log_debug(NAME" %p: %d param %u", this, seq, index); - - pw_endpoint_resource_param(resource, seq, id, index, next, result); + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + if (spa_pod_filter(&b.b, &result, param, filter) == 0) { + pw_log_debug(NAME" %p: %d param %u", this, seq, index); + pw_endpoint_resource_param(resource, seq, id, index, next, result); + count++; + } + spa_pod_dynamic_builder_clean(&b); - if (++count == num) + if (count == num) break; } return 0;
View file
pipewire-0.3.71.tar.gz/src/modules/module-session-manager/client-session/endpoint-link.c -> pipewire-0.3.72.tar.gz/src/modules/module-session-manager/client-session/endpoint-link.c
Changed
@@ -10,6 +10,7 @@ #include <pipewire/extensions/session-manager.h> #include <spa/pod/filter.h> +#include <spa/pod/dynamic.h> #include "endpoint-link.h" #include "client-session.h" @@ -39,8 +40,8 @@ struct endpoint_link *this = data->link; struct spa_pod *result; struct spa_pod *param; - uint8_t buffer1024; - struct spa_pod_builder b = { 0 }; + uint8_t buffer2048; + struct spa_pod_dynamic_builder b = { 0 }; uint32_t index; uint32_t next = start; uint32_t count = 0; @@ -55,15 +56,15 @@ if (param == NULL || !spa_pod_is_object_id(param, id)) continue; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - if (spa_pod_filter(&b, &result, param, filter) != 0) - continue; - - pw_log_debug(NAME" %p: %d param %u", this, seq, index); - - pw_endpoint_link_resource_param(resource, seq, id, index, next, result); + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + if (spa_pod_filter(&b.b, &result, param, filter) == 0) { + pw_log_debug(NAME" %p: %d param %u", this, seq, index); + pw_endpoint_link_resource_param(resource, seq, id, index, next, result); + count++; + } + spa_pod_dynamic_builder_clean(&b); - if (++count == num) + if (count == num) break; } return 0;
View file
pipewire-0.3.71.tar.gz/src/modules/module-session-manager/client-session/session.c -> pipewire-0.3.72.tar.gz/src/modules/module-session-manager/client-session/session.c
Changed
@@ -10,6 +10,7 @@ #include <pipewire/extensions/session-manager.h> #include <spa/pod/filter.h> +#include <spa/pod/dynamic.h> #include "session.h" #include "client-session.h" @@ -39,8 +40,8 @@ struct session *this = data->session; struct spa_pod *result; struct spa_pod *param; - uint8_t buffer1024; - struct spa_pod_builder b = { 0 }; + uint8_t buffer2048; + struct spa_pod_dynamic_builder b = { 0 }; uint32_t index; uint32_t next = start; uint32_t count = 0; @@ -55,15 +56,15 @@ if (param == NULL || !spa_pod_is_object_id(param, id)) continue; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - if (spa_pod_filter(&b, &result, param, filter) != 0) - continue; - - pw_log_debug(NAME" %p: %d param %u", this, seq, index); - - pw_session_resource_param(resource, seq, id, index, next, result); + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + if (spa_pod_filter(&b.b, &result, param, filter) == 0) { + pw_log_debug(NAME" %p: %d param %u", this, seq, index); + pw_session_resource_param(resource, seq, id, index, next, result); + count++; + } + spa_pod_dynamic_builder_clean(&b); - if (++count == num) + if (count == num) break; } return 0;
View file
pipewire-0.3.71.tar.gz/src/modules/module-session-manager/endpoint-link.c -> pipewire-0.3.72.tar.gz/src/modules/module-session-manager/endpoint-link.c
Changed
@@ -10,6 +10,7 @@ #include <spa/utils/result.h> #include <spa/pod/builder.h> #include <spa/pod/filter.h> +#include <spa/pod/dynamic.h> #define MAX_PARAMS 32 @@ -84,8 +85,8 @@ struct param_data *pdata; struct spa_pod *result; struct spa_pod *param; - uint8_t buffer1024; - struct spa_pod_builder b = { 0 }; + uint8_t buffer2048; + struct spa_pod_dynamic_builder b = { 0 }; uint32_t index; uint32_t next = start; uint32_t count = 0; @@ -103,15 +104,15 @@ param = *pw_array_get_unchecked(&pdata->params, index, struct spa_pod*); - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - if (spa_pod_filter(&b, &result, param, filter) != 0) - continue; + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + if (spa_pod_filter(&b.b, &result, param, filter) == 0) { + pw_log_debug(NAME" %p: %d param %u", impl, seq, index); + pw_endpoint_link_resource_param(d->resource, seq, id, index, next, result); + count++; + } + spa_pod_dynamic_builder_clean(&b); - pw_log_debug(NAME" %p: %d param %u", impl, seq, index); - - pw_endpoint_link_resource_param(d->resource, seq, id, index, next, result); - - if (++count == num) + if (count == num) return 0; } }
View file
pipewire-0.3.71.tar.gz/src/modules/module-session-manager/endpoint-stream.c -> pipewire-0.3.72.tar.gz/src/modules/module-session-manager/endpoint-stream.c
Changed
@@ -10,6 +10,7 @@ #include <spa/utils/result.h> #include <spa/pod/builder.h> #include <spa/pod/filter.h> +#include <spa/pod/dynamic.h> #define MAX_PARAMS 32 @@ -84,8 +85,8 @@ struct param_data *pdata; struct spa_pod *result; struct spa_pod *param; - uint8_t buffer1024; - struct spa_pod_builder b = { 0 }; + uint8_t buffer2048; + struct spa_pod_dynamic_builder b = { 0 }; uint32_t index; uint32_t next = start; uint32_t count = 0; @@ -103,15 +104,15 @@ param = *pw_array_get_unchecked(&pdata->params, index, struct spa_pod*); - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - if (spa_pod_filter(&b, &result, param, filter) != 0) - continue; + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + if (spa_pod_filter(&b.b, &result, param, filter) == 0) { + pw_log_debug(NAME" %p: %d param %u", impl, seq, index); + pw_endpoint_stream_resource_param(d->resource, seq, id, index, next, result); + count++; + } + spa_pod_dynamic_builder_clean(&b); - pw_log_debug(NAME" %p: %d param %u", impl, seq, index); - - pw_endpoint_stream_resource_param(d->resource, seq, id, index, next, result); - - if (++count == num) + if (count == num) return 0; } }
View file
pipewire-0.3.71.tar.gz/src/modules/module-session-manager/endpoint.c -> pipewire-0.3.72.tar.gz/src/modules/module-session-manager/endpoint.c
Changed
@@ -9,6 +9,7 @@ #include <spa/utils/result.h> #include <spa/pod/builder.h> +#include <spa/pod/dynamic.h> #include <spa/pod/filter.h> #define MAX_PARAMS 32 @@ -84,8 +85,8 @@ struct param_data *pdata; struct spa_pod *result; struct spa_pod *param; - uint8_t buffer1024; - struct spa_pod_builder b = { 0 }; + uint8_t buffer2048; + struct spa_pod_dynamic_builder b = { 0 }; uint32_t index; uint32_t next = start; uint32_t count = 0; @@ -103,15 +104,15 @@ param = *pw_array_get_unchecked(&pdata->params, index, struct spa_pod*); - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - if (spa_pod_filter(&b, &result, param, filter) != 0) - continue; + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + if (spa_pod_filter(&b.b, &result, param, filter) == 0) { + pw_log_debug(NAME" %p: %d param %u", impl, seq, index); + pw_endpoint_resource_param(d->resource, seq, id, index, next, result); + count++; + } + spa_pod_dynamic_builder_clean(&b); - pw_log_debug(NAME" %p: %d param %u", impl, seq, index); - - pw_endpoint_resource_param(d->resource, seq, id, index, next, result); - - if (++count == num) + if (count == num) return 0; } }
View file
pipewire-0.3.71.tar.gz/src/modules/module-session-manager/session.c -> pipewire-0.3.72.tar.gz/src/modules/module-session-manager/session.c
Changed
@@ -10,6 +10,7 @@ #include <spa/utils/result.h> #include <spa/pod/builder.h> #include <spa/pod/filter.h> +#include <spa/pod/dynamic.h> #define MAX_PARAMS 32 @@ -84,8 +85,8 @@ struct param_data *pdata; struct spa_pod *result; struct spa_pod *param; - uint8_t buffer1024; - struct spa_pod_builder b = { 0 }; + uint8_t buffer2048; + struct spa_pod_dynamic_builder b = { 0 }; uint32_t index; uint32_t next = start; uint32_t count = 0; @@ -103,15 +104,15 @@ param = *pw_array_get_unchecked(&pdata->params, index, struct spa_pod*); - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - if (spa_pod_filter(&b, &result, param, filter) != 0) - continue; + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + if (spa_pod_filter(&b.b, &result, param, filter) == 0) { + pw_log_debug(NAME" %p: %d param %u", impl, seq, index); + pw_session_resource_param(d->resource, seq, id, index, next, result); + count++; + } + spa_pod_dynamic_builder_clean(&b); - pw_log_debug(NAME" %p: %d param %u", impl, seq, index); - - pw_session_resource_param(d->resource, seq, id, index, next, result); - - if (++count == num) + if (count == num) return 0; } }
View file
pipewire-0.3.71.tar.gz/src/modules/module-zeroconf-discover.c -> pipewire-0.3.72.tar.gz/src/modules/module-zeroconf-discover.c
Changed
@@ -85,11 +85,7 @@ }; struct tunnel_info { - AvahiIfIndex interface; - AvahiProtocol protocol; const char *name; - const char *type; - const char *domain; }; #define TUNNEL_INFO(...) ((struct tunnel_info){ __VA_ARGS__ }) @@ -111,11 +107,7 @@ if (t == NULL) return NULL; - t->info.interface = info->interface; - t->info.protocol = info->protocol; t->info.name = strdup(info->name); - t->info.type = strdup(info->type); - t->info.domain = strdup(info->domain); spa_list_append(&impl->tunnel_list, &t->link); return t; @@ -125,11 +117,7 @@ { struct tunnel *t; spa_list_for_each(t, &impl->tunnel_list, link) { - if (t->info.interface == info->interface && - t->info.protocol == info->protocol && - spa_streq(t->info.name, info->name) && - spa_streq(t->info.type, info->type) && - spa_streq(t->info.domain, info->domain)) + if (spa_streq(t->info.name, info->name)) return t; } return NULL; @@ -137,7 +125,12 @@ static void free_tunnel(struct tunnel *t) { - pw_impl_module_destroy(t->module); + spa_list_remove(&t->link); + if (t->module) + pw_impl_module_destroy(t->module); + free((char *) t->info.name); + + free(t); } static void impl_free(struct impl *impl) @@ -226,14 +219,8 @@ { struct tunnel *t = data; - spa_list_remove(&t->link); spa_hook_remove(&t->module_listener); - - free((char *) t->info.name); - free((char *) t->info.type); - free((char *) t->info.domain); - - free(t); + t->module = NULL; } static const struct pw_impl_module_events submodule_events = { @@ -264,11 +251,20 @@ avahi_strerror(avahi_client_errno(impl->client))); goto done; } - tinfo = TUNNEL_INFO(.interface = interface, - .protocol = protocol, - .name = name, - .type = type, - .domain = domain); + + tinfo = TUNNEL_INFO(.name = name); + + t = find_tunnel(impl, &tinfo); + if (t == NULL) + t = make_tunnel(impl, &tinfo); + if (t == NULL) { + pw_log_error("Can't make tunnel: %m"); + goto done; + } + if (t->module != NULL) { + pw_log_info("found duplicate mdns entry - skipping tunnel creation"); + goto done; + } props = pw_properties_new(NULL, NULL); if (props == NULL) { @@ -346,8 +342,6 @@ fprintf(f, "}"); fclose(f); - pw_properties_free(props); - pw_log_info("loading module args:'%s'", args); mod = pw_context_load_module(impl->context, "libpipewire-module-pulse-tunnel", @@ -359,19 +353,13 @@ goto done; } - t = make_tunnel(impl, &tinfo); - if (t == NULL) { - pw_log_error("Can't make tunnel: %m"); - pw_impl_module_destroy(mod); - goto done; - } - pw_impl_module_add_listener(mod, &t->module_listener, &submodule_events, t); t->module = mod; done: avahi_service_resolver_free(r); + pw_properties_free(props); } @@ -386,18 +374,16 @@ if (flags & AVAHI_LOOKUP_RESULT_LOCAL) return; - info = TUNNEL_INFO(.interface = interface, - .protocol = protocol, - .name = name, - .type = type, - .domain = domain); + info = TUNNEL_INFO(.name = name); t = find_tunnel(impl, &info); switch (event) { case AVAHI_BROWSER_NEW: - if (t != NULL) + if (t != NULL) { + pw_log_info("found duplicate mdns entry - skipping tunnel creation"); return; + } if (!(avahi_service_resolver_new(impl->client, interface, protocol, name, type, domain,
View file
pipewire-0.3.71.tar.gz/src/modules/spa/module-node-factory.c -> pipewire-0.3.72.tar.gz/src/modules/spa/module-node-factory.c
Changed
@@ -49,8 +49,100 @@ struct pw_resource *resource; struct spa_hook resource_listener; unsigned int linger:1; + + struct pw_core *core; + struct spa_hook core_listener; + struct spa_hook core_proxy_listener; + struct pw_proxy *proxy; + struct spa_hook proxy_listener; +}; + +static void proxy_removed(void *_data) +{ + struct node_data *nd = _data; + pw_log_debug("%p: removed", nd); + pw_proxy_destroy(nd->proxy); +} + +static void proxy_destroy(void *_data) +{ + struct node_data *nd = _data; + pw_log_debug("%p: destroy", nd); + spa_hook_remove(&nd->proxy_listener); + nd->proxy = NULL; + if (nd->node) + pw_impl_node_destroy(nd->node); +} + +static const struct pw_proxy_events proxy_events = { + PW_VERSION_PROXY_EVENTS, + .removed = proxy_removed, + .destroy = proxy_destroy, }; +static void core_error(void *data, uint32_t id, int seq, int res, const char *message) +{ + struct node_data *nd = data; + + pw_log_error("error id:%u seq:%d res:%d (%s): %s", + id, seq, res, spa_strerror(res), message); + + if (id == PW_ID_CORE && res == -EPIPE) + pw_impl_node_destroy(nd->node); +} + +static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .error = core_error, +}; + +static void core_removed(void *d) +{ + struct node_data *nd = d; + pw_log_debug("%p: removed", nd); + spa_hook_remove(&nd->core_proxy_listener); + spa_hook_remove(&nd->core_listener); + nd->core = NULL; + if (nd->node) + pw_impl_node_destroy(nd->node); +} + +static const struct pw_proxy_events core_proxy_events = { + .removed = core_removed, +}; + +static int export_node(struct node_data *nd, struct pw_properties *props) +{ + const char *str; + + str = pw_properties_get(props, PW_KEY_REMOTE_NAME); + nd->core = pw_context_connect(nd->data->context, + pw_properties_new( + PW_KEY_REMOTE_NAME, str, + NULL), + 0); + if (nd->core == NULL) { + pw_log_error("can't connect: %m"); + return -errno; + } + pw_proxy_add_listener((struct pw_proxy*)nd->core, + &nd->core_proxy_listener, + &core_proxy_events, nd); + pw_core_add_listener(nd->core, + &nd->core_listener, + &core_events, nd); + + pw_log_debug("%p: export node %p", nd, nd->node); + nd->proxy = pw_core_export(nd->core, + PW_TYPE_INTERFACE_Node, NULL, nd->node, 0); + if (nd->proxy == NULL) + return -errno; + + pw_proxy_add_listener(nd->proxy, &nd->proxy_listener, &proxy_events, nd); + + return 0; +} + static void resource_destroy(void *data) { struct node_data *nd = data; @@ -78,6 +170,10 @@ spa_hook_remove(&nd->resource_listener); nd->resource = NULL; } + if (nd->core) { + pw_core_disconnect(nd->core); + nd->core = NULL; + } } static const struct pw_impl_node_events node_events = { @@ -145,6 +241,11 @@ pw_resource_add_listener(nd->resource, &nd->resource_listener, &resource_events, nd); } + if (pw_properties_get_bool(properties, PW_KEY_OBJECT_EXPORT, false)) { + res = export_node(nd, properties); + if (res < 0) + goto error_export; + } return node; error_properties: @@ -160,6 +261,10 @@ pw_resource_errorf_id(resource, new_id, res, "can't bind node"); pw_impl_node_destroy(node); goto error_exit; +error_export: + pw_resource_errorf_id(resource, new_id, res, "can't export node"); + pw_impl_node_destroy(node); + goto error_exit; error_exit_cleanup: pw_properties_free(properties);
View file
pipewire-0.3.71.tar.gz/src/pipewire/context.c -> pipewire-0.3.72.tar.gz/src/pipewire/context.c
Changed
@@ -478,6 +478,61 @@ spa_hook_list_append(&context->listener_list, listener, events, data); } +struct listener_data { + struct spa_hook *listener; + const struct pw_context_driver_events *events; + void *data; +}; + +static int +do_add_listener(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct pw_context *context = user_data; + const struct listener_data *d = data; + spa_hook_list_append(&context->driver_listener_list, + d->listener, d->events, d->data); + return 0; +} + +SPA_EXPORT +void pw_context_driver_add_listener(struct pw_context *context, + struct spa_hook *listener, + const struct pw_context_driver_events *events, + void *data) +{ + struct listener_data d = { + .listener = listener, + .events = events, + .data = data }; + struct pw_impl_node *n; + spa_list_for_each(n, &context->driver_list, driver_link) { + SPA_FLAG_SET(n->rt.target.activation->flags, PW_NODE_ACTIVATION_FLAG_PROFILER); + } + pw_loop_invoke(context->data_loop, + do_add_listener, SPA_ID_INVALID, &d, sizeof(d), false, context); +} + +static int do_remove_listener(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct spa_hook *listener = user_data; + spa_hook_remove(listener); + return 0; +} + +SPA_EXPORT +void pw_context_driver_remove_listener(struct pw_context *context, + struct spa_hook *listener) +{ + struct pw_impl_node *n; + spa_list_for_each(n, &context->driver_list, driver_link) { + SPA_FLAG_CLEAR(n->rt.target.activation->flags, PW_NODE_ACTIVATION_FLAG_PROFILER); + } + pw_loop_invoke(context->data_loop, + do_remove_listener, SPA_ID_INVALID, NULL, 0, true, listener); +} + SPA_EXPORT const struct spa_support *pw_context_get_support(struct pw_context *context, uint32_t *n_support) { @@ -1190,7 +1245,7 @@ /* clean up the flags first */ spa_list_for_each(n, &context->node_list, link) { n->visited = false; - n->runnable = n->always_process; + n->runnable = n->always_process && n->active; } get_quantums(context, &def_quantum, &min_quantum, &max_quantum, &lim_quantum, &rate_quantum); @@ -1213,9 +1268,10 @@ collect_nodes(context, n, &collect); move_to_driver(context, &collect, n); } - /* from now on we are only interested in active driving nodes. - * We're going to see if there are active followers. */ - if (!n->driving || !n->active) + /* from now on we are only interested in active driving nodes + * with a driver_priority. We're going to see if there are + * active followers. */ + if (!n->driving || !n->active || n->priority_driver <= 0) continue; /* first active driving node is fallback */
View file
pipewire-0.3.71.tar.gz/src/pipewire/core.c -> pipewire-0.3.72.tar.gz/src/pipewire/core.c
Changed
@@ -479,7 +479,9 @@ int pw_core_disconnect(struct pw_core *core) { pw_log_debug("%p: disconnect", core); - pw_proxy_remove(&core->proxy); - pw_proxy_destroy(&core->proxy); + if (!core->removed) + pw_proxy_remove(&core->proxy); + if (!core->destroyed) + pw_proxy_destroy(&core->proxy); return 0; }
View file
pipewire-0.3.71.tar.gz/src/pipewire/extensions/client-node.h -> pipewire-0.3.72.tar.gz/src/pipewire/extensions/client-node.h
Changed
@@ -22,7 +22,7 @@ */ #define PW_TYPE_INTERFACE_ClientNode PW_TYPE_INFO_INTERFACE_BASE "ClientNode" -#define PW_VERSION_CLIENT_NODE 4 +#define PW_VERSION_CLIENT_NODE 5 struct pw_client_node; #define PW_EXTENSION_MODULE_CLIENT_NODE PIPEWIRE_MODULE_PREFIX "module-client-node"
View file
pipewire-0.3.71.tar.gz/src/pipewire/filter.c -> pipewire-0.3.72.tar.gz/src/pipewire/filter.c
Changed
@@ -151,6 +151,8 @@ unsigned int warn_mlock:1; unsigned int process_rt:1; unsigned int driving:1; + unsigned int trigger:1; + int in_emit_param_changed; }; static int get_param_index(uint32_t id) @@ -275,6 +277,8 @@ { struct param *p, *t; struct spa_list *param_list; + bool found = false; + int i, idx; if (port) param_list = &port->param_list; @@ -284,10 +288,42 @@ spa_list_for_each_safe(p, t, param_list, link) { if (id == SPA_ID_INVALID || (p->id == id && !(p->flags & PARAM_FLAG_LOCKED))) { + found = true; spa_list_remove(&p->link); free(p); } } + if (found) { + if (id == SPA_ID_INVALID) { + if (port) { + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + for (i = 0; i < N_PORT_PARAMS; i++) { + port->paramsi.flags &= ~SPA_PARAM_INFO_READ; + port->paramsi.user++; + } + } else { + impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + for (i = 0; i < N_NODE_PARAMS; i++) { + impl->paramsi.flags &= ~SPA_PARAM_INFO_READ; + impl->paramsi.user++; + } + } + } else { + if (port) { + if ((idx = get_port_param_index(id)) != -1) { + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->paramsidx.flags &= ~SPA_PARAM_INFO_READ; + port->paramsidx.user++; + } + } else { + if ((idx = get_param_index(id)) != -1) { + impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + impl->paramsidx.flags &= ~SPA_PARAM_INFO_READ; + impl->paramsidx.user++; + } + } + } + } } static struct port *alloc_port(struct filter *filter, @@ -433,12 +469,19 @@ return enum_params(impl, &impl->param_list, seq, id, start, num, filter); } -static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) +static inline void emit_param_changed(struct filter *impl, void *port, + uint32_t id, const struct spa_pod *param) { - struct filter *impl = object; struct pw_filter *filter = &impl->this; + if (impl->in_emit_param_changed++ == 0) + pw_filter_emit_param_changed(filter, port, id, param); + impl->in_emit_param_changed--; +} - pw_filter_emit_param_changed(filter, NULL, id, param); +static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) +{ + struct filter *impl = object; + emit_param_changed(impl, NULL, id, param); return 0; } @@ -776,7 +819,6 @@ static int handle_latency(struct filter *impl, struct port *port, const struct spa_pod *param) { - struct pw_filter *filter = &impl->this; struct spa_latency_info info; int res; @@ -796,7 +838,7 @@ return 0; if (SPA_FLAG_IS_SET(impl->flags, PW_FILTER_FLAG_CUSTOM_LATENCY)) { - pw_filter_emit_param_changed(filter, port->user_data, + emit_param_changed(impl, port->user_data, SPA_PARAM_Latency, param); } else { default_latency(impl, port, info.direction); @@ -848,7 +890,7 @@ } if (emit) - pw_filter_emit_param_changed(filter, port->user_data, id, param); + emit_param_changed(impl, port->user_data, id, param); if (filter->state == PW_FILTER_STATE_ERROR) return filter->error_res; @@ -966,12 +1008,12 @@ static void call_process(struct filter *impl) { - pw_log_trace("%p: call process", impl); + pw_log_trace_fp("%p: call process", impl); if (SPA_FLAG_IS_SET(impl->flags, PW_FILTER_FLAG_RT_PROCESS)) { - spa_callbacks_call(&impl->rt_callbacks, struct pw_filter_events, - process, 0, impl->rt.position); - } - else { + if (impl->rt_callbacks.funcs) + spa_callbacks_call_fast(&impl->rt_callbacks, struct pw_filter_events, + process, 0, impl->rt.position); + } else { pw_loop_invoke(impl->main_loop, do_call_process, 1, NULL, 0, false, impl); } @@ -1000,6 +1042,7 @@ struct port *p; struct buffer *b; bool drained = true; + int res = 0; pw_log_trace_fp("%p: do process %p", impl, impl->rt.position); @@ -1041,6 +1084,7 @@ continue; if (p->direction == SPA_DIRECTION_INPUT) { + res |= SPA_STATUS_NEED_DATA; if (SPA_UNLIKELY(io->status != SPA_STATUS_HAVE_DATA)) continue; @@ -1053,17 +1097,21 @@ } io->status = SPA_STATUS_NEED_DATA; } else { - if (SPA_UNLIKELY(io->status == SPA_STATUS_HAVE_DATA)) + if (SPA_UNLIKELY(io->status == SPA_STATUS_HAVE_DATA)) { + res |= SPA_STATUS_HAVE_DATA; continue; + } if ((b = pop_queue(p, &p->queued)) != NULL) { pw_log_trace_fp("%p: pop %d %p", impl, b->id, io); io->buffer_id = b->id; io->status = SPA_STATUS_HAVE_DATA; + res |= SPA_STATUS_HAVE_DATA; drained = false; } else { io->buffer_id = SPA_ID_INVALID; io->status = SPA_STATUS_NEED_DATA; + res |= SPA_STATUS_NEED_DATA; } } } @@ -1071,7 +1119,7 @@ if (SPA_UNLIKELY(drained && impl->draining)) call_drained(impl); - return SPA_STATUS_NEED_DATA | SPA_STATUS_HAVE_DATA; + return res; } static const struct spa_node_methods impl_node = { @@ -1429,10 +1477,22 @@ free(impl); } +static int +do_remove_callbacks(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct filter *impl = user_data; + spa_zero(impl->rt_callbacks); + return 0; +} + static void hook_removed(struct spa_hook *hook) { struct filter *impl = hook->priv; - spa_zero(impl->rt_callbacks); + if (impl->data_loop) + pw_loop_invoke(impl->data_loop, do_remove_callbacks, 1, NULL, 0, true, impl); + else + spa_zero(impl->rt_callbacks); hook->priv = NULL; hook->removed = NULL; } @@ -1525,8 +1585,10 @@ static void node_event_destroy(void *data) { struct pw_filter *filter = data; + struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this); spa_hook_remove(&filter->node_listener); filter->node = NULL; + impl->data_loop = NULL; } static const struct pw_impl_node_events node_events = { @@ -1595,6 +1657,10 @@ pw_properties_set(filter->properties, PW_KEY_NODE_DRIVER, "true"); if ((pw_properties_get(filter->properties, PW_KEY_NODE_WANT_DRIVER) == NULL)) pw_properties_set(filter->properties, PW_KEY_NODE_WANT_DRIVER, "true"); + if (flags & PW_FILTER_FLAG_TRIGGER) { + pw_properties_set(filter->properties, PW_KEY_NODE_TRIGGER, "true"); + impl->trigger = true; + } if (filter->core == NULL) { filter->core = pw_context_connect(impl->context, @@ -1922,17 +1988,16 @@ struct pw_buffer *pw_filter_dequeue_buffer(void *port_data) { struct port *p = SPA_CONTAINER_OF(port_data, struct port, user_data); - struct filter *impl = p->filter; struct buffer *b; int res; if (SPA_UNLIKELY((b = pop_queue(p, &p->dequeued)) == NULL)) { res = -errno; - pw_log_debug("%p: no more buffers: %m", impl); + pw_log_trace_fp("%p: no more buffers: %m", p->filter); errno = -res; return NULL; } - pw_log_trace_fp("%p: dequeue buffer %d", impl, b->id); + pw_log_trace_fp("%p: dequeue buffer %d", p->filter, b->id); return &b->this; } @@ -2042,14 +2107,16 @@ struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this); int res = 0; - pw_log_trace_fp("%p", impl); + pw_log_trace_fp("%p: driving:%d", impl, impl->driving); - if (!impl->driving) { - res = pw_loop_invoke(impl->main_loop, - do_trigger_request_process, 1, NULL, 0, false, impl); - } else { + if (impl->trigger) { + pw_impl_node_trigger(filter->node); + } else if (impl->driving) { res = pw_loop_invoke(impl->data_loop, do_trigger_process, 1, NULL, 0, false, impl); + } else { + res = pw_loop_invoke(impl->main_loop, + do_trigger_request_process, 1, NULL, 0, false, impl); } return res; }
View file
pipewire-0.3.71.tar.gz/src/pipewire/filter.h -> pipewire-0.3.72.tar.gz/src/pipewire/filter.h
Changed
@@ -104,6 +104,11 @@ PW_FILTER_FLAG_CUSTOM_LATENCY = (1 << 3), /**< don't call the default latency algorithm * but emit the param_changed event for the * ports when Latency params are received. */ + PW_FILTER_FLAG_TRIGGER = (1 << 4), /**< the filter will not be scheduled + * automatically but _trigger_process() + * needs to be called. This can be used + * when the filter depends on processing + * of other filters. */ }; enum pw_filter_port_flags {
View file
pipewire-0.3.71.tar.gz/src/pipewire/impl-link.c -> pipewire-0.3.72.tar.gz/src/pipewire/impl-link.c
Changed
@@ -27,7 +27,6 @@ struct impl { struct pw_impl_link this; - unsigned int io_set:1; unsigned int activated:1; struct pw_work_queue *work; @@ -57,7 +56,7 @@ struct pw_node_peer *peer; spa_list_for_each(peer, &onode->peer_list, link) { - if (peer->target.node == inode) { + if (peer->target.id == inode->info.id) { pw_log_debug("exiting peer %p from %p to %p", peer, onode, inode); peer->ref++; return peer; @@ -70,10 +69,8 @@ peer->ref = 1; peer->output = onode; peer->active_count = 0; - peer->target.node = inode; - peer->target.activation = inode->rt.activation; - peer->target.system = inode->data_system; - peer->target.fd = inode->source.fd; + copy_target(&peer->target, &inode->rt.target); + peer->target.flags = PW_NODE_TARGET_PEER; spa_list_append(&onode->peer_list, &peer->link); pw_log_debug("new peer %p from %p to %p", peer, onode, inode); @@ -105,8 +102,8 @@ peer->target.active = true; } } - pw_log_trace("%p: node:%p state:%p pending:%d/%d", peer->output, - peer->target.node, state, state->pending, state->required); + pw_log_trace("%p: node:%s state:%p pending:%d/%d", peer->output, + peer->target.name, state, state->pending, state->required); } static void pw_node_peer_deactivate(struct pw_node_peer *peer) @@ -122,8 +119,8 @@ peer->target.active = false; } } - pw_log_trace("%p: node:%p state:%p pending:%d/%d", peer->output, - peer->target.node, state, state->pending, state->required); + pw_log_trace("%p: node:%s state:%p pending:%d/%d", peer->output, + peer->target.name, state, state->pending, state->required); } @@ -160,12 +157,16 @@ pw_link_state_as_string(state), error); if (state == PW_LINK_STATE_ERROR) { - pw_log_error("(%s) %s -> error (%s)", link->name, - pw_link_state_as_string(old), error); + pw_log_error("(%s) %s -> error (%s) (%s-%s)", link->name, + pw_link_state_as_string(old), error, + pw_impl_port_state_as_string(link->output->state), + pw_impl_port_state_as_string(link->input->state)); } else { - pw_log_info("(%s) %s -> %s", link->name, + pw_log_info("(%s) %s -> %s (%s-%s)", link->name, pw_link_state_as_string(old), - pw_link_state_as_string(state)); + pw_link_state_as_string(state), + pw_impl_port_state_as_string(link->output->state), + pw_impl_port_state_as_string(link->input->state)); } pw_impl_link_emit_state_changed(link, old, state, error); @@ -485,7 +486,6 @@ { int res = 0; - mix->io = data; pw_log_debug("%p: %s port %p %d.%d set io: %d %p %zd", this, pw_direction_as_string(port->direction), port, port->port_id, mix->port.port_id, id, data, size); @@ -641,15 +641,9 @@ bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct pw_impl_link *this = user_data; - pw_log_trace("%p: activate", this); - - spa_list_append(&this->output->rt.mix_list, &this->rt.out_mix.rt_link); - spa_list_append(&this->input->rt.mix_list, &this->rt.in_mix.rt_link); - if (this->peer) pw_node_peer_activate(this->peer); - return 0; } @@ -661,20 +655,21 @@ pw_log_debug("%p: activate activated:%d state:%s", this, impl->activated, pw_link_state_as_string(this->info.state)); - if (impl->activated || !this->prepared || + if (this->destroyed || impl->activated || !this->prepared || !impl->inode->runnable || !impl->onode->runnable) return 0; - if (!impl->io_set) { - if ((res = port_set_io(this, this->output, SPA_IO_Buffers, this->io, - sizeof(struct spa_io_buffers), &this->rt.out_mix)) < 0) - return res; + if ((res = port_set_io(this, this->input, SPA_IO_Buffers, this->io, + sizeof(struct spa_io_buffers), &this->rt.in_mix)) < 0) + return res; - if ((res = port_set_io(this, this->input, SPA_IO_Buffers, this->io, - sizeof(struct spa_io_buffers), &this->rt.in_mix)) < 0) - return res; - impl->io_set = true; + if ((res = port_set_io(this, this->output, SPA_IO_Buffers, this->io, + sizeof(struct spa_io_buffers), &this->rt.out_mix)) < 0) { + port_set_io(this, this->input, SPA_IO_Buffers, NULL, 0, + &this->rt.in_mix); + return res; } + pw_loop_invoke(this->output->node->data_loop, do_activate_link, SPA_ID_INVALID, NULL, 0, false, this); @@ -828,7 +823,7 @@ if (!impl->inode->active || !impl->onode->active) return 0; - if (this->preparing || this->prepared) + if (this->destroyed || this->preparing || this->prepared) return 0; this->preparing = true; @@ -844,15 +839,9 @@ bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct pw_impl_link *this = user_data; - - pw_log_trace("%p: disable %p and %p", this, &this->rt.in_mix, &this->rt.out_mix); - - spa_list_remove(&this->rt.out_mix.rt_link); - spa_list_remove(&this->rt.in_mix.rt_link); - + pw_log_trace("%p: disable out %p", this, &this->rt.out_mix); if (this->peer) pw_node_peer_deactivate(this->peer); - return 0; } @@ -873,11 +862,11 @@ port_set_io(this, this->input, SPA_IO_Buffers, NULL, 0, &this->rt.in_mix); - impl->io_set = false; impl->activated = false; pw_log_info("(%s) deactivated", this->name); - link_update_state(this, PW_LINK_STATE_PAUSED, 0, NULL); - + link_update_state(this, this->destroyed ? + PW_LINK_STATE_INIT : PW_LINK_STATE_PAUSED, + 0, NULL); return 0; } @@ -1306,9 +1295,9 @@ output_node, output->port_id, this->rt.out_mix.port.port_id, input_node, input->port_id, this->rt.in_mix.port.port_id); - this->name = spa_aprintf("%d.%d -> %d.%d", - output_node->info.id, output->port_id, - input_node->info.id, input->port_id); + this->name = spa_aprintf("%d.%d.%d -> %d.%d.%d", + output_node->info.id, output->port_id, this->rt.out_mix.port.port_id, + input_node->info.id, input->port_id, this->rt.in_mix.port.port_id); pw_log_info("(%s) (%s) -> (%s)", this->name, output_node->name, input_node->name); pw_impl_port_emit_link_added(output, this); @@ -1448,6 +1437,8 @@ pw_log_debug("%p: destroy", impl); pw_log_info("(%s) destroy", link->name); + + link->destroyed = true; pw_impl_link_emit_destroy(link); pw_impl_link_deactivate(link);
View file
pipewire-0.3.71.tar.gz/src/pipewire/impl-node.c -> pipewire-0.3.72.tar.gz/src/pipewire/impl-node.c
Changed
@@ -41,8 +41,6 @@ unsigned int cache_params:1; unsigned int pending_play:1; - - uint64_t prev_signal_time; }; #define pw_node_resource(r,m,v,...) pw_resource_call(r,struct pw_node_events,m,v,__VA_ARGS__) @@ -89,21 +87,18 @@ return; pw_log_trace("%p: add to driver %p %p %p", this, driver, - driver->rt.activation, this->rt.activation); + driver->rt.target.activation, this->rt.target.activation); /* let the driver trigger us as part of the processing cycle */ spa_list_append(&driver->rt.target_list, &this->rt.target.link); - nstate = &this->rt.activation->state0; + nstate = &this->rt.target.activation->state0; if (!this->rt.target.active) { nstate->required++; this->rt.target.active = true; } /* trigger the driver when we complete */ - this->rt.driver_target.activation = driver->rt.activation; - this->rt.driver_target.node = driver; - this->rt.driver_target.system = driver->data_system; - this->rt.driver_target.fd = driver->source.fd; + copy_target(&this->rt.driver_target, &driver->rt.target); spa_list_append(&this->rt.target_list, &this->rt.driver_target.link); /* now increment the required states of all this node targets, including @@ -129,13 +124,13 @@ if (this->exported) return; - pw_log_trace("%p: remove from driver %p %p %p", - this, this->rt.driver_target.node, - this->rt.driver_target.activation, this->rt.activation); + pw_log_trace("%p: remove from driver %s %p %p", + this, this->rt.driver_target.name, + this->rt.driver_target.activation, this->rt.target.activation); spa_list_remove(&this->rt.target.link); - nstate = &this->rt.activation->state0; + nstate = &this->rt.target.activation->state0; if (this->rt.target.active) { nstate->required--; this->rt.target.active = false; @@ -153,7 +148,7 @@ } spa_list_remove(&this->rt.driver_target.link); - this->rt.driver_target.node = NULL; + spa_zero(this->rt.driver_target); } static int @@ -707,27 +702,31 @@ static void update_io(struct pw_impl_node *node) { + struct pw_node_target *t = &node->rt.target; + pw_log_debug("%p: id:%d", node, node->info.id); if (spa_node_set_io(node->node, SPA_IO_Position, - &node->rt.activation->position, + &t->activation->position, sizeof(struct spa_io_position)) >= 0) { - pw_log_debug("%p: set position %p", node, &node->rt.activation->position); - node->rt.position = &node->rt.activation->position; + pw_log_debug("%p: set position %p", node, &t->activation->position); + node->rt.position = &t->activation->position; node->target_rate = node->rt.position->clock.target_rate; node->target_quantum = node->rt.position->clock.target_duration; node->target_pending = false; + + pw_impl_node_emit_peer_added(node, node); } else if (node->driver) { pw_log_warn("%p: can't set position on driver", node); } if (spa_node_set_io(node->node, SPA_IO_Clock, - &node->rt.activation->position.clock, + &t->activation->position.clock, sizeof(struct spa_io_clock)) >= 0) { - pw_log_debug("%p: set clock %p", node, &node->rt.activation->position.clock); - node->rt.clock = &node->rt.activation->position.clock; + pw_log_debug("%p: set clock %p", node, &t->activation->position.clock); + node->rt.clock = &t->activation->position.clock; } } @@ -778,9 +777,10 @@ insert_driver(context, this); this->registered = true; - this->rt.activation->position.clock.id = this->global->id; + this->rt.target.activation->position.clock.id = this->global->id; this->info.id = this->global->id; + this->rt.target.id = this->info.id; pw_properties_setf(this->properties, PW_KEY_OBJECT_ID, "%d", this->info.id); pw_properties_setf(this->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, pw_global_get_serial(this->global)); @@ -830,8 +830,8 @@ pw_log_trace("%p: driver:%p->%p", node, node->driver_node, driver); - pw_log_trace("%p: set position %p", node, &driver->rt.activation->position); - node->rt.position = &driver->rt.activation->position; + pw_log_trace("%p: set position %p", node, &driver->rt.target.activation->position); + node->rt.position = &driver->rt.target.activation->position; node->target_rate = node->rt.position->clock.target_rate; node->target_quantum = node->rt.position->clock.target_duration; @@ -845,7 +845,7 @@ static void remove_segment_owner(struct pw_impl_node *driver, uint32_t node_id) { - struct pw_node_activation *a = driver->rt.activation; + struct pw_node_activation *a = driver->rt.target.activation; ATOMIC_CAS(a->segment_owner0, node_id, 0); ATOMIC_CAS(a->segment_owner1, node_id, 0); } @@ -890,7 +890,7 @@ if ((res = spa_node_set_io(node->node, SPA_IO_Position, - &driver->rt.activation->position, + &driver->rt.target.activation->position, sizeof(struct spa_io_position))) < 0) { pw_log_debug("%p: set position: %s", node, spa_strerror(res)); } @@ -901,6 +901,9 @@ pw_impl_node_emit_driver_changed(node, old, driver); + pw_impl_node_emit_peer_added(driver, node); + pw_impl_node_emit_peer_removed(old, node); + return 0; } @@ -922,6 +925,7 @@ (node->name == NULL || !spa_streq(node->name, str))) { free(node->name); node->name = strdup(str); + snprintf(node->rt.target.name, sizeof(node->rt.target.name), "%s", node->name); pw_log_debug("%p: name '%s'", node, node->name); } @@ -950,9 +954,9 @@ if (trigger != node->trigger) { node->trigger = trigger; if (trigger) - node->rt.activation->state0.required++; + node->rt.target.activation->state0.required++; else - node->rt.activation->state0.required--; + node->rt.target.activation->state0.required--; } /* group defines what nodes are scheduled together */ @@ -1077,27 +1081,40 @@ return "unknown"; } -static void dump_states(struct pw_impl_node *driver) +static void update_xrun_stats(struct pw_node_activation *a, uint64_t trigger, uint64_t delay) +{ + a->xrun_count++; + a->xrun_time = trigger; + a->xrun_delay = delay; + a->max_delay = SPA_MAX(a->max_delay, delay); +} + +static void check_states(struct pw_impl_node *driver, uint64_t nsec) { struct pw_node_target *t; - struct pw_node_activation *na = driver->rt.activation; + struct pw_node_activation *na = driver->rt.target.activation; struct spa_io_clock *cl = &na->position.clock; + enum spa_log_level level = SPA_LOG_LEVEL_DEBUG; + + if (ratelimit_test(&driver->rt.rate_limit, nsec, SPA_LOG_LEVEL_DEBUG)) + level = SPA_LOG_LEVEL_INFO; spa_list_for_each(t, &driver->rt.target_list, link) { struct pw_node_activation *a = t->activation; struct pw_node_activation_state *state = &a->state0; - if (t->node == NULL) - continue; + if (a->status == PW_NODE_ACTIVATION_TRIGGERED || a->status == PW_NODE_ACTIVATION_AWAKE) { - pw_log_info("(%s-%u) client too slow! rate:%u/%u pos:%"PRIu64" status:%s", - t->node->name, t->node->info.id, + update_xrun_stats(a, nsec / 1000, 0); + + pw_log(level, "(%s-%u) client too slow! rate:%u/%u pos:%"PRIu64" status:%s", + t->name, t->id, (uint32_t)(cl->rate.num * cl->duration), cl->rate.denom, cl->position, str_status(a->status)); } pw_log_debug("(%s-%u) state:%p pending:%d/%d s:%"PRIu64" a:%"PRIu64" f:%"PRIu64 " waiting:%"PRIu64" process:%"PRIu64" status:%s sync:%d", - t->node->name, t->node->info.id, state, + t->name, t->id, state, state->pending, state->required, a->signal_time, a->awake_time, @@ -1133,8 +1150,8 @@ struct pw_node_activation *a = t->activation; struct pw_node_activation_state *state = &a->state0; - pw_log_trace_fp("%p: state:%p pending:%d/%d", t->node, state, - state->pending, state->required); + pw_log_trace_fp("%p: (%s-%u) state:%p pending:%d/%d", t->node, + t->name, t->id, state, state->pending, state->required); if (pw_node_activation_state_dec(state, 1)) { a->status = PW_NODE_ACTIVATION_TRIGGERED; @@ -1146,6 +1163,27 @@ return 0; } +static inline void calculate_stats(struct pw_impl_node *this, struct pw_node_activation *a) +{ + uint64_t signal_time = a->signal_time; + uint64_t prev_signal_time = a->prev_signal_time; + uint64_t process_time = a->finish_time - a->signal_time; + uint64_t period_time = signal_time - prev_signal_time; + + if (SPA_LIKELY(signal_time > prev_signal_time)) { + float load = (float) process_time / (float) period_time; + a->cpu_load0 = (a->cpu_load0 + load) / 2.0f; + a->cpu_load1 = (a->cpu_load1 * 7.0f + load) / 8.0f; + a->cpu_load2 = (a->cpu_load2 * 31.0f + load) / 32.0f; + } + pw_log_trace_fp("%p: graph completed wait:%"PRIu64" run:%"PRIu64 + " busy:%"PRIu64" period:%"PRIu64" cpu:%f:%f:%f", this, + a->awake_time - signal_time, + a->finish_time - a->awake_time, + process_time, period_time, + a->cpu_load0, a->cpu_load1, a->cpu_load2); +} + /* The main processing entry point of a node. This is called from the data-loop and usually * as a result of signaling the eventfd of the node. * @@ -1155,7 +1193,7 @@ { struct pw_impl_node *this = data; struct pw_impl_port *p; - struct pw_node_activation *a = this->rt.activation; + struct pw_node_activation *a = this->rt.target.activation; struct spa_system *data_system = this->data_system; int status; uint64_t nsec; @@ -1194,16 +1232,20 @@ nsec = get_time_ns(data_system); - pw_log_trace_fp("%p: finished %"PRIu64, this, nsec); + pw_log_trace_fp("%p: finished status:%d %"PRIu64, this, status, nsec); a->status = PW_NODE_ACTIVATION_FINISHED; a->finish_time = nsec; /* we don't need to trigger targets when the node was driving the - * graph because that means we finished the graph. Also don't schedule - * peers when the node returns OK, because that means the resume will - * happen asynchronously later (unimplemented though). */ - if (SPA_LIKELY(!this->driving && status != SPA_STATUS_OK)) + * graph because that means we finished the graph. */ + if (SPA_LIKELY(!this->driving)) { trigger_targets(this, status, nsec); + } else { + /* calculate CPU time when finished */ + a->signal_time = this->driver_start; + calculate_stats(this, a); + pw_context_driver_emit_complete(this->context, this); + } if (SPA_UNLIKELY(status & SPA_STATUS_DRAINED)) pw_context_driver_emit_drained(this->context, this); @@ -1213,7 +1255,7 @@ int pw_impl_node_trigger(struct pw_impl_node *node) { - struct pw_node_activation *a = node->rt.activation; + struct pw_node_activation *a = node->rt.target.activation; struct pw_node_activation_state *state = &a->state0; if (pw_node_activation_state_dec(state, 1)) { @@ -1361,15 +1403,14 @@ spa_list_init(&this->rt.output_mix); spa_list_init(&this->rt.target_list); - this->rt.activation = this->activation->map->ptr; - this->rt.target.activation = this->rt.activation; + this->rt.target.activation = this->activation->map->ptr; this->rt.target.node = this; this->rt.target.system = this->data_system; this->rt.target.fd = this->source.fd; - reset_position(this, &this->rt.activation->position); - this->rt.activation->sync_timeout = DEFAULT_SYNC_TIMEOUT; - this->rt.activation->sync_left = 0; + reset_position(this, &this->rt.target.activation->position); + this->rt.target.activation->sync_timeout = DEFAULT_SYNC_TIMEOUT; + this->rt.target.activation->sync_left = 0; this->rt.rate_limit.interval = 2 * SPA_NSEC_PER_SEC; this->rt.rate_limit.burst = 1; @@ -1602,20 +1643,6 @@ .event = node_event, }; -static inline void calculate_stats(struct pw_impl_node *this, struct pw_node_activation *a) -{ - uint64_t signal_time = a->signal_time; - uint64_t prev_signal_time = a->prev_signal_time; - if (SPA_LIKELY(signal_time > prev_signal_time)) { - uint64_t process_time = a->finish_time - a->signal_time; - uint64_t period_time = signal_time - prev_signal_time; - float load = (float) process_time / (float) period_time; - a->cpu_load0 = (a->cpu_load0 + load) / 2.0f; - a->cpu_load1 = (a->cpu_load1 * 7.0f + load) / 8.0f; - a->cpu_load2 = (a->cpu_load2 * 31.0f + load) / 32.0f; - } -} - #define SYNC_CHECK 0 #define SYNC_START 1 #define SYNC_STOP 2 @@ -1623,7 +1650,7 @@ static inline int check_updates(struct pw_impl_node *node, uint32_t *reposition_owner) { int res = SYNC_CHECK; - struct pw_node_activation *a = node->rt.activation; + struct pw_node_activation *a = node->rt.target.activation; uint32_t command; if (SPA_UNLIKELY(a->position.offset == INT64_MIN)) @@ -1651,15 +1678,15 @@ return res; } -static void do_reposition(struct pw_impl_node *driver, struct pw_impl_node *node) +static void do_reposition(struct pw_impl_node *driver, struct pw_node_target *target) { - struct pw_node_activation *a = driver->rt.activation; + struct pw_node_activation *a = driver->rt.target.activation; struct spa_io_segment *dst, *src; - src = &node->rt.activation->reposition; + src = &target->activation->reposition; dst = &a->position.segments0; - pw_log_info("%p: update position:%"PRIu64, node, src->position); + pw_log_info("%p: %u update position:%"PRIu64, driver, target->id, src->position); dst->version = src->version; dst->flags = src->flags; @@ -1685,16 +1712,16 @@ } } -static inline void update_position(struct pw_impl_node *node, int all_ready) +static inline void update_position(struct pw_impl_node *node, int all_ready, uint64_t nsec) { - struct pw_node_activation *a = node->rt.activation; + struct pw_node_activation *a = node->rt.target.activation; if (SPA_UNLIKELY(a->position.state == SPA_IO_POSITION_STATE_STARTING)) { if (!all_ready && --a->sync_left == 0) { pw_log_warn("(%s-%u) sync timeout, going to RUNNING", node->name, node->info.id); + check_states(node, nsec); pw_context_driver_emit_timeout(node->context, node); - dump_states(node); all_ready = true; } if (all_ready) @@ -1709,12 +1736,11 @@ */ static int node_ready(void *data, int status) { - struct pw_impl_node *node = data, *reposition_node = NULL; - struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); + struct pw_impl_node *node = data; struct pw_impl_node *driver = node->driver_node; - struct pw_node_activation *a = node->rt.activation; + struct pw_node_activation *a = node->rt.target.activation; struct spa_system *data_system = node->data_system; - struct pw_node_target *t; + struct pw_node_target *t, *reposition_target = NULL;; struct pw_impl_port *p; uint64_t nsec; @@ -1738,39 +1764,13 @@ uint32_t owner2, reposition_owner; uint64_t min_timeout = UINT64_MAX; - if (SPA_UNLIKELY(state->pending > 0)) { + if (SPA_UNLIKELY(a->status != PW_NODE_ACTIVATION_FINISHED)) { + pw_log_debug("(%s-%u) graph not finished: state:%p quantum:%"PRIu64 + " pending %d/%d", node->name, node->info.id, + state, a->position.clock.duration, + state->pending, state->required); + check_states(node, nsec); pw_context_driver_emit_incomplete(node->context, node); - if (ratelimit_test(&node->rt.rate_limit, a->signal_time, SPA_LOG_LEVEL_DEBUG)) { - pw_log_debug("(%s-%u) graph not finished: state:%p quantum:%"PRIu64 - " pending %d/%d", node->name, node->info.id, - state, a->position.clock.duration, - state->pending, state->required); - dump_states(node); - } - node_trigger(node); - } else { - uint64_t signal_time = a->signal_time; - /* old nodes set the TRIGGERED status on node_ready, patch this - * up here to avoid errors in pw-top */ - a->status = PW_NODE_ACTIVATION_FINISHED; - a->signal_time = a->prev_signal_time; - a->prev_signal_time = impl->prev_signal_time; - - /* calculate CPU time */ - calculate_stats(node, a); - - pw_log_trace_fp("%p: graph completed wait:%"PRIu64" run:%"PRIu64 - " busy:%"PRIu64" period:%"PRIu64" cpu:%f:%f:%f", node, - a->awake_time - a->signal_time, - a->finish_time - a->awake_time, - a->finish_time - a->signal_time, - a->signal_time - a->prev_signal_time, - a->cpu_load0, a->cpu_load1, a->cpu_load2); - - pw_context_driver_emit_complete(node->context, node); - - a->prev_signal_time = a->signal_time; - a->signal_time = signal_time; } /* This update is done too late, the driver should do this @@ -1794,25 +1794,22 @@ spa_list_for_each(t, &driver->rt.target_list, link) { struct pw_node_activation *ta = t->activation; + uint32_t id = t->id; ta->status = PW_NODE_ACTIVATION_NOT_TRIGGERED; pw_node_activation_state_reset(&ta->state0); - if (SPA_LIKELY(t->node)) { - uint32_t id = t->node->info.id; + /* this is the node with reposition info */ + if (SPA_UNLIKELY(id == reposition_owner)) + reposition_target = t; - /* this is the node with reposition info */ - if (SPA_UNLIKELY(id == reposition_owner)) - reposition_node = t->node; + /* update extra segment info if it is the owner */ + if (SPA_UNLIKELY(id == owner0)) + a->position.segments0.bar = ta->segment.bar; + if (SPA_UNLIKELY(id == owner1)) + a->position.segments0.video = ta->segment.video; - /* update extra segment info if it is the owner */ - if (SPA_UNLIKELY(id == owner0)) - a->position.segments0.bar = ta->segment.bar; - if (SPA_UNLIKELY(id == owner1)) - a->position.segments0.video = ta->segment.video; - - min_timeout = SPA_MIN(min_timeout, ta->sync_timeout); - } + min_timeout = SPA_MIN(min_timeout, ta->sync_timeout); if (SPA_UNLIKELY(update_sync)) { ta->pending_sync = target_sync; @@ -1823,24 +1820,21 @@ } a->status = PW_NODE_ACTIVATION_TRIGGERED; - /* remote nodes set the signal_time before writing the ready - * eventfd */ - if (!node->remote) - a->signal_time = nsec; - impl->prev_signal_time = a->prev_signal_time; a->prev_signal_time = a->signal_time; + a->signal_time = nsec; + node->driver_start = nsec; a->sync_timeout = SPA_MIN(min_timeout, DEFAULT_SYNC_TIMEOUT); - if (SPA_UNLIKELY(reposition_node)) { - do_reposition(node, reposition_node); + if (SPA_UNLIKELY(reposition_target != NULL)) { + do_reposition(node, reposition_target); sync_type = SYNC_START; reposition_owner = 0; - reposition_node = NULL; + reposition_target = NULL; goto again; } - update_position(node, all_ready); + update_position(node, all_ready, nsec); pw_context_driver_emit_start(node->context, node); } @@ -1855,9 +1849,7 @@ a->status = PW_NODE_ACTIVATION_FINISHED; a->finish_time = nsec; } - if (!node->remote && (status & SPA_STATUS_HAVE_DATA)) { - /* remote nodes have done the output mix already before - * they wrote the ready eventfd */ + if (status & SPA_STATUS_HAVE_DATA) { spa_list_for_each(p, &node->rt.output_mix, rt.node_link) spa_node_process_fast(p->mix); } @@ -1879,25 +1871,17 @@ return 0; } -static void update_xrun_stats(struct pw_node_activation *a, uint64_t trigger, uint64_t delay) -{ - a->xrun_count++; - a->xrun_time = trigger; - a->xrun_delay = delay; - a->max_delay = SPA_MAX(a->max_delay, delay); -} - static int node_xrun(void *data, uint64_t trigger, uint64_t delay, struct spa_pod *info) { struct pw_impl_node *this = data; - struct pw_node_activation *a = this->rt.activation; + struct pw_node_activation *a = this->rt.target.activation; struct pw_node_activation *da = this->rt.driver_target.activation; + struct spa_system *data_system = this->data_system; + uint64_t nsec = get_time_ns(data_system); update_xrun_stats(a, trigger, delay); - if (da && da != a) - update_xrun_stats(da, trigger, delay); - if (ratelimit_test(&this->rt.rate_limit, a->signal_time, SPA_LOG_LEVEL_INFO)) { + if (ratelimit_test(&this->rt.rate_limit, nsec, SPA_LOG_LEVEL_INFO)) { struct spa_fraction rate; if (da) { struct spa_io_clock *cl = &da->position.clock; @@ -1980,6 +1964,7 @@ active = node->active; node->active = false; + node->runnable = false; pw_log_debug("%p: destroy", impl); pw_log_info("(%s-%u) destroy", node->name, node->info.id);
View file
pipewire-0.3.71.tar.gz/src/pipewire/impl-port.c -> pipewire-0.3.72.tar.gz/src/pipewire/impl-port.c
Changed
@@ -27,6 +27,7 @@ struct impl { struct pw_impl_port this; struct spa_node mix_node; /**< mix node implementation */ + struct spa_list mix_list; struct spa_list param_list; struct spa_list pending_list; @@ -69,7 +70,7 @@ port->info.change_mask = 0; } -static const char *port_state_as_string(enum pw_impl_port_state state) +const char *pw_impl_port_state_as_string(enum pw_impl_port_state state) { switch (state) { case PW_IMPL_PORT_STATE_ERROR: @@ -100,7 +101,8 @@ pw_log(state == PW_IMPL_PORT_STATE_ERROR ? SPA_LOG_LEVEL_ERROR : SPA_LOG_LEVEL_DEBUG, "%p: state %s -> %s (%s)", port, - port_state_as_string(old), port_state_as_string(state), error); + pw_impl_port_state_as_string(old), + pw_impl_port_state_as_string(state), error); pw_impl_port_emit_state_changed(port, old, state, error); @@ -111,6 +113,72 @@ } } +static struct pw_impl_port_mix *find_mix(struct pw_impl_port *port, + enum spa_direction direction, uint32_t port_id) +{ + struct pw_impl_port_mix *mix; + spa_list_for_each(mix, &port->mix_list, link) { + if (mix->port.direction == direction && mix->port.port_id == port_id) + return mix; + } + return NULL; +} + +static int +do_add_mix(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct pw_impl_port_mix *mix = user_data; + struct pw_impl_port *this = mix->p; + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + pw_log_trace("%p: add mix %p", this, mix); + if (!mix->active) { + spa_list_append(&impl->mix_list, &mix->rt_link); + mix->active = true; + } + return 0; +} + +static int +do_remove_mix(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct pw_impl_port_mix *mix = user_data; + struct pw_impl_port *this = mix->p; + pw_log_trace("%p: remove mix %p", this, mix); + if (mix->active) { + spa_list_remove(&mix->rt_link); + mix->active = false; + } + return 0; +} + +static int port_set_io(void *object, + enum spa_direction direction, uint32_t port_id, uint32_t id, + void *data, size_t size) +{ + struct impl *impl = object; + struct pw_impl_port *this = &impl->this; + struct pw_impl_port_mix *mix; + + mix = find_mix(this, direction, port_id); + if (mix == NULL) + return -ENOENT; + + if (id == SPA_IO_Buffers) { + if (data == NULL || size == 0) { + pw_loop_invoke(this->node->data_loop, + do_remove_mix, SPA_ID_INVALID, NULL, 0, true, mix); + mix->io = NULL; + } else if (data != NULL && size >= sizeof(struct spa_io_buffers)) { + mix->io = data; + pw_loop_invoke(this->node->data_loop, + do_add_mix, SPA_ID_INVALID, NULL, 0, false, mix); + } + } + return 0; +} + static int tee_process(void *object) { struct impl *impl = object; @@ -119,7 +187,7 @@ struct spa_io_buffers *io = &this->rt.io; pw_log_trace_fp("%p: tee input %d %d", this, io->status, io->buffer_id); - spa_list_for_each(mix, &this->rt.mix_list, rt_link) { + spa_list_for_each(mix, &impl->mix_list, rt_link) { pw_log_trace_fp("%p: port %d %p->%p %d", this, mix->port.port_id, io, mix->io, mix->io->buffer_id); *mix->io = *io; @@ -136,13 +204,13 @@ pw_log_trace_fp("%p: tee reuse buffer %d %d", this, port_id, buffer_id); spa_node_port_reuse_buffer(this->node->node, this->port_id, buffer_id); - return 0; } static const struct spa_node_methods schedule_tee_node = { SPA_VERSION_NODE_METHODS, .process = tee_process, + .port_set_io = port_set_io, .port_reuse_buffer = tee_reuse_buffer, }; @@ -156,7 +224,7 @@ if (SPA_UNLIKELY(PW_IMPL_PORT_IS_CONTROL(this))) return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA; - spa_list_for_each(mix, &this->rt.mix_list, rt_link) { + spa_list_for_each(mix, &impl->mix_list, rt_link) { pw_log_trace_fp("%p: mix input %d %p->%p %d %d", this, mix->port.port_id, mix->io, io, mix->io->status, mix->io->buffer_id); *io = *mix->io; @@ -169,11 +237,10 @@ static int schedule_mix_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *impl = object; - struct pw_impl_port *this = &impl->this; struct pw_impl_port_mix *mix; - spa_list_for_each(mix, &this->rt.mix_list, rt_link) { - pw_log_trace_fp("%p: reuse buffer %d %d", this, port_id, buffer_id); + spa_list_for_each(mix, &impl->mix_list, rt_link) { + pw_log_trace_fp("%p: reuse buffer %d %d", impl, port_id, buffer_id); /* FIXME send reuse buffer to peer */ break; } @@ -183,6 +250,7 @@ static const struct spa_node_methods schedule_mix_node = { SPA_VERSION_NODE_METHODS, .process = schedule_mix_input, + .port_set_io = port_set_io, .port_reuse_buffer = schedule_mix_reuse_buffer, }; @@ -263,6 +331,9 @@ res = pw_impl_port_call_release_mix(port, mix); + if (port->destroying) + return res; + if ((res = spa_node_remove_port(port->mix, port->direction, port_id)) < 0 && res != -ENOTSUP) pw_log_warn("can't remove mix port %d: %s", port_id, spa_strerror(res)); @@ -276,6 +347,8 @@ port->direction, port->port_id, SPA_IO_Buffers, NULL, sizeof(port->rt.io)); + + pw_impl_port_set_param(port, SPA_PARAM_Format, 0, NULL); } return res; } @@ -472,8 +545,10 @@ spa_list_init(&impl->param_list); spa_list_init(&impl->pending_list); impl->cache_params = true; + spa_list_init(&impl->mix_list); this = &impl->this; + pw_log_debug("%p: new %s %d", this, pw_direction_as_string(direction), port_id); @@ -512,7 +587,6 @@ spa_list_init(&this->links); spa_list_init(&this->mix_list); - spa_list_init(&this->rt.mix_list); spa_list_init(&this->control_list0); spa_list_init(&this->control_list1); @@ -1643,7 +1717,7 @@ int res = 0, res2; pw_log_debug("%p: %d:%d.%d: %d buffers flags:%d state:%d n_mix:%d", port, - port->direction, port->port_id, mix->id, + port->direction, port->port_id, mix->port.port_id, n_buffers, flags, port->state, port->n_mix); if (n_buffers == 0 && port->state <= PW_IMPL_PORT_STATE_READY)
View file
pipewire-0.3.71.tar.gz/src/pipewire/impl-port.h -> pipewire-0.3.72.tar.gz/src/pipewire/impl-port.h
Changed
@@ -98,6 +98,9 @@ /** Get the port id */ uint32_t pw_impl_port_get_id(struct pw_impl_port *port); +/** Get the port state as a string */ +const char *pw_impl_port_state_as_string(enum pw_impl_port_state state); + /** Get the port parent node or NULL when not yet set */ struct pw_impl_node *pw_impl_port_get_node(struct pw_impl_port *port);
View file
pipewire-0.3.71.tar.gz/src/pipewire/introspect.c -> pipewire-0.3.72.tar.gz/src/pipewire/introspect.c
Changed
@@ -212,6 +212,7 @@ } info->n_params = n_params; for (; i < info->n_params; i++) { + spa_zero(info->paramsi); info->paramsi.id = update->paramsi.id; info->paramsi.flags = update->paramsi.flags; info->paramsi.user = 1; @@ -285,6 +286,7 @@ } info->n_params = n_params; for (; i < info->n_params; i++) { + spa_zero(info->paramsi); info->paramsi.id = update->paramsi.id; info->paramsi.flags = update->paramsi.flags; info->paramsi.user = 1; @@ -448,6 +450,7 @@ } info->n_params = n_params; for (; i < info->n_params; i++) { + spa_zero(info->paramsi); info->paramsi.id = update->paramsi.id; info->paramsi.flags = update->paramsi.flags; info->paramsi.user = 1;
View file
pipewire-0.3.71.tar.gz/src/pipewire/keys.h -> pipewire-0.3.72.tar.gz/src/pipewire/keys.h
Changed
@@ -52,7 +52,8 @@ #define PW_KEY_OBJECT_LINGER "object.linger" /**< the object lives on even after the client * that created it has been destroyed */ #define PW_KEY_OBJECT_REGISTER "object.register" /**< If the object should be registered. */ - +#define PW_KEY_OBJECT_EXPORT "object.export" /**< If the object should be exported, + * since 0.3.72 */ /* config */ #define PW_KEY_CONFIG_PREFIX "config.prefix" /**< a config prefix directory */
View file
pipewire-0.3.71.tar.gz/src/pipewire/private.h -> pipewire-0.3.72.tar.gz/src/pipewire/private.h
Changed
@@ -423,6 +423,13 @@ void (*complete) (void *data, struct pw_impl_node *node); }; +void pw_context_driver_add_listener(struct pw_context *context, + struct spa_hook *listener, + const struct pw_context_driver_events *events, + void *data); +void pw_context_driver_remove_listener(struct pw_context *context, + struct spa_hook *listener); + #define pw_registry_resource(r,m,v,...) pw_resource_call(r, struct pw_registry_events,m,v,##__VA_ARGS__) #define pw_registry_resource_global(r,...) pw_registry_resource(r,global,0,__VA_ARGS__) #define pw_registry_resource_global_remove(r,...) pw_registry_resource(r,global_remove,0,__VA_ARGS__) @@ -584,6 +591,11 @@ struct pw_node_target { struct spa_list link; +#define PW_NODE_TARGET_NONE 0 +#define PW_NODE_TARGET_PEER 1 + uint32_t flags; + uint32_t id; + char name128; struct pw_impl_node *node; struct pw_node_activation *activation; struct spa_system *system; @@ -591,6 +603,16 @@ unsigned int active:1; }; +static inline void copy_target(struct pw_node_target *dst, const struct pw_node_target *src) +{ + dst->id = src->id; + memcpy(dst->name, src->name, sizeof(dst->name)); + dst->node = src->node; + dst->activation = src->activation; + dst->system = src->system; + dst->fd = src->fd; +} + struct pw_node_activation { #define PW_NODE_ACTIVATION_NOT_TRIGGERED 0 #define PW_NODE_ACTIVATION_TRIGGERED 1 @@ -616,9 +638,13 @@ * used when driver segment_owner has this node id */ /* for drivers, shared with all nodes */ - uint32_t segment_owner32; /* id of owners for each segment info struct. + uint32_t segment_owner16; /* id of owners for each segment info struct. * nodes that want to update segment info need to * CAS their node id in this array. */ + uint32_t padding15; +#define PW_NODE_ACTIVATION_FLAG_NONE 0 +#define PW_NODE_ACTIVATION_FLAG_PROFILER (1<<0) /* the profiler is running */ + uint32_t flags; /* extra flags */ struct spa_io_position position; /* contains current position and segment info. * extra info is updated by nodes that have set * themselves as owner in the segment structs */ @@ -764,7 +790,6 @@ struct { struct spa_io_clock *clock; /**< io area of the clock or NULL */ struct spa_io_position *position; - struct pw_node_activation *activation; struct spa_list target_list; /* list of targets to signal after * this node */ @@ -781,6 +806,8 @@ struct spa_fraction target_rate; uint64_t target_quantum; + uint64_t driver_start; + void *user_data; /**< extra user data */ }; @@ -796,6 +823,7 @@ uint32_t id; uint32_t peer_id; unsigned int have_buffers:1; + unsigned int active:1; }; struct pw_impl_port_implementation { @@ -884,8 +912,6 @@ struct { struct spa_io_buffers io; /**< io area of the port */ - struct spa_io_clock clock; /**< io area of the clock */ - struct spa_list mix_list; struct spa_list node_link; } rt; /**< data only accessed from the data thread */ unsigned int added:1; @@ -964,6 +990,7 @@ unsigned int preparing:1; unsigned int prepared:1; unsigned int passive:1; + unsigned int destroyed:1; }; #define pw_resource_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_resource_events, m, v, ##__VA_ARGS__)
View file
pipewire-0.3.71.tar.gz/src/pipewire/properties.c -> pipewire-0.3.72.tar.gz/src/pipewire/properties.c
Changed
@@ -706,8 +706,11 @@ if (value == NULL || len == 0) { fprintf(file, "%snull%s", LITERAL(c), NORMAL(c)); } else if (spa_json_is_container(value, len) && !c->recurse) { - len = spa_json_container_len(it, value, len); - fprintf(file, "%s%.*s%s", CONTAINER(c), len, value, NORMAL(c)); + spa_json_enter_container(it, &sub, value0); + if (spa_json_container_len(&sub, value, len) == len) + fprintf(file, "%s%.*s%s", CONTAINER(c), len, value, NORMAL(c)); + else + encode_string(c, STRING(c), value, len, NORMAL(c)); } else if (spa_json_is_array(value, len)) { fprintf(file, ""); spa_json_enter(it, &sub); @@ -782,15 +785,11 @@ fprintf(f, "%s%s%s: ", KEY(c), key, NORMAL(c)); } value = it->value; - len = value ? strlen(value) : 0; spa_json_init(&sub, value, len); - if ((len = spa_json_next(&sub, &value)) < 0) + if (c->recurse && spa_json_next(&sub, &value) < 0) break; - if (!spa_json_is_container(value, len)) - len = value ? strlen(value) : 0; - dump(c, c->indent, &sub, value, len); count++; }
View file
pipewire-0.3.71.tar.gz/src/pipewire/stream.c -> pipewire-0.3.72.tar.gz/src/pipewire/stream.c
Changed
@@ -158,6 +158,7 @@ unsigned int using_trigger:1; unsigned int trigger:1; int in_set_param; + int in_emit_param_changed; }; static int get_param_index(uint32_t id) @@ -268,14 +269,42 @@ static void clear_params(struct stream *impl, uint32_t id) { struct param *p, *t; + bool found = false; + int i, idx; spa_list_for_each_safe(p, t, &impl->param_list, link) { if (id == SPA_ID_INVALID || (p->id == id && !(p->flags & PARAM_FLAG_LOCKED))) { + found = true; spa_list_remove(&p->link); free(p); } } + if (found) { + if (id == SPA_ID_INVALID) { + impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + for (i = 0; i < N_NODE_PARAMS; i++) { + impl->paramsi.flags &= ~SPA_PARAM_INFO_READ; + impl->paramsi.user++; + } + impl->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + for (i = 0; i < N_PORT_PARAMS; i++) { + impl->port_paramsi.flags &= ~SPA_PARAM_INFO_READ; + impl->port_paramsi.user++; + } + } else { + if ((idx = get_param_index(id)) != -1) { + impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + impl->paramsidx.flags &= ~SPA_PARAM_INFO_READ; + impl->paramsidx.user++; + } + if ((idx = get_port_param_index(id)) != -1) { + impl->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + impl->port_paramsidx.flags &= ~SPA_PARAM_INFO_READ; + impl->port_paramsidx.user++; + } + } + } } static int update_params(struct stream *impl, uint32_t id, @@ -424,13 +453,16 @@ static inline void call_process(struct stream *impl) { pw_log_trace_fp("%p: call process rt:%u", impl, impl->process_rt); - if (impl->direction == SPA_DIRECTION_OUTPUT && update_requested(impl) <= 0) + if (impl->n_buffers == 0 || + (impl->direction == SPA_DIRECTION_OUTPUT && update_requested(impl) <= 0)) return; - if (impl->process_rt) - spa_callbacks_call(&impl->rt_callbacks, struct pw_stream_events, process, 0); - else + if (impl->process_rt) { + if (impl->rt_callbacks.funcs) + spa_callbacks_call_fast(&impl->rt_callbacks, struct pw_stream_events, process, 0); + } else { pw_loop_invoke(impl->main_loop, do_call_process, 1, NULL, 0, false, impl); + } } static int @@ -561,16 +593,24 @@ return enum_params(object, false, seq, id, start, num, filter); } +static inline void emit_param_changed(struct stream *impl, + uint32_t id, const struct spa_pod *param) +{ + struct pw_stream *stream = &impl->this; + if (impl->in_emit_param_changed++ == 0) + pw_stream_emit_param_changed(stream, id, param); + impl->in_emit_param_changed--; +} + static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct stream *impl = object; - struct pw_stream *stream = &impl->this; if (id != SPA_PARAM_Props) return -ENOTSUP; if (impl->in_set_param == 0) - pw_stream_emit_param_changed(stream, id, param); + emit_param_changed(impl, id, param); return 0; } @@ -883,7 +923,7 @@ break; } - pw_stream_emit_param_changed(stream, id, param); + emit_param_changed(impl, id, param); if (stream->state == PW_STREAM_STATE_ERROR) return stream->error_res; @@ -996,12 +1036,15 @@ /* push new buffer */ pw_log_trace_fp("%p: push %d %p", stream, b->id, io); if (queue_push(impl, &impl->dequeued, b) == 0) { - copy_position(impl, impl->dequeued.incount); if (b->busy) ATOMIC_INC(b->busy->count); - call_process(impl); } } + if (!queue_is_empty(impl, &impl->dequeued)) { + copy_position(impl, impl->dequeued.incount); + call_process(impl); + } + if (io->status != SPA_STATUS_NEED_DATA || io->buffer_id == SPA_ID_INVALID) { /* pop buffer to recycle */ if ((b = queue_pop(impl, &impl->queued))) { @@ -1342,8 +1385,10 @@ static void node_event_destroy(void *data) { struct pw_stream *stream = data; + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); spa_hook_remove(&stream->node_listener); stream->node = NULL; + impl->data_loop = NULL; } static void node_event_info(void *data, const struct pw_node_info *info) @@ -1515,7 +1560,7 @@ impl->allow_mlock = context->settings.mem_allow_mlock; impl->warn_mlock = context->settings.mem_warn_mlock; - spa_hook_list_append(&impl->context->driver_listener_list, + pw_context_driver_add_listener(impl->context, &impl->context_listener, &context_events, impl); return impl; @@ -1683,7 +1728,8 @@ spa_hook_list_clean(&impl->hooks); spa_hook_list_clean(&stream->listener_list); - spa_hook_remove(&impl->context_listener); + pw_context_driver_remove_listener(impl->context, + &impl->context_listener); if (impl->data.context) pw_context_destroy(impl->data.context); @@ -1692,10 +1738,22 @@ free(impl); } +static int +do_remove_callbacks(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct stream *impl = user_data; + spa_zero(impl->rt_callbacks); + return 0; +} + static void hook_removed(struct spa_hook *hook) { struct stream *impl = hook->priv; - spa_zero(impl->rt_callbacks); + if (impl->data_loop) + pw_loop_invoke(impl->data_loop, do_remove_callbacks, 1, NULL, 0, true, impl); + else + spa_zero(impl->rt_callbacks); hook->priv = NULL; hook->removed = NULL; } @@ -1953,10 +2011,12 @@ /* XXX this is deprecated but still used by the portal and its apps */ pw_properties_setf(stream->properties, PW_KEY_NODE_TARGET, "%d", target_id); - if ((flags & PW_STREAM_FLAG_AUTOCONNECT) && + if ((str = getenv("PIPEWIRE_AUTOCONNECT")) != NULL) + pw_properties_set(stream->properties, + PW_KEY_NODE_AUTOCONNECT, spa_atob(str) ? "true" : "false"); + else if ((flags & PW_STREAM_FLAG_AUTOCONNECT) && pw_properties_get(stream->properties, PW_KEY_NODE_AUTOCONNECT) == NULL) { - str = getenv("PIPEWIRE_AUTOCONNECT"); - pw_properties_set(stream->properties, PW_KEY_NODE_AUTOCONNECT, str ? str : "true"); + pw_properties_set(stream->properties, PW_KEY_NODE_AUTOCONNECT, "true"); } if (flags & PW_STREAM_FLAG_DRIVER) pw_properties_set(stream->properties, PW_KEY_NODE_DRIVER, "true"); @@ -2471,7 +2531,7 @@ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); int res = 0; - pw_log_trace_fp("%p", impl); + pw_log_trace_fp("%p: trigger:%d driving:%d", impl, impl->trigger, impl->driving); /* flag to check for old or new behaviour */ impl->using_trigger = true;
View file
pipewire-0.3.71.tar.gz/src/pipewire/stream.h -> pipewire-0.3.72.tar.gz/src/pipewire/stream.h
Changed
@@ -151,6 +151,9 @@ * * \section sec_stream_environment Environment Variables * + * The environment variable PIPEWIRE_AUTOCONNECT can be used to override the + * flag and force apps to autoconnect or not. + * */ /** \defgroup pw_stream Stream *
View file
pipewire-0.3.71.tar.gz/src/pipewire/thread-loop.c -> pipewire-0.3.72.tar.gz/src/pipewire/thread-loop.c
Changed
@@ -50,7 +50,7 @@ { int res; if ((res = pthread_mutex_lock(&this->lock)) != 0) - pw_log_error("%p: thread:%lu: %s", this, pthread_self(), strerror(res)); + pw_log_error("%p: thread:%p: %s", this, (void *) pthread_self(), strerror(res)); else this->recurse++; return -res; @@ -62,7 +62,7 @@ spa_return_val_if_fail(this->recurse > 0, -EIO); this->recurse--; if ((res = pthread_mutex_unlock(&this->lock)) != 0) { - pw_log_error("%p: thread:%lu: %s", this, pthread_self(), strerror(res)); + pw_log_error("%p: thread:%p: %s", this, (void *) pthread_self(), strerror(res)); this->recurse++; } return -res; @@ -97,13 +97,13 @@ /* if lock taken by something else, error */ if ((res = pthread_mutex_trylock(&this->lock)) != 0) { - pw_log_debug("%p: thread:%lu: %s", this, pthread_self(), strerror(res)); + pw_log_debug("%p: thread:%p: %s", this, (void *) pthread_self(), strerror(res)); return -res; } /* we could take the lock, check if we actually locked it somewhere */ res = this->recurse > 0 ? 1 : -EPERM; if (res < 0) - pw_log_debug("%p: thread:%lu: recurse:%d", this, pthread_self(), this->recurse); + pw_log_debug("%p: thread:%p: recurse:%d", this, (void *) pthread_self(), this->recurse); pthread_mutex_unlock(&this->lock); return res; } @@ -398,7 +398,7 @@ while (loop->n_waiting_for_accept > 0) { int res; if ((res = pthread_cond_wait(&loop->accept_cond, &loop->lock)) != 0) - pw_log_error("%p: thread:%lu: %s", loop, pthread_self(), strerror(res)); + pw_log_error("%p: thread:%p: %s", loop, (void *) pthread_self(), strerror(res)); } } } @@ -418,7 +418,7 @@ loop->n_waiting++; loop->recurse--; if ((res = pthread_cond_wait(&loop->cond, &loop->lock)) != 0) - pw_log_error("%p: thread:%lu: %s", loop, pthread_self(), strerror(res)); + pw_log_error("%p: thread:%p: %s", loop, (void *) pthread_self(), strerror(res)); loop->recurse++; loop->n_waiting--; pw_log_trace("%p, waiting done %d", loop, loop->n_waiting);
View file
pipewire-0.3.71.tar.gz/src/tools/pw-top.c -> pipewire-0.3.72.tar.gz/src/tools/pw-top.c
Changed
@@ -22,6 +22,8 @@ #define MAX_FORMAT 16 #define MAX_NAME 128 +#define XRUN_INVALID (uint32_t)-1 + struct driver { int64_t count; float cpu_load3; @@ -38,6 +40,7 @@ int64_t awake; int64_t finish; struct spa_fraction latency; + uint32_t xrun_count; }; struct node { @@ -49,8 +52,6 @@ struct measurement measurement; struct driver info; struct node *driver; - uint32_t errors; - int32_t last_error_status; uint32_t generation; char formatMAX_FORMAT+1; struct pw_proxy *proxy; @@ -319,6 +320,7 @@ int res; spa_zero(m); + m.xrun_count = XRUN_INVALID; if ((res = spa_pod_parse_struct(pod, SPA_POD_Int(&id), SPA_POD_String(&name), @@ -327,7 +329,8 @@ SPA_POD_Long(&m.awake), SPA_POD_Long(&m.finish), SPA_POD_Int(&m.status), - SPA_POD_Fraction(&m.latency))) < 0) + SPA_POD_Fraction(&m.latency), + SPA_POD_OPT_Int(&m.xrun_count))) < 0) return res; if ((n = find_node(d, id)) == NULL) @@ -338,12 +341,6 @@ n->info = point->info; point->driver = n; n->generation = d->generation; - - if (m.status != 3) { - n->errors++; - if (n->last_error_status == -1) - n->last_error_status = m.status; - } return 0; } @@ -356,6 +353,7 @@ int res; spa_zero(m); + m.xrun_count = XRUN_INVALID; if ((res = spa_pod_parse_struct(pod, SPA_POD_Int(&id), SPA_POD_String(&name), @@ -364,7 +362,8 @@ SPA_POD_Long(&m.awake), SPA_POD_Long(&m.finish), SPA_POD_Int(&m.status), - SPA_POD_Fraction(&m.latency))) < 0) + SPA_POD_Fraction(&m.latency), + SPA_POD_OPT_Int(&m.xrun_count))) < 0) return res; if ((n = find_node(d, id)) == NULL) @@ -376,11 +375,6 @@ d->pending_refresh = true; } n->generation = d->generation; - if (m.status != 3) { - n->errors++; - if (n->last_error_status == -1) - n->last_error_status = m.status; - } return 0; } @@ -476,7 +470,8 @@ print_time(buf2, active, 64, busy), print_perc(buf3, active, 64, waiting, quantum), print_perc(buf4, active, 64, busy, quantum), - i->xrun_count + n->errors, + n->measurement.xrun_count == XRUN_INVALID ? + i->xrun_count : n->measurement.xrun_count, active ? n->format : "", n->driver == n ? "" : " + ", n->name); @@ -487,8 +482,6 @@ n->driver = n; spa_zero(n->measurement); spa_zero(n->info); - n->errors = 0; - n->last_error_status = 0; } static void do_refresh(struct data *d)
View file
pipewire-0.3.71.tar.gz/test/test-logger.c -> pipewire-0.3.72.tar.gz/test/test-logger.c
Changed
@@ -504,7 +504,7 @@ } sd_journal_seek_tail(journal); - sd_journal_next(journal); + sd_journal_previous(journal); spa_scnprintf(token, sizeof(token), "MARK %s:%d", __func__, __LINE__); spa_logt_info(iface, &topic, "%s", token); @@ -572,7 +572,7 @@ } sd_journal_seek_tail(journal); - sd_journal_next(journal); + sd_journal_previous(journal); spa_scnprintf(token, sizeof(token), "MARK %s:%d", __func__, __LINE__);
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.