mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-31 22:25:38 -04:00
Compare commits
98 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f79bcae5d | ||
|
|
fc9aa51619 | ||
|
|
62a719d71a | ||
|
|
eda42ef2fc | ||
|
|
c507c4b0ff | ||
|
|
06941f7315 | ||
|
|
76db05a0f8 | ||
|
|
483b59a9d9 | ||
|
|
331d5e0351 | ||
|
|
b24ceda8b2 | ||
|
|
6115a240d1 | ||
|
|
2a3f92e67f | ||
|
|
61168adb92 | ||
|
|
603aae2dc8 | ||
|
|
a968027bdc | ||
|
|
9207fea992 | ||
|
|
a66377cf42 | ||
|
|
e7610de305 | ||
|
|
2a8d5c1f40 | ||
|
|
6b9f340219 | ||
|
|
f5c9944e61 | ||
|
|
835d8b7801 | ||
|
|
f7f2e3e52a | ||
|
|
e040430967 | ||
|
|
4e0137696f | ||
|
|
c3e08ad9c9 | ||
|
|
515de13b8a | ||
|
|
0ea1bc3841 | ||
|
|
b9bad88eed | ||
|
|
85479cf203 | ||
|
|
c2c255b5f9 | ||
|
|
dc1f73ceaf | ||
|
|
f3cb35dad3 | ||
|
|
5a00232cdf | ||
|
|
3a231a807d | ||
|
|
3aaf91d9c6 | ||
|
|
d28d195745 | ||
|
|
28109587fd | ||
|
|
9b52c07871 | ||
|
|
3d33acce1d | ||
|
|
1cbe4e1782 | ||
|
|
750b88c3ca | ||
|
|
37dbf93cc4 | ||
|
|
2d4af3ced5 | ||
|
|
5d741a2b3c | ||
|
|
7d27668fb2 | ||
|
|
0135baa60f | ||
|
|
49c4c44dce | ||
|
|
957bde02cd | ||
|
|
f857a50734 | ||
|
|
8c1e1ea17f | ||
|
|
63bf21d7b8 | ||
|
|
3abda54d80 | ||
|
|
d20a1523b6 | ||
|
|
ac42f55916 | ||
|
|
a0be431c7f | ||
|
|
35591922ce | ||
|
|
f120b595f0 | ||
|
|
07f49a7559 | ||
|
|
cf5db17aa6 | ||
|
|
f0a432118a | ||
|
|
f571253ff3 | ||
|
|
99b94015a7 | ||
|
|
48a959bf2e | ||
|
|
1388f16c47 | ||
|
|
ce6d2fce9a | ||
|
|
98e7ae39c6 | ||
|
|
a9eba98e8e | ||
|
|
2d26fbcddf | ||
|
|
8d7175a1e7 | ||
|
|
81408597f4 | ||
|
|
58c412c9dc | ||
|
|
081cb6848e | ||
|
|
4176caca30 | ||
|
|
b87764bd07 | ||
|
|
2eb8cf5dc7 | ||
|
|
fb43eb8ee2 | ||
|
|
d58d2a2375 | ||
|
|
ba3a36b3d1 | ||
|
|
e16c184228 | ||
|
|
9696e2c62b | ||
|
|
b4d88143da | ||
|
|
15e200846a | ||
|
|
f4417f4e2c | ||
|
|
edffc87ebf | ||
|
|
f7b1ba2a40 | ||
|
|
4f077e1f27 | ||
|
|
2167945eff | ||
|
|
2397a984f7 | ||
|
|
17eaf83fe8 | ||
|
|
8d55213288 | ||
|
|
06438cbc9a | ||
|
|
ae16463c3c | ||
|
|
78981a8d9b | ||
|
|
901401e6f1 | ||
|
|
5c1be35018 | ||
|
|
79d232c1f2 | ||
|
|
277b80c903 |
61 changed files with 1575 additions and 574 deletions
136
NEWS
136
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
41
meson.build
41
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'
|
||||
|
|
|
|||
|
|
@ -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<<SPA_CONTROL_UMP;
|
||||
if (p->object->port.type_id == TYPE_ID_OSC)
|
||||
types |= 1u<<SPA_CONTROL_OSC;
|
||||
if (types != 0)
|
||||
spa_pod_builder_add(b,
|
||||
SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int(types),
|
||||
0);
|
||||
|
||||
*param = spa_pod_builder_pop(b, &f);
|
||||
break;
|
||||
}
|
||||
case TYPE_ID_VIDEO:
|
||||
*param = spa_pod_builder_add_object(b,
|
||||
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
|
||||
|
|
@ -3429,24 +3483,6 @@ static const char* type_to_string(jack_port_type_id_t type_id)
|
|||
}
|
||||
}
|
||||
|
||||
static const char* type_to_format_dsp(jack_port_type_id_t type_id)
|
||||
{
|
||||
switch(type_id) {
|
||||
case TYPE_ID_AUDIO:
|
||||
return JACK_DEFAULT_AUDIO_TYPE;
|
||||
case TYPE_ID_VIDEO:
|
||||
return JACK_DEFAULT_VIDEO_TYPE;
|
||||
case TYPE_ID_OSC:
|
||||
return JACK_DEFAULT_OSC_TYPE;
|
||||
case TYPE_ID_MIDI:
|
||||
return JACK_DEFAULT_MIDI_TYPE;
|
||||
case TYPE_ID_UMP:
|
||||
return JACK_DEFAULT_UMP_TYPE;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool type_is_dsp(jack_port_type_id_t type_id)
|
||||
{
|
||||
switch(type_id) {
|
||||
|
|
@ -5507,7 +5543,7 @@ jack_port_t * jack_port_register (jack_client_t *client,
|
|||
|
||||
spa_list_init(&p->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;
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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'))
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<<SPA_CONTROL_UMP));
|
||||
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control));
|
||||
break;
|
||||
|
||||
case SPA_PARAM_Format:
|
||||
|
|
@ -541,8 +540,7 @@ impl_node_port_enum_params(void *object, int seq,
|
|||
param = spa_pod_builder_add_object(&b,
|
||||
SPA_TYPE_OBJECT_Format, SPA_PARAM_Format,
|
||||
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_Int(1u<<SPA_CONTROL_UMP));
|
||||
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control));
|
||||
break;
|
||||
|
||||
case SPA_PARAM_Buffers:
|
||||
|
|
@ -635,7 +633,7 @@ static int port_set_format(void *object, struct seq_port *port,
|
|||
port->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"=<bool, default false>] "
|
||||
"[ api.alsa.seq.min-pool=<min-pool, default 500>] "
|
||||
"[ api.alsa.seq.max-pool=<max-pool, default 2000>]"
|
||||
"[ api.alsa.seq.ump = <boolean> ]"
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,12 @@ extern "C" {
|
|||
#include <stddef.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
#ifdef HAVE_ALSA_UMP
|
||||
#include <alsa/ump_msg.h>
|
||||
#endif
|
||||
|
||||
#include <spa/support/plugin.h>
|
||||
#include <spa/support/loop.h>
|
||||
|
|
@ -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];
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
4
src/daemon/pipewire.conf.avail/50-raop.conf.in
Normal file
4
src/daemon/pipewire.conf.avail/50-raop.conf.in
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
context.modules = [
|
||||
# Use mDNS to detect and load module-raop-sink
|
||||
{ name = libpipewire-module-raop-discover }
|
||||
]
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
conf_files = [
|
||||
'10-rates.conf',
|
||||
'20-upmix.conf',
|
||||
'50-raop.conf',
|
||||
]
|
||||
|
||||
foreach c : conf_files
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
[Unit]
|
||||
Description=PipeWire Multimedia System Sockets
|
||||
ConditionUser=!root
|
||||
|
||||
[Socket]
|
||||
Priority=6
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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<<SPA_DATA_MemFd) |
|
||||
(1<<SPA_DATA_MemPtr)),
|
||||
0);
|
||||
port_params[0] = spa_pod_builder_pop (&b, &f);
|
||||
port_params[n_params++] = spa_pod_builder_pop (&b, &f);
|
||||
|
||||
port_params[1] = spa_pod_builder_add_object (&b,
|
||||
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_Header),
|
||||
SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_header)));
|
||||
|
||||
port_params[2] = 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)));
|
||||
if (sink->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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 = <str>`: interface name to use
|
||||
* - `net.ip =<str>`: multicast IP address, default "225.3.19.154"
|
||||
* - `net.port =<int>`: control port, default 19000
|
||||
|
|
@ -63,11 +85,11 @@
|
|||
* - `source.ip =<str>`: IP address to bind to, default "0.0.0.0"
|
||||
* - `source.port =<int>`: 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=<remote> ) " \
|
||||
"( driver.mode=<sink|source|duplex> ) " \
|
||||
"( driver.mode=<sink|source|duplex> ) " \
|
||||
"( local.ifname=<interface name> ) " \
|
||||
"( net.ip=<ip address to use, default 225.3.19.154> ) " \
|
||||
"( net.port=<port to use, default 19000> ) " \
|
||||
|
|
@ -151,11 +171,11 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
|
|||
"( source.ip=<ip address to bind, default 0.0.0.0> ) " \
|
||||
"( source.port=<port to bind, default 0> ) " \
|
||||
"( netjack2.client-name=<name of the NETJACK2 client> ) " \
|
||||
"( netjack2.save=<bool, save ports> ) " \
|
||||
"( netjack2.latency=<latency in cycles, default 2> ) " \
|
||||
"( midi.ports=<number of midi ports> ) " \
|
||||
"( audio.channels=<number of channels> ) " \
|
||||
"( audio.position=<channel map> ) " \
|
||||
"( audio.ports=<number of midi ports, default -1> ) " \
|
||||
"( midi.ports=<number of midi ports, default -1> ) " \
|
||||
"( audio.channels=<number of channels, default 0> ) " \
|
||||
"( audio.position=<channel map, default null> ) " \
|
||||
"( source.props=<properties> ) " \
|
||||
"( sink.props=<properties> ) "
|
||||
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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=<remote> ) " \
|
||||
|
|
@ -151,8 +167,8 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
|
|||
"( netjack2.connect=<autoconnect ports, default false> ) " \
|
||||
"( netjack2.sample-rate=<sampl erate, default 48000> ) "\
|
||||
"( netjack2.period-size=<period size, default 1024> ) " \
|
||||
"( midi.ports=<number of midi ports> ) " \
|
||||
"( audio.channels=<number of channels> ) " \
|
||||
"( midi.ports=<number of midi ports, default 1> ) " \
|
||||
"( audio.channels=<number of channels, default 2> ) " \
|
||||
"( audio.position=<channel map> ) " \
|
||||
"( source.props=<properties> ) " \
|
||||
"( sink.props=<properties> ) "
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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' },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue