diff --git a/NEWS b/NEWS index 66ce22fcb..84569fe10 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,136 @@ +# PipeWire 1.4.4 (2025-05-29) + +This is a quick bugfix release that is API and ABI compatible with +previous 1.x releases. + +## Highlights + - Provide better compatibility with 1.2 for MIDI. + - Fix mpv buffer negotiation regression. + - Improve GStreamer compatibility with libcamera. + + +## SPA + - Provide conversions to old style midi in the ALSA sequencer. + - Negotiate only to UMP when using a newer library. + - Fix negotiation direction for buffers, prefer the converter + suggestion instead of the application until we can be sure + applications make good suggestions. + +## GStreamer + - Allow a minimum of 1 buffers again instead of 8. libcamera will + allocate only 4 buffers so we need to support this. + + +Older versions: + +# PipeWire 1.4.3 (2025-05-22) + +This is a bugfix release that is API and ABI compatible with +previous 1.x releases. + +## Highlights + - Many netjack2 improvements. The driver/manager roles were fixed, + MIDI is written correctly and errors are handled better. + - Improvements to UMP sysex handling. + - More small bug fixes and improvements. + + +## PipeWire + - Let all commands go to the node. This makes it possible to send + custom commands. + +## Modules + - Many netjack2 improvements. The driver/manager roles were fixed, + MIDI is written correctly and errors are handled better. + - Improve the filter-graph state management in filter-chain. + +## SPA + - Use default value of filter. (#4619) + - Fix UMP program change conversion to MIDI 1.0. (#4664) + - Skip only the first buffer for raw formats in v4l2 to avoid + dropping important headers when dealing with encoded formats. + - Fix ebur128 port name. (#4667) + - Only convert UMP/MIDI, pass other controls. Fixes OSC and other + control types in the mixer. (#4692) + - Improve UMP sysex handling in alsa seq. + - Improve ALSA audio.channels support. Only use this when the value + is within the valid range. + +## Tools + - debug UMP SysRT messages correctly in pw-mididump. + +## JACK + - Handle sysex in UMP better by appending the converted midi1 + sysex. + +# PipeWire 1.4.2 (2025-04-14) + +This is a bugfix release that is API and ABI compatible with +previous 1.x releases. + +## Highlights + - Do extra checks for MIDI to avoid 100% CPU usage on older kernels. + - Fix some potential crashes in POD builder. + - pw-cat streaming improvements on stdout/stdin. + - Small fixes and improvements. + + +## PipeWire + - Make the service files depend on DBus to avoid startup races. + +## SPA + - Do extra checks for MIDI to avoid 100% CPU usage on older kernels. + - Use Header metadata by default in videoadapter. + - Handle set_format result from v4l2 better. + - Handle crash when POD builder overflows in the filter. + - Work around a libebur128 bug. (#4646) + +## Tools + - pw-cat prefers AU format when streaming on stdout/stdin. (#4629) + - Improve pw-cat verbose sndfile format debug. + - Add the missing --channel-map long option to pw-loopback. + +## GStreamer + - Fix a leak in the deviceprovider. (#4616) + - Fix negotiation and make renegotiation better. + +# PipeWire 1.4.1 (2025-03-14) + +This is a quick bugfix release that is API and ABI compatible with +previous 1.x releases. + +## Highlights + - Handle SplitPCM wrong channels specifications. This fixes some + problems with disappearing devices. + - Add backwards compatibility support for when the kernel does not + support UMP. Also fix UMP output. This restores MIDI support for + older kernels/ALSA. + - Fix a crash in audioconvert because the resampler was not using the + right number of channels. + - Some compilation fixes and small improvements. + + +## PipeWire + - Don't emit events when disconnecting a stream. (#3314) + - Fix some compilation problems. (#4603) + +## Modules + - Bump the ROC requirement to version 0.4.0 + +## SPA + - Handle SplitPCM too few or too many channels. Add an error string + to the device names when the UCM config has an error. + - Add backwards compatibility support for when the kernel does not + support UMP. + - Configure the channels in the resampler correctly in all + cases. (#4595) + - Fix UMP output. + - Use the right samplerate of the filter-graph in audioconvert in + all cases. + +## Bluetooth + - Fix a crash with an incomming call. + # PipeWire 1.4.0 (2025-03-06) This is the 1.4 release that is API and ABI compatible with previous @@ -99,9 +232,6 @@ the 1.2 release last year, including: ## JACK - Add an option to disable the MIDI2 port flags. (#4584) - -Older versions: - # PipeWire 1.3.83 (2025-02-20) This is the third and hopefully last 1.4 release candidate that diff --git a/doc/dox/programs/pw-link.1.md b/doc/dox/programs/pw-link.1.md index aff53d600..83a1d8372 100644 --- a/doc/dox/programs/pw-link.1.md +++ b/doc/dox/programs/pw-link.1.md @@ -37,7 +37,7 @@ output ports and their links. List output ports \par -i | \--input -List output ports +List input ports \par -l | \--links List links diff --git a/meson.build b/meson.build index 3dfdc1842..bca05ecd6 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('pipewire', ['c' ], - version : '1.4.0', + version : '1.4.4', license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ], meson_version : '>= 0.61.1', default_options : [ 'warning_level=3', @@ -127,21 +127,30 @@ if have_cpp add_project_arguments(cxx.get_supported_arguments(cxx_flags), language: 'cpp') endif -sse_args = '-msse' -sse2_args = '-msse2' -ssse3_args = '-mssse3' -sse41_args = '-msse4.1' -fma_args = '-mfma' -avx_args = '-mavx' -avx2_args = '-mavx2' +have_sse = false +have_sse2 = false +have_ssse3 = false +have_sse41 = false +have_fma = false +have_avx = false +have_avx2 = false +if host_machine.cpu_family() in ['x86', 'x86_64'] + sse_args = '-msse' + sse2_args = '-msse2' + ssse3_args = '-mssse3' + sse41_args = '-msse4.1' + fma_args = '-mfma' + avx_args = '-mavx' + avx2_args = '-mavx2' -have_sse = cc.has_argument(sse_args) -have_sse2 = cc.has_argument(sse2_args) -have_ssse3 = cc.has_argument(ssse3_args) -have_sse41 = cc.has_argument(sse41_args) -have_fma = cc.has_argument(fma_args) -have_avx = cc.has_argument(avx_args) -have_avx2 = cc.has_argument(avx2_args) + have_sse = cc.has_argument(sse_args) + have_sse2 = cc.has_argument(sse2_args) + have_ssse3 = cc.has_argument(ssse3_args) + have_sse41 = cc.has_argument(sse41_args) + have_fma = cc.has_argument(fma_args) + have_avx = cc.has_argument(avx_args) + have_avx2 = cc.has_argument(avx2_args) +endif have_neon = false if host_machine.cpu_family() == 'aarch64' @@ -481,7 +490,7 @@ endif summary({'intl support': libintl_dep.found()}, bool_yn: true) need_alsa = get_option('pipewire-alsa').enabled() or 'media-session' in get_option('session-managers') -alsa_dep = dependency('alsa', version : '>=1.2.10', required: need_alsa) +alsa_dep = dependency('alsa', version : '>=1.2.6', required: need_alsa) summary({'pipewire-alsa': alsa_dep.found()}, bool_yn: true) if host_machine.system() == 'freebsd' or host_machine.system() == 'midnightbsd' diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index 839a9fc24..695938f90 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -1538,7 +1538,7 @@ static inline jack_midi_data_t* midi_event_reserve(void *port_buffer, res = ev->inline_data; } else { mb->write_pos += data_size; - ev->byte_offset = mb->buffer_size - 1 - mb->write_pos; + ev->byte_offset = mb->buffer_size - mb->write_pos; res = SPA_PTROFF(mb, ev->byte_offset, uint8_t); } mb->event_count += 1; @@ -1546,14 +1546,37 @@ static inline jack_midi_data_t* midi_event_reserve(void *port_buffer, return res; } +static inline int midi_event_append(void *port_buffer, const jack_midi_data_t *data, size_t data_size) +{ + struct midi_buffer *mb = port_buffer; + struct midi_event *events = SPA_PTROFF(mb, sizeof(*mb), struct midi_event); + struct midi_event *ev; + size_t old_size; + uint8_t *old, *buf; + + ev = &events[--mb->event_count]; + mb->write_pos -= ev->size; + old_size = ev->size; + if (old_size <= MIDI_INLINE_MAX) + old = ev->inline_data; + else + old = SPA_PTROFF(mb, ev->byte_offset, uint8_t); + buf = midi_event_reserve(port_buffer, ev->time, old_size + data_size); + if (SPA_UNLIKELY(buf == NULL)) + return -ENOBUFS; + memmove(buf, old, old_size); + memcpy(buf+old_size, data, data_size); + return 0; +} + static inline int midi_event_write(void *port_buffer, jack_nframes_t time, const jack_midi_data_t *data, size_t data_size, bool fix) { jack_midi_data_t *retbuf = midi_event_reserve (port_buffer, time, data_size); - if (SPA_UNLIKELY(retbuf == NULL)) - return -ENOBUFS; + if (SPA_UNLIKELY(retbuf == NULL)) + return -ENOBUFS; memcpy (retbuf, data, data_size); if (fix) fix_midi_event(retbuf, data_size); @@ -1566,6 +1589,7 @@ static void convert_to_event(struct spa_pod_sequence **seq, uint32_t n_seq, void uint64_t state = 0; uint32_t i; int res = 0; + bool in_sysex = false; for (i = 0; i < n_seq; i++) c[i] = spa_pod_control_first(&seq[i]->body); @@ -1620,17 +1644,30 @@ static void convert_to_event(struct spa_pod_sequence **seq, uint32_t n_seq, void void *data = SPA_POD_BODY(&next->value); size_t size = SPA_POD_BODY_SIZE(&next->value); uint8_t ev[32]; + bool was_sysex = in_sysex; if (type == TYPE_ID_MIDI) { int ev_size = spa_ump_to_midi(data, size, ev, sizeof(ev)); if (ev_size <= 0) break; + size = ev_size; data = ev; + + if (!in_sysex && ev[0] == 0xf0) + in_sysex = true; + if (in_sysex && ev[ev_size-1] == 0xf7) + in_sysex = false; + } else if (type != TYPE_ID_UMP) break; - if ((res = midi_event_write(midi, next->offset, data, size, fix)) < 0) + if (was_sysex) + res = midi_event_append(midi, data, size); + else + res = midi_event_write(midi, next->offset, data, size, fix); + + if (res < 0) pw_log_warn("midi %p: can't write event: %s", midi, spa_strerror(res)); } @@ -2512,11 +2549,28 @@ static int param_enum_format(struct client *c, struct port *p, case TYPE_ID_UMP: case TYPE_ID_OSC: case TYPE_ID_MIDI: - *param = spa_pod_builder_add_object(b, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + { + struct spa_pod_frame f; + int32_t types = 0; + + spa_pod_builder_push_object(b, &f, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); + spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), + 0); + if (p->object->port.type_id == TYPE_ID_UMP) + types |= 1u<object->port.type_id == TYPE_ID_OSC) + types |= 1u<mix); - pw_properties_set(p->props, PW_KEY_FORMAT_DSP, type_to_format_dsp(type_id)); + pw_properties_set(p->props, PW_KEY_FORMAT_DSP, type_to_string(type_id)); pw_properties_set(p->props, PW_KEY_PORT_NAME, port_name); if (flags > 0x1f) { pw_properties_setf(p->props, PW_KEY_PORT_EXTRA, @@ -5754,8 +5790,8 @@ static void *get_buffer_input_midi(struct port *p, jack_nframes_t frames) convert_to_event(seq, n_seq, mb, p->client->fix_midi_events, p->object->port.type_id); memcpy(ptr, mb, sizeof(struct midi_buffer) + (mb->event_count * sizeof(struct midi_event))); - if (mb->write_pos) { - size_t offs = mb->buffer_size - 1 - mb->write_pos; + if (mb->write_pos > 0) { + size_t offs = mb->buffer_size - mb->write_pos; memcpy(SPA_PTROFF(ptr, offs, void), SPA_PTROFF(mb, offs, void), mb->write_pos); } return ptr; diff --git a/spa/include/spa/control/ump-utils.h b/spa/include/spa/control/ump-utils.h index 9f57efb93..463b12d14 100644 --- a/spa/include/spa/control/ump-utils.h +++ b/spa/include/spa/control/ump-utils.h @@ -96,9 +96,17 @@ SPA_API_CONTROL_UMP_UTILS int spa_ump_to_midi(uint32_t *ump, size_t ump_size, if (ump_size < 8) return 0; midi[size++] = (ump[0] >> 16) | 0x80; - if (midi[0] < 0xc0 || midi[0] > 0xdf) + switch (midi[0] & 0xf0) { + case 0xc0: + midi[size++] = (ump[1] >> 24); + break; + default: midi[size++] = (ump[0] >> 8) & 0x7f; - midi[size++] = (ump[1] >> 25); + SPA_FALLTHROUGH; + case 0xd0: + midi[size++] = (ump[1] >> 25); + break; + } break; case 0x0: /* Utility Messages */ diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index 553f75512..46b2f64c1 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -326,6 +326,31 @@ spa_pod_builder_reserve_bytes(struct spa_pod_builder *builder, uint32_t len) return SPA_POD_BODY(spa_pod_builder_deref(builder, offset)); } +SPA_API_POD_BUILDER uint32_t +spa_pod_builder_bytes_start(struct spa_pod_builder *builder) +{ + uint32_t offset = builder->state.offset; + const struct spa_pod_bytes p = SPA_POD_INIT_Bytes(0); + spa_pod_builder_raw(builder, &p, sizeof(p)); + return offset; +} +SPA_API_POD_BUILDER int +spa_pod_builder_bytes_append(struct spa_pod_builder *builder, uint32_t offset, + const void *data, uint32_t size) +{ + int res = spa_pod_builder_raw(builder, data, size); + struct spa_pod *pod = spa_pod_builder_deref(builder, offset); + if (pod) + pod->size += size; + return res; +} + +SPA_API_POD_BUILDER int +spa_pod_builder_bytes_end(struct spa_pod_builder *builder, uint32_t offset SPA_UNUSED) +{ + return spa_pod_builder_pad(builder, builder->state.offset); +} + #define SPA_POD_INIT_Pointer(type,value) ((struct spa_pod_pointer){ { sizeof(struct spa_pod_pointer_body), SPA_TYPE_Pointer }, { (type), 0, (value) } }) SPA_API_POD_BUILDER int diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 6a47472e4..9e265f9b7 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -139,7 +139,7 @@ spa_pod_filter_prop(struct spa_pod_builder *b, const struct spa_pod_prop *p2) { const struct spa_pod *v1, *v2; - struct spa_pod_choice *nc; + struct spa_pod_choice *nc, dummy; uint32_t j, k, nalt1, nalt2; void *alt1, *alt2, *a1, *a2; uint32_t type, size, p1c, p2c; @@ -175,9 +175,13 @@ spa_pod_filter_prop(struct spa_pod_builder *b, spa_pod_builder_prop(b, p1->key, p1->flags & p2->flags); spa_pod_builder_push_choice(b, &f, 0, 0); nc = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f); + /* write to dummy value when builder overflows. We don't want to error + * because overflowing is a way to determine the required buffer size. */ + if (nc == NULL) + nc = &dummy; /* default value */ - spa_pod_builder_primitive(b, v1); + spa_pod_builder_primitive(b, v2); if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_None) || (p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Enum) || @@ -185,10 +189,10 @@ spa_pod_filter_prop(struct spa_pod_builder *b, (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Enum)) { int n_copied = 0; /* copy all equal values but don't copy the default value again */ - for (j = 0, a1 = alt1; j < nalt1; j++, a1 = SPA_PTROFF(a1, size, void)) { - for (k = 0, a2 = alt2; k < nalt2; k++, a2 = SPA_PTROFF(a2,size,void)) { + for (j = 0, a2 = alt2; j < nalt2; j++, a2 = SPA_PTROFF(a2, size, void)) { + for (k = 0, a1 = alt1; k < nalt1; k++, a1 = SPA_PTROFF(a1,size,void)) { if (spa_pod_compare_value(type, a1, a2, size) == 0) { - if (p1c == SPA_CHOICE_Enum || j > 0) + if (p2c == SPA_CHOICE_Enum || j > 0) spa_pod_builder_raw(b, a1, size); n_copied++; } diff --git a/spa/include/spa/utils/json-core.h b/spa/include/spa/utils/json-core.h index 31bf772f1..10c87e68c 100644 --- a/spa/include/spa/utils/json-core.h +++ b/spa/include/spa/utils/json-core.h @@ -268,6 +268,8 @@ SPA_API_JSON int spa_json_next(struct spa_json * iter, const char **value) if (--utf8_remain == 0) iter->state = __STRING | flag; continue; + default: + break; } _SPA_ERROR(CHARACTERS_NOT_ALLOWED); case __ESC: @@ -276,12 +278,17 @@ SPA_API_JSON int spa_json_next(struct spa_json * iter, const char **value) case 'n': case 'r': case 't': case 'u': iter->state = __STRING | flag; continue; + default: + break; } _SPA_ERROR(INVALID_ESCAPE); case __COMMENT: switch (cur) { case '\n': case '\r': iter->state = __STRUCT | flag; + break; + default: + break; } break; default: @@ -299,6 +306,8 @@ SPA_API_JSON int spa_json_next(struct spa_json * iter, const char **value) case __COMMENT: /* trailing comment */ return 0; + default: + break; } if ((iter->state & __SUB_FLAG) && (iter->state & __KEY_FLAG)) { diff --git a/spa/meson.build b/spa/meson.build index 9b5f89604..9faaae48a 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -43,9 +43,13 @@ if get_option('spa-plugins').allowed() endif # plugin-specific dependencies - alsa_dep = dependency('alsa', version : '>=1.2.10', required: get_option('alsa')) + alsa_dep = dependency('alsa', version : '>=1.2.6', required: get_option('alsa')) summary({'ALSA': alsa_dep.found()}, bool_yn: true, section: 'Backend') + if alsa_dep.version().version_compare('>=1.2.10') + cdata.set('HAVE_ALSA_UMP', true) + endif + bluez_dep = dependency('bluez', version : '>= 4.101', required: get_option('bluez5')) bluez_gio_dep = dependency('gio-2.0', required : get_option('bluez5')) bluez_gio_unix_dep = dependency('gio-unix-2.0', required : get_option('bluez5')) diff --git a/spa/plugins/alsa/acp-tool.c b/spa/plugins/alsa/acp-tool.c index cff9edb80..c12401100 100644 --- a/spa/plugins/alsa/acp-tool.c +++ b/spa/plugins/alsa/acp-tool.c @@ -132,6 +132,18 @@ static ACP_PRINTF_FUNC(6,0) void log_func(void *data, int level, const char *file, int line, const char *func, const char *fmt, va_list arg) { + static const char * const levels[] = { "E", "W", "N", "I", "D", "T" }; + const char *level_str = levels[SPA_CLAMP(level, 0, (int)SPA_N_ELEMENTS(levels) - 1)]; + + if (file) { + const char *p = strrchr(file, '/'); + if (p) + file = p + 1; + } + + fprintf(stderr, "%s %16s:%-5d ", level_str, file ? file : "", line); + while (level-- > 1) + fprintf(stderr, " "); vfprintf(stderr, fmt, arg); fprintf(stderr, "\n"); } diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index 7b49c8be6..63a32eafb 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -498,6 +498,7 @@ static void add_profiles(pa_card *impl) int n_profiles, n_ports, n_devices; uint32_t idx; const char *arr; + bool broken_ucm = false; n_devices = 0; pa_dynarray_init(&impl->out.devices, device_free); @@ -541,6 +542,9 @@ static void add_profiles(pa_card *impl) dev->ports, NULL); pa_dynarray_append(&ap->out.devices, dev); + + if (m->split && m->split->broken) + broken_ucm = true; } } @@ -564,6 +568,9 @@ static void add_profiles(pa_card *impl) dev->ports, NULL); pa_dynarray_append(&ap->out.devices, dev); + + if (m->split && m->split->broken) + broken_ucm = true; } } cp->n_devices = pa_dynarray_size(&ap->out.devices); @@ -571,6 +578,22 @@ static void add_profiles(pa_card *impl) pa_hashmap_put(impl->profiles, ap->name, cp); } + + /* Add a conspicuous notice if there are errors in the UCM profile */ + if (broken_ucm) { + const char *desc; + char *new_desc = NULL; + + desc = pa_proplist_gets(impl->proplist, PA_PROP_DEVICE_DESCRIPTION); + if (!desc) + desc = ""; + new_desc = spa_aprintf(_("%s [ALSA UCM error]"), desc); + pa_log_notice("Errors in ALSA UCM profile for card %s", desc); + if (new_desc) + pa_proplist_sets(impl->proplist, PA_PROP_DEVICE_DESCRIPTION, new_desc); + free(new_desc); + } + pa_dynarray_init(&impl->out.ports, NULL); n_ports = 0; PA_HASHMAP_FOREACH(dp, impl->ports, state) { diff --git a/spa/plugins/alsa/acp/alsa-ucm.c b/spa/plugins/alsa/acp/alsa-ucm.c index 9061a1f26..73466f004 100644 --- a/spa/plugins/alsa/acp/alsa-ucm.c +++ b/spa/plugins/alsa/acp/alsa-ucm.c @@ -353,6 +353,15 @@ static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd const char *device_name; int i; uint32_t hw_channels; + const char *pcm_name; + const char *rule_name; + + if (spa_streq(prefix, "Playback")) + pcm_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SINK); + else + pcm_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SOURCE); + if (!pcm_name) + pcm_name = ""; device_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_NAME); if (!device_name) @@ -372,16 +381,23 @@ static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd if (pa_atou(value, &idx) < 0) break; - if (idx >= hw_channels) - goto fail; + if (idx >= hw_channels) { + pa_log_notice("Error in ALSA UCM profile for %s (%s): %sChannel%d=%d >= %sChannels=%d", + pcm_name, device_name, prefix, i, idx, prefix, hw_channels); + split->broken = true; + } value = ucm_get_string(uc_mgr, "%sChannelPos%d/%s", prefix, i, device_name); - if (!value) + if (!value) { + rule_name = "ChannelPos"; goto fail; + } map = snd_pcm_chmap_parse_string(value); - if (!map) + if (!map) { + rule_name = "ChannelPos value"; goto fail; + } if (map->channels == 1) { pa_log_debug("Split %s channel %d -> device %s channel %d: %s (%d)", @@ -391,6 +407,7 @@ static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd free(map); } else { free(map); + rule_name = "channel map parsing"; goto fail; } } @@ -405,7 +422,7 @@ static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd return split; fail: - pa_log_warn("Invalid SplitPCM ALSA UCM rule for device %s", device_name); + pa_log_warn("Invalid SplitPCM ALSA UCM %s for device %s (%s)", rule_name, pcm_name, device_name); pa_xfree(split); return NULL; } @@ -2383,7 +2400,7 @@ static void mapping_init_eld(pa_alsa_mapping *m, snd_pcm_t *pcm) dev->eld_device = pcm_device; } -static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode) { +static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode, bool max_channels) { snd_pcm_t* pcm; pa_sample_spec try_ss = ucm->default_sample_spec; pa_channel_map try_map; @@ -2391,6 +2408,11 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, bool exact_channels = m->channel_map.channels > 0; if (!m->split) { + if (max_channels) { + errno = EINVAL; + return NULL; + } + if (exact_channels) { try_map = m->channel_map; try_ss.channels = try_map.channels; @@ -2402,8 +2424,8 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, return NULL; } - exact_channels = true; - try_ss.channels = m->split->hw_channels; + exact_channels = false; + try_ss.channels = max_channels ? PA_CHANNELS_MAX : m->split->hw_channels; pa_channel_map_init_extend(&try_map, try_ss.channels, PA_CHANNEL_MAP_AUX); } @@ -2416,15 +2438,40 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, &try_map, mode, &try_period_size, &try_buffer_size, 0, NULL, NULL, NULL, NULL, exact_channels); if (pcm) { - if (!exact_channels) + if (m->split) { + const char *mode_name = mode == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture"; + + if (try_map.channels < m->split->hw_channels) { + pa_logl((max_channels ? PA_LOG_NOTICE : PA_LOG_DEBUG), + "Error in ALSA UCM profile for %s (%s): %sChannels=%d > avail %d", + m->device_strings[0], m->name, mode_name, m->split->hw_channels, try_map.channels); + + /* Retry with max channel count, in case ALSA rounded down */ + if (!max_channels) { + pa_alsa_close(&pcm); + return mapping_open_pcm(ucm, m, mode, true); + } + + /* Just accept whatever we got... Some of the routings won't get connected + * anywhere */ + m->split->hw_channels = try_map.channels; + m->split->broken = true; + } else if (try_map.channels > m->split->hw_channels) { + pa_log_notice("Error in ALSA UCM profile for %s (%s): %sChannels=%d < avail %d", + m->device_strings[0], m->name, mode_name, m->split->hw_channels, try_map.channels); + m->split->hw_channels = try_map.channels; + m->split->broken = true; + } + } else if (!exact_channels) { m->channel_map = try_map; + } mapping_init_eld(m, pcm); } return pcm; } -static void pa_alsa_init_proplist_split_pcm(pa_idxset *mappings, pa_alsa_mapping *leader, pa_direction_t direction) +static void pa_alsa_init_split_pcm(pa_idxset *mappings, pa_alsa_mapping *leader, pa_direction_t direction) { pa_proplist *props = pa_proplist_new(); uint32_t idx; @@ -2445,6 +2492,9 @@ static void pa_alsa_init_proplist_split_pcm(pa_idxset *mappings, pa_alsa_mapping pa_proplist_update(m->output_proplist, PA_UPDATE_REPLACE, props); else pa_proplist_update(m->input_proplist, PA_UPDATE_REPLACE, props); + + /* Update HW channel count to match probed one */ + m->split->hw_channels = leader->split->hw_channels; } pa_proplist_free(props); @@ -2464,7 +2514,7 @@ static void profile_finalize_probing(pa_alsa_profile *p) { if (!m->split) pa_alsa_init_proplist_pcm(NULL, m->output_proplist, m->output_pcm); else - pa_alsa_init_proplist_split_pcm(p->output_mappings, m, PA_DIRECTION_OUTPUT); + pa_alsa_init_split_pcm(p->output_mappings, m, PA_DIRECTION_OUTPUT); pa_alsa_close(&m->output_pcm); } @@ -2479,7 +2529,7 @@ static void profile_finalize_probing(pa_alsa_profile *p) { if (!m->split) pa_alsa_init_proplist_pcm(NULL, m->input_proplist, m->input_pcm); else - pa_alsa_init_proplist_split_pcm(p->input_mappings, m, PA_DIRECTION_INPUT); + pa_alsa_init_split_pcm(p->input_mappings, m, PA_DIRECTION_INPUT); pa_alsa_close(&m->input_pcm); } @@ -2521,7 +2571,7 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * pa_log_info("Set ucm verb to %s", verb_name); if ((snd_use_case_set(ucm->ucm_mgr, "_verb", verb_name)) < 0) { - pa_log("Failed to set verb %s", verb_name); + pa_log("Profile '%s': failed to set verb %s", p->name, verb_name); p->supported = false; continue; } @@ -2536,8 +2586,10 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * if (m->split && !m->split->leader) continue; - m->output_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_PLAYBACK); + m->output_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_PLAYBACK, false); if (!m->output_pcm) { + pa_log_info("Profile '%s' mapping '%s': output PCM open failed", + p->name, m->name); p->supported = false; break; } @@ -2554,8 +2606,10 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * if (m->split && !m->split->leader) continue; - m->input_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_CAPTURE); + m->input_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_CAPTURE, false); if (!m->input_pcm) { + pa_log_info("Profile '%s' mapping '%s': input PCM open failed", + p->name, m->name); p->supported = false; break; } @@ -2564,6 +2618,7 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * if (!p->supported) { profile_finalize_probing(p); + pa_log_info("Profile %s not supported", p->name); continue; } diff --git a/spa/plugins/alsa/acp/alsa-ucm.h b/spa/plugins/alsa/acp/alsa-ucm.h index 74dbe8219..8e4c1a7d1 100644 --- a/spa/plugins/alsa/acp/alsa-ucm.h +++ b/spa/plugins/alsa/acp/alsa-ucm.h @@ -185,6 +185,7 @@ struct pa_alsa_ucm_split { int channels; int idx[PA_CHANNELS_MAX]; enum snd_pcm_chmap_position pos[PA_CHANNELS_MAX]; + bool broken; }; struct pa_alsa_ucm_device { diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 36834eaed..4c159e020 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -162,6 +162,11 @@ static int alsa_set_param(struct state *state, const char *k, const char *s) int fmt_change = 0; if (spa_streq(k, SPA_KEY_AUDIO_CHANNELS)) { state->default_channels = atoi(s); + if (state->default_channels > SPA_AUDIO_MAX_CHANNELS) { + spa_log_warn(state->log, "%p: %s: %s > %d, clamping", + state, k, s, SPA_AUDIO_MAX_CHANNELS); + state->default_channels = SPA_AUDIO_MAX_CHANNELS; + } fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_RATE)) { state->default_rate = atoi(s); @@ -1563,15 +1568,18 @@ static int add_channels(struct state *state, bool all, uint32_t index, uint32_t spa_log_debug(state->log, "channels (%d %d) default:%d all:%d", min, max, state->default_channels, all); - if (state->default_channels != 0 && !all) { - if (min < state->default_channels) - min = state->default_channels; - if (max > state->default_channels) - max = state->default_channels; - } min = SPA_MIN(min, SPA_AUDIO_MAX_CHANNELS); max = SPA_MIN(max, SPA_AUDIO_MAX_CHANNELS); + if (state->default_channels != 0 && !all) { + if (min > state->default_channels || + max < state->default_channels) + spa_log_warn(state->log, "given audio.channels %d out of range:%d-%d", + state->default_channels, min, max); + else + min = max = state->default_channels; + } + spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_channels, 0); if (state->props.use_chmap && (maps = snd_pcm_query_chmaps(hndl)) != NULL) { @@ -1842,10 +1850,12 @@ static int enum_iec958_formats(struct state *state, uint32_t index, uint32_t *ne spa_log_debug(state->log, "rate (%d %d)", rmin, rmax); if (state->default_rate != 0) { - if (rmin < state->default_rate) - rmin = state->default_rate; - if (rmax > state->default_rate) - rmax = state->default_rate; + if (rmin > state->default_rate || + rmax < state->default_rate) + spa_log_warn(state->log, "given audio.rate %d out of range:%d-%d", + state->default_rate, rmin, rmax); + else + rmin = rmax = state->default_rate; } spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_iec958Codec, 0); diff --git a/spa/plugins/alsa/alsa-seq-bridge.c b/spa/plugins/alsa/alsa-seq-bridge.c index f85b41e28..445808017 100644 --- a/spa/plugins/alsa/alsa-seq-bridge.c +++ b/spa/plugins/alsa/alsa-seq-bridge.c @@ -275,7 +275,7 @@ static void emit_port_info(struct seq_state *this, struct seq_port *port, bool f snprintf(alias, sizeof(alias), "%s:%s", client_name, port_name); clean_name(alias); - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_OBJECT_PATH, path); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, name); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_ALIAS, alias); @@ -529,8 +529,7 @@ impl_node_port_enum_params(void *object, int seq, param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), - SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int(1u<have_format = false; } else { struct spa_audio_info info = { 0 }; - uint32_t types; + uint32_t types = 0; if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return err; @@ -646,13 +644,12 @@ static int port_set_format(void *object, struct seq_port *port, if ((err = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, - SPA_FORMAT_CONTROL_types, SPA_POD_Int(&types))) < 0) + SPA_FORMAT_CONTROL_types, SPA_POD_OPT_Int(&types))) < 0) return err; - if (types != 1u << SPA_CONTROL_UMP) - return -EINVAL; port->current_format = info; port->have_format = true; + port->control_types = types; } port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; @@ -931,6 +928,7 @@ impl_init(const struct spa_handle_factory *factory, this->quantum_limit = 8192; this->min_pool_size = 500; this->max_pool_size = 2000; + this->ump = true; for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; @@ -949,6 +947,8 @@ impl_init(const struct spa_handle_factory *factory, spa_atou32(s, &this->min_pool_size, 0); } else if (spa_streq(k, "api.alsa.seq.max-pool")) { spa_atou32(s, &this->max_pool_size, 0); + } else if (spa_streq(k, "api.alsa.seq.ump")) { + this->ump = spa_atob(s); } } @@ -992,6 +992,7 @@ static const struct spa_dict_item info_items[] = { "["SPA_KEY_API_ALSA_DISABLE_LONGNAME"=] " "[ api.alsa.seq.min-pool=] " "[ api.alsa.seq.max-pool=]" + "[ api.alsa.seq.ump = ]" }, }; diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 2a4ebd2c4..75d9c604a 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -24,7 +24,7 @@ #define CHECK(s,msg,...) if ((res = (s)) < 0) { spa_log_error(state->log, msg ": %s", ##__VA_ARGS__, snd_strerror(res)); return res; } -static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_queue) +static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_queue, bool probe_ump) { struct props *props = &state->props; int res; @@ -37,13 +37,47 @@ static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_qu 0)) < 0) return res; - if ((res = snd_seq_set_client_midi_version(conn->hndl, SND_SEQ_CLIENT_UMP_MIDI_2_0)) < 0) { - snd_seq_close(conn->hndl); - spa_log_info(state->log, "%p: ALSA failed to enable UMP MIDI: %s", - state, snd_strerror(res)); - return res; + if (!state->ump) { + spa_log_info(state->log, "%p: ALSA UMP MIDI disabled", state); + return 0; } +#ifdef HAVE_ALSA_UMP + res = snd_seq_set_client_midi_version(conn->hndl, SND_SEQ_CLIENT_UMP_MIDI_2_0); + if (!res) { + snd_seq_client_info_t *info = NULL; + + /* Double check client version */ + res = snd_seq_client_info_malloc(&info); + if (!res) + res = snd_seq_get_client_info(conn->hndl, info); + if (!res) { + res = snd_seq_client_info_get_midi_version(info); + if (res == SND_SEQ_CLIENT_UMP_MIDI_2_0) + res = 0; + else + res = -EIO; + } + if (info) + snd_seq_client_info_free(info); + } +#else + res = -EOPNOTSUPP; +#endif + + if (res < 0) { + spa_log_lev(state->log, (probe_ump ? SPA_LOG_LEVEL_INFO : SPA_LOG_LEVEL_ERROR), + "%p: ALSA failed to enable UMP MIDI: %s", state, snd_strerror(res)); + if (!probe_ump) { + snd_seq_close(conn->hndl); + return res; /* either all are UMP or none are UMP */ + } + + state->ump = false; + } else { + spa_log_debug(state->log, "%p: ALSA UMP MIDI enabled", state); + state->ump = true; + } return 0; } @@ -172,7 +206,7 @@ static void init_ports(struct seq_state *state) } } -static void debug_event(struct seq_state *state, snd_seq_ump_event_t *ev) +static void debug_event(struct seq_state *state, snd_seq_event_t *ev) { if (SPA_LIKELY(!spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) return; @@ -196,21 +230,74 @@ static void debug_event(struct seq_state *state, snd_seq_ump_event_t *ev) ev->queue); } +#ifdef HAVE_ALSA_UMP +static void debug_ump_event(struct seq_state *state, snd_seq_ump_event_t *ev) +{ + if (SPA_LIKELY(!spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) + return; + + spa_log_trace(state->log, "event type:%d flags:0x%x", ev->type, ev->flags); + switch (ev->flags & SND_SEQ_TIME_STAMP_MASK) { + case SND_SEQ_TIME_STAMP_TICK: + spa_log_trace(state->log, " time: %d ticks", ev->time.tick); + break; + case SND_SEQ_TIME_STAMP_REAL: + spa_log_trace(state->log, " time = %d.%09d", + (int)ev->time.time.tv_sec, + (int)ev->time.time.tv_nsec); + break; + } + spa_log_trace(state->log, " source:%d.%d dest:%d.%d queue:%d", + ev->source.client, + ev->source.port, + ev->dest.client, + ev->dest.port, + ev->queue); +} +#endif + static void alsa_seq_on_sys(struct spa_source *source) { struct seq_state *state = source->data; - snd_seq_ump_event_t *ev; + const bool ump = state->ump; int res; - while (snd_seq_ump_event_input(state->sys.hndl, &ev) > 0) { - const snd_seq_addr_t *addr = &ev->data.addr; + while (1) { + const snd_seq_addr_t *addr; + snd_seq_event_type_t type; + + if (ump) { +#ifdef HAVE_ALSA_UMP + snd_seq_ump_event_t *ev; + + res = snd_seq_ump_event_input(state->sys.hndl, &ev); + if (res <= 0) + break; + + debug_ump_event(state, ev); + + addr = &ev->data.addr; + type = ev->type; +#else + spa_assert_not_reached(); +#endif + } else { + snd_seq_event_t *ev; + + res = snd_seq_event_input(state->sys.hndl, &ev); + if (res <= 0) + break; + + debug_event(state, ev); + + addr = &ev->data.addr; + type = ev->type; + } if (addr->client == state->event.addr.client) continue; - debug_event(state, ev); - - switch (ev->type) { + switch (type) { case SND_SEQ_EVENT_CLIENT_START: case SND_SEQ_EVENT_CLIENT_CHANGE: spa_log_info(state->log, "client add/change %d", addr->client); @@ -244,7 +331,7 @@ static void alsa_seq_on_sys(struct spa_source *source) break; default: spa_log_info(state->log, "unhandled event %d: %d:%d", - ev->type, addr->client, addr->port); + type, addr->client, addr->port); break; } @@ -269,8 +356,8 @@ int spa_alsa_seq_open(struct seq_state *state) spa_zero(reserve); for (i = 0; i < 16; i++) { - spa_log_debug(state->log, "close %d", i); - if ((res = seq_open(state, &reserve[i], false)) < 0) + spa_log_debug(state->log, "open %d", i); + if ((res = seq_open(state, &reserve[i], false, (i == 0))) < 0) break; } if (i >= 2) { @@ -316,7 +403,6 @@ int spa_alsa_seq_open(struct seq_state *state) state->sys.source.func = alsa_seq_on_sys; state->sys.source.data = state; - spa_loop_add_source(state->main_loop, &state->sys.source); /* increase event queue timer resolution */ snd_seq_queue_timer_alloca(&timer); @@ -361,6 +447,8 @@ int spa_alsa_seq_open(struct seq_state *state) state->timerfd = res; + spa_loop_add_source(state->main_loop, &state->sys.source); + state->opened = true; return 0; @@ -498,7 +586,8 @@ static int prepare_buffer(struct seq_state *state, struct seq_port *port) spa_pod_builder_init(&port->builder, port->buffer->buf->datas[0].data, port->buffer->buf->datas[0].maxsize); - spa_pod_builder_push_sequence(&port->builder, &port->frame, 0); + spa_pod_builder_push_sequence(&port->builder, &port->frame, 0); + port->ev_offset = SPA_IDX_INVALID; return 0; } @@ -529,21 +618,57 @@ static int process_recycle(struct seq_state *state) static int process_read(struct seq_state *state) { - snd_seq_ump_event_t *ev; struct seq_stream *stream = &state->streams[SPA_DIRECTION_OUTPUT]; + const bool ump = state->ump; uint32_t i; - uint32_t *data; - long size; - int res; + uint8_t midi1_data[MAX_EVENT_SIZE]; + uint32_t ump_data[MAX_EVENT_SIZE]; + int res = -1; /* copy all new midi events into their port buffers */ - while ((res = snd_seq_ump_event_input(state->event.hndl, &ev)) > 0) { - const snd_seq_addr_t *addr = &ev->source; + while (1) { + const snd_seq_addr_t *addr; struct seq_port *port; uint64_t ev_time, diff; - uint32_t offset; + uint32_t offset, ev_type; + void *event; + uint8_t *data_ptr; + size_t data_size = 0; + long size; + uint64_t ump_state = 0; + snd_seq_event_type_t SPA_UNUSED type; - debug_event(state, ev); + if (ump) { +#ifdef HAVE_ALSA_UMP + snd_seq_ump_event_t *ev; + + res = snd_seq_ump_event_input(state->event.hndl, &ev); + if (res <= 0) + break; + + debug_ump_event(state, ev); + + event = ev; + addr = &ev->source; + ev_time = SPA_TIMESPEC_TO_NSEC(&ev->time.time); + type = ev->type; +#else + spa_assert_not_reached(); +#endif + } else { + snd_seq_event_t *ev; + + res = snd_seq_event_input(state->event.hndl, &ev); + if (res <= 0) + break; + + debug_event(state, ev); + + event = ev; + addr = &ev->source; + ev_time = SPA_TIMESPEC_TO_NSEC(&ev->time.time); + type = ev->type; + } if ((port = find_port(state, stream, addr)) == NULL) { spa_log_debug(state->log, "unknown port %d.%d", @@ -554,17 +679,13 @@ static int process_read(struct seq_state *state) continue; if ((res = prepare_buffer(state, port)) < 0) { - spa_log_debug(state->log, "can't prepare buffer port:%p %d.%d: %s", + spa_log_warn(state->log, "can't prepare buffer port:%p %d.%d: %s", port, addr->client, addr->port, spa_strerror(res)); continue; } - data = (uint32_t*)&ev->ump[0]; - size = spa_ump_message_size(snd_ump_msg_hdr_type(ev->ump[0])) * 4; - /* queue_time is the estimated current time of the queue as calculated by * the DLL. Calculate the age of the event. */ - ev_time = SPA_TIMESPEC_TO_NSEC(&ev->time.time); if (state->queue_time > ev_time) diff = state->queue_time - ev_time; else @@ -577,17 +698,81 @@ static int process_read(struct seq_state *state) else offset = 0; - spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%ld port:%d.%d", - ev->type, ev_time, offset, size, addr->client, addr->port); + if (ump) { +#ifdef HAVE_ALSA_UMP + snd_seq_ump_event_t *ev = event; - spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_UMP); - spa_pod_builder_bytes(&port->builder, data, size); + data_ptr = (uint8_t*)&ev->ump[0]; + data_size = spa_ump_message_size(snd_ump_msg_hdr_type(ev->ump[0])) * 4; +#else + spa_assert_not_reached(); +#endif + } else { + snd_seq_event_t *ev = event; + + snd_midi_event_reset_decode(stream->codec); + if ((size = snd_midi_event_decode(stream->codec, midi1_data, sizeof(midi1_data), ev)) < 0) { + spa_log_warn(state->log, "decode failed: %s", snd_strerror(data_size)); + continue; + } + data_ptr = midi1_data; + data_size = size; + } + + ev_type = (port->control_types & (1u << SPA_CONTROL_UMP)) ? + SPA_CONTROL_UMP : SPA_CONTROL_Midi; + + spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%ld port:%d.%d", + type, ev_time, offset, data_size, addr->client, addr->port); + + if ((ump && ev_type == SPA_CONTROL_UMP) || + (!ump && ev_type == SPA_CONTROL_Midi)) { + /* no conversion needed */ + spa_pod_builder_control(&port->builder, offset, ev_type); + spa_pod_builder_bytes(&port->builder, data_ptr, data_size); + } + else if (ump) { + bool continued = port->ev_offset != SPA_IDX_INVALID; + + /* UMP -> MIDI */ + size = spa_ump_to_midi((uint32_t*)data_ptr, data_size, + midi1_data, sizeof(midi1_data)); + if (size < 0) + continue; + + if (!continued) { + spa_pod_builder_control(&port->builder, offset, ev_type); + port->ev_offset = spa_pod_builder_bytes_start(&port->builder); + if (midi1_data[0] == 0xf0) + continued = true; + } else { + if (midi1_data[size-1] == 0xf7) + continued = false; + } + spa_pod_builder_bytes_append(&port->builder, port->ev_offset, midi1_data, size); + + if (!continued) { + spa_pod_builder_bytes_end(&port->builder, port->ev_offset); + port->ev_offset = SPA_IDX_INVALID; + } + } else { + /* MIDI -> UMP */ + while (data_size > 0) { + size = spa_ump_from_midi(&data_ptr, &data_size, + ump_data, sizeof(ump_data), 0, &ump_state); + if (size <= 0) + break; + + spa_pod_builder_control(&port->builder, offset, ev_type); + spa_pod_builder_bytes(&port->builder, ump_data, size); + } + } /* make sure we can fit at least one control event of max size otherwise * we keep the event in the queue and try to copy it in the next cycle */ if (port->builder.state.offset + - sizeof(struct spa_pod_control) + - MAX_EVENT_SIZE > port->buffer->buf->datas[0].maxsize) + sizeof(struct spa_pod_control) + + MAX_EVENT_SIZE > port->buffer->buf->datas[0].maxsize) break; } if (res < 0 && res != -EAGAIN) @@ -604,6 +789,8 @@ static int process_read(struct seq_state *state) continue; if (prepare_buffer(state, port) >= 0) { + if (port->ev_offset != SPA_IDX_INVALID) + spa_pod_builder_bytes_end(&port->builder, port->ev_offset); spa_pod_builder_pop(&port->builder, &port->frame); port->buffer->buf->datas[0].chunk->offset = 0; @@ -651,6 +838,7 @@ static int process_read(struct seq_state *state) static int process_write(struct seq_state *state) { struct seq_stream *stream = &state->streams[SPA_DIRECTION_INPUT]; + const bool ump = state->ump; uint32_t i; int err, res = 0; @@ -661,9 +849,9 @@ static int process_write(struct seq_state *state) struct spa_pod_sequence *pod; struct spa_data *d; struct spa_pod_control *c; - snd_seq_ump_event_t ev; uint64_t out_time; snd_seq_real_time_t out_rt; + bool first = true; if (!port->valid || io == NULL) continue; @@ -689,31 +877,101 @@ static int process_write(struct seq_state *state) SPA_POD_SEQUENCE_FOREACH(pod, c) { size_t body_size; uint8_t *body; - - if (c->type != SPA_CONTROL_UMP) - continue; + int size; body = SPA_POD_BODY(&c->value); body_size = SPA_POD_BODY_SIZE(&c->value); - spa_zero(ev); - - memcpy(ev.ump, body, SPA_MIN(sizeof(ev.ump), (size_t)body_size)); - - snd_seq_ev_set_source(&ev, state->event.addr.port); - snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port); out_time = state->queue_time + NSEC_FROM_CLOCK(&state->rate, c->offset); - out_rt.tv_nsec = out_time % SPA_NSEC_PER_SEC; out_rt.tv_sec = out_time / SPA_NSEC_PER_SEC; - snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt); - spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%zd port:%d.%d", - ev.type, out_time, c->offset, body_size, port->addr.client, port->addr.port); + spa_log_trace_fp(state->log, "event time:%"PRIu64" offset:%d size:%zd port:%d.%d", + out_time, c->offset, body_size, port->addr.client, port->addr.port); - if ((err = snd_seq_ump_event_output(state->event.hndl, &ev)) < 0) { - spa_log_warn(state->log, "failed to output event: %s", - snd_strerror(err)); + if (ump) { +#ifdef HAVE_ALSA_UMP + uint8_t *ump_data; + uint32_t data[MAX_EVENT_SIZE]; + snd_seq_ump_event_t ev; + + do { + switch (c->type) { + case SPA_CONTROL_UMP: + ump_data = body; + size = body_size; + body_size = 0; + break; + case SPA_CONTROL_Midi: + size = spa_ump_from_midi(&body, &body_size, + data, sizeof(data), 0, &port->ump_state); + ump_data = (uint8_t*)data; + break; + default: + size = 0; + body_size = 0; + continue; + } + if (size <= 0) + break; + + snd_seq_ump_ev_clear(&ev); + snd_seq_ev_set_ump_data(&ev, ump_data, SPA_MIN(sizeof(ev.ump), (size_t)size)); + snd_seq_ev_set_source(&ev, state->event.addr.port); + snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port); + snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt); + + if ((err = snd_seq_ump_event_output(state->event.hndl, &ev)) < 0) { + spa_log_warn(state->log, "failed to output event: %s", + snd_strerror(err)); + } + } while (body_size > 0); +#else + spa_assert_not_reached(); +#endif + } else { + snd_seq_event_t ev; + uint8_t data[MAX_EVENT_SIZE]; + uint8_t *midi_data; + + switch (c->type) { + case SPA_CONTROL_UMP: + if ((size = spa_ump_to_midi((uint32_t *)body, body_size, data, sizeof(data))) <= 0) + continue; + midi_data = data; + break; + case SPA_CONTROL_Midi: + midi_data = body; + size = body_size; + break; + default: + continue; + } + + if (first) + snd_seq_ev_clear(&ev); + + if ((size = snd_midi_event_encode(stream->codec, midi_data, size, &ev)) < 0) { + spa_log_warn(state->log, "failed to encode event: %s", snd_strerror(size)); + snd_midi_event_reset_encode(stream->codec); + first = true; + continue; + } + first = false; + if (ev.type == SND_SEQ_EVENT_NONE) + /* this can happen when the event is not complete yet, like + * a sysex message and we need to encode some more data. */ + continue; + + snd_seq_ev_set_source(&ev, state->event.addr.port); + snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port); + snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt); + + if ((err = snd_seq_event_output(state->event.hndl, &ev)) < 0) { + spa_log_warn(state->log, "failed to output event: %s", + snd_strerror(err)); + } + first = true; } } } diff --git a/spa/plugins/alsa/alsa-seq.h b/spa/plugins/alsa/alsa-seq.h index 0f2c192c3..7e9fc4297 100644 --- a/spa/plugins/alsa/alsa-seq.h +++ b/spa/plugins/alsa/alsa-seq.h @@ -12,8 +12,12 @@ extern "C" { #include #include +#include "config.h" + #include +#ifdef HAVE_ALSA_UMP #include +#endif #include #include @@ -76,7 +80,10 @@ struct seq_port { struct buffer *buffer; struct spa_pod_builder builder; struct spa_pod_frame frame; + uint32_t ev_offset; + uint64_t ump_state; + uint32_t control_types; struct spa_audio_info current_format; unsigned int have_format:1; unsigned int valid:1; @@ -153,6 +160,7 @@ struct seq_state { unsigned int opened:1; unsigned int started:1; unsigned int following:1; + unsigned int ump:1; struct seq_stream streams[2]; diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 6e393bd0d..95ec85e0d 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -442,28 +442,32 @@ static int negotiate_buffers(struct impl *this) state = 0; param = NULL; - if ((res = node_port_enum_params_sync(this, this->follower, - this->direction, 0, + if ((res = node_port_enum_params_sync(this, this->target, + SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) < 0) { if (res == -ENOENT) param = NULL; else { - debug_params(this, this->follower, this->direction, 0, - SPA_PARAM_Buffers, param, "follower buffers", res); + debug_params(this, this->target, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_Buffers, param, "target buffers", res); return res; } } state = 0; - if ((res = node_port_enum_params_sync(this, this->target, - SPA_DIRECTION_REVERSE(this->direction), 0, + if ((res = node_port_enum_params_sync(this, this->follower, + this->direction, 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) != 1) { - debug_params(this, this->target, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_Buffers, param, "convert buffers", res); - return -ENOTSUP; + if (res == -ENOENT) + res = 0; + else { + debug_params(this, this->follower, this->direction, 0, + SPA_PARAM_Buffers, param, "follower buffers", res); + return res < 0 ? res : -ENOTSUP; + } } if (param == NULL) return -ENOTSUP; @@ -497,7 +501,7 @@ static int negotiate_buffers(struct impl *this) if (this->async) buffers = SPA_MAX(2u, buffers); - spa_log_debug(this->log, "%p: buffers:%d, blocks:%d, size:%d, stride:%d align:%d %d:%d", + spa_log_info(this->log, "%p: buffers:%d, blocks:%d, size:%d, stride:%d align:%d %d:%d", this, buffers, blocks, size, stride, align, follower_alloc, conv_alloc); align = SPA_MAX(align, this->max_align); @@ -941,27 +945,13 @@ static int negotiate_format(struct impl *this) spa_node_send_command(this->follower, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamBegin)); - /* first try the ideal converter format, which is likely passthrough */ - tstate = 0; - fres = node_port_enum_params_sync(this, this->target, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_EnumFormat, &tstate, - NULL, &format, &b); - if (fres == 1) { - fstate = 0; - res = node_port_enum_params_sync(this, this->follower, - this->direction, 0, - SPA_PARAM_EnumFormat, &fstate, - format, &format, &b); - if (res == 1) - goto found; - } - - /* then try something the follower can accept */ + /* The target has been negotiated on its other ports and so it can propose + * a passthrough format or an ideal conversion. We use the suggestions of the + * target to find the best follower format */ for (fstate = 0;;) { format = NULL; - res = node_port_enum_params_sync(this, this->follower, - this->direction, 0, + res = node_port_enum_params_sync(this, this->target, + SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_EnumFormat, &fstate, NULL, &format, &b); @@ -971,8 +961,8 @@ static int negotiate_format(struct impl *this) break; tstate = 0; - fres = node_port_enum_params_sync(this, this->target, - SPA_DIRECTION_REVERSE(this->direction), 0, + fres = node_port_enum_params_sync(this, this->follower, + this->direction, 0, SPA_PARAM_EnumFormat, &tstate, format, &format, &b); if (fres == 0 && res == 1) @@ -981,7 +971,6 @@ static int negotiate_format(struct impl *this) res = fres; break; } -found: if (format == NULL) { debug_params(this, this->follower, this->direction, 0, SPA_PARAM_EnumFormat, format, "follower format", res); @@ -997,6 +986,8 @@ found: format = merge_objects(this, &b, SPA_PARAM_Format, (struct spa_pod_object*)format, (struct spa_pod_object*)def); + if (format == NULL) + return -ENOSPC; spa_pod_fixate(format); @@ -1043,7 +1034,10 @@ static int impl_node_send_command(void *object, const struct spa_command *comman break; } - if ((res = spa_node_send_command(this->target, command)) < 0) { + res = spa_node_send_command(this->target, command); + if (res == -ENOTSUP && this->target != this->follower) + res = 0; + if (res < 0) { spa_log_error(this->log, "%p: can't send command %d: %s", this, SPA_NODE_COMMAND_ID(command), spa_strerror(res)); @@ -1612,13 +1606,13 @@ port_enum_formats_for_convert(struct impl *this, int seq, enum spa_direction dir uint32_t count = 0; struct spa_result_node_params result; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - result.id = id; result.next = start; next: result.index = result.next; + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + if (result.next < 0x100000) { /* Enumerate follower formats first, until we have enough or we run out */ if ((res = node_port_enum_params_sync(this, this->follower, direction, port_id, id, diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 47859e654..c36028bcd 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -210,7 +210,6 @@ struct stage_context { uint32_t src_idx; uint32_t dst_idx; uint32_t final_idx; - uint32_t n_datas; struct port *ctrlport; }; @@ -219,8 +218,6 @@ struct stage { bool passthrough; uint32_t in_idx; uint32_t out_idx; - uint32_t n_in; - uint32_t n_out; void *data; void (*run) (struct stage *stage, struct stage_context *c); }; @@ -365,7 +362,7 @@ static void emit_port_info(struct impl *this, struct port *port, bool full) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_IGNORE_LATENCY, "true"); } else if (PORT_IS_CONTROL(this, port->direction, port->id)) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "control"); - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"); } if (this->group_name[0] != '\0') items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, this->group_name); @@ -1003,13 +1000,13 @@ static int setup_filter_graph(struct impl *this, struct spa_filter_graph *graph) { int res; char rate_str[64]; - struct dir *in; + struct dir *dir; if (graph == NULL) return 0; - in = &this->dir[SPA_DIRECTION_INPUT]; - snprintf(rate_str, sizeof(rate_str), "%d", in->format.info.raw.rate); + dir = &this->dir[SPA_DIRECTION_REVERSE(this->direction)]; + snprintf(rate_str, sizeof(rate_str), "%d", dir->format.info.raw.rate); spa_filter_graph_deactivate(graph); res = spa_filter_graph_activate(graph, @@ -1217,7 +1214,7 @@ static int parse_prop_params(struct impl *this, struct spa_pod *params) while (true) { const char *name; struct spa_pod *pod; - char value[512]; + char value[4096]; if (spa_pod_parser_get_string(&prs, &name) < 0) break; @@ -1971,13 +1968,19 @@ static int setup_resample(struct impl *this) struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; int res; + uint32_t channels; + + if (this->direction == SPA_DIRECTION_INPUT) + channels = in->format.info.raw.channels; + else + channels = out->format.info.raw.channels; spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d", this, spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), - out->format.info.raw.channels, + channels, in->format.info.raw.rate, spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), - out->format.info.raw.channels, + channels, out->format.info.raw.rate); if (this->props.resample_disabled && !this->resample_peaks && @@ -1987,7 +1990,7 @@ static int setup_resample(struct impl *this) if (this->resample.free) resample_free(&this->resample); - this->resample.channels = out->format.info.raw.channels; + this->resample.channels = channels; this->resample.i_rate = in->format.info.raw.rate; this->resample.o_rate = out->format.info.raw.rate; this->resample.log = this->log; @@ -3217,8 +3220,6 @@ static void add_wav_stage(struct impl *impl, struct stage_context *ctx) s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = ctx->src_idx; - s->n_in = ctx->n_datas; - s->n_out = ctx->n_datas; s->data = NULL; s->run = run_wav_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); @@ -3230,7 +3231,7 @@ static void run_dst_remap_stage(struct stage *s, struct stage_context *c) struct impl *impl = s->impl; struct dir *dir = &impl->dir[SPA_DIRECTION_OUTPUT]; uint32_t i; - for (i = 0; i < s->n_in; i++) { + for (i = 0; i < dir->conv.n_channels; i++) { c->datas[s->out_idx][i] = c->datas[s->in_idx][dir->remap[i]]; spa_log_trace_fp(impl->log, "%p: output remap %d -> %d", impl, i, dir->remap[i]); } @@ -3242,8 +3243,6 @@ static void add_dst_remap_stage(struct impl *impl, struct stage_context *ctx) s->passthrough = false; s->in_idx = ctx->dst_idx; s->out_idx = CTX_DATA_REMAP_DST; - s->n_in = ctx->n_datas; - s->n_out = ctx->n_datas; s->data = NULL; s->run = run_dst_remap_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); @@ -3269,8 +3268,6 @@ static void add_src_remap_stage(struct impl *impl, struct stage_context *ctx) s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = CTX_DATA_REMAP_SRC; - s->n_in = ctx->n_datas; - s->n_out = ctx->n_datas; s->data = NULL; s->run = run_src_remap_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); @@ -3304,8 +3301,6 @@ static void add_src_convert_stage(struct impl *impl, struct stage_context *ctx) s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = ctx->dst_idx; - s->n_in = ctx->n_datas; - s->n_out = ctx->n_datas; s->data = NULL; s->run = run_src_convert_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); @@ -3334,8 +3329,6 @@ static void add_resample_stage(struct impl *impl, struct stage_context *ctx) s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = ctx->dst_idx; - s->n_in = ctx->n_datas; - s->n_out = ctx->n_datas; s->data = NULL; s->run = run_resample_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); @@ -3383,8 +3376,6 @@ static void add_filter_stage(struct impl *impl, uint32_t i, struct filter_graph s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = ctx->dst_idx; - s->n_in = ctx->n_datas; - s->n_out = ctx->n_datas; s->data = fg; s->run = run_filter_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); @@ -3399,8 +3390,6 @@ static void add_channelmix_stage(struct impl *impl, struct stage_context *ctx) s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = ctx->dst_idx; - s->n_in = ctx->n_datas; - s->n_out = ctx->n_datas; s->data = NULL; s->run = run_channelmix_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); @@ -3434,8 +3423,6 @@ static void add_dst_convert_stage(struct impl *impl, struct stage_context *ctx) s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = ctx->final_idx; - s->n_in = ctx->n_datas; - s->n_out = ctx->n_datas; s->data = NULL; s->run = run_dst_convert_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); @@ -3828,14 +3815,14 @@ static int impl_node_process(void *object) ctx.in_samples = n_samples; ctx.n_samples = n_samples; ctx.n_out = n_out; - ctx.src_idx = CTX_DATA_SRC; - ctx.dst_idx = CTX_DATA_DST; - ctx.final_idx = CTX_DATA_DST; - ctx.n_datas = dir->conv.n_channels; ctx.ctrlport = ctrlport; - if (this->recalc) + if (SPA_UNLIKELY(this->recalc)) { + ctx.src_idx = CTX_DATA_SRC; + ctx.dst_idx = CTX_DATA_DST; + ctx.final_idx = CTX_DATA_DST; recalc_stages(this, &ctx); + } for (i = 0; i < this->n_stages; i++) { struct stage *s = &this->stages[i]; diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 359696903..8d5354d31 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -1406,18 +1406,44 @@ static void hfp_hf_hangup(void *data, enum spa_bt_telephony_error *err, uint8_t struct rfcomm_call_data *call_data = data; struct rfcomm *rfcomm = call_data->rfcomm; struct impl *backend = rfcomm->backend; + struct spa_bt_telephony_call *call, *tcall; + bool found_held = false; + bool hfp_hf_in_progress = false; char reply[20]; bool res; + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_HELD) + found_held = true; + } + switch (call_data->call->state) { case CALL_STATE_ACTIVE: case CALL_STATE_DIALING: case CALL_STATE_ALERTING: case CALL_STATE_INCOMING: - rfcomm_send_cmd(rfcomm, "AT+CHUP"); + if (found_held) { + if (!rfcomm->chld_supported) { + *err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; + return; + } else if (rfcomm->hfp_hf_in_progress) { + *err = BT_TELEPHONY_ERROR_IN_PROGRESS; + return; + } + + rfcomm_send_cmd(rfcomm, "AT+CHLD=1"); + hfp_hf_in_progress = true; + } else { + rfcomm_send_cmd(rfcomm, "AT+CHUP"); + } break; case CALL_STATE_WAITING: + if (rfcomm->hfp_hf_in_progress) { + *err = BT_TELEPHONY_ERROR_IN_PROGRESS; + return; + } rfcomm_send_cmd(rfcomm, "AT+CHLD=0"); + hfp_hf_in_progress = true; break; default: spa_log_info(backend->log, "Call not incoming, waiting or active: skip hangup"); @@ -1435,6 +1461,24 @@ static void hfp_hf_hangup(void *data, enum spa_bt_telephony_error *err, uint8_t return; } + if (hfp_hf_in_progress) { + if (call_data->call->state != CALL_STATE_WAITING) { + spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_ACTIVE) { + call->state = CALL_STATE_DISCONNECTED; + telephony_call_notify_updated_props(call); + telephony_call_destroy(call); + } + } + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_HELD) { + call->state = CALL_STATE_ACTIVE; + telephony_call_notify_updated_props(call); + } + } + } + rfcomm->hfp_hf_in_progress = true; + } *err = BT_TELEPHONY_ERROR_NONE; } @@ -2286,6 +2330,26 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) } SPA_FALLTHROUGH; case hfp_hf_chld: + rfcomm->slc_configured = true; + + if (!rfcomm->codec_negotiation_supported) { + if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) { + // TODO: We should manage the missing transport + } else { + spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); + } + } + + rfcomm->telephony_ag = telephony_ag_new(backend->telephony, 0); + rfcomm->telephony_ag->address = strdup(rfcomm->device->address); + telephony_ag_set_callbacks(rfcomm->telephony_ag, + &telephony_ag_callbacks, rfcomm); + if (rfcomm->transport) { + rfcomm->telephony_ag->transport.codec = rfcomm->transport->codec; + rfcomm->telephony_ag->transport.state = rfcomm->transport->state; + } + telephony_ag_register(rfcomm->telephony_ag); + rfcomm_send_cmd(rfcomm, "AT+CLIP=1"); rfcomm->hf_state = hfp_hf_clip; break; @@ -2312,25 +2376,6 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) SPA_FALLTHROUGH; case hfp_hf_nrec: rfcomm->hf_state = hfp_hf_slc1; - rfcomm->slc_configured = true; - - if (!rfcomm->codec_negotiation_supported) { - if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) { - // TODO: We should manage the missing transport - } else { - spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); - } - } - - rfcomm->telephony_ag = telephony_ag_new(backend->telephony, 0); - rfcomm->telephony_ag->address = strdup(rfcomm->device->address); - telephony_ag_set_callbacks(rfcomm->telephony_ag, - &telephony_ag_callbacks, rfcomm); - if (rfcomm->transport) { - rfcomm->telephony_ag->transport.codec = rfcomm->transport->codec; - rfcomm->telephony_ag->transport.state = rfcomm->transport->state; - } - telephony_ag_register(rfcomm->telephony_ag); if (rfcomm->hfp_hf_clcc) { rfcomm_send_cmd(rfcomm, "AT+CLCC"); @@ -3318,6 +3363,7 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag } else if (profile == SPA_BT_PROFILE_HFP_AG) { /* Start SLC connection */ unsigned int hf_features = SPA_BT_HFP_HF_FEATURE_CLIP | SPA_BT_HFP_HF_FEATURE_3WAY | + SPA_BT_HFP_HF_FEATURE_ECNR | SPA_BT_HFP_HF_FEATURE_ENHANCED_CALL_STATUS | SPA_BT_HFP_HF_FEATURE_ESCO_S4; bool has_msbc = device_supports_codec(backend, rfcomm->device, HFP_AUDIO_CODEC_MSBC); diff --git a/spa/plugins/bluez5/midi-node.c b/spa/plugins/bluez5/midi-node.c index b5fd179ea..cfba2bfcf 100644 --- a/spa/plugins/bluez5/midi-node.c +++ b/spa/plugins/bluez5/midi-node.c @@ -2024,13 +2024,13 @@ impl_init(const struct spa_handle_factory *factory, for (i = 0; i < N_PORTS; ++i) { struct port *port = &this->ports[i]; static const struct spa_dict_item in_port_items[] = { - SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP"), + SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "in"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_ALIAS, "in"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, "group.0"), }; static const struct spa_dict_item out_port_items[] = { - SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP"), + SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "out"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_ALIAS, "out"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, "group.0"), diff --git a/spa/plugins/control/mixer.c b/spa/plugins/control/mixer.c index 439bea76b..cfe3c394b 100644 --- a/spa/plugins/control/mixer.c +++ b/spa/plugins/control/mixer.c @@ -298,7 +298,8 @@ static int port_enum_formats(void *object, struct port *port, *param = spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), + SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int(SPA_ID_INVALID)); break; default: return 0; @@ -671,6 +672,14 @@ static inline int event_sort(struct spa_pod_control *a, struct spa_pod_control * } } +static inline bool control_needs_conversion(struct port *port, uint32_t type) +{ + /* we only converter between midi and UMP and only when the port + * does not support the current type */ + return (type == SPA_CONTROL_Midi || type == SPA_CONTROL_UMP) && + port->types && (port->types & (1u << type)) == 0; +} + static int impl_node_process(void *object) { struct impl *this = object; @@ -782,7 +791,7 @@ static int impl_node_process(void *object) if (next == NULL) break; - if (outport->types && (outport->types & (1u << next->type)) == 0) { + if (control_needs_conversion(outport, next->type)) { uint8_t *data = SPA_POD_BODY(&next->value); size_t size = SPA_POD_BODY_SIZE(&next->value); diff --git a/spa/plugins/filter-graph/ebur128_plugin.c b/spa/plugins/filter-graph/ebur128_plugin.c index 22bf5f513..a7aeeef99 100644 --- a/spa/plugins/filter-graph/ebur128_plugin.c +++ b/spa/plugins/filter-graph/ebur128_plugin.c @@ -235,6 +235,8 @@ static void ebur128_cleanup(void * Instance) static void ebur128_activate(void * Instance) { struct ebur128_impl *impl = Instance; + unsigned long max_window; + int major, minor, patch; int mode = 0, i; int modes[] = { EBUR128_MODE_M, @@ -264,12 +266,17 @@ static void ebur128_activate(void * Instance) mode |= modes[i]; } + ebur128_get_version(&major, &minor, &patch); + max_window = impl->max_window; + if (major == 1 && minor == 2 && (patch == 5 || patch == 6)) + max_window = (max_window + 999) / 1000; + for (i = 0; i < 7; i++) { impl->st[i] = ebur128_init(1, impl->rate, mode); if (impl->st[i]) { ebur128_set_channel(impl->st[i], i, channels[i]); ebur128_set_max_history(impl->st[i], impl->max_history); - ebur128_set_max_window(impl->st[i], impl->max_window); + ebur128_set_max_window(impl->st[i], max_window); } } } @@ -349,7 +356,7 @@ static struct spa_fga_port ebur128_ports[] = { .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, { .index = PORT_OUT_SHORTTERM, - .name = "Shorttem LUFS", + .name = "Shortterm LUFS", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, { .index = PORT_OUT_GLOBAL, diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index bc9ff154a..91afd62a5 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -256,9 +256,8 @@ static int impl_process(void *object, if (out[i] == NULL) continue; - port = i < graph->n_output ? &graph->output[i] : NULL; - - if (port && port->desc) + port = &graph->output[i]; + if (port->desc) port->desc->connect_port(*port->hndl, port->port, out[i]); else memset(out[i], 0, n_samples * sizeof(float)); diff --git a/spa/plugins/filter-graph/lv2_plugin.c b/spa/plugins/filter-graph/lv2_plugin.c index a2af22d6a..63c83caaa 100644 --- a/spa/plugins/filter-graph/lv2_plugin.c +++ b/spa/plugins/filter-graph/lv2_plugin.c @@ -234,7 +234,7 @@ struct instance { LV2_Options_Option options[6]; LV2_Feature options_feature; - const LV2_Feature *features[7]; + const LV2_Feature *features[8]; const LV2_Worker_Interface *work_iface; @@ -328,9 +328,11 @@ static void *lv2_instantiate(const struct spa_fga_plugin *plugin, const struct s c->atom_Float, &fsample_rate }; i->options[5] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, NULL }; - i->options_feature.URI = LV2_OPTIONS__options; - i->options_feature.data = i->options; - i->features[n_features++] = &i->options_feature; + i->options_feature.URI = LV2_OPTIONS__options; + i->options_feature.data = i->options; + i->features[n_features++] = &i->options_feature; + i->features[n_features++] = NULL; + spa_assert(n_features <= SPA_N_ELEMENTS(i->features)); i->instance = lilv_plugin_instantiate(p->p, SampleRate, i->features); if (i->instance == NULL) { diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index f570499d1..0dffbc92b 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -273,7 +273,7 @@ next: default: return spa_libcamera_enum_controls(impl, GET_OUT_PORT(impl, 0), - seq, result.index - 2, num, filter); + seq, result.index, 2, num, filter); } break; } @@ -535,7 +535,7 @@ next: switch (id) { case SPA_PARAM_PropInfo: - return spa_libcamera_enum_controls(impl, port, seq, start, num, filter); + return spa_libcamera_enum_controls(impl, port, seq, start, 0, num, filter); case SPA_PARAM_EnumFormat: return spa_libcamera_enum_format(impl, port, seq, start, num, filter); diff --git a/spa/plugins/libcamera/libcamera-utils.cpp b/spa/plugins/libcamera/libcamera-utils.cpp index 92185c4b0..bbd351afe 100644 --- a/spa/plugins/libcamera/libcamera-utils.cpp +++ b/spa/plugins/libcamera/libcamera-utils.cpp @@ -495,7 +495,7 @@ static uint32_t prop_id_to_control(struct impl *impl, uint32_t prop_id) static int spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq, - uint32_t start, uint32_t num, + uint32_t start, uint32_t offset, uint32_t num, const struct spa_pod *filter) { const ControlInfoMap &info = impl->camera->controls(); @@ -513,7 +513,7 @@ spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq, result.next = start; auto it = info.begin(); - for (skip = result.next; skip; skip--) + for (skip = result.next - offset; skip; skip--) it++; if (false) { diff --git a/spa/plugins/v4l2/v4l2-source.c b/spa/plugins/v4l2/v4l2-source.c index f19dffde8..b06dda994 100644 --- a/spa/plugins/v4l2/v4l2-source.c +++ b/spa/plugins/v4l2/v4l2-source.c @@ -675,7 +675,7 @@ static int port_set_format(struct impl *this, struct port *port, const struct spa_pod *format) { struct spa_video_info info; - int res; + int res = 0; spa_zero(info); @@ -755,7 +755,7 @@ static int port_set_format(struct impl *this, struct port *port, emit_port_info(this, port, false); emit_node_info(this, false); - return 0; + return res; } static int impl_node_port_set_param(void *object, diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c index be38ab32d..887094eff 100644 --- a/spa/plugins/v4l2/v4l2-utils.c +++ b/spa/plugins/v4l2/v4l2-utils.c @@ -1873,7 +1873,12 @@ static int spa_v4l2_stream_on(struct impl *this) spa_log_debug(this->log, "starting"); - port->first_buffer = true; + if (port->current_format.media_subtype == SPA_MEDIA_SUBTYPE_raw || + port->current_format.media_subtype == SPA_MEDIA_SUBTYPE_mjpg || + port->current_format.media_subtype == SPA_MEDIA_SUBTYPE_jpeg) + port->first_buffer = true; + else + port->first_buffer = false; mmap_read(this); type = V4L2_BUF_TYPE_VIDEO_CAPTURE; diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 8c1c8de77..5111b9380 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -403,6 +403,7 @@ static int negotiate_buffers(struct impl *this) uint32_t i, size, buffers, blocks, align, flags, stride = 0; uint32_t *aligns; struct spa_data *datas; + struct spa_meta metas[1]; uint64_t follower_flags, conv_flags; spa_log_debug(this->log, "%p: n_buffers:%d", this, this->n_buffers); @@ -415,28 +416,32 @@ static int negotiate_buffers(struct impl *this) state = 0; param = NULL; - if ((res = spa_node_port_enum_params_sync(this->follower, - this->direction, 0, + if ((res = spa_node_port_enum_params_sync(this->target, + SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) < 0) { if (res == -ENOENT) param = NULL; else { - debug_params(this, this->follower, this->direction, 0, - SPA_PARAM_Buffers, param, "follower buffers", res); + debug_params(this, this->target, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_Buffers, param, "target buffers", res); return res; } } state = 0; - if ((res = spa_node_port_enum_params_sync(this->target, - SPA_DIRECTION_REVERSE(this->direction), 0, + if ((res = spa_node_port_enum_params_sync(this->follower, + this->direction, 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) != 1) { - debug_params(this, this->target, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_Buffers, param, "convert buffers", res); - return -ENOTSUP; + if (res == -ENOENT) + res = 0; + else { + debug_params(this, this->follower, this->direction, 0, + SPA_PARAM_Buffers, param, "follower buffers", res); + return res < 0 ? res : -ENOTSUP; + } } if (param == NULL) return -ENOTSUP; @@ -470,7 +475,7 @@ static int negotiate_buffers(struct impl *this) if (this->async) buffers = SPA_MAX(2u, buffers); - spa_log_debug(this->log, "%p: buffers:%d, blocks:%d, size:%d, stride:%d align:%d %d:%d", + spa_log_info(this->log, "%p: buffers:%d, blocks:%d, size:%d, stride:%d align:%d %d:%d", this, buffers, blocks, size, stride, align, follower_alloc, conv_alloc); align = SPA_MAX(align, this->max_align); @@ -484,9 +489,11 @@ static int negotiate_buffers(struct impl *this) datas[i].maxsize = size; aligns[i] = align; } + metas[0].type = SPA_META_Header; + metas[0].size = sizeof(struct spa_meta_header); free(this->buffers); - this->buffers = spa_buffer_alloc_array(buffers, flags, 0, NULL, blocks, datas, aligns); + this->buffers = spa_buffer_alloc_array(buffers, flags, 1, metas, blocks, datas, aligns); if (this->buffers == NULL) return -errno; this->n_buffers = buffers; @@ -905,27 +912,13 @@ static int negotiate_format(struct impl *this) spa_node_send_command(this->follower, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamBegin)); - /* first try the ideal converter format, which is likely passthrough */ - tstate = 0; - fres = spa_node_port_enum_params_sync(this->target, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_EnumFormat, &tstate, - NULL, &format, &b); - if (fres == 1) { - fstate = 0; - res = spa_node_port_enum_params_sync(this->follower, - this->direction, 0, - SPA_PARAM_EnumFormat, &fstate, - format, &format, &b); - if (res == 1) - goto found; - } - - /* then try something the follower can accept */ + /* The target has been negotiated on its other ports and so it can propose + * a passthrough format or an ideal conversion. We use the suggestions of the + * target to find the best follower format */ for (fstate = 0;;) { format = NULL; - res = spa_node_port_enum_params_sync(this->follower, - this->direction, 0, + res = spa_node_port_enum_params_sync(this->target, + SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_EnumFormat, &fstate, NULL, &format, &b); @@ -935,8 +928,8 @@ static int negotiate_format(struct impl *this) break; tstate = 0; - fres = spa_node_port_enum_params_sync(this->target, - SPA_DIRECTION_REVERSE(this->direction), 0, + fres = spa_node_port_enum_params_sync(this->follower, + this->direction, 0, SPA_PARAM_EnumFormat, &tstate, format, &format, &b); if (fres == 0 && res == 1) @@ -945,7 +938,6 @@ static int negotiate_format(struct impl *this) res = fres; break; } -found: if (format == NULL) { debug_params(this, this->follower, this->direction, 0, SPA_PARAM_EnumFormat, format, "follower format", res); @@ -961,6 +953,8 @@ found: format = merge_objects(this, &b, SPA_PARAM_Format, (struct spa_pod_object*)format, (struct spa_pod_object*)def); + if (format == NULL) + return -ENOSPC; spa_pod_fixate(format); @@ -1007,7 +1001,10 @@ static int impl_node_send_command(void *object, const struct spa_command *comman break; } - if ((res = spa_node_send_command(this->target, command)) < 0) { + res = spa_node_send_command(this->target, command); + if (res == -ENOTSUP && this->target != this->follower) + res = 0; + if (res < 0) { spa_log_error(this->log, "%p: can't send command %d: %s", this, SPA_NODE_COMMAND_ID(command), spa_strerror(res)); diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 955bfa93a..3da0e5677 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -228,7 +228,7 @@ static void emit_port_info(struct impl *this, struct port *port, bool full) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_IGNORE_LATENCY, "true"); } else if (PORT_IS_CONTROL(this, port->direction, port->id)) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "control"); - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"); } if (this->group_name[0] != '\0') items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, this->group_name); @@ -1812,6 +1812,7 @@ static int impl_node_process(void *object) } sbuf = &in_port->buffers[input->buffer_id]; + input->status = SPA_STATUS_NEED_DATA; if ((dbuf = peek_buffer(this, out_port)) == NULL) { spa_log_error(this->log, "%p: out of buffers", this); @@ -1902,8 +1903,6 @@ static int impl_node_process(void *object) output->buffer_id = dbuf->id; output->status = SPA_STATUS_HAVE_DATA; - input->status = SPA_STATUS_NEED_DATA; - return SPA_STATUS_HAVE_DATA; } diff --git a/src/daemon/pipewire.conf.avail/50-raop.conf.in b/src/daemon/pipewire.conf.avail/50-raop.conf.in new file mode 100644 index 000000000..088f4a597 --- /dev/null +++ b/src/daemon/pipewire.conf.avail/50-raop.conf.in @@ -0,0 +1,4 @@ +context.modules = [ + # Use mDNS to detect and load module-raop-sink + { name = libpipewire-module-raop-discover } +] diff --git a/src/daemon/pipewire.conf.avail/meson.build b/src/daemon/pipewire.conf.avail/meson.build index 8b071ba42..b936a217b 100644 --- a/src/daemon/pipewire.conf.avail/meson.build +++ b/src/daemon/pipewire.conf.avail/meson.build @@ -1,6 +1,7 @@ conf_files = [ '10-rates.conf', '20-upmix.conf', + '50-raop.conf', ] foreach c : conf_files diff --git a/src/daemon/systemd/user/meson.build b/src/daemon/systemd/user/meson.build index 10227629d..a96409f2b 100644 --- a/src/daemon/systemd/user/meson.build +++ b/src/daemon/systemd/user/meson.build @@ -11,6 +11,12 @@ systemd_config = configuration_data() systemd_config.set('PW_BINARY', pipewire_bindir / 'pipewire') systemd_config.set('PW_PULSE_BINARY', pipewire_bindir / 'pipewire-pulse') +pw_service_reqs = '' +if get_option('dbus').enabled() + pw_service_reqs += 'dbus.service ' +endif +systemd_config.set('PW_SERVICE_REQS', pw_service_reqs) + configure_file(input : 'pipewire.service.in', output : 'pipewire.service', configuration : systemd_config, diff --git a/src/daemon/systemd/user/pipewire.service.in b/src/daemon/systemd/user/pipewire.service.in index b9b137351..f0c8d9cd5 100644 --- a/src/daemon/systemd/user/pipewire.service.in +++ b/src/daemon/systemd/user/pipewire.service.in @@ -13,7 +13,8 @@ Description=PipeWire Multimedia Service # # After=pipewire.socket is not needed, as it is already implicit in the # socket-service relationship, see systemd.socket(5). -Requires=pipewire.socket +Requires=pipewire.socket @PW_SERVICE_REQS@ +ConditionUser=!root [Service] LockPersonality=yes diff --git a/src/daemon/systemd/user/pipewire.socket b/src/daemon/systemd/user/pipewire.socket index 16e23a7b6..890342abb 100644 --- a/src/daemon/systemd/user/pipewire.socket +++ b/src/daemon/systemd/user/pipewire.socket @@ -1,5 +1,6 @@ [Unit] Description=PipeWire Multimedia System Sockets +ConditionUser=!root [Socket] Priority=6 diff --git a/src/examples/midi-src.c b/src/examples/midi-src.c index edcaa0f08..12f86d6f2 100644 --- a/src/examples/midi-src.c +++ b/src/examples/midi-src.c @@ -213,7 +213,7 @@ int main(int argc, char *argv[]) PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), pw_properties_new( - PW_KEY_FORMAT_DSP, "32 bit raw UMP", + PW_KEY_FORMAT_DSP, "8 bit raw midi", PW_KEY_PORT_NAME, "output", NULL), NULL, 0); diff --git a/src/gst/gstpipewiredeviceprovider.c b/src/gst/gstpipewiredeviceprovider.c index c9d0d7ad7..5bdd09b7d 100644 --- a/src/gst/gstpipewiredeviceprovider.c +++ b/src/gst/gstpipewiredeviceprovider.c @@ -327,6 +327,7 @@ static void do_add_nodes(GstPipeWireDeviceProvider *self) gst_object_ref_sink (device), compare_device_session_priority); } else { + gst_object_ref (device); gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), device); } } @@ -484,7 +485,8 @@ destroy_node (void *data) } if (nd->dev != NULL) { - gst_device_provider_device_remove (provider, GST_DEVICE (nd->dev)); + gst_device_provider_device_remove (provider, nd->dev); + gst_clear_object (&nd->dev); } if (nd->caps) gst_caps_unref(nd->caps); diff --git a/src/gst/gstpipewireformat.c b/src/gst/gstpipewireformat.c index 24115325e..903d216e2 100644 --- a/src/gst/gstpipewireformat.c +++ b/src/gst/gstpipewireformat.c @@ -1065,8 +1065,19 @@ handle_rect_prop (const struct spa_pod_prop *prop, const char *width, const char { if (n_items < 3) return; - gst_caps_set_simple (res, width, GST_TYPE_INT_RANGE, rect[1].width, rect[2].width, - height, GST_TYPE_INT_RANGE, rect[1].height, rect[2].height, NULL); + + if (rect[1].width == rect[2].width && + rect[1].height == rect[2].height) { + gst_caps_set_simple (res, + width, G_TYPE_INT, rect[1].width, + height, G_TYPE_INT, rect[1].height, + NULL); + } else { + gst_caps_set_simple (res, + width, GST_TYPE_INT_RANGE, rect[1].width, rect[2].width, + height, GST_TYPE_INT_RANGE, rect[1].height, rect[2].height, + NULL); + } break; } case SPA_CHOICE_Enum: @@ -1117,8 +1128,17 @@ handle_fraction_prop (const struct spa_pod_prop *prop, const char *key, GstCaps { if (n_items < 3) return; - gst_caps_set_simple (res, key, GST_TYPE_FRACTION_RANGE, fract[1].num, fract[1].denom, - fract[2].num, fract[2].denom, NULL); + + if (fract[1].num == fract[2].num && + fract[1].denom == fract[2].denom) { + gst_caps_set_simple (res, key, GST_TYPE_FRACTION, + fract[1].num, fract[1].denom, NULL); + } else { + gst_caps_set_simple (res, key, GST_TYPE_FRACTION_RANGE, + fract[1].num, fract[1].denom, + fract[2].num, fract[2].denom, + NULL); + } break; } case SPA_CHOICE_Enum: diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c index aa9d94b99..b79ae6a14 100644 --- a/src/gst/gstpipewiresink.c +++ b/src/gst/gstpipewiresink.c @@ -305,6 +305,7 @@ gst_pipewire_sink_update_params (GstPipeWireSink *sink) struct spa_pod_builder b = { NULL }; uint8_t buffer[1024]; struct spa_pod_frame f; + guint n_params = 0; config = gst_buffer_pool_get_config (GST_BUFFER_POOL (pool)); gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers, &max_buffers); @@ -325,20 +326,22 @@ gst_pipewire_sink_update_params (GstPipeWireSink *sink) (1<is_video) { + port_params[n_params++] = spa_pod_builder_add_object (&b, + SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop), + SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_region))); + } pw_thread_loop_lock (sink->stream->core->loop); - pw_stream_update_params (sink->stream->pwstream, port_params, 3); + pw_stream_update_params (sink->stream->pwstream, port_params, n_params); pw_thread_loop_unlock (sink->stream->core->loop); } diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 1cd049418..994534c60 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -41,7 +41,7 @@ GST_DEBUG_CATEGORY_STATIC (pipewire_src_debug); #define GST_CAT_DEFAULT pipewire_src_debug #define DEFAULT_ALWAYS_COPY false -#define DEFAULT_MIN_BUFFERS 8 +#define DEFAULT_MIN_BUFFERS 1 #define DEFAULT_MAX_BUFFERS INT32_MAX #define DEFAULT_RESEND_LAST false #define DEFAULT_KEEPALIVE_TIME 0 @@ -1091,14 +1091,34 @@ handle_format_change (GstPipeWireSrc *pwsrc, } pw_peer_caps = gst_caps_from_format (param); + if (pw_peer_caps && pwsrc->possible_caps) { + GST_DEBUG_OBJECT (pwsrc, "peer caps %" GST_PTR_FORMAT, pw_peer_caps); + GST_DEBUG_OBJECT (pwsrc, "possible caps %" GST_PTR_FORMAT, pwsrc->possible_caps); + pwsrc->caps = gst_caps_intersect_full (pw_peer_caps, pwsrc->possible_caps, GST_CAPS_INTERSECT_FIRST); + + /* + * We expect pw_peer_caps to be fixed caps as we receive that from + * PipeWire. See pw_context_find_format() and SPA_PARAM_Format. + * possible_caps can be non-fixated caps based on what is downstream + * in the pipeline. + * + * The intersection result above might give us non-fixated caps. A + * possible scenario for this is the below pipeline. + * pipewiresrc ! audioconvert ! audio/x-raw,rate=44100,channels=2 ! .. + * + * So we fixate the caps explicitly here. + */ + pwsrc->caps = gst_caps_fixate (pwsrc->caps); gst_caps_maybe_fixate_dma_format (pwsrc->caps); } - if (pwsrc->caps && gst_caps_is_fixed (pwsrc->caps)) { + if (pwsrc->caps) { + g_return_if_fail (gst_caps_is_fixed (pwsrc->caps)); + pwsrc->negotiated = TRUE; structure = gst_caps_get_structure (pwsrc->caps, 0); @@ -1127,14 +1147,15 @@ handle_format_change (GstPipeWireSrc *pwsrc, #ifdef HAVE_GSTREAMER_DMA_DRM } #endif + } else { + /* Don't provide bufferpool for audio if not requested by the + * application/user */ + if (pwsrc->use_bufferpool != USE_BUFFERPOOL_YES) + pwsrc->use_bufferpool = USE_BUFFERPOOL_NO; } } else { pwsrc->negotiated = FALSE; pwsrc->is_video = FALSE; - - /* Don't provide bufferpool for audio if not requested by the application/user */ - if (pwsrc->use_bufferpool != USE_BUFFERPOOL_YES) - pwsrc->use_bufferpool = USE_BUFFERPOOL_NO; } if (pwsrc->caps) { @@ -1158,7 +1179,7 @@ handle_format_change (GstPipeWireSrc *pwsrc, pwsrc->min_buffers, pwsrc->max_buffers), SPA_PARAM_BUFFERS_blocks, SPA_POD_CHOICE_RANGE_Int(0, 1, INT32_MAX), - SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX), + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(0, 1, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(buffertypes)); diff --git a/src/modules/meson.build b/src/modules/meson.build index 51db77334..be9912dd6 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -620,7 +620,7 @@ if build_module_raop endif summary({'raop-sink (requires OpenSSL)': build_module_raop}, bool_yn: true, section: 'Optional Modules') -roc_dep = dependency('roc', version: '>= 0.3.0', required: get_option('roc')) +roc_dep = dependency('roc', version: '>= 0.4.0', required: get_option('roc')) summary({'ROC': roc_dep.found()}, bool_yn: true, section: 'Streaming between daemons') pipewire_module_rtp_source = shared_library('pipewire-module-rtp-source', diff --git a/src/modules/module-client-node/remote-node.c b/src/modules/module-client-node/remote-node.c index 8b5c7daf3..52134eaa4 100644 --- a/src/modules/module-client-node/remote-node.c +++ b/src/modules/module-client-node/remote-node.c @@ -481,15 +481,16 @@ static int client_node_command(void *_data, const struct spa_command *command) pw_proxy_error(proxy, res, "suspend failed"); } break; - case SPA_NODE_COMMAND_RequestProcess: - res = pw_impl_node_send_command(node, command); - break; default: - pw_log_warn("unhandled node command %d (%s)", id, - spa_debug_type_find_name(spa_type_node_command_id, id)); - res = -ENOTSUP; - pw_proxy_errorf(proxy, res, "command %d (%s) not supported", id, - spa_debug_type_find_name(spa_type_node_command_id, id)); + res = pw_impl_node_send_command(node, command); + if (res < 0) { + pw_log_warn("node command %d (%s) error: %s (%d)", id, + spa_debug_type_find_name(spa_type_node_command_id, id), + spa_strerror(res), res); + pw_proxy_errorf(proxy, res, "command %d (%s) error: %s (%d)", id, + spa_debug_type_find_name(spa_type_node_command_id, id), + spa_strerror(res), res); + } } return res; } diff --git a/src/modules/module-ffado-driver.c b/src/modules/module-ffado-driver.c index 4345e5a19..1c9c7c590 100644 --- a/src/modules/module-ffado-driver.c +++ b/src/modules/module-ffado-driver.c @@ -772,7 +772,7 @@ static int make_stream_ports(struct stream *s) break; case ffado_stream_type_midi: props = pw_properties_new( - PW_KEY_FORMAT_DSP, "32 bit raw UMP", + PW_KEY_FORMAT_DSP, "8 bit raw midi", PW_KEY_PORT_NAME, port->name, PW_KEY_PORT_PHYSICAL, "true", PW_KEY_PORT_TERMINAL, "true", diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 1446cc437..50075a779 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -972,7 +972,7 @@ static void param_tag_changed(struct impl *impl, const struct spa_pod *param) pw_stream_update_params(impl->playback, params, 1); } -static void state_changed(void *data, enum pw_stream_state old, +static void capture_state_changed(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct impl *impl = data; @@ -1048,7 +1048,9 @@ static void param_changed(void *data, uint32_t id, const struct spa_pod *param, struct spa_audio_info_raw info; spa_zero(info); if (param == NULL) { - spa_filter_graph_deactivate(graph); + pw_log_info("module %p: filter deactivate", impl); + if (capture) + spa_filter_graph_deactivate(graph); impl->rate = 0; } else { if ((res = spa_format_audio_raw_parse(param, &info)) < 0) @@ -1087,7 +1089,7 @@ static const struct pw_stream_events in_stream_events = { .destroy = capture_destroy, .process = capture_process, .io_changed = io_changed, - .state_changed = state_changed, + .state_changed = capture_state_changed, .param_changed = capture_param_changed }; @@ -1108,7 +1110,6 @@ static const struct pw_stream_events out_stream_events = { .destroy = playback_destroy, .process = playback_process, .io_changed = io_changed, - .state_changed = state_changed, .param_changed = playback_param_changed, }; @@ -1153,10 +1154,11 @@ static int setup_streams(struct impl *impl) SPA_PARAM_EnumFormat, &impl->capture_info); for (i = 0;; i++) { - if ((offs = pw_array_add(&offsets, sizeof(uint32_t))) != NULL) - *offs = b.b.state.offset; + uint32_t save = b.b.state.offset; if (spa_filter_graph_enum_prop_info(graph, i, &b.b, NULL) != 1) break; + if ((offs = pw_array_add(&offsets, sizeof(uint32_t))) != NULL) + *offs = save; } if ((offs = pw_array_add(&offsets, sizeof(uint32_t))) != NULL) diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 48e8c6f8e..7cce66b0b 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -247,6 +247,8 @@ static void midi_to_jack(struct impl *impl, float *dst, float *src, uint32_t n_s struct spa_pod_sequence *seq; struct spa_pod_control *c; int res; + uint8_t tmp[n_samples * 4]; + size_t tmp_size = 0; jack.midi_clear_buffer(dst); if (src == NULL) @@ -260,23 +262,32 @@ static void midi_to_jack(struct impl *impl, float *dst, float *src, uint32_t n_s seq = (struct spa_pod_sequence*)pod; SPA_POD_SEQUENCE_FOREACH(seq, c) { - uint8_t data[16]; int size; if (c->type != SPA_CONTROL_UMP) continue; + switch (c->type) { + case SPA_CONTROL_UMP: + size = spa_ump_to_midi(SPA_POD_BODY(&c->value), + SPA_POD_BODY_SIZE(&c->value), &tmp[tmp_size], sizeof(tmp) - tmp_size); + if (size <= 0) + continue; + tmp_size += size; + break; + case SPA_CONTROL_Midi: + tmp_size = SPA_POD_BODY_SIZE(&c->value); + memcpy(tmp, SPA_POD_BODY(&c->value), SPA_MIN(sizeof(tmp), tmp_size)); + break; + } - size = spa_ump_to_midi(SPA_POD_BODY(&c->value), - SPA_POD_BODY_SIZE(&c->value), data, sizeof(data)); - if (size <= 0) - continue; - - if (impl->fix_midi) - fix_midi_event(data, size); - - if ((res = jack.midi_event_write(dst, c->offset, data, size)) < 0) - pw_log_warn("midi %p: can't write event: %s", dst, - spa_strerror(res)); + if (tmp[0] != 0xf0 || tmp[tmp_size-1] == 0xf7) { + if (impl->fix_midi) + fix_midi_event(tmp, tmp_size); + if ((res = jack.midi_event_write(dst, c->offset, tmp, tmp_size)) < 0) + pw_log_warn("midi %p: can't write event: %s", dst, + spa_strerror(res)); + tmp_size = 0; + } } } @@ -503,7 +514,7 @@ static void make_stream_ports(struct stream *s) } else { snprintf(name, sizeof(name), "%s_%d", prefix, i - s->info.channels); props = pw_properties_new( - PW_KEY_FORMAT_DSP, "32 bit raw UMP", + PW_KEY_FORMAT_DSP, "8 bit raw midi", PW_KEY_PORT_NAME, name, PW_KEY_PORT_PHYSICAL, "true", NULL); diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index 3675a33f7..011052e7b 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -45,7 +45,26 @@ /** \page page_module_netjack2_driver Netjack2 driver * * The netjack2-driver module provides a source or sink that is following a - * netjack2 manager. + * netjack2 manager. It is meant to be used over stable (ethernet) network + * connections with minimal latency and jitter. + * + * The driver normally decides how many ports it will send and receive from the + * manager. By default however, these values are set to -1 so that the manager + * decides on the number of ports. + * + * With the global or per stream audio.port and midi.ports properties this + * behaviour can be adjusted. + * + * The driver will send out UDP messages on a (typically) multicast address to + * inform the manager of the available driver. This will then instruct the manager + * to configure and start the driver. + * + * On the driver side, a sink and/or source with the specified numner of audio and + * midi ports will be created. On the manager side there will be a corresponding + * source and/or sink created respectively. + * + * The driver will be scheduled with exactly the same period as the manager but with + * a configurable number of periods of delay (see netjack2.latency, default 2). * * ## Module Name * @@ -53,7 +72,10 @@ * * ## Module Options * - * - `driver.mode`: the driver mode, sink|source|duplex, default duplex + * - `driver.mode`: the driver mode, sink|source|duplex, default duplex. This set the + * per stream audio.port and midi.ports default from -1 to 0. sink mode defaults to + * no source ports, source mode to no sink ports and duplex leaves the defaults as + * they are. * - `local.ifname = `: interface name to use * - `net.ip =`: multicast IP address, default "225.3.19.154" * - `net.port =`: control port, default 19000 @@ -63,11 +85,11 @@ * - `source.ip =`: IP address to bind to, default "0.0.0.0" * - `source.port =`: port to bind to, default 0 (allocate) * - `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. + * - `audio.ports`: the number of audio ports. Can also be added to the stream props. + * A value of -1 will configure to the number of audio ports on the manager. * - `midi.ports`: the number of midi ports. Can also be added to the stream props. + * A value of -1 will configure to the number of midi ports on the manager. * - `source.props`: Extra properties for the source filter. * - `sink.props`: Extra properties for the sink filter. * @@ -93,15 +115,14 @@ * 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.ports = -1 * #audio.channels = 2 * #audio.position = [ FL FR ] * source.props = { - * # extra sink properties + * # extra source properties * } * sink.props = { * # extra sink properties @@ -133,15 +154,14 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #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 DEFAULT_MIDI_PORTS -1 +#define DEFAULT_AUDIO_PORTS -1 #define FOLLOWER_INIT_TIMEOUT 1 #define FOLLOWER_INIT_RETRY -1 #define MODULE_USAGE "( remote.name= ) " \ - "( driver.mode= ) " \ + "( driver.mode= ) " \ "( local.ifname= ) " \ "( net.ip= ) " \ "( net.port= ) " \ @@ -151,11 +171,11 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); "( source.ip= ) " \ "( source.port= ) " \ "( netjack2.client-name= ) " \ - "( netjack2.save= ) " \ "( netjack2.latency= ) " \ - "( midi.ports= ) " \ - "( audio.channels= ) " \ - "( audio.position= ) " \ + "( audio.ports= ) " \ + "( midi.ports= ) " \ + "( audio.channels= ) " \ + "( audio.position= ) " \ "( source.props= ) " \ "( sink.props= ) " @@ -182,9 +202,13 @@ struct stream { struct pw_filter *filter; struct spa_hook listener; + int32_t wanted_n_midi; + int32_t wanted_n_audio; + + struct spa_io_position *position; + struct spa_audio_info_raw info; - uint32_t n_midi; uint32_t n_ports; struct port *ports[MAX_PORTS]; @@ -222,8 +246,6 @@ struct impl { struct spa_hook core_proxy_listener; struct spa_hook core_listener; - struct spa_io_position *position; - struct stream source; struct stream sink; @@ -369,11 +391,10 @@ static void source_process(void *d, struct spa_io_position *position) 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; + s->position = area; break; default: break; @@ -431,7 +452,7 @@ static void make_stream_ports(struct stream *s) } else { snprintf(name, sizeof(name), "midi%d", i - s->info.channels); props = pw_properties_new( - PW_KEY_FORMAT_DSP, "32 bit raw UMP", + PW_KEY_FORMAT_DSP, "8 bit raw midi", PW_KEY_AUDIO_CHANNEL, name, PW_KEY_PORT_PHYSICAL, "true", NULL); @@ -458,6 +479,7 @@ static void make_stream_ports(struct stream *s) s->ports[i] = port; } + pw_filter_set_active(s->filter, true); } static struct spa_pod *make_props_param(struct spa_pod_builder *b, @@ -523,7 +545,6 @@ static void stream_param_changed(void *data, void *port_data, uint32_t 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"); @@ -558,6 +579,7 @@ static int make_stream(struct stream *s, const char *name) const struct spa_pod *params[4]; uint8_t buffer[1024]; struct spa_pod_builder b; + int res; n_params = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); @@ -583,12 +605,18 @@ static int make_stream(struct stream *s, const char *name) SPA_PARAM_Format, &s->info); params[n_params++] = make_props_param(&b, &s->volume); - return pw_filter_connect(s->filter, + if ((res = 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); + params, n_params)) < 0) + return res; + + if (s->info.channels == 0) + make_stream_ports(s); + + return res; } static int create_filters(struct impl *impl) @@ -616,6 +644,24 @@ static inline uint64_t get_time_nsec(struct impl *impl) return nsec; } +static void update_clock(struct impl *impl, struct stream *s, uint64_t nsec, uint32_t nframes) +{ + if (s->position) { + struct spa_io_clock *c = &s->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; + } +} + static void on_data_io(void *data, int fd, uint32_t mask) { @@ -648,27 +694,13 @@ on_data_io(void *data, int fd, uint32_t mask) impl->frame_time += nframes; - pw_log_trace_fp("process %d %u %u %p %"PRIu64, nframes, source_running, - sink_running, impl->position, impl->frame_time); + pw_log_trace_fp("process %d %u %u %"PRIu64, nframes, source_running, + sink_running, 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); @@ -676,12 +708,16 @@ on_data_io(void *data, int fd, uint32_t mask) impl->done = false; impl->triggered = true; impl->driving = MODE_SOURCE; - pw_filter_trigger_process(impl->source.filter); + update_clock(impl, &impl->source, nsec, nframes); + if (pw_filter_trigger_process(impl->source.filter) < 0) + pw_log_warn("source not ready"); } 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); + update_clock(impl, &impl->sink, nsec, nframes); + if (pw_filter_trigger_process(impl->sink.filter) < 0) + pw_log_warn("sink not ready"); } else { sink_running = false; impl->done = true; @@ -706,7 +742,7 @@ static bool is_multicast(struct sockaddr *sa, socklen_t salen) 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) + bool loop, int ttl, int dscp, const char *ifname) { int af, fd, val, res; struct timeval timeout; @@ -722,7 +758,13 @@ static int make_socket(struct sockaddr_storage *src, socklen_t src_len, pw_log_error("setsockopt failed: %m"); goto error; } - +#ifdef SO_BINDTODEVICE + if (ifname && setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname)) < 0) { + res = -errno; + pw_log_error("setsockopt(SO_BINDTODEVICE) failed: %m"); + goto error; + } +#endif #ifdef SO_PRIORITY val = 6; if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0) @@ -789,13 +831,12 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p int res; struct netjack2_peer *peer = &impl->peer; uint32_t i; + const char *media; 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 || @@ -807,23 +848,35 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p pw_log_warn("invalid follower setup"); return -EINVAL; } + /* the params are from the perspective of the manager, so send is our + * receive (source) and recv is our send (sink) */ + SPA_SWAP(peer->params.send_audio_channels, peer->params.recv_audio_channels); + SPA_SWAP(peer->params.send_midi_channels, peer->params.recv_midi_channels); 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; - if ((uint32_t)peer->params.send_audio_channels != impl->source.info.channels) { - impl->source.info.channels = peer->params.send_audio_channels; - for (i = 0; i < SPA_MIN(impl->source.info.channels, SPA_AUDIO_MAX_CHANNELS); i++) - impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; + impl->sink.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels; + if (impl->sink.n_ports > MAX_PORTS) { + pw_log_warn("Too many follower sink ports %d > %d", impl->sink.n_ports, MAX_PORTS); + return -EINVAL; } - impl->sink.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels; impl->sink.info.rate = peer->params.sample_rate; - if ((uint32_t)peer->params.recv_audio_channels != impl->sink.info.channels) { - impl->sink.info.channels = peer->params.recv_audio_channels; - for (i = 0; i < SPA_MIN(impl->sink.info.channels, SPA_AUDIO_MAX_CHANNELS); i++) + if ((uint32_t)peer->params.send_audio_channels != impl->sink.info.channels) { + impl->sink.info.channels = SPA_MIN(peer->params.send_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS); + for (i = 0; i < impl->sink.info.channels; i++) impl->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } + impl->source.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels; + if (impl->source.n_ports > MAX_PORTS) { + pw_log_warn("Too many follower source ports %d > %d", impl->source.n_ports, MAX_PORTS); + return -EINVAL; + } + impl->source.info.rate = peer->params.sample_rate; + if ((uint32_t)peer->params.recv_audio_channels != impl->source.info.channels) { + impl->source.info.channels = SPA_MIN(peer->params.recv_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS); + for (i = 0; i < impl->source.info.channels; i++) + impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; + } impl->samplerate = peer->params.sample_rate; impl->period_size = peer->params.period_size; @@ -843,6 +896,20 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p pw_properties_setf(impl->source.props, PW_KEY_NODE_FORCE_QUANTUM, "%u", impl->period_size); + media = impl->sink.info.channels > 0 ? "Audio" : "Midi"; + if (pw_properties_get(impl->sink.props, PW_KEY_MEDIA_CLASS) == NULL) + pw_properties_setf(impl->sink.props, PW_KEY_MEDIA_CLASS, "%s/Sink", media); + + media = impl->source.info.channels > 0 ? "Audio" : "Midi"; + if (pw_properties_get(impl->source.props, PW_KEY_MEDIA_CLASS) == NULL) + pw_properties_setf(impl->source.props, PW_KEY_MEDIA_CLASS, "%s/Source", media); + + impl->mode = 0; + if (impl->source.n_ports > 0) + impl->mode |= MODE_SOURCE; + if (impl->sink.n_ports > 0) + impl->mode |= MODE_SINK; + if ((res = create_filters(impl)) < 0) return res; @@ -942,10 +1009,12 @@ static int send_follower_available(struct impl *impl) 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); + /* send/recv is from the perspective of the manager, so what we send (sink) + * is recv on the manager and vice versa. */ + params.recv_audio_channels = htonl(impl->sink.wanted_n_audio); + params.send_audio_channels = htonl(impl->source.wanted_n_audio); + params.recv_midi_channels = htonl(impl->sink.wanted_n_midi); + params.send_midi_channels = htonl(impl->source.wanted_n_midi); params.sample_encoder = htonl(NJ2_ENCODER_FLOAT); params.follower_sync_mode = htonl(1); params.network_latency = htonl(impl->latency); @@ -982,9 +1051,11 @@ static int create_netjack2_socket(struct impl *impl) 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); + str = pw_properties_get(impl->props, "local.ifname"); fd = make_socket(&impl->src_addr, impl->src_len, - &impl->dst_addr, impl->dst_len, impl->loop, impl->ttl, impl->dscp); + &impl->dst_addr, impl->dst_len, impl->loop, impl->ttl, impl->dscp, + str); if (fd < 0) { res = -errno; pw_log_error("can't create socket: %s", spa_strerror(res)); @@ -1151,8 +1222,7 @@ static void parse_audio_info(const struct pw_properties *props, struct spa_audio { spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( - SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), - SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), &props->dict, SPA_KEY_AUDIO_CHANNELS, SPA_KEY_AUDIO_POSITION, NULL); @@ -1220,20 +1290,20 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) 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; + pw_properties_set(impl->sink.props, "audio.ports", "0"); + pw_properties_set(impl->sink.props, "midi.ports", "0"); } else if (spa_streq(str, "sink")) { - impl->mode = MODE_SINK; - } else if (spa_streq(str, "duplex")) { - impl->mode = MODE_DUPLEX; - } else { + pw_properties_set(impl->source.props, "audio.ports", "0"); + pw_properties_set(impl->source.props, "midi.ports", "0"); + } else if (!spa_streq(str, "duplex")) { 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); @@ -1245,11 +1315,9 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) 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"); @@ -1264,22 +1332,20 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) 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); + copy_props(impl, props, "midi.ports"); + copy_props(impl, props, "audio.ports"); 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, + impl->source.wanted_n_midi = pw_properties_get_int32(impl->source.props, "midi.ports", DEFAULT_MIDI_PORTS); - impl->sink.n_midi = pw_properties_get_uint32(impl->sink.props, + impl->sink.wanted_n_midi = pw_properties_get_int32(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->source.wanted_n_audio = pw_properties_get_int32(impl->source.props, + "audio.ports", DEFAULT_AUDIO_PORTS); + impl->sink.wanted_n_audio = pw_properties_get_int32(impl->sink.props, + "audio.ports", DEFAULT_AUDIO_PORTS); impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index 8229b5d5f..fe482fd94 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -49,6 +49,19 @@ * The netjack2 manager module listens for new netjack2 driver messages and will * start a communication channel with them. * + * Messages are received on a (typically) multicast address. + * + * Normally, the driver will specify the number of send and receive channels it + * wants to set up with the manager. If the driver however specifies a don't-care + * value of -1, the audio.ports and midi.ports configuration values of the manager + * are used. + * + * The manager will create the corresponding streams to send and receive data + * to/from the drivers. These are usually sink and sources but with the + * netjack2.connect property, these will be streams that will be autoconnected to + * the default source and sink by the session manager. + * + * * ## Module Name * * `libpipewire-module-netjack2-manager` @@ -67,8 +80,11 @@ * - `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. + * - `audio.ports`: the number of audio ports. Can also be added to the stream props. This + * is the default suggestion for drivers that don't specify any number of audio channels. + * - `midi.ports`: the number of midi ports. Can also be added to the stream props. This + * is the default suggestion for drivers that don't specify any number of midi channels. + * - `audio.position`: default channel position for the number of audio.ports. * - `source.props`: Extra properties for the source filter. * - `sink.props`: Extra properties for the sink filter. * @@ -99,11 +115,12 @@ * #netjack2.period-size = 1024 * #netjack2.encoding = float # float|opus * #netjack2.kbps = 64 + * #audio.ports = 0 * #midi.ports = 0 * #audio.channels = 2 * #audio.position = [ FL FR ] * source.props = { - * # extra sink properties + * # extra source properties * } * sink.props = { * # extra sink properties @@ -137,8 +154,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define DEFAULT_PERIOD_SIZE 1024 #define DEFAULT_ENCODING "float" #define DEFAULT_KBPS 64 -#define DEFAULT_CHANNELS 2 -#define DEFAULT_POSITION "[ FL FR ]" +#define DEFAULT_AUDIO_PORTS 2 #define DEFAULT_MIDI_PORTS 1 #define MODULE_USAGE "( remote.name= ) " \ @@ -151,8 +167,8 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); "( netjack2.connect= ) " \ "( netjack2.sample-rate= ) "\ "( netjack2.period-size= ) " \ - "( midi.ports= ) " \ - "( audio.channels= ) " \ + "( midi.ports= ) " \ + "( audio.channels= ) " \ "( audio.position= ) " \ "( source.props= ) " \ "( sink.props= ) " @@ -183,9 +199,12 @@ struct stream { struct pw_filter *filter; struct spa_hook listener; - struct spa_audio_info_raw info; + struct spa_io_position *position; + struct spa_audio_info_raw info; + uint32_t n_audio; uint32_t n_midi; + uint32_t n_ports; struct port *ports[MAX_PORTS]; @@ -202,7 +221,10 @@ struct follower { struct spa_list link; struct impl *impl; - struct spa_io_position *position; +#define MODE_SINK (1<<0) +#define MODE_SOURCE (1<<1) +#define MODE_DUPLEX (MODE_SINK|MODE_SOURCE) + uint32_t mode; struct stream source; struct stream sink; @@ -227,6 +249,7 @@ struct follower { unsigned int done:1; unsigned int new_xrun:1; unsigned int started:1; + unsigned int freeing:1; }; struct impl { @@ -235,10 +258,6 @@ struct impl { struct pw_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; @@ -284,6 +303,7 @@ static void stream_destroy(void *d) struct stream *s = d; uint32_t i; + s->running = false; spa_hook_remove(&s->listener); for (i = 0; i < s->n_ports; i++) s->ports[i] = NULL; @@ -354,9 +374,17 @@ static void sink_process(void *d, struct spa_io_position *position) pw_loop_update_io(s->impl->data_loop, follower->socket, SPA_IO_IN); } -static void source_process(void *d, struct spa_io_position *position) +static int stop_follower(struct follower *follower); + +static int do_stop_follower(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + stop_follower(user_data); + return 0; +} + +static inline void handle_source_process(struct stream *s, struct spa_io_position *position) { - struct stream *s = d; struct follower *follower = s->follower; uint32_t nframes = position->clock.duration; struct data_info midi[s->n_ports]; @@ -365,28 +393,57 @@ static void source_process(void *d, struct spa_io_position *position) set_info(s, nframes, midi, &n_midi, audio, &n_audio); - netjack2_manager_sync_wait(&follower->peer); + if (netjack2_manager_sync_wait(&follower->peer) < 0) { + pw_loop_invoke(s->impl->main_loop, do_stop_follower, 0, NULL, 0, false, follower); + return; + } netjack2_recv_data(&follower->peer, midi, n_midi, audio, n_audio); } +static void source_process(void *d, struct spa_io_position *position) +{ + struct stream *s = d; + struct follower *follower = s->follower; + + if (!(follower->mode & MODE_SINK)) + sink_process(&follower->sink, position); + + handle_source_process(s, position); +} + static void follower_free(struct follower *follower) { struct impl *impl = follower->impl; + if (follower->freeing) + return; + + follower->freeing = true; + spa_list_remove(&follower->link); - if (follower->source.filter) + if (follower->socket) { + pw_loop_destroy_source(impl->data_loop, follower->socket); + follower->socket = NULL; + } + if (follower->setup_socket) { + pw_loop_destroy_source(impl->main_loop, follower->setup_socket); + follower->setup_socket = NULL; + } + + if (follower->source.filter) { pw_filter_destroy(follower->source.filter); - if (follower->sink.filter) + follower->source.filter = NULL; + } + if (follower->sink.filter) { pw_filter_destroy(follower->sink.filter); + follower->sink.filter = NULL; + } pw_properties_free(follower->source.props); + follower->source.props = NULL; pw_properties_free(follower->sink.props); - - if (follower->socket) - pw_loop_destroy_source(impl->data_loop, follower->socket); - if (follower->setup_socket) - pw_loop_destroy_source(impl->main_loop, follower->setup_socket); + follower->sink.props = NULL; netjack2_cleanup(&follower->peer); free(follower); @@ -420,10 +477,13 @@ 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); - stop_follower(follower); + pw_loop_destroy_source(impl->main_loop, follower->setup_socket); + follower->setup_socket = NULL; + pw_loop_invoke(impl->main_loop, do_stop_follower, 0, NULL, 0, false, follower); return; } if (mask & SPA_IO_IN) { @@ -468,23 +528,32 @@ on_data_io(void *data, int fd, uint32_t mask) pw_log_warn("error:%08x", mask); pw_loop_destroy_source(impl->data_loop, follower->socket); follower->socket = NULL; + pw_loop_invoke(impl->main_loop, do_stop_follower, 0, NULL, 0, false, follower); return; } if (mask & SPA_IO_IN) { pw_loop_update_io(impl->data_loop, follower->socket, 0); - pw_filter_trigger_process(follower->source.filter); + if (follower->mode & MODE_SOURCE) { + if (pw_filter_trigger_process(follower->source.filter) < 0) { + pw_log_warn("source not ready"); + handle_source_process(&follower->source, follower->source.position); + } + } else { + /* There is no source, handle the source receive side (without ports) + * with the sink position io */ + handle_source_process(&follower->source, follower->sink.position); + } } } 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; + s->position = area; break; default: break; @@ -521,6 +590,9 @@ static void make_stream_ports(struct stream *s) struct spa_latency_info latency; const struct spa_pod *params[1]; + if (s->ready) + return; + for (i = 0; i < s->n_ports; i++) { struct port *port = s->ports[i]; if (port != NULL) { @@ -542,7 +614,7 @@ static void make_stream_ports(struct stream *s) } else { snprintf(name, sizeof(name), "midi%d", i - s->info.channels); props = pw_properties_new( - PW_KEY_FORMAT_DSP, "32 bit raw UMP", + PW_KEY_FORMAT_DSP, "8 bit raw midi", PW_KEY_PORT_PHYSICAL, "true", PW_KEY_AUDIO_CHANNEL, name, NULL); @@ -571,6 +643,9 @@ static void make_stream_ports(struct stream *s) s->ports[i] = port; } + s->ready = true; + if (s->follower->started) + pw_filter_set_active(s->filter, true); } static struct spa_pod *make_props_param(struct spa_pod_builder *b, @@ -636,9 +711,6 @@ static void stream_param_changed(void *data, void *port_data, uint32_t 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"); @@ -674,6 +746,7 @@ static int make_stream(struct stream *s, const char *name) uint8_t buffer[1024]; struct spa_pod_builder b; uint32_t flags; + int res; n_params = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); @@ -693,7 +766,8 @@ static int make_stream(struct stream *s, const char *name) } else { pw_filter_add_listener(s->filter, &s->listener, &source_events, s); - flags |= PW_FILTER_FLAG_TRIGGER; + if (s->follower->mode & MODE_SINK) + flags |= PW_FILTER_FLAG_TRIGGER; } reset_volume(&s->volume, s->info.channels); @@ -705,18 +779,23 @@ static int make_stream(struct stream *s, const char *name) SPA_PARAM_Format, &s->info); params[n_params++] = make_props_param(&b, &s->volume); - return pw_filter_connect(s->filter, flags, params, n_params); + if ((res = pw_filter_connect(s->filter, flags, params, n_params)) < 0) + return res; + + if (s->info.channels == 0) + make_stream_ports(s); + + return res; } static int create_filters(struct follower *follower) { - struct impl *impl = follower->impl; int res = 0; - if (impl->mode & MODE_SINK) + if (follower->mode & MODE_SINK) res = make_stream(&follower->sink, "NETJACK2 Send"); - if (impl->mode & MODE_SOURCE) + if (follower->mode & MODE_SOURCE) res = make_stream(&follower->source, "NETJACK2 Receive"); return res; @@ -860,6 +939,8 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param struct follower *follower; char buffer[256]; struct netjack2_peer *peer; + uint32_t i; + const char *media; pw_log_info("got follower available"); nj2_dump_session_params(params); @@ -891,6 +972,12 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param parse_audio_info(follower->source.props, &follower->source.info); parse_audio_info(follower->sink.props, &follower->sink.info); + follower->source.n_audio = pw_properties_get_uint32(follower->source.props, + "audio.ports", follower->source.info.channels ? + follower->source.info.channels : DEFAULT_AUDIO_PORTS); + follower->sink.n_audio = pw_properties_get_uint32(follower->sink.props, + "audio.ports", follower->sink.info.channels ? + follower->sink.info.channels : DEFAULT_AUDIO_PORTS); 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, @@ -925,29 +1012,64 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param peer->params.sample_encoder = impl->encoding; peer->params.kbps = impl->kbps; + /* params send and recv are from the manager point of view and reversed for the + * driver. So, for us send = sink and recv = source */ if (peer->params.send_audio_channels < 0) - peer->params.send_audio_channels = follower->sink.info.channels; + peer->params.send_audio_channels = follower->sink.n_audio; if (peer->params.recv_audio_channels < 0) - peer->params.recv_audio_channels = follower->source.info.channels; + peer->params.recv_audio_channels = follower->source.n_audio; 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 = peer->params.recv_audio_channels + peer->params.recv_midi_channels; + follower->source.info.rate = peer->params.sample_rate; + if ((uint32_t)peer->params.recv_audio_channels != follower->source.info.channels) { + follower->source.info.channels = SPA_MIN(peer->params.recv_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS); + for (i = 0; i < follower->source.info.channels; i++) + follower->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; + } + follower->sink.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels; + follower->sink.info.rate = peer->params.sample_rate; + if ((uint32_t)peer->params.send_audio_channels != follower->sink.info.channels) { + follower->sink.info.channels = SPA_MIN(peer->params.send_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS); + for (i = 0; i < follower->sink.info.channels; i++) + follower->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; + } - 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"); + pw_log_error("too many ports source:%d sink:%d max:%d", follower->source.n_ports, + follower->sink.n_ports, MAX_PORTS); res = -EINVAL; goto cleanup; } + media = follower->sink.info.channels > 0 ? "Audio" : "Midi"; + if (pw_properties_get_bool(follower->sink.props, "netjack2.connect", DEFAULT_CONNECT)) { + if (pw_properties_get(follower->sink.props, PW_KEY_NODE_AUTOCONNECT) == NULL) + pw_properties_set(follower->sink.props, PW_KEY_NODE_AUTOCONNECT, "true"); + if (pw_properties_get(follower->sink.props, PW_KEY_MEDIA_CLASS) == NULL) + pw_properties_setf(follower->sink.props, PW_KEY_MEDIA_CLASS, "Stream/Input/%s", media); + } else { + if (pw_properties_get(follower->sink.props, PW_KEY_MEDIA_CLASS) == NULL) + pw_properties_setf(follower->sink.props, PW_KEY_MEDIA_CLASS, "%s/Sink", media); + } + media = follower->source.info.channels > 0 ? "Audio" : "Midi"; + if (pw_properties_get_bool(follower->source.props, "netjack2.connect", DEFAULT_CONNECT)) { + if (pw_properties_get(follower->source.props, PW_KEY_NODE_AUTOCONNECT) == NULL) + pw_properties_set(follower->source.props, PW_KEY_NODE_AUTOCONNECT, "true"); + if (pw_properties_get(follower->source.props, PW_KEY_MEDIA_CLASS) == NULL) + pw_properties_setf(follower->source.props, PW_KEY_MEDIA_CLASS, "Stream/Output/%s", media); + } else { + if (pw_properties_get(follower->source.props, PW_KEY_MEDIA_CLASS) == NULL) + pw_properties_setf(follower->source.props, PW_KEY_MEDIA_CLASS, "%s/Source", media); + } + + follower->mode = 0; + if (follower->sink.n_ports > 0) + follower->mode |= MODE_SINK; + if (follower->source.n_ports > 0) + follower->mode |= MODE_SOURCE; if ((res = create_filters(follower)) < 0) goto create_failed; @@ -1082,8 +1204,7 @@ static int create_netjack2_socket(struct impl *impl) impl->dscp = pw_properties_get_uint32(impl->props, "net.dscp", DEFAULT_NET_DSCP); str = pw_properties_get(impl->props, "local.ifname"); - fd = make_announce_socket(&impl->src_addr, impl->src_len, - pw_properties_get(impl->props, "local.ifname")); + fd = make_announce_socket(&impl->src_addr, impl->src_len, str); if (fd < 0) { res = fd; pw_log_error("can't create socket: %s", spa_strerror(res)); @@ -1173,8 +1294,7 @@ static void parse_audio_info(const struct pw_properties *props, struct spa_audio { spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( - SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), - SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), &props->dict, SPA_KEY_AUDIO_CHANNELS, SPA_KEY_AUDIO_POSITION, NULL); @@ -1238,20 +1358,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) 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", @@ -1303,33 +1409,17 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_LOOP_NAME); copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_NODE_NETWORK); + copy_props(impl, props, PW_KEY_NODE_GROUP); 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); + copy_props(impl, props, "audio.ports"); + copy_props(impl, props, "midi.ports"); copy_props(impl, props, "netjack2.connect"); - if (pw_properties_get_bool(impl->sink_props, "netjack2.connect", DEFAULT_CONNECT)) { - if (pw_properties_get(impl->sink_props, PW_KEY_NODE_AUTOCONNECT) == NULL) - pw_properties_set(impl->sink_props, PW_KEY_NODE_AUTOCONNECT, "true"); - if (pw_properties_get(impl->sink_props, PW_KEY_MEDIA_CLASS) == NULL) - pw_properties_set(impl->sink_props, PW_KEY_MEDIA_CLASS, "Stream/Input/Audio"); - } else { - if (pw_properties_get(impl->sink_props, PW_KEY_MEDIA_CLASS) == NULL) - pw_properties_set(impl->sink_props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); - } - if (pw_properties_get_bool(impl->source_props, "netjack2.connect", DEFAULT_CONNECT)) { - if (pw_properties_get(impl->source_props, PW_KEY_NODE_AUTOCONNECT) == NULL) - pw_properties_set(impl->source_props, PW_KEY_NODE_AUTOCONNECT, "true"); - if (pw_properties_get(impl->source_props, PW_KEY_MEDIA_CLASS) == NULL) - pw_properties_set(impl->source_props, PW_KEY_MEDIA_CLASS, "Stream/Output/Audio"); - } else { - if (pw_properties_get(impl->source_props, PW_KEY_MEDIA_CLASS) == NULL) - pw_properties_set(impl->source_props, PW_KEY_MEDIA_CLASS, "Audio/Source"); - } - 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); diff --git a/src/modules/module-netjack2/packets.h b/src/modules/module-netjack2/packets.h index af60a6c78..69791cd11 100644 --- a/src/modules/module-netjack2/packets.h +++ b/src/modules/module-netjack2/packets.h @@ -120,7 +120,7 @@ struct nj2_packet_header { 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') */ + uint32_t is_last; /* is it the last packet of a given cycle (1=yes or 0=no) */ } __attribute__ ((packed)); #define UDP_HEADER_SIZE 64 /* 40 bytes for IP header in IPV6, 20 in IPV4, 8 for UDP, so take 64 */ diff --git a/src/modules/module-netjack2/peer.c b/src/modules/module-netjack2/peer.c index bac6f1322..b289c9425 100644 --- a/src/modules/module-netjack2/peer.c +++ b/src/modules/module-netjack2/peer.c @@ -242,17 +242,78 @@ static inline void fix_midi_event(uint8_t *data, size_t size) } } +static inline void *n2j_midi_buffer_reserve(struct nj2_midi_buffer *buf, + uint32_t offset, uint32_t size) +{ + struct nj2_midi_event *ev; + void *ptr; + + if (size <= 0) + return NULL; + + size_t used_size = sizeof(*buf) + buf->write_pos + + ((buf->event_count + 1) * sizeof(struct nj2_midi_event)); + + ev = &buf->event[buf->event_count]; + ev->time = offset; + ev->size = size; + if (size <= MIDI_INLINE_MAX) { + ptr = ev->buffer; + } else { + if (used_size + size > buf->buffer_size) + return NULL; + buf->write_pos += size; + ev->offset = buf->buffer_size - buf->write_pos; + ptr = SPA_PTROFF(buf, ev->offset, void); + } + buf->event_count++; + return ptr; +} + +static inline void n2j_midi_buffer_write(struct nj2_midi_buffer *buf, + uint32_t offset, void *data, uint32_t size) +{ + void *ptr = n2j_midi_buffer_reserve(buf, offset, size); + if (ptr != NULL) + memcpy(ptr, data, size); + else + buf->lost_events++; +} + +static inline void n2j_midi_buffer_append(struct nj2_midi_buffer *buf, + void *data, uint32_t size) +{ + struct nj2_midi_event *ev; + uint32_t old_size; + uint8_t *old_ptr, *new_ptr; + + ev = &buf->event[--buf->event_count]; + old_size = ev->size; + if (old_size <= MIDI_INLINE_MAX) { + old_ptr = ev->buffer; + } else { + buf->write_pos -= old_size; + old_ptr = SPA_PTROFF(buf, ev->offset, void); + } + new_ptr = n2j_midi_buffer_reserve(buf, ev->time, old_size + size); + if (new_ptr == NULL) { + buf->lost_events++; + } else { + memmove(new_ptr, old_ptr, old_size); + memcpy(new_ptr+old_size, data, size); + } +} + 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; - int free_size; + bool in_sysex = false; buf->magic = MIDI_BUFFER_MAGIC; - buf->buffer_size = peer->quantum_limit * sizeof(float); + buf->buffer_size = peer->params.period_size * sizeof(float); buf->nframes = n_samples; buf->write_pos = 0; buf->event_count = 0; @@ -269,12 +330,10 @@ static void midi_to_netjack2(struct netjack2_peer *peer, seq = (struct spa_pod_sequence*)pod; - free_size = buf->buffer_size - sizeof(*buf); - SPA_POD_SEQUENCE_FOREACH(seq, c) { int size; uint8_t data[16]; - void *ptr; + bool was_sysex = in_sysex; if (c->type != SPA_CONTROL_UMP) continue; @@ -284,29 +343,24 @@ static void midi_to_netjack2(struct netjack2_peer *peer, if (size <= 0) continue; - if (c->offset >= n_samples || - size >= free_size) { + if (c->offset >= n_samples) { buf->lost_events++; continue; } - if (peer->fix_midi) + if (!in_sysex && data[0] == 0xf0) + in_sysex = true; + + if (!in_sysex && peer->fix_midi) fix_midi_event(data, size); - ev = &buf->event[buf->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); + if (in_sysex && data[size-1] == 0xf7) + in_sysex = false; + + if (was_sysex) + n2j_midi_buffer_append(buf, data, size); + else + n2j_midi_buffer_write(buf, c->offset, data, size); } if (buf->write_pos > 0) memmove(SPA_PTROFF(buf, sizeof(*buf) + buf->event_count * sizeof(struct nj2_midi_event), void), @@ -314,15 +368,27 @@ static void midi_to_netjack2(struct netjack2_peer *peer, buf->write_pos); } +static inline void netjack2_clear_midi(float *dst, uint32_t size) +{ + struct spa_pod_builder b = { 0, }; + struct spa_pod_frame f; + spa_pod_builder_init(&b, dst, size); + spa_pod_builder_push_sequence(&b, &f, 0); + spa_pod_builder_pop(&b, &f); +} + 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; + size_t offset = size - buf->write_pos - sizeof(*buf) - + (buf->event_count * sizeof(struct nj2_midi_event)); 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++) { + + for (i = 0; i < buf->event_count; i++) { struct nj2_midi_event *ev = &buf->event[i]; uint8_t *data; size_t s; @@ -330,8 +396,8 @@ static inline void netjack2_to_midi(float *dst, uint32_t size, struct nj2_midi_b 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 if (ev->offset > offset) + data = SPA_PTROFF(buf, ev->offset - offset, void); else continue; @@ -339,9 +405,10 @@ static inline void netjack2_to_midi(float *dst, uint32_t size, struct nj2_midi_b while (s > 0) { uint32_t ump[4]; int ump_size = spa_ump_from_midi(&data, &s, ump, sizeof(ump), 0, &state); - if (ump_size <= 0) + if (ump_size <= 0) { + pw_log_warn("invalid MIDI received: %s", spa_strerror(ump_size)); break; - + } spa_pod_builder_control(&b, ev->time, SPA_CONTROL_UMP); spa_pod_builder_bytes(&b, ump, ump_size); } @@ -362,7 +429,7 @@ static int netjack2_send_sync(struct netjack2_peer *peer, uint32_t nframes) is_last = peer->params.send_midi_channels == 0 && peer->params.send_audio_channels == 0 ? 1 : 0; - strcpy(header.type, "header"); + strncpy(header.type, "header", sizeof(header.type)); header.data_type = htonl('s'); header.data_stream = htonl(peer->our_stream); header.id = htonl(peer->params.id); @@ -416,7 +483,7 @@ static int netjack2_send_midi(struct netjack2_peer *peer, uint32_t nframes, max_size = peer->params.mtu - sizeof(header); num_packets = (midi_size + max_size-1) / max_size; - strcpy(header.type, "header"); + strncpy(header.type, "header", sizeof(header.type)); header.data_type = htonl('m'); header.data_stream = htonl(peer->our_stream); header.id = htonl(peer->params.id); @@ -468,7 +535,7 @@ static int netjack2_send_float(struct netjack2_peer *peer, uint32_t nframes, sub_period_bytes = sub_period_size * sizeof(float) + sizeof(int32_t); num_packets = nframes / sub_period_size; - strcpy(header.type, "header"); + strncpy(header.type, "header", sizeof(header.type)); header.data_type = htonl('a'); header.data_stream = htonl(peer->our_stream); header.id = htonl(peer->params.id); @@ -543,7 +610,7 @@ static int netjack2_send_opus(struct netjack2_peer *peer, uint32_t nframes, } } - strcpy(header.type, "header"); + strncpy(header.type, "header", sizeof(header.type)); header.data_type = htonl('a'); header.data_stream = htonl(peer->our_stream); header.id = htonl(peer->params.id); @@ -611,7 +678,7 @@ static int netjack2_send_int(struct netjack2_peer *peer, uint32_t nframes, memset(ap, 0, max_encoded); } - strcpy(header.type, "header"); + strncpy(header.type, "header", sizeof(header.type)); header.data_type = htonl('a'); header.data_stream = htonl(peer->our_stream); header.id = htonl(peer->params.id); @@ -691,7 +758,7 @@ static inline int32_t netjack2_driver_sync_wait(struct netjack2_peer *peer) receive_error: pw_log_warn("recv error: %m"); - return 0; + return -errno; } static inline int32_t netjack2_manager_sync_wait(struct netjack2_peer *peer) @@ -735,7 +802,7 @@ static inline int32_t netjack2_manager_sync_wait(struct netjack2_peer *peer) receive_error: pw_log_warn("recv error: %m"); - return 0; + return -errno; } static int netjack2_recv_midi(struct netjack2_peer *peer, struct nj2_packet_header *header, uint32_t *count, @@ -1031,7 +1098,7 @@ static int netjack2_recv_data(struct netjack2_peer *peer, } for (i = 0; i < n_midi; i++) { if (!midi[i].filled && midi[i].data != NULL) - netjack2_to_midi(midi[i].data, peer->params.period_size * sizeof(float), NULL); + netjack2_clear_midi(midi[i].data, peer->params.period_size * sizeof(float)); } peer->sync.cycle = ntohl(header.cycle); return 0; diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 9d9406811..8eef429f0 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -1331,10 +1331,12 @@ static int rtsp_post_auth_setup_reply(void *data, int status, const struct spa_d static int rtsp_do_post_auth_setup(struct impl *impl) { - static const unsigned char content[33] = - "\x01" - "\x59\x02\xed\xe9\x0d\x4e\xf2\xbd\x4c\xb6\x8a\x63\x30\x03\x82\x07" - "\xa9\x4d\xbd\x50\xd8\xaa\x46\x5b\x5d\x8c\x01\x2a\x0c\x7e\x1d\x4e"; + static const uint8_t content[33] = { + 0x01, + 0x59, 0x02, 0xed, 0xe9, 0x0d, 0x4e, 0xf2, 0xbd, + 0x4c, 0xb6, 0x8a, 0x63, 0x30, 0x03, 0x82, 0x07, + 0xa9, 0x4d, 0xbd, 0x50, 0xd8, 0xaa, 0x46, 0x5b, + 0x5d, 0x8c, 0x01, 0x2a, 0x0c, 0x7e, 0x1d, 0x4e }; return pw_rtsp_client_url_send(impl->rtsp, "/auth-setup", "POST", &impl->headers->dict, "application/octet-stream", content, sizeof(content), diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c index f4d3fa3b8..cbd3ee994 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -390,7 +390,7 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, res = -EINVAL; goto out; } - pw_properties_set(props, PW_KEY_FORMAT_DSP, "32 bit raw UMP"); + pw_properties_set(props, PW_KEY_FORMAT_DSP, "8 bit raw midi"); impl->stride = impl->format_info->size; impl->rate = pw_properties_get_uint32(props, "midi.rate", 10000); if (impl->rate == 0) diff --git a/src/modules/module-vban/stream.c b/src/modules/module-vban/stream.c index efa5af370..10eb34a82 100644 --- a/src/modules/module-vban/stream.c +++ b/src/modules/module-vban/stream.c @@ -307,7 +307,7 @@ struct vban_stream *vban_stream_new(struct pw_core *core, res = -EINVAL; goto out; } - pw_properties_set(props, PW_KEY_FORMAT_DSP, "32 bit raw UMP"); + pw_properties_set(props, PW_KEY_FORMAT_DSP, "8 bit raw midi"); impl->stride = impl->format_info->size; impl->rate = pw_properties_get_uint32(props, "midi.rate", 10000); if (impl->rate == 0) diff --git a/src/pipewire/filter.c b/src/pipewire/filter.c index 519c1e2f7..03706e98a 100644 --- a/src/pipewire/filter.c +++ b/src/pipewire/filter.c @@ -1855,8 +1855,10 @@ void *pw_filter_add_port(struct pw_filter *filter, add_control_dsp_port_params(impl, p, 1u << SPA_CONTROL_Midi); else if (spa_streq(str, "8 bit raw control")) add_control_dsp_port_params(impl, p, 0); - else if (spa_streq(str, "32 bit raw UMP")) + else if (spa_streq(str, "32 bit raw UMP")) { add_control_dsp_port_params(impl, p, 1u << SPA_CONTROL_UMP); + pw_properties_set(props, PW_KEY_FORMAT_DSP, "8 bit raw midi"); + } } /* then override with user provided if any */ if (update_params(impl, p, SPA_ID_INVALID, params, n_params) < 0) diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index 3df092ad8..a9de783e3 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -388,11 +388,10 @@ static int do_negotiate(struct pw_impl_link *this) } } - pw_log_pod(SPA_LOG_LEVEL_DEBUG, format); - SPA_POD_OBJECT_ID(format) = SPA_PARAM_Format; pw_log_debug("%p: doing set format %p fixated:%d", this, format, spa_pod_is_fixated(format)); + pw_log_pod(SPA_LOG_LEVEL_INFO, format); if (out_state == PW_IMPL_PORT_STATE_CONFIGURE) { pw_log_debug("%p: doing set format on output", this); @@ -405,7 +404,7 @@ static int do_negotiate(struct pw_impl_link *this) goto error; } if (SPA_RESULT_IS_ASYNC(res)) { - pw_log_info("output set format %d", res); + pw_log_debug("output set format %d", res); busy_id = pw_work_queue_add(impl->work, &this->output_link, spa_node_sync(output->node->node, res), complete_ready, this); @@ -425,7 +424,7 @@ static int do_negotiate(struct pw_impl_link *this) goto error; } if (SPA_RESULT_IS_ASYNC(res2)) { - pw_log_info("input set format %d", res2); + pw_log_debug("input set format %d", res2); busy_id = pw_work_queue_add(impl->work, &this->input_link, spa_node_sync(input->node->node, res2), complete_ready, this); diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index 351f98b06..e5636d561 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -660,19 +660,20 @@ static int node_send_command(void *object, const struct spa_command *command) struct resource_data *data = object; struct pw_impl_node *node = data->node; uint32_t id = SPA_NODE_COMMAND_ID(command); + int res; pw_log_debug("%p: got command %d (%s)", node, id, spa_debug_type_find_name(spa_type_node_command_id, id)); switch (id) { case SPA_NODE_COMMAND_Suspend: - suspend_node(node); + res = suspend_node(node); break; default: - spa_node_send_command(node->node, command); + res = spa_node_send_command(node->node, command); break; } - return 0; + return res; } static const struct pw_node_methods node_methods = { diff --git a/src/pipewire/loop.h b/src/pipewire/loop.h index 9de1cbf3f..8bb762170 100644 --- a/src/pipewire/loop.h +++ b/src/pipewire/loop.h @@ -141,7 +141,7 @@ pw_loop_add_signal(struct pw_loop *object, int signal_number, PW_API_LOOP_IMPL void pw_loop_destroy_source(struct pw_loop *object, struct spa_source *source) { - return spa_loop_utils_destroy_source(object->utils, source); + spa_loop_utils_destroy_source(object->utils, source); } /** diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 5e5aed3b7..f5684d5e7 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -446,7 +446,7 @@ static inline void call_process(struct stream *impl) if (impl->n_buffers == 0 || (impl->direction == SPA_DIRECTION_OUTPUT && update_requested(impl) <= 0)) return; - if (impl->rt_callbacks.funcs) + if (impl->rt_callbacks.funcs && !impl->disconnecting) spa_callbacks_call_fast(&impl->rt_callbacks, struct pw_stream_events, process, 0); } @@ -2053,7 +2053,7 @@ pw_stream_connect(struct pw_stream *stream, pw_properties_set(impl->port_props, PW_KEY_FORMAT_DSP, str); else if (impl->media_type == SPA_MEDIA_TYPE_application && impl->media_subtype == SPA_MEDIA_SUBTYPE_control) - pw_properties_set(impl->port_props, PW_KEY_FORMAT_DSP, "32 bit raw UMP"); + pw_properties_set(impl->port_props, PW_KEY_FORMAT_DSP, "8 bit raw midi"); if (pw_properties_get(impl->port_props, PW_KEY_PORT_GROUP) == NULL) pw_properties_set(impl->port_props, PW_KEY_PORT_GROUP, "stream.0"); diff --git a/src/tools/midifile.c b/src/tools/midifile.c index 80c5bbb1c..a58a1bc97 100644 --- a/src/tools/midifile.c +++ b/src/tools/midifile.c @@ -940,22 +940,58 @@ static int dump_event_ump(FILE *out, const struct midi_event *ev) dump_mem(out, "Utility", ev->data, ev->size); break; case 0x1: - dump_mem(out, "SysRT", ev->data, ev->size); + { + uint8_t b[3] = { (d[0] >> 16) & 0x7f, (d[0] >> 8) & 0x7f, d[0] & 0x7f }; + switch (b[0]) { + case 0xf1: + fprintf(out, "MIDI Time Code Quarter Frame: type %d values %d", + b[1] >> 4, b[1] & 0xf); + break; + case 0xf2: + fprintf(out, "Song Position Pointer: value %d", + ((int)b[2] << 7 | b[1])); + break; + case 0xf3: + fprintf(out, "Song Select: value %d", b[1]); + break; + case 0xf6: + fprintf(out, "Tune Request"); + break; + case 0xf8: + fprintf(out, "Timing Clock"); + break; + case 0xfa: + fprintf(out, "Start Sequence"); + break; + case 0xfb: + fprintf(out, "Continue Sequence"); + break; + case 0xfc: + fprintf(out, "Stop Sequence"); + break; + case 0xfe: + fprintf(out, "Active Sensing"); + break; + case 0xff: + fprintf(out, "System Reset"); + break; + default: + dump_mem(out, "SysRT", ev->data, ev->size); + break; + } break; + } case 0x2: { struct midi_event ev1; - uint8_t msg[4]; + uint8_t b[3] = { d[0] >> 16, d[0] >> 8, d[0] }; ev1 = *ev; - msg[0] = (d[0] >> 16); - msg[1] = (d[0] >> 8); - msg[2] = (d[0]); - if (msg[0] >= 0xc0 && msg[0] <= 0xdf) + if (b[0] >= 0xc0 && b[0] <= 0xdf) ev1.size = 2; else ev1.size = 3; - ev1.data = msg; + ev1.data = b; dump_event_midi1(out, &ev1); break; } diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index f485a08f1..928afed0e 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -1343,7 +1343,7 @@ static void format_from_filename(SF_INFO *info, const char *filename) } } if (format == -1) - format = SF_FORMAT_WAV; + format = spa_streq(filename, "-") ? SF_FORMAT_AU : SF_FORMAT_WAV; if (format == SF_FORMAT_WAV && info->channels > 2) format = SF_FORMAT_WAVEX; @@ -1436,6 +1436,21 @@ static int setup_encodedfile(struct data *data) } #endif +static const char *endianness_to_name(int format) +{ + switch (format & SF_FORMAT_ENDMASK) { + case SF_ENDIAN_FILE: + return "Default Endian"; + case SF_ENDIAN_LITTLE: + return "Little Endian"; + case SF_ENDIAN_BIG: + return "Big Endian"; + case SF_ENDIAN_CPU: + return "CPU Endian"; + } + return "unknown"; +} + static int setup_sndfile(struct data *data) { const struct format_info *fi = NULL; @@ -1473,9 +1488,21 @@ static int setup_sndfile(struct data *data) return -EIO; } - if (data->verbose) - fprintf(stderr, "sndfile: opened file \"%s\" format %08x channels:%d rate:%d\n", - data->filename, info.format, info.channels, info.samplerate); + if (data->verbose) { + SF_FORMAT_INFO ti, sti; + spa_zero(ti); + ti.format = info.format & SF_FORMAT_TYPEMASK; + if (sf_command(NULL, SFC_GET_FORMAT_INFO, &ti, sizeof(ti)) != 0) + ti.name = "unknown"; + spa_zero(sti); + sti.format = info.format & SF_FORMAT_SUBMASK; + if (sf_command(NULL, SFC_GET_FORMAT_INFO, &sti, sizeof(sti)) != 0) + sti.name = "unknown"; + + fprintf(stderr, "sndfile: opened file \"%s\" format \"%s %s %s\" channels:%d rate:%d\n", + data->filename, endianness_to_name(info.format), + ti.name, sti.name, info.channels, info.samplerate); + } if (data->channels > 0 && info.channels != (int)data->channels) { fprintf(stderr, "sndfile: given channels (%u) don't match file channels (%d)\n", data->channels, info.channels); @@ -1511,7 +1538,6 @@ static int setup_sndfile(struct data *data) /* try native format first, else decode to float */ if ((fi = format_info_by_sf_format(info.format)) == NULL) fi = format_info_by_sf_format(SF_FORMAT_FLOAT); - } if (fi == NULL) return -EIO; @@ -1959,7 +1985,7 @@ int main(int argc, char *argv[]) SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); - pw_properties_set(data.props, PW_KEY_FORMAT_DSP, "32 bit raw UMP"); + pw_properties_set(data.props, PW_KEY_FORMAT_DSP, "8 bit raw midi"); break; case TYPE_DSD: { diff --git a/src/tools/pw-loopback.c b/src/tools/pw-loopback.c index 65a613c15..1d76eb284 100644 --- a/src/tools/pw-loopback.c +++ b/src/tools/pw-loopback.c @@ -103,6 +103,7 @@ int main(int argc, char *argv[]) { "group", required_argument, NULL, 'g' }, { "name", required_argument, NULL, 'n' }, { "channels", required_argument, NULL, 'c' }, + { "channel-map", required_argument, NULL, 'm' }, { "latency", required_argument, NULL, 'l' }, { "delay", required_argument, NULL, 'd' }, { "capture", required_argument, NULL, 'C' },