mirror of
				https://gitlab.freedesktop.org/pipewire/pipewire.git
				synced 2025-11-03 09:01:54 -05: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