Projects
Essentials
pipewire-aptx
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 13
View file
pipewire-0.3.54.tar.gz/src/daemon/filter-chain/duplicate-FL.conf
Deleted
@@ -1,49 +0,0 @@ -# An example filter chain for duplicating the FL channel -# to FL and FR. -# -# Copy this file into a conf.d/ directory -# -context.modules = - { name = libpipewire-module-filter-chain - args = { - node.description = "Remap example" - media.name = "Remap example" - filter.graph = { - nodes = - { - name = copyIL - type = builtin - label = copy - } - { - name = copyOL - type = builtin - label = copy - } - { - name = copyOR - type = builtin - label = copy - } - - links = - # we can only tee from nodes, not inputs so we need - # to copy the inputs and then tee. - { output = "copyIL:Out" input = "copyOL:In" } - { output = "copyIL:Out" input = "copyOR:In" } - - inputs = "copyIL:In" - outputs = "copyOL:Out" "copyOR:Out" - } - capture.props = { - node.name = "remap_input.remap-FL-to-FL-FR" - audio.position = FL - stream.dont-remix = true - } - playback.props = { - node.name = "remap_output.remap-FL-to-FL-FR" - audio.position = FL FR - } - } - } -
View file
pipewire-0.3.54.tar.gz/NEWS -> pipewire-0.3.56.tar.gz/NEWS
Changed
@@ -1,3 +1,113 @@ +# PipeWire 0.3.56 (2022-07-19) + +This is a quick bugfix release that is API and ABI compatible with previous +0.3.x releases. + +## Highlights + - A critical bug that could crash JACK apps was fixed. + - Some more regressions in audiomixer were fixed. This should fix crackling + and stuttering in some cases as well as some channel mapping regressions. + - A bug in the alsa plugin was fixed that could cause stuttering in VMs. + - Bluetooth sources should have improved latency and rate control. + - Many more bugfixes and improvements. + + +## Modules + - An experimental AVB module was added. It can expose PipeWire as an AVB + entity and initiate (broken) streaming between entities. + - module-loopback now handles the cases where the input and output channels + are different without crashing or producing silence. + - The filter-chain module now correctly calculates the output size without + crashing in some cases. It also skips invalid ports instead of crashing. + - Handle and report pthread errors better. + +## SPA + - The resampler qualities were tweaked a little. + - A bug that would sometimes cut off the last part of a buffer was fixed in + the alsa plugin. This could cause broken audio in VMs. (#2536) + - Access to the alsa mixer and devices is now checked more thoroughly. + (#2534) + - The spa-resample tool can now also handle large downsampling rates without + crashing. + - Audioconverter now uses rounding for float to int conversions, which + reduces distortions. Compilation of the c functions was separated and uses + its own optimization flags now. Unit tests were added. (#2543) + - Noise shaping was improved in audioconvert. A new Wannamaker 3 tap shaper + was added. + - Audioconvert now uses a pattern for generating keep alive noise. This + should have much less energy and be even more inaudible. (#2540) + - A channel mapping bug was fixed in audioconvert. Unit tests were added. + - The dsp audio mixer would sometimes not mix enough and cause dropouts. + (#2525) + +## JACK + - A critical bug in the mixer was fixed. It would cause most JACK apps to + segfault at startup. + +## Bluetooth + - A new rate control algorithm was implemented for the sources. + - The media role on HSP/HFP streams is now fixed. + +## Pulse Server + - Add the resampler delay to delay reporting as well. + + +Older versions: + +# PipeWire 0.3.55 (2022-07-12) + +This is a quick bugfix release that is API and ABI compatible with previous +0.3.x releases. + +## Highlights + - Fix some more critical bugs in the new audioconvert and the queueing + in pw-stream that causes stuttering and hickups. + - HFP hardware volumes are now saved and restored. + - Format conversions and mixing was improved. + - Small bug fixes and improvements. + +## PipeWire + - The queueing in pw-stream was improved with support for buffer prefetch + in async mode. + - Add a pw-filter unit test. + +## tools + - pw-midiplay should now work again after improvements in pw-stream. + +## modules + - The RAOP module was improved to support auth_setup. + - The RAOP module should now handle timing packets better. + - Add some more filter-chain examples. + - The filter-chain now has a separate config file with the boilerplate + settings. The examples are now just config snippets that can be dropped + in .conf.d/ directories, such as the filter-chain.conf.d/ one. + - Start suggesting to use target.object instead of node.target in docs + and examples. + +## SPA + - Use the cosh window again for the resampler. It should now + give better resampler quality. (#2483) + - Rework the mixer functions. They were rewritten for higher precision and + better performance. Add unit tests and benchmarks. + - Improve format conversion for 32bits for avoid errors in clang because + of undefined behaviour at extreme ranges. + - Fix a bug in audioconvert where it would not consume the right + amount of samples when the resampler was disabled. This could cause + skipping and hickups. (#2519) + - Fix bug in audioconvert where it would try to convert the input samples + multiple times, causing strange artifacts when upmixing. + - Be more strict about valid JSON floats. + - device.vendor.id and device.product.id should now always show up in + 0xXXXX format and should not be converted to floats in pw-dump anymore. + - Add triangular dither, add unit tests for noise generation, add some + more optimizations. + +## Bluetooth + - HFP and A2DP now expose different routes and thus can have different + volumes. + - HW Volumes for HFP are now synced better. Volume changes from HW buttons + are now also saved. + # PipeWire 0.3.54 (2022-07-07) This is a quick bugfix release that is API and ABI compatible with previous @@ -47,9 +157,6 @@ - The source was rewritten to use a ringbuffer. This avoids regressions caused by audioconvert. -Older versions: - - # PipeWire 0.3.53 (2022-06-30) This is a bugfix release that is API and ABI compatible with previous
View file
pipewire-0.3.54.tar.gz/doc/pipewire-modules.dox -> pipewire-0.3.56.tar.gz/doc/pipewire-modules.dox
Changed
@@ -51,6 +51,7 @@ - \subpage page_module_access - \subpage page_module_adapter +- \subpage page_module_avb - \subpage page_module_client_device - \subpage page_module_client_node - \subpage page_module_echo_cancel
View file
pipewire-0.3.54.tar.gz/doc/tutorial4.c -> pipewire-0.3.56.tar.gz/doc/tutorial4.c
Changed
@@ -96,7 +96,7 @@ pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, - argc > 1 ? (uint32_t)atoi(argv1) : PW_ID_ANY, + PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS,
View file
pipewire-0.3.54.tar.gz/doc/tutorial4.dox -> pipewire-0.3.56.tar.gz/doc/tutorial4.dox
Changed
@@ -118,8 +118,8 @@ pw_main_loop_run(data.loop); \endcode -To connect we specify that we have a `PW_DIRECTION_OUTPUT` stream. `PW_ID_ANY` -means that we are ok with connecting to any consumer. Next we set some flags: +To connect we specify that we have a `PW_DIRECTION_OUTPUT` stream. The third argument +is always `PW_ID_ANY`. Next we set some flags: - `PW_STREAM_FLAG_AUTOCONNECT`: Automatically connect this stream. This instructs the session manager to link us to some consumer.
View file
pipewire-0.3.54.tar.gz/doc/tutorial5.c -> pipewire-0.3.56.tar.gz/doc/tutorial5.c
Changed
@@ -83,19 +83,23 @@ const struct spa_pod *params1; uint8_t buffer1024; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + struct pw_properties *props; pw_init(&argc, &argv); data.loop = pw_main_loop_new(NULL); + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Camera", + NULL); + if (argc > 1) + pw_properties_set(props, PW_KEY_TARGET_OBJECT, argv1); + data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "video-capture", - pw_properties_new( - PW_KEY_MEDIA_TYPE, "Video", - PW_KEY_MEDIA_CATEGORY, "Capture", - PW_KEY_MEDIA_ROLE, "Camera", - NULL), + props, &stream_events, &data); @@ -122,7 +126,7 @@ pw_stream_connect(data.stream, PW_DIRECTION_INPUT, - argc > 1 ? (uint32_t)atoi(argv1) : PW_ID_ANY, + PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS, params, 1);
View file
pipewire-0.3.54.tar.gz/doc/tutorial5.dox -> pipewire-0.3.56.tar.gz/doc/tutorial5.dox
Changed
@@ -23,18 +23,25 @@ Video Capture stream. \code{.c} + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Camera", + NULL); + if (argc > 1) + pw_properties_set(props, PW_KEY_TARGET_OBJECT, argv1); + data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "video-capture", - pw_properties_new( - PW_KEY_MEDIA_TYPE, "Video", - PW_KEY_MEDIA_CATEGORY, "Capture", - PW_KEY_MEDIA_ROLE, "Camera", - NULL), + props, &stream_events, &data); \endcode +We also optionally allow the user to pass the name of the target node where the session +manager is supposed to connect the node. The user may also give the value of the +unique target node serial (`PW_KEY_OBJECT_SERIAL`) as the value. + In addition to the `process` event, we are also going to listen to a new event, `param_changed`: @@ -122,7 +129,7 @@ \code{.c} pw_stream_connect(data.stream, PW_DIRECTION_INPUT, - argc > 1 ? (uint32_t)atoi(argv1) : PW_ID_ANY, + PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS, params, 1); @@ -130,9 +137,8 @@ pw_main_loop_run(data.loop); \endcode -To connect we specify that we have a `PW_DIRECTION_INPUT` stream. `PW_ID_ANY` -means that we are ok with connecting to any producer. We also allow the user -to pass an optional target id. +To connect we specify that we have a `PW_DIRECTION_INPUT` stream. The third +argument is always `PW_ID_ANY`. We're setting the `PW_STREAM_FLAG_AUTOCONNECT` flag to make an automatic connection to a suitable camera and `PW_STREAM_FLAG_MAP_BUFFERS` to let the
View file
pipewire-0.3.54.tar.gz/meson.build -> pipewire-0.3.56.tar.gz/meson.build
Changed
@@ -1,5 +1,5 @@ project('pipewire', 'c' , - version : '0.3.54', + version : '0.3.56', license : 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' , meson_version : '>= 0.59.0', default_options : 'warning_level=3',
View file
pipewire-0.3.54.tar.gz/meson_options.txt -> pipewire-0.3.56.tar.gz/meson_options.txt
Changed
@@ -245,3 +245,7 @@ description: 'Build legacy rtkit module', type: 'boolean', value: 'true') +option('avb', + description: 'Enable AVB code', + type: 'feature', + value: 'auto')
View file
pipewire-0.3.54.tar.gz/pipewire-jack/src/meson.build -> pipewire-0.3.56.tar.gz/pipewire-jack/src/meson.build
Changed
@@ -78,7 +78,7 @@ endif pkgconfig.generate(filebase : 'jack', - libraries : pipewire_jack, pipewire_jackserver, + libraries : pipewire_jack, name : 'jack', description : 'PipeWire JACK API', version : '1.9.17',
View file
pipewire-0.3.54.tar.gz/pipewire-jack/src/metadata.c -> pipewire-0.3.56.tar.gz/pipewire-jack/src/metadata.c
Changed
@@ -210,7 +210,7 @@ pthread_mutex_unlock(&globals.lock); if (c->property_callback && changed > 0) { - pw_log_info("emit %lu %s", subject, key); + pw_log_info("emit %"PRIu64" %s", (uint64_t)subject, key); c->property_callback(subject, key, change, c->property_arg); } return changed;
View file
pipewire-0.3.54.tar.gz/pipewire-jack/src/pipewire-jack.c -> pipewire-0.3.56.tar.gz/pipewire-jack/src/pipewire-jack.c
Changed
@@ -67,7 +67,7 @@ #define JACK_PORT_TYPE_SIZE 32 #define MONITOR_EXT " Monitor" -#define MAX_MIDI_MIX 1024 +#define MAX_MIX 1024 #define MAX_BUFFER_FRAMES 8192 #define MAX_ALIGN 16 @@ -106,9 +106,9 @@ #define OBJECT_CHUNK 8 #define RECYCLE_THRESHOLD 128 -typedef void (*mix2_func) (float *dst, float *src1, float *src2, int n_samples); +typedef void (*mix_func) (float *dst, float *src, uint32_t n_src, bool aligned, uint32_t n_samples); -static mix2_func mix2; +static mix_func mix_function; struct object { struct spa_list link; @@ -736,38 +736,40 @@ #if defined (__SSE__) #include <xmmintrin.h> -static void mix2_sse(float *dst, float *src1, float *src2, int n_samples) +static void mix_sse(float *dst, float *src, uint32_t n_src, bool aligned, uint32_t n_samples) { - int n, unrolled; - __m128 in2; + uint32_t i, n, unrolled; + __m128 in1; - if (SPA_IS_ALIGNED(src1, 16) && - SPA_IS_ALIGNED(src2, 16) && - SPA_IS_ALIGNED(dst, 16)) - unrolled = n_samples / 4; + if (SPA_IS_ALIGNED(dst, 16) && aligned) + unrolled = n_samples & ~3; else unrolled = 0; - for (n = 0; unrolled--; n += 4) { - in0 = _mm_load_ps(&src1n), - in1 = _mm_load_ps(&src2n), - in0 = _mm_add_ps(in0, in1); + for (n = 0; n < unrolled; n += 4) { + in0 = _mm_load_ps(&src0n); + for (i = 1; i < n_src; i++) + in0 = _mm_add_ps(in0, _mm_load_ps(&srcin)); _mm_store_ps(&dstn, in0); } for (; n < n_samples; n++) { - in0 = _mm_load_ss(&src1n), - in1 = _mm_load_ss(&src2n), - in0 = _mm_add_ss(in0, in1); + in0 = _mm_load_ss(&src0n); + for (i = 1; i < n_src; i++) + in0 = _mm_add_ss(in0, _mm_load_ss(&srcin)); _mm_store_ss(&dstn, in0); } } #endif -static void mix2_c(float *dst, float *src1, float *src2, int n_samples) +static void mix_c(float *dst, float *src, uint32_t n_src, bool aligned, uint32_t n_samples) { - int i; - for (i = 0; i < n_samples; i++) - dsti = src1i + src2i; + uint32_t n, i; + for (n = 0; n < n_samples; n++) { + float t = src0n; + for (i = 1; i < n_src; i++) + t += srcin; + dstn = t; + } } SPA_EXPORT @@ -3281,13 +3283,13 @@ support = pw_context_get_support(client->context.context, &n_support); - mix2 = mix2_c; + mix_function = mix_c; cpu_iface = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); if (cpu_iface) { #if defined (__SSE__) uint32_t flags = spa_cpu_get_flags(cpu_iface); if (flags & SPA_CPU_FLAG_SSE) - mix2 = mix2_sse; + mix_function = mix_sse; #endif } client->context.old_thread_utils = @@ -4416,13 +4418,14 @@ { struct mix *mix; struct buffer *b; - int layer = 0; void *ptr = NULL; + float *mix_ptrMAX_MIX, *np; + uint32_t n_ptr = 0; + bool ptr_aligned = true; spa_list_for_each(mix, &p->mix, port_link) { struct spa_data *d; uint32_t offset, size; - void *np; pw_log_trace_fp("%p: port %s mix %d.%d get buffer %d", p->client, p->object->port.name, p->port_id, mix->id, frames); @@ -4436,14 +4439,20 @@ if (size / sizeof(float) < frames) continue; - np = SPA_PTROFF(d->data, offset, void); - if (layer++ == 0) { - ptr = np; - } else { - mix2(p->emptyptr, ptr, np, frames); - ptr = p->emptyptr; - p->zeroed = false; - } + np = SPA_PTROFF(d->data, offset, float); + if (!SPA_IS_ALIGNED(np, 16)) + ptr_aligned = false; + + mix_ptrn_ptr++ = np; + if (n_ptr == MAX_MIX) + break; + } + if (n_ptr == 1) { + ptr = mix_ptr0; + } else if (n_ptr > 1) { + ptr = p->emptyptr; + mix_function(ptr, mix_ptr, n_ptr, ptr_aligned, frames); + p->zeroed = false; } if (ptr == NULL) ptr = init_buffer(p); @@ -4454,7 +4463,7 @@ { struct mix *mix; void *ptr = p->emptyptr; - struct spa_pod_sequence *seqMAX_MIDI_MIX; + struct spa_pod_sequence *seqMAX_MIX; uint32_t n_seq = 0; jack_midi_clear_buffer(ptr); @@ -4478,7 +4487,7 @@ continue; seqn_seq++ = pod; - if (n_seq == MAX_MIDI_MIX) + if (n_seq == MAX_MIX) break; } convert_to_midi(seq, n_seq, ptr, p->client->fix_midi_events);
View file
pipewire-0.3.54.tar.gz/pipewire-v4l2/src/v4l2-func.c -> pipewire-0.3.56.tar.gz/pipewire-v4l2/src/v4l2-func.c
Changed
@@ -22,6 +22,16 @@ * DEALINGS IN THE SOFTWARE. */ + +/* + * We need to export open* etc., but _FORTIFY_SOURCE defines conflicting + * always_inline versions. Disable _FORTIFY_SOURCE for this file, so we + * can define our overrides. + */ +#ifdef _FORTIFY_SOURCE +#undef _FORTIFY_SOURCE +#endif + #include <stdio.h> #include <errno.h> #include <fcntl.h>
View file
pipewire-0.3.54.tar.gz/spa/include/spa/node/node.h -> pipewire-0.3.56.tar.gz/spa/include/spa/node/node.h
Changed
@@ -632,6 +632,12 @@ * * When the node can accept new input in the next cycle, the * SPA_STATUS_NEED_DATA bit will be set. + * + * Note that the node might return SPA_STATUS_NEED_DATA even when + * no input ports have this status. This means that the amount of + * data still available on the input ports is likely not going to + * be enough for the next cycle and the host might need to prefetch + * data for the next cycle. */ int (*process) (void *object); };
View file
pipewire-0.3.54.tar.gz/spa/include/spa/utils/defs.h -> pipewire-0.3.56.tar.gz/spa/include/spa/utils/defs.h
Changed
@@ -131,13 +131,13 @@ ({ \ __typeof__(a) _min_a = (a); \ __typeof__(b) _min_b = (b); \ - SPA_LIKELY(_min_a < _min_b) ? _min_a : _min_b; \ + SPA_LIKELY(_min_a <= _min_b) ? _min_a : _min_b; \ }) #define SPA_MAX(a,b) \ ({ \ __typeof__(a) _max_a = (a); \ __typeof__(b) _max_b = (b); \ - SPA_LIKELY(_max_a > _max_b) ? _max_a : _max_b; \ + SPA_LIKELY(_max_a >= _max_b) ? _max_a : _max_b; \ }) #define SPA_CLAMP(v,low,high) \ ({ \
View file
pipewire-0.3.54.tar.gz/spa/include/spa/utils/json.h -> pipewire-0.3.56.tar.gz/spa/include/spa/utils/json.h
Changed
@@ -240,6 +240,8 @@ static inline int spa_json_parse_float(const char *val, int len, float *result) { char *end; + if (strspn(val, "+-0123456789.Ee") < (size_t)len) + return 0; *result = spa_strtof(val, &end); return len > 0 && end == val + len; }
View file
pipewire-0.3.54.tar.gz/spa/plugins/alsa/alsa-pcm.c -> pipewire-0.3.56.tar.gz/spa/plugins/alsa/alsa-pcm.c
Changed
@@ -1971,13 +1971,14 @@ size_t n_bytes, n_frames; struct buffer *b; struct spa_data *d; - uint32_t i, offs, size; + uint32_t i, offs, size, last_offset; b = spa_list_first(&state->ready, struct buffer, link); d = b->buf->datas; offs = d0.chunk->offset + state->ready_offset; - size = d0.chunk->size - state->ready_offset; + last_offset = d0.chunk->size; + size = last_offset - state->ready_offset; offs = SPA_MIN(offs, d0.maxsize); size = SPA_MIN(d0.maxsize - offs, size); @@ -2003,7 +2004,7 @@ state->ready_offset += n_bytes; - if (state->ready_offset >= size) { + if (state->ready_offset >= last_offset) { spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); state->io->buffer_id = b->id;
View file
pipewire-0.3.54.tar.gz/spa/plugins/alsa/alsa-udev.c -> pipewire-0.3.56.tar.gz/spa/plugins/alsa/alsa-udev.c
Changed
@@ -477,9 +477,14 @@ if ((str = udev_device_get_property_value(dev, "SUBSYSTEM")) && *str) { itemsn_items++ = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_SUBSYSTEM, str); } - if ((str = udev_device_get_property_value(dev, "ID_VENDOR_ID")) && *str) - itemsn_items++ = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_ID, str); - + if ((str = udev_device_get_property_value(dev, "ID_VENDOR_ID")) && *str) { + int32_t val; + if (spa_atoi32(str, &val, 16)) { + char *dec = alloca(12); /* 0xffffffff is max */ + snprintf(dec, 12, "0x%04x", val); + itemsn_items++ = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_ID, dec); + } + } str = udev_device_get_property_value(dev, "ID_VENDOR_FROM_DATABASE"); if (!(str && *str)) { str = udev_device_get_property_value(dev, "ID_VENDOR_ENC"); @@ -494,9 +499,14 @@ if (str && *str) { itemsn_items++ = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_NAME, str); } - if ((str = udev_device_get_property_value(dev, "ID_MODEL_ID")) && *str) - itemsn_items++ = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PRODUCT_ID, str); - + if ((str = udev_device_get_property_value(dev, "ID_MODEL_ID")) && *str) { + int32_t val; + if (spa_atoi32(str, &val, 16)) { + char *dec = alloca(12); /* 0xffffffff is max */ + snprintf(dec, 12, "0x%04x", val); + itemsn_items++ = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PRODUCT_ID, dec); + } + } str = udev_device_get_property_value(dev, "ID_MODEL_FROM_DATABASE"); if (!(str && *str)) { str = udev_device_get_property_value(dev, "ID_MODEL_ENC"); @@ -528,11 +538,35 @@ static bool check_access(struct impl *this, struct device *device) { - char path128; - bool accessible; + char path128, prefix32; + DIR *snd = NULL; + struct dirent *entry; + bool accessible = false; snprintf(path, sizeof(path), "/dev/snd/controlC%u", device->id); - accessible = access(path, R_OK|W_OK) >= 0; + if (access(path, R_OK|W_OK) >= 0 && (snd = opendir("/dev/snd"))) { + /* + * It's possible that controlCX is accessible before pcmCX* or + * the other way around. Return true only if all devices are + * accessible. + */ + + accessible = true; + spa_scnprintf(prefix, sizeof(prefix), "pcmC%uD", device->id); + while ((entry = readdir(snd)) != NULL) { + if (!(entry->d_type == DT_CHR && + spa_strstartswith(entry->d_name, prefix))) + continue; + + snprintf(path, sizeof(path), "/dev/snd/%.32s", entry->d_name); + if (access(path, R_OK|W_OK) < 0) { + accessible = false; + break; + } + } + closedir(snd); + } + if (accessible != device->accessible) spa_log_debug(this->log, "%s accessible:%u", path, accessible); device->accessible = accessible; @@ -645,10 +679,6 @@ /* Device becomes accessible or not busy */ if ((event->mask & (IN_ATTRIB | IN_CLOSE_WRITE))) { bool access; - - if ((event->mask & IN_ATTRIB) && - spa_strstartswith(event->name, "pcm")) - continue; if (sscanf(event->name, "controlC%u", &id) != 1 && sscanf(event->name, "pcmC%uD", &id) != 1) continue;
View file
pipewire-0.3.54.tar.gz/spa/plugins/audioconvert/audioconvert.c -> pipewire-0.3.56.tar.gz/spa/plugins/audioconvert/audioconvert.c
Changed
@@ -628,8 +628,8 @@ param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("dither.noise"), - SPA_PROP_INFO_description, SPA_POD_String("Add dithering noise"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(this->dir1.conv.noise, 0, 16), + SPA_PROP_INFO_description, SPA_POD_String("Add noise bits"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(this->dir1.conv.noise_bits, 0, 16), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 23: @@ -643,7 +643,7 @@ 0); spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0); spa_pod_builder_push_struct(&b, &f1); - for (i = 0; i < SPA_N_ELEMENTS(channelmix_upmix_info); i++) { + for (i = 0; i < SPA_N_ELEMENTS(dither_method_info); i++) { spa_pod_builder_string(&b, dither_method_infoi.label); spa_pod_builder_string(&b, dither_method_infoi.description); } @@ -719,7 +719,7 @@ spa_pod_builder_string(&b, "resample.disable"); spa_pod_builder_bool(&b, p->resample_disabled); spa_pod_builder_string(&b, "dither.noise"); - spa_pod_builder_int(&b, this->dir1.conv.noise); + spa_pod_builder_int(&b, this->dir1.conv.noise_bits); spa_pod_builder_string(&b, "dither.method"); spa_pod_builder_string(&b, dither_method_infothis->dir1.conv.method.label); spa_pod_builder_pop(&b, &f1); @@ -792,7 +792,7 @@ else if (spa_streq(k, "resample.disable")) this->props.resample_disabled = spa_atob(s); else if (spa_streq(k, "dither.noise")) - spa_atou32(s, &this->dir1.conv.noise, 0); + spa_atou32(s, &this->dir1.conv.noise_bits, 0); else if (spa_streq(k, "dither.method")) this->dir1.conv.method = dither_method_from_label(s); else @@ -1452,7 +1452,7 @@ spa_log_debug(this->log, "%p: got converter features %08x:%08x quant:%d:%d" " passthrough:%d remap:%d %s", this, this->cpu_flags, out->conv.cpu_flags, out->conv.method, - out->conv.noise, out->conv.is_passthrough, remap, out->conv.func_name); + out->conv.noise_bits, out->conv.is_passthrough, remap, out->conv.func_name); return 0; } @@ -2246,6 +2246,33 @@ struct spa_io_buffers *io, *ctrlio = NULL; const struct spa_pod_sequence *ctrl = NULL; + /* calculate quantum scale, this is how many samples we need to produce or + * consume. Also update the rate scale, this is sent to the resampler to adjust + * the rate, either when the graph clock changed or when the user adjusted the + * rate. */ + if (SPA_LIKELY(this->io_position)) { + double r = this->rate_scale; + + quant_samples = this->io_position->clock.duration; + if (this->direction == SPA_DIRECTION_INPUT) { + if (this->io_position->clock.rate.denom != this->resample.o_rate) + r = (double) this->io_position->clock.rate.denom / this->resample.o_rate; + else + r = 1.0; + } else { + if (this->io_position->clock.rate.denom != this->resample.i_rate) + r = (double) this->resample.i_rate / this->io_position->clock.rate.denom; + else + r = 1.0; + } + if (this->rate_scale != r) { + spa_log_info(this->log, "scale %f->%f", this->rate_scale, r); + this->rate_scale = r; + } + } + else + quant_samples = this->quantum_limit; + dir = &this->dirSPA_DIRECTION_INPUT; in_passthrough = dir->conv.is_passthrough; max_in = UINT32_MAX; @@ -2335,57 +2362,17 @@ } } - /* calculate quantum scale */ - if (SPA_LIKELY(this->io_position)) { - double r = this->rate_scale; - - quant_samples = this->io_position->clock.duration; - if (this->direction == SPA_DIRECTION_INPUT) { - if (this->io_position->clock.rate.denom != this->resample.o_rate) - r = (double) this->io_position->clock.rate.denom / this->resample.o_rate; - else - r = 1.0; - } else { - if (this->io_position->clock.rate.denom != this->resample.i_rate) - r = (double) this->resample.i_rate / this->io_position->clock.rate.denom; - else - r = 1.0; - } - if (this->rate_scale != r) { - spa_log_info(this->log, "scale %f->%f", this->rate_scale, r); - this->rate_scale = r; - } - } - else - quant_samples = this->quantum_limit; - - if (SPA_UNLIKELY(draining)) - n_samples = SPA_MIN(max_in, this->quantum_limit); - else { - n_samples = max_in - SPA_MIN(max_in, this->in_offset); - } - - resample_passthrough = resample_is_passthrough(this); - + /* calculate how many samples we are going to produce. */ if (this->direction == SPA_DIRECTION_INPUT) { - uint32_t out = resample_update_rate_match(this, resample_passthrough, quant_samples, 0); - if (!in_avail || this->drained) { - spa_log_trace_fp(this->log, "%p: no input drained:%d", this, this->drained); - /* no input, ask for more */ - res |= this->drained ? SPA_STATUS_DRAINED : SPA_STATUS_NEED_DATA; - return res; - } /* in split mode we need to output exactly the size of the * duration so we don't try to flush early */ - n_samples = SPA_MIN(n_samples, out); max_out = quant_samples; flush_out = false; } else { /* in merge mode we consume one duration of samples and * always output the resulting data */ - n_samples = SPA_MIN(n_samples, quant_samples); max_out = this->quantum_limit; - flush_out = flush_in = true; + flush_out = true; } dir = &this->dirSPA_DIRECTION_OUTPUT; @@ -2418,6 +2405,7 @@ dst_datasremap = SPA_PTR_ALIGN(this->scratch, MAX_ALIGN, void); spa_log_trace_fp(this->log, "%p: empty output %d->%d", this, i * port->blocks + j, remap); + max_out = SPA_MIN(max_out, this->empty_size / port->stride); } } } else { @@ -2436,7 +2424,7 @@ volume *= this->props.channel.mute ? 0.0f : this->props.channel.volumesremap; - mon_max = SPA_MIN(bd->maxsize / port->stride, n_samples); + mon_max = SPA_MIN(bd->maxsize / port->stride, max_in); volume_process(&this->volume, bd->data, src_datasremap, volume, mon_max); @@ -2465,6 +2453,39 @@ } } } + + + /* calculate how many samples at most we are going to consume. If we're + * draining, we consume as much as we can. Otherwise we consume what is + * left. */ + if (SPA_UNLIKELY(draining)) + n_samples = SPA_MIN(max_in, this->quantum_limit); + else { + n_samples = max_in - SPA_MIN(max_in, this->in_offset); + } + /* we only need to output the remaining samples */ + n_out = max_out - SPA_MIN(max_out, this->out_offset); + + resample_passthrough = resample_is_passthrough(this); + + /* calculate how many samples we are going to consume. */ + if (this->direction == SPA_DIRECTION_INPUT) { + uint32_t n_in; + /* then figure out how much input samples we need to consume */ + n_in = resample_update_rate_match(this, resample_passthrough, n_out, 0); + if (!in_avail || this->drained) { + spa_log_trace_fp(this->log, "%p: no input drained:%d", this, this->drained); + /* no input, ask for more */ + res |= this->drained ? SPA_STATUS_DRAINED : SPA_STATUS_NEED_DATA; + return res; + } + n_samples = SPA_MIN(n_samples, n_in); + } else { + /* in merge mode we consume one duration of samples */ + n_samples = SPA_MIN(n_samples, quant_samples); + flush_in = true; + } + mix_passthrough = SPA_FLAG_IS_SET(this->mix.flags, CHANNELMIX_FLAG_IDENTITY) && (ctrlport == NULL || ctrlport->ctrl == NULL); @@ -2482,27 +2503,35 @@ dst_remap = (void **)dst_datas; } - n_out = max_out - SPA_MIN(max_out, this->out_offset); - dir = &this->dirSPA_DIRECTION_INPUT; if (!in_passthrough) { if (mix_passthrough && resample_passthrough && out_passthrough) out_datas = (void **)dst_remap; else out_datas = (void **)this->tmp_datas(tmp++) & 1; - } else { - out_datas = (void **)src_datas; - } - if (dir->need_remap) { - for (i = 0; i < dir->conv.n_channels; i++) { - remap_src_datasi = out_datasdir->remapi; - spa_log_trace_fp(this->log, "%p: input remap %d -> %d", this, dir->remapi, i); + + if (dir->need_remap) { + for (i = 0; i < dir->conv.n_channels; i++) { + remap_src_datasi = out_datasdir->remapi; + spa_log_trace_fp(this->log, "%p: input remap %d -> %d", this, dir->remapi, i); + } + } else { + for (i = 0; i < dir->conv.n_channels; i++) + remap_src_datasi = out_datasi; } - out_datas = (void **)remap_src_datas; - } - if (!in_passthrough) { + spa_log_trace_fp(this->log, "%p: input convert %d", this, n_samples); - convert_process(&dir->conv, out_datas, src_datas, n_samples); + convert_process(&dir->conv, remap_src_datas, src_datas, n_samples); + } else { + if (dir->need_remap) { + for (i = 0; i < dir->conv.n_channels; i++) { + remap_src_datasdir->remapi = (void *)src_datasi; + spa_log_trace_fp(this->log, "%p: input remap %d -> %d", this, dir->remapi, i); + } + out_datas = (void **)remap_src_datas; + } else { + out_datas = (void **)src_datas; + } } if (!mix_passthrough) { @@ -2542,8 +2571,8 @@ this->in_offset += in_len; n_samples = out_len; } else { - this->in_offset += n_samples; n_samples = SPA_MIN(n_samples, n_out); + this->in_offset += n_samples; } this->out_offset += n_samples; @@ -2551,7 +2580,7 @@ dir = &this->dirSPA_DIRECTION_OUTPUT; if (dir->need_remap) { for (i = 0; i < dir->conv.n_channels; i++) { - remap_dst_datasi = out_datasdir->remapi; + remap_dst_datasdir->remapi = out_datasi; spa_log_trace_fp(this->log, "%p: output remap %d -> %d", this, i, dir->remapi); } in_datas = (const void**)remap_dst_datas; @@ -2626,9 +2655,10 @@ spa_log_trace_fp(this->log, "%p: no output buffer", this); } } - resample_update_rate_match(this, resample_passthrough, + if (resample_update_rate_match(this, resample_passthrough, max_out - this->out_offset, - max_in - this->in_offset); + max_in - this->in_offset) > 0) + res |= SPA_STATUS_NEED_DATA; return res; }
View file
pipewire-0.3.54.tar.gz/spa/plugins/audioconvert/biquad.c -> pipewire-0.3.56.tar.gz/spa/plugins/audioconvert/biquad.c
Changed
@@ -9,8 +9,6 @@ */ -#include "config.h" - #include <spa/utils/defs.h> #include <math.h>
View file
pipewire-0.3.54.tar.gz/spa/plugins/audioconvert/crossover.c -> pipewire-0.3.56.tar.gz/spa/plugins/audioconvert/crossover.c
Changed
@@ -3,8 +3,6 @@ * found in the LICENSE file. */ -#include "config.h" - #include <float.h> #include <string.h>
View file
pipewire-0.3.54.tar.gz/spa/plugins/audioconvert/fmt-ops-avx2.c -> pipewire-0.3.56.tar.gz/spa/plugins/audioconvert/fmt-ops-avx2.c
Changed
@@ -34,6 +34,15 @@ # define _mm256_setr_m128i(v0, v1) _mm256_set_m128i((v1), (v0)) #endif +#define _MM_CLAMP_PS(r,min,max) \ + _mm_min_ps(_mm_max_ps(r, min), max) + +#define _MM256_CLAMP_PS(r,min,max) \ + _mm256_min_ps(_mm256_max_ps(r, min), max) + +#define _MM_CLAMP_SS(r,min,max) \ + _mm_min_ss(_mm_max_ss(r, min), max) + static void conv_s16_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) @@ -538,8 +547,9 @@ uint32_t n, unrolled; __m128 in1; __m128i out4; - __m128 scale = _mm_set1_ps(S32_SCALE); - __m128 int_max = _mm_set1_ps(S32_MAX); + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_max = _mm_set1_ps(S24_MAX); + __m128 int_min = _mm_set1_ps(S24_MIN); if (SPA_IS_ALIGNED(s0, 16)) unrolled = n_samples & ~3; @@ -548,8 +558,9 @@ for(n = 0; n < unrolled; n += 4) { in0 = _mm_mul_ps(_mm_load_ps(&s0n), scale); - in0 = _mm_min_ps(in0, int_max); - out0 = _mm_cvttps_epi32(in0); + in0 = _MM_CLAMP_PS(in0, int_min, int_max); + out0 = _mm_cvtps_epi32(in0); + out0 = _mm_slli_epi32(out0, 8); out1 = _mm_shuffle_epi32(out0, _MM_SHUFFLE(0, 3, 2, 1)); out2 = _mm_shuffle_epi32(out0, _MM_SHUFFLE(1, 0, 3, 2)); out3 = _mm_shuffle_epi32(out0, _MM_SHUFFLE(2, 1, 0, 3)); @@ -563,8 +574,8 @@ for(; n < n_samples; n++) { in0 = _mm_load_ss(&s0n); in0 = _mm_mul_ss(in0, scale); - in0 = _mm_min_ss(in0, int_max); - *d = _mm_cvtss_si32(in0); + in0 = _MM_CLAMP_SS(in0, int_min, int_max); + *d = _mm_cvtss_si32(in0) << 8; d += n_channels; } } @@ -578,8 +589,9 @@ uint32_t n, unrolled; __m256 in2; __m256i out2, t2; - __m256 scale = _mm256_set1_ps(S32_SCALE); - __m256 int_max = _mm256_set1_ps(S32_MAX); + __m256 scale = _mm256_set1_ps(S24_SCALE); + __m256 int_min = _mm256_set1_ps(S24_MIN); + __m256 int_max = _mm256_set1_ps(S24_MAX); if (SPA_IS_ALIGNED(s0, 32) && SPA_IS_ALIGNED(s1, 32)) @@ -591,11 +603,13 @@ in0 = _mm256_mul_ps(_mm256_load_ps(&s0n), scale); in1 = _mm256_mul_ps(_mm256_load_ps(&s1n), scale); - in0 = _mm256_min_ps(in0, int_max); - in1 = _mm256_min_ps(in1, int_max); + in0 = _MM256_CLAMP_PS(in0, int_min, int_max); + in1 = _MM256_CLAMP_PS(in1, int_min, int_max); - out0 = _mm256_cvttps_epi32(in0); /* a0 a1 a2 a3 a4 a5 a6 a7 */ - out1 = _mm256_cvttps_epi32(in1); /* b0 b1 b2 b3 b4 b5 b6 b7 */ + out0 = _mm256_cvtps_epi32(in0); /* a0 a1 a2 a3 a4 a5 a6 a7 */ + out1 = _mm256_cvtps_epi32(in1); /* b0 b1 b2 b3 b4 b5 b6 b7 */ + out0 = _mm256_slli_epi32(out0, 8); + out1 = _mm256_slli_epi32(out1, 8); t0 = _mm256_unpacklo_epi32(out0, out1); /* a0 b0 a1 b1 a4 b4 a5 b5 */ t1 = _mm256_unpackhi_epi32(out0, out1); /* a2 b2 a3 b3 a6 b6 a7 b7 */ @@ -624,8 +638,9 @@ for(; n < n_samples; n++) { __m128 in2; __m128i out2; - __m128 scale = _mm_set1_ps(S32_SCALE); - __m128 int_max = _mm_set1_ps(S32_MAX); + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 int_max = _mm_set1_ps(S24_MAX); in0 = _mm_load_ss(&s0n); in1 = _mm_load_ss(&s1n); @@ -633,8 +648,9 @@ in0 = _mm_unpacklo_ps(in0, in1); in0 = _mm_mul_ps(in0, scale); - in0 = _mm_min_ps(in0, int_max); - out0 = _mm_cvttps_epi32(in0); + in0 = _MM_CLAMP_PS(in0, int_min, int_max); + out0 = _mm_cvtps_epi32(in0); + out0 = _mm_slli_epi32(out0, 8); _mm_storel_epi64((__m128i*)d, out0); d += n_channels; } @@ -649,8 +665,9 @@ uint32_t n, unrolled; __m256 in4; __m256i out4, t4; - __m256 scale = _mm256_set1_ps(S32_SCALE); - __m256 int_max = _mm256_set1_ps(S32_MAX); + __m256 scale = _mm256_set1_ps(S24_SCALE); + __m256 int_min = _mm256_set1_ps(S24_MIN); + __m256 int_max = _mm256_set1_ps(S24_MAX); if (SPA_IS_ALIGNED(s0, 32) && SPA_IS_ALIGNED(s1, 32) && @@ -666,15 +683,19 @@ in2 = _mm256_mul_ps(_mm256_load_ps(&s2n), scale); in3 = _mm256_mul_ps(_mm256_load_ps(&s3n), scale); - in0 = _mm256_min_ps(in0, int_max); - in1 = _mm256_min_ps(in1, int_max); - in2 = _mm256_min_ps(in2, int_max); - in3 = _mm256_min_ps(in3, int_max); + in0 = _MM256_CLAMP_PS(in0, int_min, int_max); + in1 = _MM256_CLAMP_PS(in1, int_min, int_max); + in2 = _MM256_CLAMP_PS(in2, int_min, int_max); + in3 = _MM256_CLAMP_PS(in3, int_min, int_max); - out0 = _mm256_cvttps_epi32(in0); /* a0 a1 a2 a3 a4 a5 a6 a7 */ - out1 = _mm256_cvttps_epi32(in1); /* b0 b1 b2 b3 b4 b5 b6 b7 */ - out2 = _mm256_cvttps_epi32(in2); /* c0 c1 c2 c3 c4 c5 c6 c7 */ - out3 = _mm256_cvttps_epi32(in3); /* d0 d1 d2 d3 d4 d5 d6 d7 */ + out0 = _mm256_cvtps_epi32(in0); /* a0 a1 a2 a3 a4 a5 a6 a7 */ + out1 = _mm256_cvtps_epi32(in1); /* b0 b1 b2 b3 b4 b5 b6 b7 */ + out2 = _mm256_cvtps_epi32(in2); /* c0 c1 c2 c3 c4 c5 c6 c7 */ + out3 = _mm256_cvtps_epi32(in3); /* d0 d1 d2 d3 d4 d5 d6 d7 */ + out0 = _mm256_slli_epi32(out0, 8); + out1 = _mm256_slli_epi32(out1, 8); + out2 = _mm256_slli_epi32(out2, 8); + out3 = _mm256_slli_epi32(out3, 8); t0 = _mm256_unpacklo_epi32(out0, out1); /* a0 b0 a1 b1 a4 b4 a5 b5 */ t1 = _mm256_unpackhi_epi32(out0, out1); /* a2 b2 a3 b3 a6 b6 a7 b7 */ @@ -699,8 +720,9 @@ for(; n < n_samples; n++) { __m128 in4; __m128i out4; - __m128 scale = _mm_set1_ps(S32_SCALE); - __m128 int_max = _mm_set1_ps(S32_MAX); + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 int_max = _mm_set1_ps(S24_MAX); in0 = _mm_load_ss(&s0n); in1 = _mm_load_ss(&s1n); @@ -712,8 +734,9 @@ in0 = _mm_unpacklo_ps(in0, in1); in0 = _mm_mul_ps(in0, scale); - in0 = _mm_min_ps(in0, int_max); - out0 = _mm_cvttps_epi32(in0); + in0 = _MM_CLAMP_PS(in0, int_min, int_max); + out0 = _mm_cvtps_epi32(in0); + out0 = _mm_slli_epi32(out0, 8); _mm_storeu_si128((__m128i*)d, out0); d += n_channels; } @@ -755,8 +778,8 @@ for(n = 0; n < unrolled; n += 8) { in0 = _mm_mul_ps(_mm_load_ps(&s0n), int_scale); in1 = _mm_mul_ps(_mm_load_ps(&s0n+4), int_scale); - out0 = _mm_cvttps_epi32(in0); - out1 = _mm_cvttps_epi32(in1); + out0 = _mm_cvtps_epi32(in0); + out1 = _mm_cvtps_epi32(in1); out0 = _mm_packs_epi32(out0, out1); d0*n_channels = _mm_extract_epi16(out0, 0); @@ -771,7 +794,7 @@ } for(; n < n_samples; n++) { in0 = _mm_mul_ss(_mm_load_ss(&s0n), int_scale); - in0 = _mm_min_ss(int_max, _mm_max_ss(in0, int_min)); + in0 = _MM_CLAMP_SS(in0, int_min, int_max); *d = _mm_cvtss_si32(in0); d += n_channels; } @@ -798,8 +821,8 @@ in0 = _mm256_mul_ps(_mm256_load_ps(&s0n+0), int_scale); in1 = _mm256_mul_ps(_mm256_load_ps(&s1n+0), int_scale); - out0 = _mm256_cvttps_epi32(in0); /* a0 a1 a2 a3 a4 a5 a6 a7 */ - out1 = _mm256_cvttps_epi32(in1); /* b0 b1 b2 b3 b4 b5 b6 b7 */ + out0 = _mm256_cvtps_epi32(in0); /* a0 a1 a2 a3 a4 a5 a6 a7 */ + out1 = _mm256_cvtps_epi32(in1); /* b0 b1 b2 b3 b4 b5 b6 b7 */ t0 = _mm256_unpacklo_epi32(out0, out1); /* a0 b0 a1 b1 a4 b4 a5 b5 */ t1 = _mm256_unpackhi_epi32(out0, out1); /* a2 b2 a3 b3 a6 b6 a7 b7 */ @@ -825,8 +848,8 @@ in0 = _mm_mul_ss(_mm_load_ss(&s0n), int_scale); in1 = _mm_mul_ss(_mm_load_ss(&s1n), int_scale); - in0 = _mm_min_ss(int_max, _mm_max_ss(in0, int_min)); - in1 = _mm_min_ss(int_max, _mm_max_ss(in1, int_min)); + in0 = _MM_CLAMP_SS(in0, int_min, int_max); + in1 = _MM_CLAMP_SS(in1, int_min, int_max); d0 = _mm_cvtss_si32(in0); d1 = _mm_cvtss_si32(in1); d += n_channels; @@ -858,10 +881,10 @@ in2 = _mm256_mul_ps(_mm256_load_ps(&s2n), int_scale); in3 = _mm256_mul_ps(_mm256_load_ps(&s3n), int_scale); - t0 = _mm256_cvttps_epi32(in0); /* a0 a1 a2 a3 a4 a5 a6 a7 */ - t1 = _mm256_cvttps_epi32(in1); /* b0 b1 b2 b3 b4 b5 b6 b7 */ - t2 = _mm256_cvttps_epi32(in2); /* c0 c1 c2 c3 c4 c5 c6 c7 */ - t3 = _mm256_cvttps_epi32(in3); /* d0 d1 d2 d3 d4 d5 d6 d7 */ + t0 = _mm256_cvtps_epi32(in0); /* a0 a1 a2 a3 a4 a5 a6 a7 */ + t1 = _mm256_cvtps_epi32(in1); /* b0 b1 b2 b3 b4 b5 b6 b7 */ + t2 = _mm256_cvtps_epi32(in2); /* c0 c1 c2 c3 c4 c5 c6 c7 */ + t3 = _mm256_cvtps_epi32(in3); /* d0 d1 d2 d3 d4 d5 d6 d7 */ t0 = _mm256_packs_epi32(t0, t2); /* a0 a1 a2 a3 c0 c1 c2 c3 a4 a5 a6 a7 c4 c5 c6 c7 */ t1 = _mm256_packs_epi32(t1, t3); /* b0 b1 b2 b3 d0 d1 d2 d3 b4 b5 b6 b7 d4 d5 d6 d7 */ @@ -904,10 +927,10 @@ in1 = _mm_mul_ss(_mm_load_ss(&s1n), int_scale); in2 = _mm_mul_ss(_mm_load_ss(&s2n), int_scale); in3 = _mm_mul_ss(_mm_load_ss(&s3n), int_scale); - in0 = _mm_min_ss(int_max, _mm_max_ss(in0, int_min)); - in1 = _mm_min_ss(int_max, _mm_max_ss(in1, int_min)); - in2 = _mm_min_ss(int_max, _mm_max_ss(in2, int_min)); - in3 = _mm_min_ss(int_max, _mm_max_ss(in3, int_min)); + in0 = _MM_CLAMP_SS(in0, int_min, int_max); + in1 = _MM_CLAMP_SS(in1, int_min, int_max); + in2 = _MM_CLAMP_SS(in2, int_min, int_max); + in3 = _MM_CLAMP_SS(in3, int_min, int_max); d0 = _mm_cvtss_si32(in0); d1 = _mm_cvtss_si32(in1); d2 = _mm_cvtss_si32(in2); @@ -956,10 +979,10 @@ in2 = _mm256_mul_ps(_mm256_load_ps(&s2n), int_scale); in3 = _mm256_mul_ps(_mm256_load_ps(&s3n), int_scale); - t0 = _mm256_cvttps_epi32(in0); /* a0 a1 a2 a3 a4 a5 a6 a7 */ - t1 = _mm256_cvttps_epi32(in1); /* b0 b1 b2 b3 b4 b5 b6 b7 */ - t2 = _mm256_cvttps_epi32(in2); /* c0 c1 c2 c3 c4 c5 c6 c7 */ - t3 = _mm256_cvttps_epi32(in3); /* d0 d1 d2 d3 d4 d5 d6 d7 */ + t0 = _mm256_cvtps_epi32(in0); /* a0 a1 a2 a3 a4 a5 a6 a7 */ + t1 = _mm256_cvtps_epi32(in1); /* b0 b1 b2 b3 b4 b5 b6 b7 */ + t2 = _mm256_cvtps_epi32(in2); /* c0 c1 c2 c3 c4 c5 c6 c7 */ + t3 = _mm256_cvtps_epi32(in3); /* d0 d1 d2 d3 d4 d5 d6 d7 */ t0 = _mm256_packs_epi32(t0, t2); /* a0 a1 a2 a3 c0 c1 c2 c3 a4 a5 a6 a7 c4 c5 c6 c7 */ t1 = _mm256_packs_epi32(t1, t3); /* b0 b1 b2 b3 d0 d1 d2 d3 b4 b5 b6 b7 d4 d5 d6 d7 */ @@ -987,10 +1010,10 @@ in1 = _mm_mul_ss(_mm_load_ss(&s1n), int_scale); in2 = _mm_mul_ss(_mm_load_ss(&s2n), int_scale); in3 = _mm_mul_ss(_mm_load_ss(&s3n), int_scale); - in0 = _mm_min_ss(int_max, _mm_max_ss(in0, int_min)); - in1 = _mm_min_ss(int_max, _mm_max_ss(in1, int_min)); - in2 = _mm_min_ss(int_max, _mm_max_ss(in2, int_min)); - in3 = _mm_min_ss(int_max, _mm_max_ss(in3, int_min)); + in0 = _MM_CLAMP_SS(in0, int_min, int_max); + in1 = _MM_CLAMP_SS(in1, int_min, int_max); + in2 = _MM_CLAMP_SS(in2, int_min, int_max); + in3 = _MM_CLAMP_SS(in3, int_min, int_max); d0 = _mm_cvtss_si32(in0); d1 = _mm_cvtss_si32(in1); d2 = _mm_cvtss_si32(in2); @@ -1021,10 +1044,10 @@ in2 = _mm256_mul_ps(_mm256_load_ps(&s0n+8), int_scale); in3 = _mm256_mul_ps(_mm256_load_ps(&s1n+8), int_scale); - out0 = _mm256_cvttps_epi32(in0); /* a0 a1 a2 a3 a4 a5 a6 a7 */ - out1 = _mm256_cvttps_epi32(in1); /* b0 b1 b2 b3 b4 b5 b6 b7 */ - out2 = _mm256_cvttps_epi32(in2); /* a0 a1 a2 a3 a4 a5 a6 a7 */ - out3 = _mm256_cvttps_epi32(in3); /* b0 b1 b2 b3 b4 b5 b6 b7 */ + out0 = _mm256_cvtps_epi32(in0); /* a0 a1 a2 a3 a4 a5 a6 a7 */ + out1 = _mm256_cvtps_epi32(in1); /* b0 b1 b2 b3 b4 b5 b6 b7 */ + out2 = _mm256_cvtps_epi32(in2); /* a0 a1 a2 a3 a4 a5 a6 a7 */ + out3 = _mm256_cvtps_epi32(in3); /* b0 b1 b2 b3 b4 b5 b6 b7 */ t0 = _mm256_unpacklo_epi32(out0, out1); /* a0 b0 a1 b1 a4 b4 a5 b5 */ t1 = _mm256_unpackhi_epi32(out0, out1); /* a2 b2 a3 b3 a6 b6 a7 b7 */ @@ -1047,8 +1070,8 @@ in0 = _mm_mul_ss(_mm_load_ss(&s0n), int_scale); in1 = _mm_mul_ss(_mm_load_ss(&s1n), int_scale); - in0 = _mm_min_ss(int_max, _mm_max_ss(in0, int_min)); - in1 = _mm_min_ss(int_max, _mm_max_ss(in1, int_min)); + in0 = _MM_CLAMP_SS(in0, int_min, int_max); + in1 = _MM_CLAMP_SS(in1, int_min, int_max); d0 = _mm_cvtss_si32(in0); d1 = _mm_cvtss_si32(in1); d += 2;
View file
pipewire-0.3.54.tar.gz/spa/plugins/audioconvert/fmt-ops-c.c -> pipewire-0.3.56.tar.gz/spa/plugins/audioconvert/fmt-ops-c.c
Changed
@@ -33,7 +33,7 @@ #include "fmt-ops.h" #include "law.h" -#define MAKE_COPY(size) \ +#define MAKE_COPY(size) \ void conv_copy ##size## d_c(struct convert *conv, \ void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, \ uint32_t n_samples) \ @@ -55,7 +55,7 @@ MAKE_COPY(32); MAKE_COPY(64); -#define MAKE_D_TO_D(sname,stype,dname,dtype,func) \ +#define MAKE_D_TO_D(sname,stype,dname,dtype,func) \ void conv_ ##sname## d_to_ ##dname## d_c(struct convert *conv, \ void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, \ uint32_t n_samples) \ @@ -65,11 +65,11 @@ const stype *s = srci; \ dtype *d = dsti; \ for (j = 0; j < n_samples; j++) \ - dj = func (sj); \ + dj = func (sj); \ } \ } -#define MAKE_I_TO_I(sname,stype,dname,dtype,func) \ +#define MAKE_I_TO_I(sname,stype,dname,dtype,func) \ void conv_ ##sname## _to_ ##dname## _c(struct convert *conv, \ void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, \ uint32_t n_samples) \ @@ -79,10 +79,10 @@ dtype *d = dst0; \ n_samples *= conv->n_channels; \ for (j = 0; j < n_samples; j++) \ - dj = func (sj); \ + dj = func (sj); \ } -#define MAKE_I_TO_D(sname,stype,dname,dtype,func) \ +#define MAKE_I_TO_D(sname,stype,dname,dtype,func) \ void conv_ ##sname## _to_ ##dname## d_c(struct convert *conv, \ void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, \ uint32_t n_samples) \ @@ -92,11 +92,11 @@ uint32_t i, j, n_channels = conv->n_channels; \ for (j = 0; j < n_samples; j++) { \ for (i = 0; i < n_channels; i++) \ - dij = func (*s++); \ + dij = func (*s++); \ } \ } -#define MAKE_D_TO_I(sname,stype,dname,dtype,func) \ +#define MAKE_D_TO_I(sname,stype,dname,dtype,func) \ void conv_ ##sname## d_to_ ##dname## _c(struct convert *conv, \ void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, \ uint32_t n_samples) \ @@ -106,7 +106,7 @@ uint32_t i, j, n_channels = conv->n_channels; \ for (j = 0; j < n_samples; j++) { \ for (i = 0; i < n_channels; i++) \ - *d++ = func (sij); \ + *d++ = func (sij); \ } \ } @@ -164,8 +164,7 @@ MAKE_I_TO_I(f64, double, f32, float, (float)); MAKE_I_TO_D(f64, double, f32, float, (float)); MAKE_D_TO_I(f64, double, f32, float, (float)); -MAKE_I_TO_D(f64s, double, f32, float, bswap_64); /* FIXME */ - +MAKE_I_TO_D(f64s, uint64_t, f32, float, (float)F64S_TO_F64); /* from f32 */ MAKE_D_TO_D(f32, float, u8, uint8_t, F32_TO_U8); @@ -221,7 +220,7 @@ MAKE_I_TO_I(f32, float, f64, double, (double)); MAKE_I_TO_D(f32, float, f64, double, (double)); MAKE_D_TO_I(f32, float, f64, double, (double)); -MAKE_D_TO_I(f32, float, f64s, double, bswap_32); /* FIXME */ +MAKE_D_TO_I(f32, float, f64s, uint64_t, F64_TO_F64S); static inline int32_t @@ -231,132 +230,154 @@ return (int32_t)(*state); } -static inline void update_dither_c(struct convert *conv, uint32_t n_samples) +static inline void update_noise_c(struct convert *conv, uint32_t n_samples) { uint32_t n; - float *dither = conv->dither, scale = conv->scale; + float *noise = conv->noise, scale = conv->scale; uint32_t *state = &conv->random0; - - for (n = 0; n < n_samples; n++) - dithern = lcnoise(state) * scale; + int32_t *prev = &conv->prev0, old, new; + + switch (conv->noise_method) { + case NOISE_METHOD_RECTANGULAR: + for (n = 0; n < n_samples; n++) + noisen = lcnoise(state) * scale; + break; + case NOISE_METHOD_TRIANGULAR: + for (n = 0; n < n_samples; n++) + noisen = (lcnoise(state) - lcnoise(state)) * scale; + break; + case NOISE_METHOD_TRIANGULAR_HF: + old = *prev; + for (n = 0; n < n_samples; n++) { + new = lcnoise(state); + noisen = (new - old) * scale; + old = new; + } + *prev = old; + break; + case NOISE_METHOD_PATTERN: + old = *prev; + for (n = 0; n < n_samples; n++) + noisen = conv->scale * (1-((old++>>10)&1)); + *prev = old; + break; + } } -#define MAKE_D_dither(dname,dtype,func) \ -void conv_f32d_to_ ##dname## d_dither_c(struct convert *conv, \ +#define MAKE_D_noise(dname,dtype,func) \ +void conv_f32d_to_ ##dname## d_noise_c(struct convert *conv, \ void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, \ uint32_t n_samples) \ { \ - uint32_t i, j, k, chunk, n_channels = conv->n_channels, dither_size = conv->dither_size; \ - float *dither = conv->dither; \ - update_dither_c(conv, SPA_MIN(n_samples, dither_size)); \ + uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ + float *noise = conv->noise; \ + update_noise_c(conv, SPA_MIN(n_samples, noise_size)); \ for (i = 0; i < n_channels; i++) { \ const float *s = srci; \ dtype *d = dsti; \ for (j = 0; j < n_samples;) { \ - chunk = SPA_MIN(n_samples - j, dither_size); \ + chunk = SPA_MIN(n_samples - j, noise_size); \ for (k = 0; k < chunk; k++, j++) \ - dj = func (sj, ditherk); \ + dj = func (sj, noisek); \ } \ } \ } -#define MAKE_I_dither(dname,dtype,func) \ -void conv_f32d_to_ ##dname## _dither_c(struct convert *conv, \ +#define MAKE_I_noise(dname,dtype,func) \ +void conv_f32d_to_ ##dname## _noise_c(struct convert *conv, \ void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, \ uint32_t n_samples) \ { \ const float **s = (const float **) src; \ dtype *d = dst0; \ - uint32_t i, j, k, chunk, n_channels = conv->n_channels, dither_size = conv->dither_size; \ - float *dither = conv->dither; \ - update_dither_c(conv, SPA_MIN(n_samples, dither_size)); \ + uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ + float *noise = conv->noise; \ + update_noise_c(conv, SPA_MIN(n_samples, noise_size)); \ for (j = 0; j < n_samples;) { \ - chunk = SPA_MIN(n_samples - j, dither_size); \ + chunk = SPA_MIN(n_samples - j, noise_size); \ for (k = 0; k < chunk; k++, j++) { \ for (i = 0; i < n_channels; i++) \ - *d++ = func (sij, ditherk); \ + *d++ = func (sij, noisek); \ } \ } \ } -MAKE_D_dither(u8, uint8_t, F32_TO_U8_D); -MAKE_I_dither(u8, uint8_t, F32_TO_U8_D); -MAKE_D_dither(s8, int8_t, F32_TO_S8_D); -MAKE_I_dither(s8, int8_t, F32_TO_S8_D); -MAKE_D_dither(s16, int16_t, F32_TO_S16_D); -MAKE_I_dither(s16, int16_t, F32_TO_S16_D); -MAKE_I_dither(s16s, uint16_t, F32_TO_S16S_D); -MAKE_D_dither(s32, int32_t, F32_TO_S32_D); -MAKE_I_dither(s32, int32_t, F32_TO_S32_D); -MAKE_I_dither(s32s, uint32_t, F32_TO_S32S_D); -MAKE_D_dither(s24, int24_t, F32_TO_S24_D); -MAKE_I_dither(s24, int24_t, F32_TO_S24_D); -MAKE_I_dither(s24s, int24_t, F32_TO_S24_D); -MAKE_D_dither(s24_32, int32_t, F32_TO_S24_32_D); -MAKE_I_dither(s24_32, int32_t, F32_TO_S24_32_D); -MAKE_I_dither(s24_32s, int32_t, F32_TO_S24_32S_D); - - -#define SHAPER5(type,s,scale,offs,sh,min,max,d) \ +MAKE_D_noise(u8, uint8_t, F32_TO_U8_D); +MAKE_I_noise(u8, uint8_t, F32_TO_U8_D); +MAKE_D_noise(s8, int8_t, F32_TO_S8_D); +MAKE_I_noise(s8, int8_t, F32_TO_S8_D); +MAKE_D_noise(s16, int16_t, F32_TO_S16_D); +MAKE_I_noise(s16, int16_t, F32_TO_S16_D); +MAKE_I_noise(s16s, uint16_t, F32_TO_S16S_D); +MAKE_D_noise(s32, int32_t, F32_TO_S32_D); +MAKE_I_noise(s32, int32_t, F32_TO_S32_D); +MAKE_I_noise(s32s, uint32_t, F32_TO_S32S_D); +MAKE_D_noise(s24, int24_t, F32_TO_S24_D); +MAKE_I_noise(s24, int24_t, F32_TO_S24_D); +MAKE_I_noise(s24s, int24_t, F32_TO_S24_D); +MAKE_D_noise(s24_32, int32_t, F32_TO_S24_32_D); +MAKE_I_noise(s24_32, int32_t, F32_TO_S24_32_D); +MAKE_I_noise(s24_32s, int32_t, F32_TO_S24_32S_D); + +#define SHAPER(type,s,scale,offs,sh,min,max,d) \ ({ \ type t; \ - float v = s * scale + offs + \ - - sh->eidx * 2.033f \ - + sh->e(idx - 1) & NS_MASK * 2.165f \ - - sh->e(idx - 2) & NS_MASK * 1.959f \ - + sh->e(idx - 3) & NS_MASK * 1.590f \ - - sh->e(idx - 4) & NS_MASK * 0.6149f; \ - t = (type)SPA_CLAMP(v + d, min, max); \ - idx = (idx + 1) & NS_MASK; \ - sh->eidx = t - v; \ + float v = s * scale + offs; \ + for (n = 0; n < n_ns; n++) \ + v += sh->eidx + n * nsn; \ + t = FTOI(type, v, 1.0f, 0.0f, d, min, max); \ + idx = (idx - 1) & NS_MASK; \ + sh->eidx = sh->eidx + NS_MAX = v - t; \ t; \ }) -#define F32_TO_U8_SH(s,sh,d) SHAPER5(uint8_t, s, U8_SCALE, U8_OFFS, sh, U8_MIN, U8_MAX, d) -#define F32_TO_S8_SH(s,sh,d) SHAPER5(int8_t, s, S8_SCALE, 0, sh, S8_MIN, S8_MAX, d) -#define F32_TO_S16_SH(s,sh,d) SHAPER5(int16_t, s, S16_SCALE, 0, sh, S16_MIN, S16_MAX, d) +#define F32_TO_U8_SH(s,sh,d) SHAPER(uint8_t, s, U8_SCALE, U8_OFFS, sh, U8_MIN, U8_MAX, d) +#define F32_TO_S8_SH(s,sh,d) SHAPER(int8_t, s, S8_SCALE, 0, sh, S8_MIN, S8_MAX, d) +#define F32_TO_S16_SH(s,sh,d) SHAPER(int16_t, s, S16_SCALE, 0, sh, S16_MIN, S16_MAX, d) #define F32_TO_S16S_SH(s,sh,d) bswap_16(F32_TO_S16_SH(s,sh,d)) -#define MAKE_D_shaped(dname,dtype,func) \ +#define MAKE_D_shaped(dname,dtype,func) \ void conv_f32d_to_ ##dname## d_shaped_c(struct convert *conv, \ void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, \ uint32_t n_samples) \ { \ - uint32_t i, j, k, chunk, n_channels = conv->n_channels, dither_size = conv->dither_size; \ - float *dither = conv->dither; \ - update_dither_c(conv, SPA_MIN(n_samples, dither_size)); \ + uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ + const float *noise = conv->noise, *ns = conv->ns; \ + uint32_t n, n_ns = conv->n_ns; \ + update_noise_c(conv, SPA_MIN(n_samples, noise_size)); \ for (i = 0; i < n_channels; i++) { \ const float *s = srci; \ dtype *d = dsti; \ struct shaper *sh = &conv->shaperi; \ uint32_t idx = sh->idx; \ for (j = 0; j < n_samples;) { \ - chunk = SPA_MIN(n_samples - j, dither_size); \ + chunk = SPA_MIN(n_samples - j, noise_size); \ for (k = 0; k < chunk; k++, j++) \ - dj = func (sj, sh, ditherk); \ + dj = func (sj, sh, noisek); \ } \ sh->idx = idx; \ } \ } -#define MAKE_I_shaped(dname,dtype,func) \ +#define MAKE_I_shaped(dname,dtype,func) \ void conv_f32d_to_ ##dname## _shaped_c(struct convert *conv, \ void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, \ uint32_t n_samples) \ { \ dtype *d0 = dst0; \ - uint32_t i, j, k, chunk, n_channels = conv->n_channels, dither_size = conv->dither_size; \ - float *dither = conv->dither; \ - update_dither_c(conv, SPA_MIN(n_samples, dither_size)); \ + uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ + const float *noise = conv->noise, *ns = conv->ns; \ + uint32_t n, n_ns = conv->n_ns; \ + update_noise_c(conv, SPA_MIN(n_samples, noise_size)); \ for (i = 0; i < n_channels; i++) { \ const float *s = srci; \ dtype *d = &d0i; \ struct shaper *sh = &conv->shaperi; \ uint32_t idx = sh->idx; \ for (j = 0; j < n_samples;) { \ - chunk = SPA_MIN(n_samples - j, dither_size); \ + chunk = SPA_MIN(n_samples - j, noise_size); \ for (k = 0; k < chunk; k++, j++) \ - dj*n_channels = func (sj, sh, ditherk); \ + dj*n_channels = func (sj, sh, noisek); \ } \ sh->idx = idx; \ } \ @@ -370,22 +391,22 @@ MAKE_I_shaped(s16, int16_t, F32_TO_S16_SH); MAKE_I_shaped(s16s, uint16_t, F32_TO_S16S_SH); -#define MAKE_DEINTERLEAVE(size,type,func) \ - MAKE_I_TO_D(size,type,size,type,func) - -MAKE_DEINTERLEAVE(8, uint8_t, (uint8_t)); -MAKE_DEINTERLEAVE(16, uint16_t, (uint16_t)); -MAKE_DEINTERLEAVE(24, uint24_t, (uint24_t)); -MAKE_DEINTERLEAVE(32, uint32_t, (uint32_t)); -MAKE_DEINTERLEAVE(32s, uint32_t, bswap_32); -MAKE_DEINTERLEAVE(64, uint64_t, (uint64_t)); - -#define MAKE_INTERLEAVE(size,type,func) \ - MAKE_D_TO_I(size,type,size,type,func) - -MAKE_INTERLEAVE(8, uint8_t, (uint8_t)); -MAKE_INTERLEAVE(16, uint16_t, (uint16_t)); -MAKE_INTERLEAVE(24, uint24_t, (uint24_t)); -MAKE_INTERLEAVE(32, uint32_t, (uint32_t)); -MAKE_INTERLEAVE(32s, uint32_t, bswap_32); -MAKE_INTERLEAVE(64, uint64_t, (uint64_t)); +#define MAKE_DEINTERLEAVE(size1,size2, type,func) \ + MAKE_I_TO_D(size1,type,size2,type,func) + +MAKE_DEINTERLEAVE(8, 8, uint8_t, (uint8_t)); +MAKE_DEINTERLEAVE(16, 16, uint16_t, (uint16_t)); +MAKE_DEINTERLEAVE(24, 24, uint24_t, (uint24_t)); +MAKE_DEINTERLEAVE(32, 32, uint32_t, (uint32_t)); +MAKE_DEINTERLEAVE(32s, 32, uint32_t, bswap_32); +MAKE_DEINTERLEAVE(64, 64, uint64_t, (uint64_t)); + +#define MAKE_INTERLEAVE(size1,size2,type,func) \ + MAKE_D_TO_I(size1,type,size2,type,func) + +MAKE_INTERLEAVE(8, 8, uint8_t, (uint8_t)); +MAKE_INTERLEAVE(16, 16, uint16_t, (uint16_t)); +MAKE_INTERLEAVE(24, 24, uint24_t, (uint24_t)); +MAKE_INTERLEAVE(32, 32, uint32_t, (uint32_t)); +MAKE_INTERLEAVE(32, 32s, uint32_t, bswap_32); +MAKE_INTERLEAVE(64, 64, uint64_t, (uint64_t));
View file
pipewire-0.3.54.tar.gz/spa/plugins/audioconvert/fmt-ops-sse2.c -> pipewire-0.3.56.tar.gz/spa/plugins/audioconvert/fmt-ops-sse2.c
Changed
@@ -26,6 +26,12 @@ #include <emmintrin.h> +#define _MM_CLAMP_PS(r,min,max) \ + _mm_min_ps(_mm_max_ps(r, min), max) + +#define _MM_CLAMP_SS(r,min,max) \ + _mm_min_ss(_mm_max_ss(r, min), max) + static void conv_s16_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) @@ -338,7 +344,7 @@ float *d0 = dst0; uint32_t n, unrolled; __m128i in; - __m128 out, factor = _mm_set1_ps(1.0f / S32_SCALE); + __m128 out, factor = _mm_set1_ps(1.0f / S24_SCALE); if (SPA_IS_ALIGNED(d0, 16)) unrolled = n_samples & ~3; @@ -350,13 +356,14 @@ s1*n_channels, s2*n_channels, s3*n_channels); + in = _mm_srai_epi32(in, 8); out = _mm_cvtepi32_ps(in); out = _mm_mul_ps(out, factor); _mm_store_ps(&d0n, out); s += 4*n_channels; } for(; n < n_samples; n++) { - out = _mm_cvtsi32_ss(factor, s0); + out = _mm_cvtsi32_ss(factor, s0>>8); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0n, out); s += n_channels; @@ -383,8 +390,9 @@ uint32_t n, unrolled; __m128 in1; __m128i out4; - __m128 scale = _mm_set1_ps(S32_SCALE); - __m128 int_max = _mm_set1_ps(S32_MAX); + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 int_max = _mm_set1_ps(S24_MAX); if (SPA_IS_ALIGNED(s0, 16)) unrolled = n_samples & ~3; @@ -393,8 +401,9 @@ for(n = 0; n < unrolled; n += 4) { in0 = _mm_mul_ps(_mm_load_ps(&s0n), scale); - in0 = _mm_min_ps(in0, int_max); - out0 = _mm_cvttps_epi32(in0); + in0 = _MM_CLAMP_PS(in0, int_min, int_max); + out0 = _mm_cvtps_epi32(in0); + out0 = _mm_slli_epi32(out0, 8); out1 = _mm_shuffle_epi32(out0, _MM_SHUFFLE(0, 3, 2, 1)); out2 = _mm_shuffle_epi32(out0, _MM_SHUFFLE(1, 0, 3, 2)); out3 = _mm_shuffle_epi32(out0, _MM_SHUFFLE(2, 1, 0, 3)); @@ -408,8 +417,8 @@ for(; n < n_samples; n++) { in0 = _mm_load_ss(&s0n); in0 = _mm_mul_ss(in0, scale); - in0 = _mm_min_ss(in0, int_max); - *d = _mm_cvtss_si32(in0); + in0 = _MM_CLAMP_SS(in0, int_min, int_max); + *d = _mm_cvtss_si32(in0) << 8; d += n_channels; } } @@ -423,8 +432,9 @@ uint32_t n, unrolled; __m128 in2; __m128i out2, t2; - __m128 scale = _mm_set1_ps(S32_SCALE); - __m128 int_max = _mm_set1_ps(S32_MAX); + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 int_max = _mm_set1_ps(S24_MAX); if (SPA_IS_ALIGNED(s0, 16) && SPA_IS_ALIGNED(s1, 16)) @@ -436,11 +446,13 @@ in0 = _mm_mul_ps(_mm_load_ps(&s0n), scale); in1 = _mm_mul_ps(_mm_load_ps(&s1n), scale); - in0 = _mm_min_ps(in0, int_max); - in1 = _mm_min_ps(in1, int_max); + in0 = _MM_CLAMP_PS(in0, int_min, int_max); + in1 = _MM_CLAMP_PS(in1, int_min, int_max); - out0 = _mm_cvttps_epi32(in0); - out1 = _mm_cvttps_epi32(in1); + out0 = _mm_cvtps_epi32(in0); + out1 = _mm_cvtps_epi32(in1); + out0 = _mm_slli_epi32(out0, 8); + out1 = _mm_slli_epi32(out1, 8); t0 = _mm_unpacklo_epi32(out0, out1); t1 = _mm_unpackhi_epi32(out0, out1); @@ -458,8 +470,9 @@ in0 = _mm_unpacklo_ps(in0, in1); in0 = _mm_mul_ps(in0, scale); - in0 = _mm_min_ps(in0, int_max); - out0 = _mm_cvttps_epi32(in0); + in0 = _MM_CLAMP_PS(in0, int_min, int_max); + out0 = _mm_cvtps_epi32(in0); + out0 = _mm_slli_epi32(out0, 8); _mm_storel_epi64((__m128i*)d, out0); d += n_channels; } @@ -474,8 +487,9 @@ uint32_t n, unrolled; __m128 in4; __m128i out4; - __m128 scale = _mm_set1_ps(S32_SCALE); - __m128 int_max = _mm_set1_ps(S32_MAX); + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 int_max = _mm_set1_ps(S24_MAX); if (SPA_IS_ALIGNED(s0, 16) && SPA_IS_ALIGNED(s1, 16) && @@ -491,17 +505,21 @@ in2 = _mm_mul_ps(_mm_load_ps(&s2n), scale); in3 = _mm_mul_ps(_mm_load_ps(&s3n), scale); - in0 = _mm_min_ps(in0, int_max); - in1 = _mm_min_ps(in1, int_max); - in2 = _mm_min_ps(in2, int_max); - in3 = _mm_min_ps(in3, int_max); + in0 = _MM_CLAMP_PS(in0, int_min, int_max); + in1 = _MM_CLAMP_PS(in1, int_min, int_max); + in2 = _MM_CLAMP_PS(in2, int_min, int_max); + in3 = _MM_CLAMP_PS(in3, int_min, int_max); _MM_TRANSPOSE4_PS(in0, in1, in2, in3); - out0 = _mm_cvttps_epi32(in0); - out1 = _mm_cvttps_epi32(in1); - out2 = _mm_cvttps_epi32(in2); - out3 = _mm_cvttps_epi32(in3); + out0 = _mm_cvtps_epi32(in0); + out1 = _mm_cvtps_epi32(in1); + out2 = _mm_cvtps_epi32(in2); + out3 = _mm_cvtps_epi32(in3); + out0 = _mm_slli_epi32(out0, 8); + out1 = _mm_slli_epi32(out1, 8); + out2 = _mm_slli_epi32(out2, 8); + out3 = _mm_slli_epi32(out3, 8); _mm_storeu_si128((__m128i*)(d + 0*n_channels), out0); _mm_storeu_si128((__m128i*)(d + 1*n_channels), out1); @@ -520,8 +538,9 @@ in0 = _mm_unpacklo_ps(in0, in1); in0 = _mm_mul_ps(in0, scale); - in0 = _mm_min_ps(in0, int_max); - out0 = _mm_cvttps_epi32(in0); + in0 = _MM_CLAMP_PS(in0, int_min, int_max); + out0 = _mm_cvtps_epi32(in0); + out0 = _mm_slli_epi32(out0, 8); _mm_storeu_si128((__m128i*)d, out0); d += n_channels; } @@ -542,43 +561,83 @@ conv_f32d_to_s32_1s_sse2(conv, &di, &srci, n_channels, n_samples); } -static inline void update_dither_sse2(struct convert *conv, uint32_t n_samples) +/* 32 bit xorshift PRNG, see https://en.wikipedia.org/wiki/Xorshift */ +#define _MM_XORSHIFT_EPI32(r) \ +({ \ + __m128i i, t; \ + i = _mm_load_si128((__m128i*)r); \ + t = _mm_slli_epi32(i, 13); \ + i = _mm_xor_si128(i, t); \ + t = _mm_srli_epi32(i, 17); \ + i = _mm_xor_si128(i, t); \ + t = _mm_slli_epi32(i, 5); \ + i = _mm_xor_si128(i, t); \ + _mm_store_si128((__m128i*)r, i); \ + i; \ +}) + + +static inline void update_noise_sse2(struct convert *conv, uint32_t n_samples) { uint32_t n; const uint32_t *r = SPA_PTR_ALIGN(conv->random, 16, uint32_t); - float *dither = SPA_PTR_ALIGN(conv->dither, 16, float); - __m128 scale = _mm_set1_ps(conv->scale), out1; - __m128i in1, t1; - - for (n = 0; n < n_samples; n += 4) { - /* 32 bit xorshift PRNG, see https://en.wikipedia.org/wiki/Xorshift */ - in0 = _mm_load_si128((__m128i*)r); - t0 = _mm_slli_epi32(in0, 13); - in0 = _mm_xor_si128(in0, t0); - t0 = _mm_srli_epi32(in0, 17); - in0 = _mm_xor_si128(in0, t0); - t0 = _mm_slli_epi32(in0, 5); - in0 = _mm_xor_si128(in0, t0); - _mm_store_si128((__m128i*)r, in0); - - out0 = _mm_cvtepi32_ps(in0); - out0 = _mm_mul_ps(out0, scale); - _mm_store_ps(&dithern, out0); + int32_t *p = SPA_PTR_ALIGN(conv->prev, 16, int32_t), op; + __m128 scale = _mm_set1_ps(conv->scale); + __m128 out1; + float *noise = SPA_PTR_ALIGN(conv->noise, 16, float); + __m128i in1, old1, new1; + + switch (conv->noise_method) { + case DITHER_METHOD_RECTANGULAR: + for (n = 0; n < n_samples; n += 4) { + in0 = _MM_XORSHIFT_EPI32(r); + out0 = _mm_cvtepi32_ps(_MM_XORSHIFT_EPI32(r)); + out0 = _mm_mul_ps(out0, scale); + _mm_store_ps(&noisen, out0); + } + break; + case DITHER_METHOD_TRIANGULAR: + for (n = 0; n < n_samples; n += 4) { + in0 = _mm_sub_epi32( _MM_XORSHIFT_EPI32(r), _MM_XORSHIFT_EPI32(r)); + out0 = _mm_cvtepi32_ps(in0); + out0 = _mm_mul_ps(out0, scale); + _mm_store_ps(&noisen, out0); + } + break; + case DITHER_METHOD_TRIANGULAR_HF: + old0 = _mm_load_si128((__m128i*)p); + for (n = 0; n < n_samples; n += 4) { + new0 = _MM_XORSHIFT_EPI32(r); + in0 = _mm_sub_epi32(old0, new0); + old0 = new0; + out0 = _mm_cvtepi32_ps(in0); + out0 = _mm_mul_ps(out0, scale); + _mm_store_ps(&noisen, out0); + } + _mm_store_si128((__m128i*)p, old0); + break; + case NOISE_METHOD_PATTERN: + op = *p; + for (n = 0; n < n_samples; n++) + noisen = conv->scale * (1-((op++>>10)&1)); + *p = op; + break; } } static void -conv_f32d_to_s32_1s_dither_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, +conv_f32d_to_s32_1s_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const float *s = src; - float *dither = SPA_PTR_ALIGN(conv->dither, 16, float); + float *noise = SPA_PTR_ALIGN(conv->noise, 16, float); int32_t *d = dst; uint32_t n, unrolled; __m128 in1; __m128i out4; - __m128 scale = _mm_set1_ps(S32_SCALE); - __m128 int_max = _mm_set1_ps(S32_MAX); + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 int_max = _mm_set1_ps(S24_MAX); if (SPA_IS_ALIGNED(s, 16)) unrolled = n_samples & ~3; @@ -587,9 +646,10 @@ for(n = 0; n < unrolled; n += 4) { in0 = _mm_mul_ps(_mm_load_ps(&sn), scale); - in0 = _mm_add_ps(in0, _mm_load_ps(&dithern)); - in0 = _mm_min_ps(in0, int_max); - out0 = _mm_cvttps_epi32(in0); + in0 = _mm_add_ps(in0, _mm_load_ps(&noisen)); + in0 = _MM_CLAMP_PS(in0, int_min, int_max); + out0 = _mm_cvtps_epi32(in0); + out0 = _mm_slli_epi32(out0, 8); out1 = _mm_shuffle_epi32(out0, _MM_SHUFFLE(0, 3, 2, 1)); out2 = _mm_shuffle_epi32(out0, _MM_SHUFFLE(1, 0, 3, 2)); out3 = _mm_shuffle_epi32(out0, _MM_SHUFFLE(2, 1, 0, 3)); @@ -603,27 +663,27 @@ for(; n < n_samples; n++) { in0 = _mm_load_ss(&sn); in0 = _mm_mul_ss(in0, scale); - in0 = _mm_add_ss(in0, _mm_load_ss(&dithern)); - in0 = _mm_min_ss(in0, int_max); - *d = _mm_cvtss_si32(in0); + in0 = _mm_add_ss(in0, _mm_load_ss(&noisen)); + in0 = _MM_CLAMP_SS(in0, int_min, int_max); + *d = _mm_cvtss_si32(in0) << 8; d += n_channels; } } void -conv_f32d_to_s32_dither_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, +conv_f32d_to_s32_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, uint32_t n_samples) { int32_t *d = dst0; uint32_t i, k, chunk, n_channels = conv->n_channels; - update_dither_sse2(conv, SPA_MIN(n_samples, conv->dither_size)); + update_noise_sse2(conv, SPA_MIN(n_samples, conv->noise_size)); for(i = 0; i < n_channels; i++) { const float *s = srci; for(k = 0; k < n_samples; k += chunk) { - chunk = SPA_MIN(n_samples - k, conv->dither_size); - conv_f32d_to_s32_1s_dither_sse2(conv, &di + k*n_channels, &sk, n_channels, chunk); + chunk = SPA_MIN(n_samples - k, conv->noise_size); + conv_f32d_to_s32_1s_noise_sse2(conv, &di + k*n_channels, &sk, n_channels, chunk); } } } @@ -796,7 +856,7 @@ } void -conv_32sd_to_32s_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, +conv_32d_to_32s_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, uint32_t n_samples) { int32_t *d = dst0; @@ -964,7 +1024,7 @@ } void -conv_32s_to_32sd_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, +conv_32s_to_32d_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, uint32_t n_samples) { const float *s = src0; @@ -997,15 +1057,15 @@ for(n = 0; n < unrolled; n += 8) { in0 = _mm_mul_ps(_mm_load_ps(&sn), int_scale); in1 = _mm_mul_ps(_mm_load_ps(&sn+4), int_scale); - out0 = _mm_cvttps_epi32(in0); - out1 = _mm_cvttps_epi32(in1); + out0 = _mm_cvtps_epi32(in0); + out1 = _mm_cvtps_epi32(in1); out0 = _mm_packs_epi32(out0, out1); _mm_storeu_si128((__m128i*)(d+0), out0); d += 8; } for(; n < n_samples; n++) { in0 = _mm_mul_ss(_mm_load_ss(&sn), int_scale); - in0 = _mm_min_ss(int_max, _mm_max_ss(in0, int_min)); + in0 = _MM_CLAMP_SS(in0, int_min, int_max); *d++ = _mm_cvtss_si32(in0); } } @@ -1047,8 +1107,8 @@ for(n = 0; n < unrolled; n += 8) { in0 = _mm_mul_ps(_mm_load_ps(&s0n), int_scale); in1 = _mm_mul_ps(_mm_load_ps(&s0n+4), int_scale); - out0 = _mm_cvttps_epi32(in0); - out1 = _mm_cvttps_epi32(in1); + out0 = _mm_cvtps_epi32(in0); + out1 = _mm_cvtps_epi32(in1); out0 = _mm_packs_epi32(out0, out1); d0*n_channels = _mm_extract_epi16(out0, 0); @@ -1063,8 +1123,8 @@ } for(; n < n_samples; n++) { in0 = _mm_mul_ss(_mm_load_ss(&s0n), int_scale); - in0 = _mm_min_ss(int_max, _mm_max_ss(in0, int_min)); - *d = _mm_cvttss_si32(in0); + in0 = _MM_CLAMP_SS(in0, int_min, int_max); + *d = _mm_cvtss_si32(in0); d += n_channels; } } @@ -1092,8 +1152,8 @@ in0 = _mm_mul_ps(_mm_load_ps(&s0n), int_scale); in1 = _mm_mul_ps(_mm_load_ps(&s1n), int_scale); - t0 = _mm_cvttps_epi32(in0); - t1 = _mm_cvttps_epi32(in1); + t0 = _mm_cvtps_epi32(in0); + t1 = _mm_cvtps_epi32(in1); t0 = _mm_packs_epi32(t0, t0); t1 = _mm_packs_epi32(t1, t1); @@ -1112,8 +1172,8 @@ for(; n < n_samples; n++) { in0 = _mm_mul_ss(_mm_load_ss(&s0n), int_scale); in1 = _mm_mul_ss(_mm_load_ss(&s1n), int_scale); - in0 = _mm_min_ss(int_max, _mm_max_ss(in0, int_min)); - in1 = _mm_min_ss(int_max, _mm_max_ss(in1, int_min)); + in0 = _MM_CLAMP_SS(in0, int_min, int_max); + in1 = _MM_CLAMP_SS(in1, int_min, int_max); d0 = _mm_cvtss_si32(in0); d1 = _mm_cvtss_si32(in1); d += n_channels; @@ -1147,10 +1207,10 @@ in2 = _mm_mul_ps(_mm_load_ps(&s2n), int_scale); in3 = _mm_mul_ps(_mm_load_ps(&s3n), int_scale); - t0 = _mm_cvttps_epi32(in0); - t1 = _mm_cvttps_epi32(in1); - t2 = _mm_cvttps_epi32(in2); - t3 = _mm_cvttps_epi32(in3); + t0 = _mm_cvtps_epi32(in0); + t1 = _mm_cvtps_epi32(in1); + t2 = _mm_cvtps_epi32(in2); + t3 = _mm_cvtps_epi32(in3); t0 = _mm_packs_epi32(t0, t2); t1 = _mm_packs_epi32(t1, t3); @@ -1172,10 +1232,10 @@ in1 = _mm_mul_ss(_mm_load_ss(&s1n), int_scale); in2 = _mm_mul_ss(_mm_load_ss(&s2n), int_scale); in3 = _mm_mul_ss(_mm_load_ss(&s3n), int_scale); - in0 = _mm_min_ss(int_max, _mm_max_ss(in0, int_min)); - in1 = _mm_min_ss(int_max, _mm_max_ss(in1, int_min)); - in2 = _mm_min_ss(int_max, _mm_max_ss(in2, int_min)); - in3 = _mm_min_ss(int_max, _mm_max_ss(in3, int_min)); + in0 = _MM_CLAMP_SS(in0, int_min, int_max); + in1 = _MM_CLAMP_SS(in1, int_min, int_max); + in2 = _MM_CLAMP_SS(in2, int_min, int_max); + in3 = _MM_CLAMP_SS(in3, int_min, int_max); d0 = _mm_cvtss_si32(in0); d1 = _mm_cvtss_si32(in1); d2 = _mm_cvtss_si32(in2); @@ -1199,6 +1259,126 @@ conv_f32d_to_s16_1s_sse2(conv, &di, &srci, n_channels, n_samples); } +static void +conv_f32d_to_s16_1s_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src; + int16_t *d = dst; + float *noise = SPA_PTR_ALIGN(conv->noise, 16, float); + uint32_t n, unrolled; + __m128 in2; + __m128i out2; + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + if (SPA_IS_ALIGNED(s0, 16)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in0 = _mm_mul_ps(_mm_load_ps(&s0n), int_scale); + in1 = _mm_mul_ps(_mm_load_ps(&s0n+4), int_scale); + in0 = _mm_add_ps(in0, _mm_load_ps(&noisen)); + in1 = _mm_add_ps(in1, _mm_load_ps(&noisen+4)); + out0 = _mm_cvtps_epi32(in0); + out1 = _mm_cvtps_epi32(in1); + out0 = _mm_packs_epi32(out0, out1); + + d0*n_channels = _mm_extract_epi16(out0, 0); + d1*n_channels = _mm_extract_epi16(out0, 1); + d2*n_channels = _mm_extract_epi16(out0, 2); + d3*n_channels = _mm_extract_epi16(out0, 3); + d4*n_channels = _mm_extract_epi16(out0, 4); + d5*n_channels = _mm_extract_epi16(out0, 5); + d6*n_channels = _mm_extract_epi16(out0, 6); + d7*n_channels = _mm_extract_epi16(out0, 7); + d += 8*n_channels; + } + for(; n < n_samples; n++) { + in0 = _mm_mul_ss(_mm_load_ss(&s0n), int_scale); + in0 = _mm_add_ss(in0, _mm_load_ss(&noisen)); + in0 = _MM_CLAMP_SS(in0, int_min, int_max); + *d = _mm_cvtss_si32(in0); + d += n_channels; + } +} + +void +conv_f32d_to_s16_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, + uint32_t n_samples) +{ + int16_t *d = dst0; + uint32_t i, k, chunk, n_channels = conv->n_channels; + + update_noise_sse2(conv, SPA_MIN(n_samples, conv->noise_size)); + + for(i = 0; i < n_channels; i++) { + const float *s = srci; + for(k = 0; k < n_samples; k += chunk) { + chunk = SPA_MIN(n_samples - k, conv->noise_size); + conv_f32d_to_s16_1s_noise_sse2(conv, &di + k*n_channels, &sk, n_channels, chunk); + } + } +} + +static void +conv_f32_to_s16_1_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, + uint32_t n_samples) +{ + const float *s = src; + int16_t *d = dst; + float *noise = SPA_PTR_ALIGN(conv->noise, 16, float); + uint32_t n, unrolled; + __m128 in2; + __m128i out2; + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + if (SPA_IS_ALIGNED(s, 16)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in0 = _mm_mul_ps(_mm_load_ps(&sn), int_scale); + in1 = _mm_mul_ps(_mm_load_ps(&sn+4), int_scale); + in0 = _mm_add_ps(in0, _mm_load_ps(&noisen)); + in1 = _mm_add_ps(in1, _mm_load_ps(&noisen+4)); + out0 = _mm_cvtps_epi32(in0); + out1 = _mm_cvtps_epi32(in1); + out0 = _mm_packs_epi32(out0, out1); + _mm_storeu_si128((__m128i*)(&dn), out0); + } + for(; n < n_samples; n++) { + in0 = _mm_mul_ss(_mm_load_ss(&sn), int_scale); + in0 = _mm_add_ss(in0, _mm_load_ss(&noisen)); + in0 = _MM_CLAMP_SS(in0, int_min, int_max); + dn = _mm_cvtss_si32(in0); + } +} + +void +conv_f32d_to_s16d_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, + uint32_t n_samples) +{ + uint32_t i, k, chunk, n_channels = conv->n_channels; + + update_noise_sse2(conv, SPA_MIN(n_samples, conv->noise_size)); + + for(i = 0; i < n_channels; i++) { + const float *s = srci; + int16_t *d = dsti; + for(k = 0; k < n_samples; k += chunk) { + chunk = SPA_MIN(n_samples - k, conv->noise_size); + conv_f32_to_s16_1_noise_sse2(conv, &dk, &sk, chunk); + } + } +} + void conv_f32d_to_s16_2_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, uint32_t n_samples) @@ -1224,10 +1404,10 @@ in2 = _mm_mul_ps(_mm_load_ps(&s0n+4), int_scale); in3 = _mm_mul_ps(_mm_load_ps(&s1n+4), int_scale); - out0 = _mm_cvttps_epi32(in0); - out1 = _mm_cvttps_epi32(in1); - out2 = _mm_cvttps_epi32(in2); - out3 = _mm_cvttps_epi32(in3); + out0 = _mm_cvtps_epi32(in0); + out1 = _mm_cvtps_epi32(in1); + out2 = _mm_cvtps_epi32(in2); + out3 = _mm_cvtps_epi32(in3); out0 = _mm_packs_epi32(out0, out2); out1 = _mm_packs_epi32(out1, out3); @@ -1243,8 +1423,8 @@ for(; n < n_samples; n++) { in0 = _mm_mul_ss(_mm_load_ss(&s0n), int_scale); in1 = _mm_mul_ss(_mm_load_ss(&s1n), int_scale); - in0 = _mm_min_ss(int_max, _mm_max_ss(in0, int_min)); - in1 = _mm_min_ss(int_max, _mm_max_ss(in1, int_min)); + in0 = _MM_CLAMP_SS(in0, int_min, int_max); + in1 = _MM_CLAMP_SS(in1, int_min, int_max); d0 = _mm_cvtss_si32(in0); d1 = _mm_cvtss_si32(in1); d += 2;
View file
pipewire-0.3.54.tar.gz/spa/plugins/audioconvert/fmt-ops.c -> pipewire-0.3.56.tar.gz/spa/plugins/audioconvert/fmt-ops.c
Changed
@@ -46,9 +46,9 @@ const char *name; uint32_t cpu_flags; -#define CONV_DITHER (1<<0) +#define CONV_NOISE (1<<0) #define CONV_SHAPE (1<<1) - uint32_t dither_flags; + uint32_t conv_flags; }; #define MAKE(fmt1,fmt2,chan,func,...) \ @@ -105,13 +105,13 @@ MAKE(F32P, F32, 0, conv_32d_to_32_c), #if defined (HAVE_SSE2) - MAKE(F32_OE, F32P, 0, conv_32s_to_32sd_sse2, SPA_CPU_FLAG_SSE2), + MAKE(F32_OE, F32P, 0, conv_32s_to_32d_sse2, SPA_CPU_FLAG_SSE2), #endif - MAKE(F32_OE, F32P, 0, conv_32s_to_32sd_c), + MAKE(F32_OE, F32P, 0, conv_32s_to_32d_c), #if defined (HAVE_SSE2) - MAKE(F32P, F32_OE, 0, conv_32sd_to_32s_sse2, SPA_CPU_FLAG_SSE2), + MAKE(F32P, F32_OE, 0, conv_32d_to_32s_sse2, SPA_CPU_FLAG_SSE2), #endif - MAKE(F32P, F32_OE, 0, conv_32sd_to_32s_c), + MAKE(F32P, F32_OE, 0, conv_32d_to_32s_c), MAKE(U32, F32, 0, conv_u32_to_f32_c), MAKE(U32, F32P, 0, conv_u32_to_f32d_c), @@ -171,20 +171,20 @@ /* from f32 */ MAKE(F32, U8, 0, conv_f32_to_u8_c), MAKE(F32P, U8P, 0, conv_f32d_to_u8d_shaped_c, 0, CONV_SHAPE), - MAKE(F32P, U8P, 0, conv_f32d_to_u8d_dither_c, 0, CONV_DITHER), + MAKE(F32P, U8P, 0, conv_f32d_to_u8d_noise_c, 0, CONV_NOISE), MAKE(F32P, U8P, 0, conv_f32d_to_u8d_c), MAKE(F32, U8P, 0, conv_f32_to_u8d_c), MAKE(F32P, U8, 0, conv_f32d_to_u8_shaped_c, 0, CONV_SHAPE), - MAKE(F32P, U8, 0, conv_f32d_to_u8_dither_c, 0, CONV_DITHER), + MAKE(F32P, U8, 0, conv_f32d_to_u8_noise_c, 0, CONV_NOISE), MAKE(F32P, U8, 0, conv_f32d_to_u8_c), MAKE(F32, S8, 0, conv_f32_to_s8_c), MAKE(F32P, S8P, 0, conv_f32d_to_s8d_shaped_c, 0, CONV_SHAPE), - MAKE(F32P, S8P, 0, conv_f32d_to_s8d_dither_c, 0, CONV_DITHER), + MAKE(F32P, S8P, 0, conv_f32d_to_s8d_noise_c, 0, CONV_NOISE), MAKE(F32P, S8P, 0, conv_f32d_to_s8d_c), MAKE(F32, S8P, 0, conv_f32_to_s8d_c), MAKE(F32P, S8, 0, conv_f32d_to_s8_shaped_c, 0, CONV_SHAPE), - MAKE(F32P, S8, 0, conv_f32d_to_s8_dither_c, 0, CONV_DITHER), + MAKE(F32P, S8, 0, conv_f32d_to_s8_noise_c, 0, CONV_NOISE), MAKE(F32P, S8, 0, conv_f32d_to_s8_c), MAKE(F32P, ALAW, 0, conv_f32d_to_alaw_c), @@ -199,7 +199,10 @@ MAKE(F32, S16, 0, conv_f32_to_s16_c), MAKE(F32P, S16P, 0, conv_f32d_to_s16d_shaped_c, 0, CONV_SHAPE), - MAKE(F32P, S16P, 0, conv_f32d_to_s16d_dither_c, 0, CONV_DITHER), +#if defined (HAVE_SSE2) + MAKE(F32P, S16P, 0, conv_f32d_to_s16d_noise_sse2, SPA_CPU_FLAG_SSE2, CONV_NOISE), +#endif + MAKE(F32P, S16P, 0, conv_f32d_to_s16d_noise_c, 0, CONV_NOISE), #if defined (HAVE_SSE2) MAKE(F32P, S16P, 0, conv_f32d_to_s16d_sse2, SPA_CPU_FLAG_SSE2), #endif @@ -208,7 +211,10 @@ MAKE(F32, S16P, 0, conv_f32_to_s16d_c), MAKE(F32P, S16, 0, conv_f32d_to_s16_shaped_c, 0, CONV_SHAPE), - MAKE(F32P, S16, 0, conv_f32d_to_s16_dither_c, 0, CONV_DITHER), +#if defined (HAVE_SSE2) + MAKE(F32P, S16, 0, conv_f32d_to_s16_noise_sse2, SPA_CPU_FLAG_SSE2, CONV_NOISE), +#endif + MAKE(F32P, S16, 0, conv_f32d_to_s16_noise_c, 0, CONV_NOISE), #if defined (HAVE_NEON) MAKE(F32P, S16, 0, conv_f32d_to_s16_neon, SPA_CPU_FLAG_NEON), #endif @@ -224,21 +230,21 @@ MAKE(F32P, S16, 0, conv_f32d_to_s16_c), MAKE(F32P, S16_OE, 0, conv_f32d_to_s16s_shaped_c, 0, CONV_SHAPE), - MAKE(F32P, S16_OE, 0, conv_f32d_to_s16s_dither_c, 0, CONV_DITHER), + MAKE(F32P, S16_OE, 0, conv_f32d_to_s16s_noise_c, 0, CONV_NOISE), MAKE(F32P, S16_OE, 0, conv_f32d_to_s16s_c), MAKE(F32, U32, 0, conv_f32_to_u32_c), MAKE(F32P, U32, 0, conv_f32d_to_u32_c), MAKE(F32, S32, 0, conv_f32_to_s32_c), - MAKE(F32P, S32P, 0, conv_f32d_to_s32d_dither_c, 0, CONV_DITHER), + MAKE(F32P, S32P, 0, conv_f32d_to_s32d_noise_c, 0, CONV_NOISE), MAKE(F32P, S32P, 0, conv_f32d_to_s32d_c), MAKE(F32, S32P, 0, conv_f32_to_s32d_c), #if defined (HAVE_SSE2) - MAKE(F32P, S32, 0, conv_f32d_to_s32_dither_sse2, SPA_CPU_FLAG_SSE2, CONV_DITHER), + MAKE(F32P, S32, 0, conv_f32d_to_s32_noise_sse2, SPA_CPU_FLAG_SSE2, CONV_NOISE), #endif - MAKE(F32P, S32, 0, conv_f32d_to_s32_dither_c, 0, CONV_DITHER), + MAKE(F32P, S32, 0, conv_f32d_to_s32_noise_c, 0, CONV_NOISE), #if defined (HAVE_AVX2) MAKE(F32P, S32, 0, conv_f32d_to_s32_avx2, SPA_CPU_FLAG_AVX2), @@ -248,33 +254,33 @@ #endif MAKE(F32P, S32, 0, conv_f32d_to_s32_c), - MAKE(F32P, S32_OE, 0, conv_f32d_to_s32s_dither_c, 0, CONV_DITHER), + MAKE(F32P, S32_OE, 0, conv_f32d_to_s32s_noise_c, 0, CONV_NOISE), MAKE(F32P, S32_OE, 0, conv_f32d_to_s32s_c), MAKE(F32, U24, 0, conv_f32_to_u24_c), MAKE(F32P, U24, 0, conv_f32d_to_u24_c), MAKE(F32, S24, 0, conv_f32_to_s24_c), - MAKE(F32P, S24P, 0, conv_f32d_to_s24d_dither_c, 0, CONV_DITHER), + MAKE(F32P, S24P, 0, conv_f32d_to_s24d_noise_c, 0, CONV_NOISE), MAKE(F32P, S24P, 0, conv_f32d_to_s24d_c), MAKE(F32, S24P, 0, conv_f32_to_s24d_c), - MAKE(F32P, S24, 0, conv_f32d_to_s24_dither_c, 0, CONV_DITHER), + MAKE(F32P, S24, 0, conv_f32d_to_s24_noise_c, 0, CONV_NOISE), MAKE(F32P, S24, 0, conv_f32d_to_s24_c), - MAKE(F32P, S24_OE, 0, conv_f32d_to_s24s_dither_c, 0, CONV_DITHER), + MAKE(F32P, S24_OE, 0, conv_f32d_to_s24s_noise_c, 0, CONV_NOISE), MAKE(F32P, S24_OE, 0, conv_f32d_to_s24s_c), MAKE(F32, U24_32, 0, conv_f32_to_u24_32_c), MAKE(F32P, U24_32, 0, conv_f32d_to_u24_32_c), MAKE(F32, S24_32, 0, conv_f32_to_s24_32_c), - MAKE(F32P, S24_32P, 0, conv_f32d_to_s24_32d_dither_c, 0, CONV_DITHER), + MAKE(F32P, S24_32P, 0, conv_f32d_to_s24_32d_noise_c, 0, CONV_NOISE), MAKE(F32P, S24_32P, 0, conv_f32d_to_s24_32d_c), MAKE(F32, S24_32P, 0, conv_f32_to_s24_32d_c), - MAKE(F32P, S24_32, 0, conv_f32d_to_s24_32_dither_c, 0, CONV_DITHER), + MAKE(F32P, S24_32, 0, conv_f32d_to_s24_32_noise_c, 0, CONV_NOISE), MAKE(F32P, S24_32, 0, conv_f32d_to_s24_32_c), - MAKE(F32P, S24_32_OE, 0, conv_f32d_to_s24_32s_dither_c, 0, CONV_DITHER), + MAKE(F32P, S24_32_OE, 0, conv_f32d_to_s24_32s_noise_c, 0, CONV_NOISE), MAKE(F32P, S24_32_OE, 0, conv_f32d_to_s24_32s_c), MAKE(F32, F64, 0, conv_f32_to_f64_c), @@ -350,7 +356,7 @@ #define MATCH_DITHER(a,b) ((a) == 0 || ((a) & (b)) == a) static const struct conv_info *find_conv_info(uint32_t src_fmt, uint32_t dst_fmt, - uint32_t n_channels, uint32_t cpu_flags, uint32_t dither_flags) + uint32_t n_channels, uint32_t cpu_flags, uint32_t conv_flags) { size_t i; @@ -359,7 +365,7 @@ conv_tablei.dst_fmt == dst_fmt && MATCH_CHAN(conv_tablei.n_channels, n_channels) && MATCH_CPU_FLAGS(conv_tablei.cpu_flags, cpu_flags) && - MATCH_DITHER(conv_tablei.dither_flags, dither_flags)) + MATCH_DITHER(conv_tablei.conv_flags, conv_flags)) return &conv_tablei; } return NULL; @@ -368,8 +374,8 @@ static void impl_convert_free(struct convert *conv) { conv->process = NULL; - free(conv->dither); - conv->dither = NULL; + free(conv->noise); + conv->noise = NULL; } static bool need_dither(uint32_t format) @@ -389,37 +395,109 @@ return false; } +/* filters based on F-weighted curves + * from 'Psychoacoustically Optimal Noise Shaping' (**) + * this filter is the "F-Weighted" noise filter described by Wannamaker + * It is designed to produce minimum audibility: */ +static const float wan3 = { /* Table 3; 3 Coefficients */ + 1.623f, -0.982f, 0.109f +}; +/* Noise shaping coefficients from1, moves most power of the + * error noise into inaudible frequency ranges. + * + * 1 + * "Minimally Audible Noise Shaping", Stanley P. Lipshitz, + * John Vanderkooy, and Robert A. Wannamaker, + * J. Audio Eng. Soc., Vol. 39, No. 11, November 1991. */ +static const float lips44 = { /* improved E-weighted (appendix: 5) */ + 2.033f, -2.165f, 1.959f, -1.590f, 0.6149f +}; + +static const struct dither_info { + uint32_t method; + uint32_t noise_method; + uint32_t rate; + const float *ns; + uint32_t n_ns; +} dither_info = { + { DITHER_METHOD_NONE, NOISE_METHOD_NONE, }, + { DITHER_METHOD_RECTANGULAR, NOISE_METHOD_RECTANGULAR, }, + { DITHER_METHOD_TRIANGULAR, NOISE_METHOD_TRIANGULAR, }, + { DITHER_METHOD_TRIANGULAR_HF, NOISE_METHOD_TRIANGULAR_HF, }, + { DITHER_METHOD_WANNAMAKER_3, NOISE_METHOD_TRIANGULAR_HF, 44100, wan3, SPA_N_ELEMENTS(wan3) }, + { DITHER_METHOD_LIPSHITZ, NOISE_METHOD_TRIANGULAR, 44100, lips44, SPA_N_ELEMENTS(lips44) } +}; + +static const struct dither_info *find_dither_info(uint32_t method, uint32_t rate) +{ + size_t i; + + for (i = 0; i < SPA_N_ELEMENTS(dither_info); i++) { + const struct dither_info *di = &dither_infoi; + if (di->method != method) + continue; + /* don't use shaped for too low rates, it moves the noise to + * audible ranges */ + if (di->ns != NULL && rate < di->rate * 3 / 4) + return find_dither_info(DITHER_METHOD_TRIANGULAR_HF, rate); + return &dither_infoi; + } + return NULL; +} + int convert_init(struct convert *conv) { const struct conv_info *info; - uint32_t i, dither_flags; + const struct dither_info *dinfo; + uint32_t i, conv_flags; - conv->scale = 1.0f / (float)(INT32_MAX >> conv->noise); + conv->scale = 1.0f / (float)(INT32_MAX); /* disable dither if not needed */ if (!need_dither(conv->dst_fmt)) conv->method = DITHER_METHOD_NONE; - /* don't use shaped for too low rates, it moves the noise to - * audible ranges */ - if (conv->method == DITHER_METHOD_SHAPED_5 && conv->rate < 32000) - conv->method = DITHER_METHOD_TRIANGULAR; - - dither_flags = 0; - if (conv->method != DITHER_METHOD_NONE || conv->noise) - dither_flags |= CONV_DITHER; - if (conv->method == DITHER_METHOD_SHAPED_5) - dither_flags |= CONV_SHAPE; + dinfo = find_dither_info(conv->method, conv->rate); + if (dinfo == NULL) + return -EINVAL; + + conv->noise_method = dinfo->noise_method; + if (conv->noise_bits > 0) { + switch (conv->noise_method) { + case NOISE_METHOD_NONE: + conv->noise_method = NOISE_METHOD_PATTERN; + conv->scale = -1.0f * (1 << (conv->noise_bits-1)); + break; + case NOISE_METHOD_RECTANGULAR: + conv->noise_method = NOISE_METHOD_TRIANGULAR; + SPA_FALLTHROUGH; + case NOISE_METHOD_TRIANGULAR: + case NOISE_METHOD_TRIANGULAR_HF: + conv->scale *= (1 << (conv->noise_bits-1)); + break; + } + } + if (conv->noise_method < NOISE_METHOD_TRIANGULAR) + conv->scale *= 0.5f; + + conv_flags = 0; + if (conv->noise_method != NOISE_METHOD_NONE) + conv_flags |= CONV_NOISE; + if (dinfo->n_ns > 0) { + conv_flags |= CONV_SHAPE; + conv->n_ns = dinfo->n_ns; + conv->ns = dinfo->ns; + } info = find_conv_info(conv->src_fmt, conv->dst_fmt, conv->n_channels, - conv->cpu_flags, dither_flags); + conv->cpu_flags, conv_flags); if (info == NULL) return -ENOTSUP; - conv->dither_size = DITHER_SIZE; - conv->dither = calloc(conv->dither_size + 16 + + conv->noise_size = DITHER_SIZE; + conv->noise = calloc(conv->noise_size + 16 + FMT_OPS_MAX_ALIGN / sizeof(float), sizeof(float)); - if (conv->dither == NULL) + if (conv->noise == NULL) return -errno; for (i = 0; i < SPA_N_ELEMENTS(conv->random); i++)
View file
pipewire-0.3.54.tar.gz/spa/plugins/audioconvert/fmt-ops.h -> pipewire-0.3.56.tar.gz/spa/plugins/audioconvert/fmt-ops.h
Changed
@@ -35,93 +35,100 @@ #include <spa/utils/defs.h> #include <spa/utils/string.h> +#define f32_round(a) lrintf(a) + +#define ITOF(type,v,scale,offs) \ + (((type)(v)) * (1.0f / (scale)) - (offs)) +#define FTOI(type,v,scale,offs,noise,min,max) \ + (type)f32_round(SPA_CLAMP((v) * (scale) + (offs) + (noise), min, max)) + #define FMT_OPS_MAX_ALIGN 32 #define U8_MIN 0u #define U8_MAX 255u #define U8_SCALE 128.f #define U8_OFFS 128.f -#define U8_TO_F32(v) ((((uint8_t)(v)) * (1.0f / U8_SCALE)) - 1.0f) -#define F32_TO_U8(v) (uint8_t)SPA_CLAMP((v) * U8_SCALE + U8_OFFS, U8_MIN, U8_MAX) -#define F32_TO_U8_D(v,d) (uint8_t)SPA_CLAMP((v) * U8_SCALE + U8_OFFS + (d), U8_MIN, U8_MAX) +#define U8_TO_F32(v) ITOF(uint8_t, v, U8_SCALE, 1.0f) +#define F32_TO_U8_D(v,d) FTOI(uint8_t, v, U8_SCALE, U8_OFFS, d, U8_MIN, U8_MAX) +#define F32_TO_U8(v) F32_TO_U8_D(v, 0.0f) #define S8_MIN -128 #define S8_MAX 127 #define S8_SCALE 128.0f -#define S8_TO_F32(v) (((int8_t)(v)) * (1.0f / S8_SCALE)) -#define F32_TO_S8(v) (int8_t)SPA_CLAMP((v) * S8_SCALE, S8_MIN, S8_MAX) -#define F32_TO_S8_D(v,d) (int8_t)SPA_CLAMP((v) * S8_SCALE + (d), S8_MIN, S8_MAX) +#define S8_TO_F32(v) ITOF(int8_t, v, S8_SCALE, 0.0f) +#define F32_TO_S8_D(v,d) FTOI(int8_t, v, S8_SCALE, 0.0f, d, S8_MIN, S8_MAX) +#define F32_TO_S8(v) F32_TO_S8_D(v, 0.0f); #define U16_MIN 0u #define U16_MAX 65535u #define U16_SCALE 32768.f #define U16_OFFS 32768.f -#define U16_TO_F32(v) ((((uint16_t)(v)) * (1.0f / U16_SCALE)) - 1.0f) -#define U16S_TO_F32(v) (((uint16_t)bswap_16((uint16_t)(v)) * (1.0f / U16_OFFS)) - 1.0f) -#define F32_TO_U16(v) (uint16_t)SPA_CLAMP((v) * U16_SCALE + U16_OFFS, U16_MIN, U16_MAX) -#define F32_TO_U16_D(v,d) (uint16_t)SPA_CLAMP((v) * U16_SCALE + U16_OFFS + (d), U16_MIN, U16_MAX) -#define F32_TO_U16S(v) bswap_16(F32_TO_U16(v)) +#define U16_TO_F32(v) ITOF(uint16_t, v, U16_SCALE, 1.0f) +#define U16S_TO_F32(v) U16_TO_F32(bswap_16(v)) +#define F32_TO_U16_D(v,d) FTOI(uint16_t, v, U16_SCALE, U16_OFFS, d, U16_MIN, U16_MAX) +#define F32_TO_U16(v) F32_TO_U16_D(v, 0.0f); #define F32_TO_U16S_D(v,d) bswap_16(F32_TO_U16_D(v,d)) +#define F32_TO_U16S(v) bswap_16(F32_TO_U16(v)) #define S16_MIN -32768 #define S16_MAX 32767 #define S16_SCALE 32768.0f -#define S16_TO_F32(v) (((int16_t)(v)) * (1.0f / S16_SCALE)) -#define S16S_TO_F32(v) (((int16_t)bswap_16(v)) * (1.0f / S16_SCALE)) -#define F32_TO_S16(v) (int16_t)SPA_CLAMP((v) * S16_SCALE, S16_MIN, S16_MAX) -#define F32_TO_S16_D(v,d) (int16_t)SPA_CLAMP((v) * S16_SCALE + (d), S16_MIN, S16_MAX) -#define F32_TO_S16S(v) bswap_16(F32_TO_S16(v)) +#define S16_TO_F32(v) ITOF(int16_t, v, S16_SCALE, 0.0f) +#define S16S_TO_F32(v) S16_TO_F32(bswap_16(v)) +#define F32_TO_S16_D(v,d) FTOI(int16_t, v, S16_SCALE, 0.0f, d, S16_MIN, S16_MAX) +#define F32_TO_S16(v) F32_TO_S16_D(v, 0.0f) #define F32_TO_S16S_D(v,d) bswap_16(F32_TO_S16_D(v,d)) +#define F32_TO_S16S(v) bswap_16(F32_TO_S16(v)) #define U24_MIN 0u #define U24_MAX 16777215u #define U24_SCALE 8388608.f #define U24_OFFS 8388608.f -#define U24_TO_F32(v) ((u24_to_u32(v) * (1.0f / U24_SCALE)) - 1.0f) -#define F32_TO_U24(v) u32_to_u24(SPA_CLAMP((v) * U24_SCALE + U24_OFFS, U24_MIN, U24_MAX)) -#define F32_TO_U24_D(v,d) u32_to_u24(SPA_CLAMP((v) * U24_SCALE + U24_OFFS + (d), U24_MIN, U24_MAX)) +#define U24_TO_F32(v) ITOF(uint32_t, u24_to_u32(v), U24_SCALE, 1.0f) +#define F32_TO_U24_D(v,d) u32_to_u24(FTOI(uint32_t, v, U24_SCALE, U24_OFFS, d, U24_MIN, U24_MAX)) +#define F32_TO_U24(v) F32_TO_U24_D(v, 0.0f) #define S24_MIN -8388608 #define S24_MAX 8388607 #define S24_SCALE 8388608.0f -#define S24_TO_F32(v) (s24_to_s32(v) * (1.0f / S24_SCALE)) -#define S24S_TO_F32(v) (s24_to_s32(bswap_s24(v)) * (1.0f / S24_SCALE)) -#define F32_TO_S24(v) s32_to_s24(SPA_CLAMP((v) * S24_SCALE, S24_MIN, S24_MAX)) +#define S24_TO_F32(v) ITOF(int32_t, s24_to_s32(v), S24_SCALE, 0.0f) +#define S24S_TO_F32(v) S24_TO_F32(bswap_s24(v)) +#define F32_TO_S24_D(v,d) s32_to_s24(FTOI(int32_t, v, S24_SCALE, 0.0f, d, S24_MIN, S24_MAX)) +#define F32_TO_S24(v) F32_TO_S24_D(v, 0.0f) #define F32_TO_S24S(v) bswap_s24(F32_TO_S24(v)) -#define F32_TO_S24_D(v,d) s32_to_s24(SPA_CLAMP((v) * S24_SCALE + (d), S24_MIN, S24_MAX)) + +#define U24_32_TO_F32(v) U32_TO_F32((v)<<8) +#define U24_32S_TO_F32(v) U24_32_TO_F32(bswap_32(v)) +#define F32_TO_U24_32_D(v,d) FTOI(uint32_t, v, U24_SCALE, U24_OFFS, d, U24_MIN, U24_MAX) +#define F32_TO_U24_32(v) F32_TO_U24_32_D(v, 0.0f) +#define F32_TO_U24_32S(v) bswap_32(F32_TO_U24_32(v)) +#define F32_TO_U24_32S_D(v,d) bswap_32(F32_TO_U24_32_D(v,d)) #define U32_MIN 0u -#define U32_MAX 4294967295 +#define U32_MAX 4294967295u #define U32_SCALE 2147483648.f #define U32_OFFS 2147483648.f -#define U32_TO_F32(v) ((((uint32_t)(v)) * (1.0f / U32_SCALE)) - 1.0f) -#define F32_TO_U32(v) (uint32_t)SPA_CLAMP((v) * U32_SCALE + U32_OFFS, U32_MIN, U32_MAX) -#define F32_TO_U32_D(v,d) (uint32_t)SPA_CLAMP((v) * U32_SCALE + U32_OFFS + (d), U32_MIN, U32_MAX) +#define U32_TO_F32(v) ITOF(uint32_t, (v) >> 8, U24_SCALE, 1.0f) +#define F32_TO_U32(v) (F32_TO_U24_32(v) << 8) +#define F32_TO_U32_D(v,d) (F32_TO_U24_32_D(v,d) << 8) + +#define S24_32_TO_F32(v) S32_TO_F32((v)<<8) +#define S24_32S_TO_F32(v) S24_32_TO_F32(bswap_32(v)) +#define F32_TO_S24_32_D(v,d) FTOI(int32_t, v, S24_SCALE, 0.0f, d, S24_MIN, S24_MAX) +#define F32_TO_S24_32(v) F32_TO_S24_32_D(v, 0.0f) +#define F32_TO_S24_32S(v) bswap_32(F32_TO_S24_32(v)) +#define F32_TO_S24_32S_D(v,d) bswap_32(F32_TO_S24_32_D(v,d)) #define S32_MIN -2147483648 -#define S32_MAX 2147483520 +#define S32_MAX 2147483647 #define S32_SCALE 2147483648.f -#define S32_TO_F32(v) (((int32_t)(v)) * (1.0f / S32_SCALE)) -#define S32S_TO_F32(v) (((int32_t)bswap_32(v)) * (1.0f / S32_SCALE)) -#define F32_TO_S32(v) (int32_t)SPA_CLAMP((v) * S32_SCALE, S32_MIN, S32_MAX) -#define F32_TO_S32_D(v,d) (int32_t)SPA_CLAMP((v) * S32_SCALE + (d), S32_MIN, S32_MAX) +#define S32_TO_F32(v) ITOF(int32_t, (v) >> 8, S24_SCALE, 0.0f) +#define S32S_TO_F32(v) S32_TO_F32(bswap_32(v)) +#define F32_TO_S32(v) (F32_TO_S24_32(v) << 8) +#define F32_TO_S32_D(v,d) (F32_TO_S24_32_D(v,d) << 8) #define F32_TO_S32S(v) bswap_32(F32_TO_S32(v)) #define F32_TO_S32S_D(v,d) bswap_32(F32_TO_S32_D(v,d)) -#define U24_32_TO_F32(v) U32_TO_F32((v)<<8) -#define U24_32S_TO_F32(v) U32_TO_F32(((int32_t)bswap_32(v))<<8) -#define F32_TO_U24_32(v) (uint32_t)SPA_CLAMP((v) * U24_SCALE + U24_OFFS, U24_MIN, U24_MAX) -#define F32_TO_U24_32S(v) bswap_32(F32_TO_U24_32(v)) -#define F32_TO_U24_32_D(v,d) (uint32_t)SPA_CLAMP((v) * U24_SCALE + U24_OFFS + (d), U24_MIN, U24_MAX) -#define F32_TO_U24_32S_D(v,d) bswap_32(F32_TO_U24_32_D(v,d)) - -#define S24_32_TO_F32(v) S32_TO_F32((v)<<8) -#define S24_32S_TO_F32(v) S32_TO_F32(((int32_t)bswap_32(v))<<8) -#define F32_TO_S24_32(v) (int32_t)SPA_CLAMP((v) * S24_SCALE, S24_MIN, S24_MAX) -#define F32_TO_S24_32S(v) bswap_32(F32_TO_S24_32(v)) -#define F32_TO_S24_32_D(v,d) (int32_t)SPA_CLAMP((v) * S24_SCALE + (d), S24_MIN, S24_MAX) -#define F32_TO_S24_32S_D(v,d) bswap_32(F32_TO_S24_32_D(v,d)) - typedef struct { #if __BYTE_ORDER == __LITTLE_ENDIAN uint8_t v3; @@ -181,21 +188,33 @@ return (int24_t) { .v1 = src.v3, .v2 = src.v2, .v3 = src.v1 }; } +#define F32_TO_F32S(v) \ + bswap_32((union { uint32_t i; float f; }){ .f = (v) }.i) +#define F32S_TO_F32(v) \ + ((union { uint32_t i; float f; }){ .i = bswap_32(v) }.f) + +#define F64_TO_F64S(v) \ + bswap_32((union { uint64_t i; double d; }){ .d = (v) }.i) +#define F64S_TO_F64(v) \ + ((union { uint64_t i; double d; }){ .i = bswap_32(v) }.d) + #define NS_MAX 8 #define NS_MASK (NS_MAX-1) struct shaper { - float eNS_MAX; + float eNS_MAX * 2; uint32_t idx; float r; }; struct convert { - uint32_t noise; + uint32_t noise_bits; #define DITHER_METHOD_NONE 0 #define DITHER_METHOD_RECTANGULAR 1 #define DITHER_METHOD_TRIANGULAR 2 -#define DITHER_METHOD_SHAPED_5 3 +#define DITHER_METHOD_TRIANGULAR_HF 3 +#define DITHER_METHOD_WANNAMAKER_3 4 +#define DITHER_METHOD_LIPSHITZ 5 uint32_t method; uint32_t src_fmt; @@ -209,8 +228,17 @@ float scale; uint32_t random16 + FMT_OPS_MAX_ALIGN/4; - float *dither; - uint32_t dither_size; + int32_t prev16 + FMT_OPS_MAX_ALIGN/4; +#define NOISE_METHOD_NONE 0 +#define NOISE_METHOD_RECTANGULAR 1 +#define NOISE_METHOD_TRIANGULAR 2 +#define NOISE_METHOD_TRIANGULAR_HF 3 +#define NOISE_METHOD_PATTERN 4 + uint32_t noise_method; + float *noise; + uint32_t noise_size; + const float *ns; + uint32_t n_ns; struct shaper shaper64; void (*process) (struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, @@ -221,14 +249,22 @@ int convert_init(struct convert *conv); static const struct dither_method_info { + uint32_t method; const char *label; const char *description; - uint32_t method; } dither_method_info = { - DITHER_METHOD_NONE = { "none", "Disabled", DITHER_METHOD_NONE }, - DITHER_METHOD_RECTANGULAR = { "rectangular", "Rectangular dithering", DITHER_METHOD_RECTANGULAR }, - DITHER_METHOD_TRIANGULAR = { "triangular", "Triangular dithering", DITHER_METHOD_TRIANGULAR }, - DITHER_METHOD_SHAPED_5 = { "shaped5", "Shaped 5 dithering", DITHER_METHOD_SHAPED_5 } + DITHER_METHOD_NONE = { DITHER_METHOD_NONE, + "none", "Disabled", }, + DITHER_METHOD_RECTANGULAR = { DITHER_METHOD_RECTANGULAR, + "rectangular", "Rectangular dithering", }, + DITHER_METHOD_TRIANGULAR = { DITHER_METHOD_TRIANGULAR, + "triangular", "Triangular dithering", }, + DITHER_METHOD_TRIANGULAR_HF = { DITHER_METHOD_TRIANGULAR_HF, + "triangular-hf", "Sloped Triangular dithering", }, + DITHER_METHOD_WANNAMAKER_3 = { DITHER_METHOD_WANNAMAKER_3, + "wannamaker3", "Wannamaker 3 dithering", }, + DITHER_METHOD_LIPSHITZ = { DITHER_METHOD_LIPSHITZ, + "shaped5", "Lipshitz 5 dithering", }, }; static inline uint32_t dither_method_from_label(const char *label) @@ -302,66 +338,66 @@ DEFINE_FUNCTION(f64s_to_f32d, c); DEFINE_FUNCTION(f64d_to_f32, c); DEFINE_FUNCTION(f32d_to_u8d, c); -DEFINE_FUNCTION(f32d_to_u8d_dither, c); +DEFINE_FUNCTION(f32d_to_u8d_noise, c); DEFINE_FUNCTION(f32d_to_u8d_shaped, c); DEFINE_FUNCTION(f32_to_u8, c); DEFINE_FUNCTION(f32_to_u8d, c); DEFINE_FUNCTION(f32d_to_u8, c); -DEFINE_FUNCTION(f32d_to_u8_dither, c); +DEFINE_FUNCTION(f32d_to_u8_noise, c); DEFINE_FUNCTION(f32d_to_u8_shaped, c); DEFINE_FUNCTION(f32d_to_s8d, c); -DEFINE_FUNCTION(f32d_to_s8d_dither, c); +DEFINE_FUNCTION(f32d_to_s8d_noise, c); DEFINE_FUNCTION(f32d_to_s8d_shaped, c); DEFINE_FUNCTION(f32_to_s8, c); DEFINE_FUNCTION(f32_to_s8d, c); DEFINE_FUNCTION(f32d_to_s8, c); -DEFINE_FUNCTION(f32d_to_s8_dither, c); +DEFINE_FUNCTION(f32d_to_s8_noise, c); DEFINE_FUNCTION(f32d_to_s8_shaped, c); DEFINE_FUNCTION(f32d_to_alaw, c); DEFINE_FUNCTION(f32d_to_ulaw, c); DEFINE_FUNCTION(f32_to_u16, c); DEFINE_FUNCTION(f32d_to_u16, c); DEFINE_FUNCTION(f32d_to_s16d, c); -DEFINE_FUNCTION(f32d_to_s16d_dither, c); +DEFINE_FUNCTION(f32d_to_s16d_noise, c); DEFINE_FUNCTION(f32d_to_s16d_shaped, c); DEFINE_FUNCTION(f32_to_s16, c); DEFINE_FUNCTION(f32_to_s16d, c); DEFINE_FUNCTION(f32d_to_s16, c); -DEFINE_FUNCTION(f32d_to_s16_dither, c); +DEFINE_FUNCTION(f32d_to_s16_noise, c); DEFINE_FUNCTION(f32d_to_s16_shaped, c); DEFINE_FUNCTION(f32d_to_s16s, c); -DEFINE_FUNCTION(f32d_to_s16s_dither, c); +DEFINE_FUNCTION(f32d_to_s16s_noise, c); DEFINE_FUNCTION(f32d_to_s16s_shaped, c); DEFINE_FUNCTION(f32_to_u32, c); DEFINE_FUNCTION(f32d_to_u32, c); DEFINE_FUNCTION(f32d_to_s32d, c); -DEFINE_FUNCTION(f32d_to_s32d_dither, c); +DEFINE_FUNCTION(f32d_to_s32d_noise, c); DEFINE_FUNCTION(f32_to_s32, c); DEFINE_FUNCTION(f32_to_s32d, c); DEFINE_FUNCTION(f32d_to_s32, c); -DEFINE_FUNCTION(f32d_to_s32_dither, c); +DEFINE_FUNCTION(f32d_to_s32_noise, c); DEFINE_FUNCTION(f32d_to_s32s, c); -DEFINE_FUNCTION(f32d_to_s32s_dither, c); +DEFINE_FUNCTION(f32d_to_s32s_noise, c); DEFINE_FUNCTION(f32_to_u24, c); DEFINE_FUNCTION(f32d_to_u24, c); DEFINE_FUNCTION(f32d_to_s24d, c); -DEFINE_FUNCTION(f32d_to_s24d_dither, c); +DEFINE_FUNCTION(f32d_to_s24d_noise, c); DEFINE_FUNCTION(f32_to_s24, c); DEFINE_FUNCTION(f32_to_s24d, c); DEFINE_FUNCTION(f32d_to_s24, c); -DEFINE_FUNCTION(f32d_to_s24_dither, c); +DEFINE_FUNCTION(f32d_to_s24_noise, c); DEFINE_FUNCTION(f32d_to_s24s, c); -DEFINE_FUNCTION(f32d_to_s24s_dither, c); +DEFINE_FUNCTION(f32d_to_s24s_noise, c); DEFINE_FUNCTION(f32_to_u24_32, c); DEFINE_FUNCTION(f32d_to_u24_32, c); DEFINE_FUNCTION(f32d_to_s24_32d, c); -DEFINE_FUNCTION(f32d_to_s24_32d_dither, c); +DEFINE_FUNCTION(f32d_to_s24_32d_noise, c); DEFINE_FUNCTION(f32_to_s24_32, c); DEFINE_FUNCTION(f32_to_s24_32d, c); DEFINE_FUNCTION(f32d_to_s24_32, c); -DEFINE_FUNCTION(f32d_to_s24_32_dither, c); +DEFINE_FUNCTION(f32d_to_s24_32_noise, c); DEFINE_FUNCTION(f32d_to_s24_32s, c); -DEFINE_FUNCTION(f32d_to_s24_32s_dither, c); +DEFINE_FUNCTION(f32d_to_s24_32s_noise, c); DEFINE_FUNCTION(f32d_to_f64d, c); DEFINE_FUNCTION(f32_to_f64, c); DEFINE_FUNCTION(f32_to_f64d, c); @@ -371,14 +407,14 @@ DEFINE_FUNCTION(16_to_16d, c); DEFINE_FUNCTION(24_to_24d, c); DEFINE_FUNCTION(32_to_32d, c); -DEFINE_FUNCTION(32s_to_32sd, c); +DEFINE_FUNCTION(32s_to_32d, c); DEFINE_FUNCTION(64_to_64d, c); DEFINE_FUNCTION(64s_to_64sd, c); DEFINE_FUNCTION(8d_to_8, c); DEFINE_FUNCTION(16d_to_16, c); DEFINE_FUNCTION(24d_to_24, c); DEFINE_FUNCTION(32d_to_32, c); -DEFINE_FUNCTION(32sd_to_32s, c); +DEFINE_FUNCTION(32d_to_32s, c); DEFINE_FUNCTION(64d_to_64, c); DEFINE_FUNCTION(64sd_to_64s, c); @@ -393,15 +429,17 @@ DEFINE_FUNCTION(s24_to_f32d, sse2); DEFINE_FUNCTION(s32_to_f32d, sse2); DEFINE_FUNCTION(f32d_to_s32, sse2); -DEFINE_FUNCTION(f32d_to_s32_dither, sse2); +DEFINE_FUNCTION(f32d_to_s32_noise, sse2); DEFINE_FUNCTION(f32_to_s16, sse2); DEFINE_FUNCTION(f32d_to_s16_2, sse2); DEFINE_FUNCTION(f32d_to_s16, sse2); +DEFINE_FUNCTION(f32d_to_s16_noise, sse2); DEFINE_FUNCTION(f32d_to_s16d, sse2); +DEFINE_FUNCTION(f32d_to_s16d_noise, sse2); DEFINE_FUNCTION(32_to_32d, sse2); -DEFINE_FUNCTION(32s_to_32sd, sse2); +DEFINE_FUNCTION(32s_to_32d, sse2); DEFINE_FUNCTION(32d_to_32, sse2); -DEFINE_FUNCTION(32sd_to_32s, sse2); +DEFINE_FUNCTION(32d_to_32s, sse2); #endif #if defined(HAVE_SSSE3) DEFINE_FUNCTION(s24_to_f32d, ssse3);
View file
pipewire-0.3.54.tar.gz/spa/plugins/audioconvert/meson.build -> pipewire-0.3.56.tar.gz/spa/plugins/audioconvert/meson.build
Changed
@@ -7,6 +7,20 @@ simd_cargs = simd_dependencies = +audioconvert_c = static_library('audioconvert_c', + 'channelmix-ops-c.c', + 'biquad.c', + 'crossover.c', + 'volume-ops-c.c', + 'resample-native-c.c', + 'resample-peaks-c.c', + 'fmt-ops-c.c' , + c_args : '-Ofast', '-ffast-math', + dependencies : spa_dep , + install : false + ) +simd_dependencies += audioconvert_c + if have_sse audioconvert_sse = static_library('audioconvert_sse', 'resample-native-sse.c', @@ -86,15 +100,10 @@ audioconvert_lib = static_library('audioconvert', 'fmt-ops.c', - 'biquad.c', - 'crossover.c', 'channelmix-ops.c', - 'channelmix-ops-c.c', 'resample-native.c', 'resample-peaks.c', - 'fmt-ops-c.c', - 'volume-ops.c', - 'volume-ops-c.c' , + 'volume-ops.c' , c_args : simd_cargs, '-O3', link_with : simd_dependencies, include_directories : configinc,
View file
pipewire-0.3.56.tar.gz/spa/plugins/audioconvert/resample-native-c.c
Added
@@ -0,0 +1,65 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "resample-native-impl.h" + +static void inner_product_c(float *d, const float * SPA_RESTRICT s, + const float * SPA_RESTRICT taps, uint32_t n_taps) +{ + float sum = 0.0f; +#if 1 + uint32_t i, j, nt2 = n_taps/2; + for (i = 0, j = n_taps-1; i < nt2; i++, j--) + sum += si * tapsi + sj * tapsj; +#else + uint32_t i; + for (i = 0; i < n_taps; i++) + sum += si * tapsi; +#endif + *d = sum; +} + +static void inner_product_ip_c(float *d, const float * SPA_RESTRICT s, + const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, + uint32_t n_taps) +{ + float sum2 = { 0.0f, 0.0f }; + uint32_t i; +#if 1 + uint32_t j, nt2 = n_taps/2; + for (i = 0, j = n_taps-1; i < nt2; i++, j--) { + sum0 += si * t0i + sj * t0j; + sum1 += si * t1i + sj * t1j; + } +#else + for (i = 0; i < n_taps; i++) { + sum0 += si * t0i; + sum1 += si * t1i; + } +#endif + *d = (sum1 - sum0) * x + sum0; +} + +MAKE_RESAMPLER_FULL(c); +MAKE_RESAMPLER_INTER(c);
View file
pipewire-0.3.54.tar.gz/spa/plugins/audioconvert/resample-native.c -> pipewire-0.3.56.tar.gz/spa/plugins/audioconvert/resample-native.c
Changed
@@ -33,22 +33,22 @@ double cutoff; }; -static const struct quality blackman_qualities = { - { 8, 0.5, }, - { 16, 0.70, }, - { 24, 0.76, }, - { 32, 0.8, }, +static const struct quality window_qualities = { + { 8, 0.53, }, + { 16, 0.67, }, + { 24, 0.75, }, + { 32, 0.80, }, { 48, 0.85, }, /* default */ - { 64, 0.90, }, - { 80, 0.92, }, - { 96, 0.933, }, - { 128, 0.950, }, - { 144, 0.955, }, - { 160, 0.958, }, - { 192, 0.965, }, - { 256, 0.975, }, - { 896, 0.997, }, - { 1024, 0.998, }, + { 64, 0.88, }, + { 80, 0.895, }, + { 96, 0.910, }, + { 128, 0.936, }, + { 144, 0.945, }, + { 160, 0.950, }, + { 192, 0.960, }, + { 256, 0.970, }, + { 896, 0.990, }, + { 1024, 0.995, }, }; static inline double sinc(double x) @@ -69,13 +69,17 @@ static inline double window_cosh(double x, double n_taps) { double R = 190.0, r; - double A = -325.1E-6 * (R * R) + 0.1677 * R - 3.149; + double A = (-325.1E-6 * R + 0.1677) * R - 3.149; + double x2; x = 2.0 * x / n_taps; - r = cosh(A * sqrt(1 - pow(x, 2))) / cosh(A); + x2 = x * x; + if (x2 >= 1.0) + return 0.0; + r = cosh(A * sqrt(1 - x2)) / cosh(A); return r; } -#define window window_blackman +#define window window_cosh static int build_filter(float *taps, uint32_t stride, uint32_t n_taps, uint32_t n_phases, double cutoff) { @@ -93,46 +97,7 @@ return 0; } -static void inner_product_c(float *d, const float * SPA_RESTRICT s, - const float * SPA_RESTRICT taps, uint32_t n_taps) -{ - float sum = 0.0f; -#if 1 - uint32_t i, j, nt2 = n_taps/2; - for (i = 0, j = n_taps-1; i < nt2; i++, j--) - sum += si * tapsi + sj * tapsj; -#else - uint32_t i; - for (i = 0; i < n_taps; i++) - sum += si * tapsi; -#endif - *d = sum; -} - -static void inner_product_ip_c(float *d, const float * SPA_RESTRICT s, - const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, - uint32_t n_taps) -{ - float sum2 = { 0.0f, 0.0f }; - uint32_t i; -#if 1 - uint32_t j, nt2 = n_taps/2; - for (i = 0, j = n_taps-1; i < nt2; i++, j--) { - sum0 += si * t0i + sj * t0j; - sum1 += si * t1i + sj * t1j; - } -#else - for (i = 0; i < n_taps; i++) { - sum0 += si * t0i; - sum1 += si * t1i; - } -#endif - *d = (sum1 - sum0) * x + sum0; -} - MAKE_RESAMPLER_COPY(c); -MAKE_RESAMPLER_FULL(c); -MAKE_RESAMPLER_INTER(c); #define MAKE(fmt,copy,full,inter,...) \ { SPA_AUDIO_FORMAT_ ##fmt, do_resample_ ##copy, #copy, \ @@ -356,7 +321,7 @@ uint32_t c, n_taps, n_phases, filter_size, in_rate, out_rate, gcd, filter_stride; uint32_t history_stride, history_size, oversample; - r->quality = SPA_CLAMP(r->quality, 0, (int) SPA_N_ELEMENTS(blackman_qualities) - 1); + r->quality = SPA_CLAMP(r->quality, 0, (int) SPA_N_ELEMENTS(window_qualities) - 1); r->free = impl_native_free; r->update_rate = impl_native_update_rate; r->in_len = impl_native_in_len; @@ -364,14 +329,15 @@ r->reset = impl_native_reset; r->delay = impl_native_delay; - q = &blackman_qualitiesr->quality; + q = &window_qualitiesr->quality; gcd = calc_gcd(r->i_rate, r->o_rate); in_rate = r->i_rate / gcd; out_rate = r->o_rate / gcd; - scale = SPA_MIN(q->cutoff * out_rate / in_rate, 1.0); + scale = SPA_MIN(q->cutoff * out_rate / in_rate, q->cutoff); + /* multiple of 8 taps to ease simd optimizations */ n_taps = SPA_ROUND_UP_N((uint32_t)ceil(q->n_taps / scale), 8); n_taps = SPA_MIN(n_taps, 1u << 18);
View file
pipewire-0.3.56.tar.gz/spa/plugins/audioconvert/resample-peaks-c.c
Added
@@ -0,0 +1,73 @@ +/* Spa + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <math.h> + +#include "resample-peaks-impl.h" + +void resample_peaks_process_c(struct resample *r, + const void * SPA_RESTRICT src, uint32_t *in_len, + void * SPA_RESTRICT dst, uint32_t *out_len) +{ + struct peaks_data *pd = r->data; + uint32_t c, i, o, end, chunk, o_count, i_count; + + if (SPA_UNLIKELY(r->channels == 0)) + return; + + for (c = 0; c < r->channels; c++) { + const float *s = srcc; + float *d = dstc, m = pd->max_fc; + + o_count = pd->o_count; + i_count = pd->i_count; + o = i = 0; + + while (i < *in_len && o < *out_len) { + end = ((uint64_t) (o_count + 1) * r->i_rate) / r->o_rate; + end = end > i_count ? end - i_count : 0; + chunk = SPA_MIN(end, *in_len); + + for (; i < chunk; i++) + m = SPA_MAX(fabsf(si), m); + + if (i == end) { + do++ = m; + m = 0.0f; + o_count++; + } + } + pd->max_fc = m; + } + + *out_len = o; + *in_len = i; + pd->o_count = o_count; + pd->i_count = i_count + i; + + while (pd->i_count >= r->i_rate) { + pd->i_count -= r->i_rate; + pd->o_count -= r->o_rate; + } +}
View file
pipewire-0.3.54.tar.gz/spa/plugins/audioconvert/resample-peaks-impl.h -> pipewire-0.3.56.tar.gz/spa/plugins/audioconvert/resample-peaks-impl.h
Changed
@@ -34,6 +34,9 @@ float max_f; }; +void resample_peaks_process_c(struct resample *r, + const void * SPA_RESTRICT src, uint32_t *in_len, + void * SPA_RESTRICT dst, uint32_t *out_len); #if defined (HAVE_SSE) void resample_peaks_process_sse(struct resample *r, const void * SPA_RESTRICT src, uint32_t *in_len,
View file
pipewire-0.3.54.tar.gz/spa/plugins/audioconvert/resample-peaks.c -> pipewire-0.3.56.tar.gz/spa/plugins/audioconvert/resample-peaks.c
Changed
@@ -29,52 +29,6 @@ #include "resample-peaks-impl.h" -static void resample_peaks_process_c(struct resample *r, - const void * SPA_RESTRICT src, uint32_t *in_len, - void * SPA_RESTRICT dst, uint32_t *out_len) -{ - struct peaks_data *pd = r->data; - uint32_t c, i, o, end, chunk, o_count, i_count; - - if (SPA_UNLIKELY(r->channels == 0)) - return; - - for (c = 0; c < r->channels; c++) { - const float *s = srcc; - float *d = dstc, m = pd->max_fc; - - o_count = pd->o_count; - i_count = pd->i_count; - o = i = 0; - - while (i < *in_len && o < *out_len) { - end = ((uint64_t) (o_count + 1) * r->i_rate) / r->o_rate; - end = end > i_count ? end - i_count : 0; - chunk = SPA_MIN(end, *in_len); - - for (; i < chunk; i++) - m = SPA_MAX(fabsf(si), m); - - if (i == end) { - do++ = m; - m = 0.0f; - o_count++; - } - } - pd->max_fc = m; - } - - *out_len = o; - *in_len = i; - pd->o_count = o_count; - pd->i_count = i_count + i; - - while (pd->i_count >= r->i_rate) { - pd->i_count -= r->i_rate; - pd->o_count -= r->o_rate; - } -} - struct resample_info { uint32_t format; uint32_t cpu_flags;
View file
pipewire-0.3.54.tar.gz/spa/plugins/audioconvert/spa-resample.c -> pipewire-0.3.56.tar.gz/spa/plugins/audioconvert/spa-resample.c
Changed
@@ -33,6 +33,7 @@ #include <spa/support/log-impl.h> #include <spa/debug/mem.h> #include <spa/utils/string.h> +#include <spa/utils/result.h> #include <sndfile.h> @@ -184,14 +185,14 @@ float outMAX_SAMPLES * channels; float ibufMAX_SAMPLES * channels; float obufMAX_SAMPLES * channels; - uint32_t in_len, out_len; - uint32_t pin_len, pout_len; + uint32_t in_len, out_len, queued; + uint32_t pin_len, pout_len; size_t read, written; const void *srcchannels; void *dstchannels; uint32_t i; - int j, k, queued; - bool flushing = false; + int res, j, k; + uint32_t flushing = UINT32_MAX; spa_zero(r); r.cpu_flags = d->cpu_flags; @@ -200,7 +201,10 @@ r.i_rate = d->iinfo.samplerate; r.o_rate = d->oinfo.samplerate; r.quality = d->quality < 0 ? DEFAULT_QUALITY : d->quality; - resample_native_init(&r); + if ((res = resample_native_init(&r)) < 0) { + fprintf(stderr, "can't init converter: %s\n", spa_strerror(res)); + return res; + } for (j = 0; j < channels; j++) srcj = &inMAX_SAMPLES * j; @@ -210,25 +214,29 @@ read = written = queued = 0; while (true) { pout_len = out_len = MAX_SAMPLES; - in_len = SPA_MIN(MAX_SAMPLES, resample_in_len(&r, out_len)) - queued; + in_len = SPA_MIN(MAX_SAMPLES, resample_in_len(&r, out_len)); + in_len -= SPA_MIN(queued, in_len); - pin_len = in_len = sf_readf_float(d->ifile, &ibufqueued * channels, in_len); + if (in_len > 0) { + pin_len = in_len = sf_readf_float(d->ifile, &ibufqueued * channels, in_len); - read += pin_len; + read += pin_len; - if (pin_len == 0) { - if (flushing) - break; + if (pin_len == 0) { + if (flushing == 0) + break; + if (flushing == UINT32_MAX) + flushing = resample_delay(&r); - flushing = true; - pin_len = in_len = resample_delay(&r); + pin_len = in_len = SPA_MIN(MAX_SAMPLES, flushing); + flushing -= in_len; - for (k = 0, i = 0; i < pin_len; i++) { - for (j = 0; j < channels; j++) - ibufk++ = 0.0; + for (k = 0, i = 0; i < pin_len; i++) { + for (j = 0; j < channels; j++) + ibufk++ = 0.0; + } } } - in_len += queued; pin_len = in_len; @@ -243,18 +251,20 @@ if (queued) memmove(ibuf, &ibufpin_len * channels, queued * channels * sizeof(float)); - for (k = 0, i = 0; i < pout_len; i++) { - for (j = 0; j < channels; j++) { - obufk++ = outMAX_SAMPLES * j + i; + if (pout_len > 0) { + for (k = 0, i = 0; i < pout_len; i++) { + for (j = 0; j < channels; j++) { + obufk++ = outMAX_SAMPLES * j + i; + } } - } - pout_len = sf_writef_float(d->ofile, obuf, pout_len); + pout_len = sf_writef_float(d->ofile, obuf, pout_len); - written += pout_len; + written += pout_len; + } } - if (d->verbose) { + if (d->verbose) fprintf(stdout, "read %zu samples, wrote %zu samples\n", read, written); - } + return 0; }
View file
pipewire-0.3.54.tar.gz/spa/plugins/audioconvert/test-audioconvert.c -> pipewire-0.3.56.tar.gz/spa/plugins/audioconvert/test-audioconvert.c
Changed
@@ -703,11 +703,18 @@ static const float data_f32p_4 = { 0.4f, 0.4f, 0.4f, 0.4f }; static const float data_f32p_5 = { 0.5f, 0.5f, 0.5f, 0.5f }; static const float data_f32p_6 = { 0.6f, 0.6f, 0.6f, 0.6f }; +static const float data_f32p_7 = { 0.7f, 0.7f, 0.7f, 0.7f }; +static const float data_f32p_8 = { 0.8f, 0.8f, 0.8f, 0.8f }; static const float data_f32_5p1 = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f }; + +static const float data_f32_7p1_remapped = { 0.1f, 0.2f, 0.5f, 0.6f, 0.7f, 0.8f, 0.3f, 0.4f, + 0.1f, 0.2f, 0.5f, 0.6f, 0.7f, 0.8f, 0.3f, 0.4f, + 0.1f, 0.2f, 0.5f, 0.6f, 0.7f, 0.8f, 0.3f, 0.4f, + 0.1f, 0.2f, 0.5f, 0.6f, 0.7f, 0.8f, 0.3f, 0.4f }; static const float data_f32_5p1_remapped = { 0.1f, 0.2f, 0.5f, 0.6f, 0.3f, 0.4f, 0.1f, 0.2f, 0.5f, 0.6f, 0.3f, 0.4f, 0.1f, 0.2f, 0.5f, 0.6f, 0.3f, 0.4f, @@ -753,6 +760,48 @@ .size = sizeof(float) * 4 }; +struct data dsp_7p1_remapped = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 8, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + SPA_AUDIO_CHANNEL_SL, + SPA_AUDIO_CHANNEL_SR, + }), + .ports = 8, + .planes = 1, + .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_7, data_f32p_8, data_f32p_5, data_f32p_6 }, + .size = sizeof(data_f32p_1) +}; + +struct data dsp_5p1_remapped_2 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FL, + }), + .ports = 6, + .planes = 1, + .data = { data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, data_f32p_2, data_f32p_1, }, + .size = sizeof(float) * 4 +}; + struct data conv_f32_48000_5p1 = { .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, .info = SPA_AUDIO_INFO_RAW_INIT( @@ -833,6 +882,28 @@ .size = sizeof(float) * 4 }; +struct data conv_f32_48000_7p1_remapped = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 8, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_SL, + SPA_AUDIO_CHANNEL_SR, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + }), + .ports = 1, + .planes = 1, + .data = { data_f32_7p1_remapped, }, + .size = sizeof(data_f32_7p1_remapped) +}; + static int test_convert_remap_dsp(struct context *ctx) { run_convert(ctx, &dsp_5p1, &conv_f32_48000_5p1); @@ -843,6 +914,10 @@ run_convert(ctx, &dsp_5p1_remapped, &conv_f32p_48000_5p1); run_convert(ctx, &dsp_5p1_remapped, &conv_f32_48000_5p1_remapped); run_convert(ctx, &dsp_5p1_remapped, &conv_f32p_48000_5p1_remapped); + run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32_48000_5p1); + run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32p_48000_5p1); + run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32_48000_5p1_remapped); + run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32p_48000_5p1_remapped); return 0; } @@ -850,12 +925,17 @@ { run_convert(ctx, &conv_f32_48000_5p1, &dsp_5p1); run_convert(ctx, &conv_f32_48000_5p1, &dsp_5p1_remapped); + run_convert(ctx, &conv_f32_48000_5p1, &dsp_5p1_remapped_2); run_convert(ctx, &conv_f32p_48000_5p1, &dsp_5p1); run_convert(ctx, &conv_f32p_48000_5p1, &dsp_5p1_remapped); + run_convert(ctx, &conv_f32p_48000_5p1, &dsp_5p1_remapped_2); run_convert(ctx, &conv_f32_48000_5p1_remapped, &dsp_5p1); run_convert(ctx, &conv_f32_48000_5p1_remapped, &dsp_5p1_remapped); + run_convert(ctx, &conv_f32_48000_5p1_remapped, &dsp_5p1_remapped_2); run_convert(ctx, &conv_f32p_48000_5p1_remapped, &dsp_5p1); run_convert(ctx, &conv_f32p_48000_5p1_remapped, &dsp_5p1_remapped); + run_convert(ctx, &conv_f32_48000_7p1_remapped, &dsp_7p1_remapped); + run_convert(ctx, &conv_f32p_48000_5p1_remapped, &dsp_5p1_remapped_2); return 0; }
View file
pipewire-0.3.54.tar.gz/spa/plugins/audioconvert/test-fmt-ops.c -> pipewire-0.3.56.tar.gz/spa/plugins/audioconvert/test-fmt-ops.c
Changed
@@ -54,7 +54,7 @@ spa_debug_mem(0, m1, size); spa_debug_mem(0, m2, size); } -// spa_assert_se(res == 0); + spa_assert_se(res == 0); } static void run_test(const char *name, @@ -127,8 +127,9 @@ static void test_f32_s8(void) { - static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; - static const int8_t out = { 0, 127, -128, 64, 192, 127, -128 }; + static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/160.f, 1.0f/256.f, -1.0f/160.f, -1.0f/256.f }; + static const int8_t out = { 0, 127, -128, 64, 192, 127, -128, 1, 0, -1, 0 }; run_test("test_f32_s8", in, sizeof(in0), out, sizeof(out0), SPA_N_ELEMENTS(out), true, true, conv_f32_to_s8_c); @@ -157,8 +158,9 @@ static void test_f32_u8(void) { - static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; - static const uint8_t out = { 128, 255, 0, 192, 64, 255, 0, }; + static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/160.f, 1.0f/256.f, -1.0f/160.f, -1.0f/256.f }; + static const uint8_t out = { 128, 255, 0, 192, 64, 255, 0, 129, 128, 127, 128 }; run_test("test_f32_u8", in, sizeof(in0), out, sizeof(out0), SPA_N_ELEMENTS(out), true, true, conv_f32_to_u8_c); @@ -187,8 +189,10 @@ static void test_f32_u16(void) { - static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; - static const uint16_t out = { 32768, 65535, 0, 49152, 16384, 65535, 0 }; + static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/49152.f, 1.0f/65536.f, -1.0f/49152.f, -1.0f/65536.f }; + static const uint16_t out = { 32768, 65535, 0, 49152, 16384, 65535, 0, + 32769, 32768, 32767, 32768 }; run_test("test_f32_u16", in, sizeof(in0), out, sizeof(out0), SPA_N_ELEMENTS(out), true, true, conv_f32_to_u16_c); @@ -209,8 +213,10 @@ static void test_f32_s16(void) { - static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; - static const int16_t out = { 0, 32767, -32768, 16384, -16384, 32767, -32768 }; + static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/49152.f, 1.0f/65536.f, -1.0f/49152.f, -1.0f/65536.f }; + static const int16_t out = { 0, 32767, -32768, 16384, -16384, 32767, -32768, + 1, 0, -1, 0 }; run_test("test_f32_s16", in, sizeof(in0), out, sizeof(out0), SPA_N_ELEMENTS(out), true, true, conv_f32_to_s16_c); @@ -267,9 +273,11 @@ static void test_f32_u32(void) { - static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; - static const uint32_t out = { 0x80000000, 0xffffffff, 0x0, 0xc0000000, 0x40000000, - 0xffffffff, 0x0 }; + static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; + static const uint32_t out = { 0x80000000, 0xffffff00, 0x0, 0xc0000000, 0x40000000, + 0xffffff00, 0x0, + 0x80000100, 0x80000000, 0x7fffff00, 0x80000000 }; run_test("test_f32_u32", in, sizeof(in0), out, sizeof(out0), SPA_N_ELEMENTS(out), true, true, conv_f32_to_u32_c); @@ -279,8 +287,8 @@ static void test_u32_f32(void) { - static const uint32_t in = { 0x80000000, 0xffffffff, 0x0, 0xc0000000, 0x40000000 }; - static const float out = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, }; + static const uint32_t in = { 0x80000000, 0xffffff00, 0x0, 0xc0000000, 0x40000000 }; + static const float out = { 0.0f, 0.999999880791f, -1.0f, 0.5f, -0.5f, }; run_test("test_u32_f32d", in, sizeof(in0), out, sizeof(out0), SPA_N_ELEMENTS(out), true, false, conv_u32_to_f32d_c); @@ -290,9 +298,11 @@ static void test_f32_s32(void) { - static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; - static const int32_t out = { 0, 0x7fffff80, 0x80000000, 0x40000000, 0xc0000000, - 0x7fffff80, 0x80000000 }; + static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; + static const int32_t out = { 0, 0x7fffff00, 0x80000000, 0x40000000, 0xc0000000, + 0x7fffff00, 0x80000000, + 0x00000100, 0x00000000, 0xffffff00, 0x00000000 }; run_test("test_f32_s32", in, sizeof(in0), out, sizeof(out0), SPA_N_ELEMENTS(out), true, true, conv_f32_to_s32_c); @@ -318,8 +328,8 @@ static void test_s32_f32(void) { - static const int32_t in = { 0, 0x7fffff80, 0x80000000, 0x40000000, 0xc0000000 }; - static const float out = { 0.0f, 0.999999940395f, -1.0f, 0.5, -0.5, }; + static const int32_t in = { 0, 0x7fffff00, 0x80000000, 0x40000000, 0xc0000000 }; + static const float out = { 0.0f, 0.999999880791, -1.0f, 0.5, -0.5, }; run_test("test_s32_f32d", in, sizeof(in0), out, sizeof(out0), SPA_N_ELEMENTS(out), true, false, conv_s32_to_f32d_c); @@ -345,10 +355,13 @@ static void test_f32_u24(void) { - static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; + static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; static const uint24_t out = { U32_TO_U24(0x00800000), U32_TO_U24(0xffffff), U32_TO_U24(0x000000), U32_TO_U24(0xc00000), U32_TO_U24(0x400000), - U32_TO_U24(0xffffff), U32_TO_U24(0x000000) }; + U32_TO_U24(0xffffff), U32_TO_U24(0x000000), + U32_TO_U24(0x800001), U32_TO_U24(0x800000), U32_TO_U24(0x7fffff), + U32_TO_U24(0x800000) }; run_test("test_f32_u24", in, sizeof(in0), out, 3, SPA_N_ELEMENTS(in), true, true, conv_f32_to_u24_c); @@ -370,10 +383,13 @@ static void test_f32_s24(void) { - static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; + static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; static const int24_t out = { S32_TO_S24(0), S32_TO_S24(0x7fffff), S32_TO_S24(0xff800000), S32_TO_S24(0x400000), S32_TO_S24(0xc00000), - S32_TO_S24(0x7fffff), S32_TO_S24(0xff800000) }; + S32_TO_S24(0x7fffff), S32_TO_S24(0xff800000), + S32_TO_S24(0x000001), S32_TO_S24(0x000000), S32_TO_S24(0xffffffff), + S32_TO_S24(0x000000) }; run_test("test_f32_s24", in, sizeof(in0), out, 3, SPA_N_ELEMENTS(in), true, true, conv_f32_to_s24_c); @@ -427,9 +443,11 @@ static void test_f32_u24_32(void) { - static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; + static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; static const uint32_t out = { 0x800000, 0xffffff, 0x0, 0xc00000, 0x400000, - 0xffffff, 0x000000 }; + 0xffffff, 0x000000, + 0x800001, 0x800000, 0x7fffff, 0x800000 }; run_test("test_f32_u24_32", in, sizeof(in0), out, sizeof(out0), SPA_N_ELEMENTS(out), true, true, conv_f32_to_u24_32_c); @@ -439,8 +457,8 @@ static void test_u24_32_f32(void) { - static const uint32_t in = { 0x800000, 0xffffff, 0x0, 0xc00000, 0x400000 }; - static const float out = { 0.0f, 0.999999880791f, -1.0f, 0.5f, -0.5f, }; + static const uint32_t in = { 0x800000, 0xffffff, 0x0, 0xc00000, 0x400000, 0x11000000 }; + static const float out = { 0.0f, 0.999999880791f, -1.0f, 0.5f, -0.5f, -1.0f }; run_test("test_u24_32_f32d", in, sizeof(in0), out, sizeof(out0), SPA_N_ELEMENTS(out), true, false, conv_u24_32_to_f32d_c); @@ -450,9 +468,11 @@ static void test_f32_s24_32(void) { - static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; + static const float in = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; static const int32_t out = { 0, 0x7fffff, 0xff800000, 0x400000, 0xffc00000, - 0x7fffff, 0xff800000 }; + 0x7fffff, 0xff800000, + 0x000001, 0x000000, 0xffffffff, 0x000000 }; run_test("test_f32_s24_32", in, sizeof(in0), out, sizeof(out0), SPA_N_ELEMENTS(out), true, true, conv_f32_to_s24_32_c); @@ -466,8 +486,8 @@ static void test_s24_32_f32(void) { - static const int32_t in = { 0, 0x7fffff, 0xff800000, 0x400000, 0xffc00000 }; - static const float out = { 0.0f, 0.999999880791f, -1.0f, 0.5f, -0.5f, }; + static const int32_t in = { 0, 0x7fffff, 0xff800000, 0x400000, 0xffc00000, 0x66800000 }; + static const float out = { 0.0f, 0.999999880791f, -1.0f, 0.5f, -0.5f, -1.0f }; run_test("test_s24_32_f32d", in, sizeof(in0), out, sizeof(out0), SPA_N_ELEMENTS(out), true, false, conv_s24_32_to_f32d_c); @@ -588,7 +608,7 @@ for (i = S32_MIN; i < S32_MAX; i+=255) { float v = S32_TO_F32(i); int32_t t = F32_TO_S32(v); - spa_assert_se(SPA_ABS(i - t) <= 128); + spa_assert_se(SPA_ABS(i - t) <= 256); } } @@ -600,7 +620,7 @@ for (i = U32_MIN; i < U32_MAX; i+=255) { float v = U32_TO_F32(i); uint32_t t = F32_TO_U32(v); - spa_assert_se(i > t ? (i - t) <= 128 : (t - i) <= 128); + spa_assert_se(i > t ? (i - t) <= 256 : (t - i) <= 256); } } @@ -626,10 +646,105 @@ } } +static void run_test_noise(uint32_t fmt, uint32_t noise, uint32_t flags) +{ + struct convert conv; + const void *ipN_CHANNELS; + void *opN_CHANNELS; + uint32_t i, range; + bool all_zero; + + spa_zero(conv); + + conv.noise_bits = noise; + conv.src_fmt = SPA_AUDIO_FORMAT_F32P; + conv.dst_fmt = fmt; + conv.n_channels = 2; + conv.rate = 44100; + conv.cpu_flags = flags; + spa_assert_se(convert_init(&conv) == 0); + fprintf(stderr, "test noise %s:\n", conv.func_name); + + memset(samp_in, 0, sizeof(samp_in)); + for (i = 0; i < conv.n_channels; i++) { + ipi = samp_in; + opi = samp_out; + } + convert_process(&conv, op, ip, N_SAMPLES); + + range = 1 << conv.noise_bits; + + all_zero = true; + for (i = 0; i < conv.n_channels * N_SAMPLES; i++) { + switch (fmt) { + case SPA_AUDIO_FORMAT_S8: + { + int8_t *d = (int8_t *)samp_out; + if (di != 0) + all_zero = false; + spa_assert_se(SPA_ABS(di - 0) <= (int8_t)range); + break; + } + case SPA_AUDIO_FORMAT_U8: + { + uint8_t *d = (uint8_t *)samp_out; + if (di != 0x80) + all_zero = false; + spa_assert_se((int8_t)SPA_ABS(di - 0x80) <= (int8_t)(range<<1)); + break; + } + case SPA_AUDIO_FORMAT_S16: + { + int16_t *d = (int16_t *)samp_out; + if (di != 0) + all_zero = false; + spa_assert_se(SPA_ABS(di - 0) <= (int16_t)range); + break; + } + case SPA_AUDIO_FORMAT_S24: + { + int24_t *d = (int24_t *)samp_out; + int32_t t = s24_to_s32(di); + if (t != 0) + all_zero = false; + spa_assert_se(SPA_ABS(t - 0) <= (int32_t)range); + break; + } + case SPA_AUDIO_FORMAT_S32: + { + int32_t *d = (int32_t *)samp_out; + if (di != 0) + all_zero = false; + spa_assert_se(SPA_ABS(di - 0) <= (int32_t)(range << 8)); + break; + } + default: + spa_assert_not_reached(); + break; + } + } + spa_assert_se(all_zero == false); + convert_free(&conv); +} + +static void test_noise(void) +{ + run_test_noise(SPA_AUDIO_FORMAT_S8, 1, 0); + run_test_noise(SPA_AUDIO_FORMAT_S8, 2, 0); + run_test_noise(SPA_AUDIO_FORMAT_U8, 1, 0); + run_test_noise(SPA_AUDIO_FORMAT_U8, 2, 0); + run_test_noise(SPA_AUDIO_FORMAT_S16, 1, 0); + run_test_noise(SPA_AUDIO_FORMAT_S16, 2, 0); + run_test_noise(SPA_AUDIO_FORMAT_S24, 1, 0); + run_test_noise(SPA_AUDIO_FORMAT_S24, 2, 0); + run_test_noise(SPA_AUDIO_FORMAT_S32, 1, 0); + run_test_noise(SPA_AUDIO_FORMAT_S32, 2, 0); +} + int main(int argc, char *argv) { cpu_flags = get_cpu_flags(); - printf("got get CPU flags %d\n", cpu_flags); + printf("got CPU flags %d\n", cpu_flags); test_f32_s8(); test_s8_f32(); @@ -664,5 +779,8 @@ test_lossless_u32(); test_swaps(); + + test_noise(); + return 0; }
View file
pipewire-0.3.56.tar.gz/spa/plugins/audiomixer/benchmark-mix-ops.c
Added
@@ -0,0 +1,222 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +#include "test-helper.h" +#include "mix-ops.h" + +static uint32_t cpu_flags; + +typedef void (*mix_func_t) (struct mix_ops *ops, void * SPA_RESTRICT dst, + const void * SPA_RESTRICT src, uint32_t n_src, uint32_t n_samples); +struct stats { + uint32_t n_samples; + uint32_t n_src; + uint64_t perf; + const char *name; + const char *impl; +}; + +#define MAX_SAMPLES 4096 +#define MAX_SRC 11 + +#define MAX_COUNT 100 + +static uint8_t samp_inMAX_SAMPLES * MAX_SRC * 8; +static uint8_t samp_outMAX_SAMPLES * 8; + +static const int sample_sizes = { 0, 1, 128, 513, 4096 }; +static const int src_counts = { 1, 2, 4, 6, 8, 11 }; + +#define MAX_RESULTS SPA_N_ELEMENTS(sample_sizes) * SPA_N_ELEMENTS(src_counts) * 70 + +static uint32_t n_results = 0; +static struct stats resultsMAX_RESULTS; + +static void run_test1(const char *name, const char *impl, mix_func_t func, int n_src, int n_samples) +{ + int i, j; + const void *ipn_src; + void *op; + struct timespec ts; + uint64_t count, t1, t2; + struct mix_ops mix; + + mix.n_channels = 1; + + for (j = 0; j < n_src; j++) + ipj = SPA_PTR_ALIGN(&samp_inj * n_samples * 4, 32, void); + op = SPA_PTR_ALIGN(samp_out, 32, void); + + clock_gettime(CLOCK_MONOTONIC, &ts); + t1 = SPA_TIMESPEC_TO_NSEC(&ts); + + count = 0; + for (i = 0; i < MAX_COUNT; i++) { + func(&mix, op, ip, n_src, n_samples); + count++; + } + clock_gettime(CLOCK_MONOTONIC, &ts); + t2 = SPA_TIMESPEC_TO_NSEC(&ts); + + spa_assert(n_results < MAX_RESULTS); + + resultsn_results++ = (struct stats) { + .n_samples = n_samples, + .n_src = n_src, + .perf = count * (uint64_t)SPA_NSEC_PER_SEC / (t2 - t1), + .name = name, + .impl = impl + }; +} + +static void run_test(const char *name, const char *impl, mix_func_t func) +{ + size_t i, j; + + for (i = 0; i < SPA_N_ELEMENTS(sample_sizes); i++) { + for (j = 0; j < SPA_N_ELEMENTS(src_counts); j++) { + run_test1(name, impl, func, src_countsj, + (sample_sizesi + (src_countsj -1)) / src_countsj); + } + } +} + +static void test_s8(void) +{ + run_test("test_s8", "c", mix_s8_c); +} +static void test_u8(void) +{ + run_test("test_u8", "c", mix_u8_c); +} + +static void test_s16(void) +{ + run_test("test_s16", "c", mix_s16_c); +} +static void test_u16(void) +{ + run_test("test_u8", "c", mix_u16_c); +} + +static void test_s24(void) +{ + run_test("test_s24", "c", mix_s24_c); +} +static void test_u24(void) +{ + run_test("test_u24", "c", mix_u24_c); +} +static void test_s24_32(void) +{ + run_test("test_s24_32", "c", mix_s24_32_c); +} +static void test_u24_32(void) +{ + run_test("test_u24_32", "c", mix_u24_32_c); +} + +static void test_s32(void) +{ + run_test("test_s32", "c", mix_s32_c); +} +static void test_u32(void) +{ + run_test("test_u32", "c", mix_u32_c); +} + +static void test_f32(void) +{ + run_test("test_f32", "c", mix_f32_c); +#if defined (HAVE_SSE) + if (cpu_flags & SPA_CPU_FLAG_SSE) { + run_test("test_f32", "sse", mix_f32_sse); + } +#endif +#if defined (HAVE_AVX) + if (cpu_flags & SPA_CPU_FLAG_AVX) { + run_test("test_f32", "avx", mix_f32_avx); + } +#endif +} + +static void test_f64(void) +{ + run_test("test_f64", "c", mix_f64_c); +#if defined (HAVE_SSE2) + if (cpu_flags & SPA_CPU_FLAG_SSE2) { + run_test("test_f64", "sse2", mix_f64_sse2); + } +#endif +} + +static int compare_func(const void *_a, const void *_b) +{ + const struct stats *a = _a, *b = _b; + int diff; + if ((diff = strcmp(a->name, b->name)) != 0) return diff; + if ((diff = a->n_samples - b->n_samples) != 0) return diff; + if ((diff = a->n_src - b->n_src) != 0) return diff; + if ((diff = b->perf - a->perf) != 0) return diff; + return 0; +} + +int main(int argc, char *argv) +{ + uint32_t i; + + cpu_flags = get_cpu_flags(); + printf("got get CPU flags %d\n", cpu_flags); + + test_s8(); + test_u8(); + test_s16(); + test_u16(); + test_s24(); + test_u24(); + test_s32(); + test_u32(); + test_s24_32(); + test_u24_32(); + test_f32(); + test_f64(); + + qsort(results, n_results, sizeof(struct stats), compare_func); + + for (i = 0; i < n_results; i++) { + struct stats *s = &resultsi; + fprintf(stderr, "%-12."PRIu64" \t%-32.32s %s \t samples %d, src %d\n", + s->perf, s->name, s->impl, s->n_samples, s->n_src); + } + return 0; +}
View file
pipewire-0.3.54.tar.gz/spa/plugins/audiomixer/meson.build -> pipewire-0.3.56.tar.gz/spa/plugins/audiomixer/meson.build
Changed
@@ -1,6 +1,5 @@ audiomixer_sources = 'audiomixer.c', - 'mix-ops.c', 'mixer-dsp.c', 'plugin.c' @@ -47,11 +46,81 @@ simd_dependencies += audiomixer_avx endif -audiomixerlib = shared_library('spa-audiomixer', +audiomixer_lib = static_library('audiomixer', + 'mix-ops.c' , + c_args : simd_cargs, '-O3', + link_with : simd_dependencies, + include_directories : configinc, + dependencies : spa_dep , + install : false + ) +audiomixer_dep = declare_dependency(link_with: audiomixer_lib) + +spa_audiomixer_lib = shared_library('spa-audiomixer', audiomixer_sources, c_args : simd_cargs, link_with : simd_dependencies, - dependencies : spa_dep, mathlib , + dependencies : spa_dep, mathlib, audiomixer_dep , install : true, install_dir : spa_plugindir / 'audiomixer' ) +spa_audiomixer_dep = declare_dependency(link_with: spa_audiomixer_lib) + +test_apps = + 'test-mix-ops', + + +foreach a : test_apps + test(a, + executable(a, a + '.c', + dependencies : spa_dep, dl_lib, pthread_lib, mathlib, audiomixer_dep , + include_directories : configinc , + link_with : test_lib , + install_rpath : spa_plugindir / 'audiomixer', + c_args : simd_cargs , + install : installed_tests_enabled, + install_dir : installed_tests_execdir / 'audiomixer'), + env : + 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')), + ) + + if installed_tests_enabled + test_conf = configuration_data() + test_conf.set('exec', installed_tests_execdir / 'audiomixer' / a) + configure_file( + input: installed_tests_template, + output: a + '.test', + install_dir: installed_tests_metadir / 'audiomixer', + configuration: test_conf + ) + endif +endforeach + +benchmark_apps = + 'benchmark-mix-ops', + + +foreach a : benchmark_apps + benchmark(a, + executable(a, a + '.c', + dependencies : spa_dep, dl_lib, pthread_lib, mathlib, audiomixer_dep , + include_directories : configinc , + c_args : simd_cargs , + install_rpath : spa_plugindir / 'audiomixer', + install : installed_tests_enabled, + install_dir : installed_tests_execdir / 'audiomixer'), + env : + 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')), + ) + + if installed_tests_enabled + test_conf = configuration_data() + test_conf.set('exec', installed_tests_execdir / 'audiomixer' / a) + configure_file( + input: installed_tests_template, + output: a + '.test', + install_dir: installed_tests_metadir / 'audiomixer', + configuration: test_conf + ) + endif +endforeach
View file
pipewire-0.3.54.tar.gz/spa/plugins/audiomixer/mix-ops-avx.c -> pipewire-0.3.56.tar.gz/spa/plugins/audiomixer/mix-ops-avx.c
Changed
@@ -86,50 +86,59 @@ static inline void mix_2(float * dst, const float * SPA_RESTRICT src, uint32_t n_samples) { - uint32_t n, unrolled; - - if (SPA_IS_ALIGNED(src, 32) && - SPA_IS_ALIGNED(dst, 32)) - unrolled = n_samples & ~15; - else - unrolled = 0; - - for (n = 0; n < unrolled; n += 16) { - __m256 in12, in22; - - in10 = _mm256_load_ps(&dstn + 0); - in11 = _mm256_load_ps(&dstn + 8); - in20 = _mm256_load_ps(&srcn + 0); - in21 = _mm256_load_ps(&srcn + 8); - - in10 = _mm256_add_ps(in10, in20); - in11 = _mm256_add_ps(in11, in21); - - _mm256_store_ps(&dstn + 0, in10); - _mm256_store_ps(&dstn + 8, in11); - } - for (; n < n_samples; n++) { - __m128 in11, in21; - in10 = _mm_load_ss(&dstn), - in20 = _mm_load_ss(&srcn), - in10 = _mm_add_ss(in10, in20); - _mm_store_ss(&dstn, in10); - } } void mix_f32_avx(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, uint32_t n_src, uint32_t n_samples) { - uint32_t i; + n_samples *= ops->n_channels; if (n_src == 0) memset(dst, 0, n_samples * ops->n_channels * sizeof(float)); - else if (dst != src0) - spa_memcpy(dst, src0, n_samples * ops->n_channels * sizeof(float)); - - for (i = 1; i + 2 < n_src; i += 3) - mix_4(dst, srci, srci + 1, srci + 2, n_samples); - for (; i < n_src; i++) - mix_2(dst, srci, n_samples * ops->n_channels); + else if (n_src == 1) { + if (dst != src0) + spa_memcpy(dst, src0, n_samples * sizeof(float)); + } else { + uint32_t i, n, unrolled; + const float **s = (const float **)src; + float *d = dst; + + if (SPA_LIKELY(SPA_IS_ALIGNED(dst, 32))) { + unrolled = n_samples & ~31; + for (i = 0; i < n_src; i++) { + if (SPA_UNLIKELY(!SPA_IS_ALIGNED(srci, 32))) { + unrolled = 0; + break; + } + } + } else + unrolled = 0; + + for (n = 0; n < unrolled; n += 32) { + __m256 in4; + + in0 = _mm256_load_ps(&s0n + 0); + in1 = _mm256_load_ps(&s0n + 8); + in2 = _mm256_load_ps(&s0n + 16); + in3 = _mm256_load_ps(&s0n + 24); + for (i = 1; i < n_src; i++) { + in0 = _mm256_add_ps(in0, _mm256_load_ps(&sin + 0)); + in1 = _mm256_add_ps(in1, _mm256_load_ps(&sin + 8)); + in2 = _mm256_add_ps(in2, _mm256_load_ps(&sin + 16)); + in3 = _mm256_add_ps(in3, _mm256_load_ps(&sin + 24)); + } + _mm256_store_ps(&dn + 0, in0); + _mm256_store_ps(&dn + 8, in1); + _mm256_store_ps(&dn + 16, in2); + _mm256_store_ps(&dn + 24, in3); + } + for (; n < n_samples; n++) { + __m128 in1; + in0 = _mm_load_ss(&s0n); + for (i = 1; i < n_src; i++) + in0 = _mm_add_ss(in0, _mm_load_ss(&sin)); + _mm_store_ss(&dn, in0); + } + } }
View file
pipewire-0.3.54.tar.gz/spa/plugins/audiomixer/mix-ops-c.c -> pipewire-0.3.56.tar.gz/spa/plugins/audiomixer/mix-ops-c.c
Changed
@@ -30,236 +30,39 @@ #include "mix-ops.h" -void -mix_s8_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - int8_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(int8_t)); - else if (dst != src0) - spa_memcpy(dst, src0, n_samples * ops->n_channels * sizeof(int8_t)); - - for (i = 1; i < n_src; i++) { - const int8_t *s = srci; - for (n = 0; n < n_samples * ops->n_channels; n++) - dn = S8_MIX(dn, sn); - } -} - -void -mix_u8_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - uint8_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(uint8_t)); - else if (dst != src0) - spa_memcpy(dst, src0, n_samples * ops->n_channels * sizeof(uint8_t)); - - for (i = 1; i < n_src; i++) { - const uint8_t *s = srci; - for (n = 0; n < n_samples * ops->n_channels; n++) - dn = U8_MIX(dn, sn); - } +#define MAKE_FUNC(name,type,atype,accum,clamp,zero) \ +void mix_ ##name## _c(struct mix_ops *ops, \ + void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, \ + uint32_t n_src, uint32_t n_samples) \ +{ \ + uint32_t i, n; \ + type *d = dst; \ + const type **s = (const type **)src; \ + n_samples *= ops->n_channels; \ + if (n_src == 0 && zero) \ + memset(dst, 0, n_samples * sizeof(type)); \ + else if (n_src == 1) { \ + if (dst != src0) \ + spa_memcpy(dst, src0, n_samples * sizeof(type)); \ + } else { \ + for (n = 0; n < n_samples; n++) { \ + atype ac = 0; \ + for (i = 0; i < n_src; i++) \ + ac = accum (ac, sin); \ + dn = clamp (ac); \ + } \ + } \ } -void -mix_s16_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - int16_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(int16_t)); - else if (dst != src0) - spa_memcpy(dst, src0, n_samples * ops->n_channels * sizeof(int16_t)); - - for (i = 1; i < n_src; i++) { - const int16_t *s = srci; - for (n = 0; n < n_samples * ops->n_channels; n++) - dn = S16_MIX(dn, sn); - } -} - -void -mix_u16_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - uint16_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(uint16_t)); - else if (dst != src0) - spa_memcpy(dst, src0, n_samples * ops->n_channels * sizeof(uint16_t)); - - for (i = 1; i < n_src; i++) { - const uint16_t *s = srci; - for (n = 0; n < n_samples * ops->n_channels; n++) - dn = U16_MIX(dn, sn); - } -} - -void -mix_s24_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - uint8_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(uint8_t) * 3); - else if (dst != src0) - spa_memcpy(dst, src0, n_samples * ops->n_channels * sizeof(uint8_t) * 3); - - for (i = 1; i < n_src; i++) { - const uint8_t *s = srci; - for (n = 0; n < n_samples * ops->n_channels; n++) { - write_s24(d, S24_MIX(read_s24(d), read_s24(s))); - d += 3; - s += 3; - } - } -} - -void -mix_u24_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - uint8_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(uint8_t) * 3); - else if (dst != src0) - spa_memcpy(dst, src0, n_samples * ops->n_channels * sizeof(uint8_t) * 3); - - for (i = 1; i < n_src; i++) { - const uint8_t *s = srci; - for (n = 0; n < n_samples * ops->n_channels; n++) { - write_u24(d, U24_MIX(read_u24(d), read_u24(s))); - d += 3; - s += 3; - } - } -} - -void -mix_s32_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - int32_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(int32_t)); - else if (dst != src0) - spa_memcpy(dst, src0, n_samples * ops->n_channels * sizeof(int32_t)); - - for (i = 1; i < n_src; i++) { - const int32_t *s = srci; - for (n = 0; n < n_samples * ops->n_channels; n++) - dn = S32_MIX(dn, sn); - } -} - -void -mix_u32_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - uint32_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(uint32_t)); - else if (dst != src0) - spa_memcpy(dst, src0, n_samples * ops->n_channels * sizeof(uint32_t)); - - for (i = 1; i < n_src; i++) { - const uint32_t *s = srci; - for (n = 0; n < n_samples * ops->n_channels; n++) - dn = U32_MIX(dn, sn); - } -} - -void -mix_s24_32_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - int32_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(int32_t)); - else if (dst != src0) - spa_memcpy(dst, src0, n_samples * ops->n_channels * sizeof(int32_t)); - - for (i = 1; i < n_src; i++) { - const int32_t *s = srci; - for (n = 0; n < n_samples * ops->n_channels; n++) - dn = S24_32_MIX(dn, sn); - } -} - -void -mix_u24_32_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - uint32_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(uint32_t)); - else if (dst != src0) - spa_memcpy(dst, src0, n_samples * ops->n_channels * sizeof(uint32_t)); - - for (i = 1; i < n_src; i++) { - const uint32_t *s = srci; - for (n = 0; n < n_samples * ops->n_channels; n++) - dn = U24_32_MIX(dn, sn); - } -} - -void -mix_f32_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - float *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(float)); - else if (dst != src0) - spa_memcpy(dst, src0, n_samples * ops->n_channels * sizeof(float)); - - for (i = 1; i < n_src; i++) { - const float *s = srci; - for (n = 0; n < n_samples * ops->n_channels; n++) - dn = F32_MIX(dn, sn); - } -} - -void -mix_f64_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - double *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(double)); - else if (dst != src0) - spa_memcpy(dst, src0, n_samples * ops->n_channels * sizeof(double)); - - for (i = 1; i < n_src; i++) { - const double *s = srci; - for (n = 0; n < n_samples * ops->n_channels; n++) - dn = F64_MIX(dn, sn); - } -} +MAKE_FUNC(s8, int8_t, int16_t, S8_ACCUM, S8_CLAMP, true); +MAKE_FUNC(u8, uint8_t, int16_t, U8_ACCUM, U8_CLAMP, false); +MAKE_FUNC(s16, int16_t, int32_t, S16_ACCUM, S16_CLAMP, true); +MAKE_FUNC(u16, uint16_t, int16_t, U16_ACCUM, U16_CLAMP, false); +MAKE_FUNC(s24, int24_t, int32_t, S24_ACCUM, S24_CLAMP, false); +MAKE_FUNC(u24, uint24_t, int32_t, U24_ACCUM, U24_CLAMP, false); +MAKE_FUNC(s32, int32_t, int64_t, S32_ACCUM, S32_CLAMP, true); +MAKE_FUNC(u32, uint32_t, int64_t, U32_ACCUM, U32_CLAMP, false); +MAKE_FUNC(s24_32, int32_t, int32_t, S24_32_ACCUM, S24_32_CLAMP, true); +MAKE_FUNC(u24_32, uint32_t, int32_t, U24_32_ACCUM, U24_32_CLAMP, false); +MAKE_FUNC(f32, float, float, F32_ACCUM, F32_CLAMP, true); +MAKE_FUNC(f64, double, double, F64_ACCUM, F64_CLAMP, true);
View file
pipewire-0.3.54.tar.gz/spa/plugins/audiomixer/mix-ops-sse.c -> pipewire-0.3.56.tar.gz/spa/plugins/audiomixer/mix-ops-sse.c
Changed
@@ -32,58 +32,56 @@ #include <xmmintrin.h> -static inline void mix_2(float * dst, const float * SPA_RESTRICT src, uint32_t n_samples) -{ - uint32_t n, unrolled; - __m128 in14, in24; - - if (SPA_LIKELY(SPA_IS_ALIGNED(src, 16) && - SPA_IS_ALIGNED(dst, 16))) - unrolled = n_samples & ~15; - else - unrolled = 0; - - for (n = 0; n < unrolled; n += 16) { - in10 = _mm_load_ps(&dstn+ 0); - in11 = _mm_load_ps(&dstn+ 4); - in12 = _mm_load_ps(&dstn+ 8); - in13 = _mm_load_ps(&dstn+12); - - in20 = _mm_load_ps(&srcn+ 0); - in21 = _mm_load_ps(&srcn+ 4); - in22 = _mm_load_ps(&srcn+ 8); - in23 = _mm_load_ps(&srcn+12); - - in10 = _mm_add_ps(in10, in20); - in11 = _mm_add_ps(in11, in21); - in12 = _mm_add_ps(in12, in22); - in13 = _mm_add_ps(in13, in23); - - _mm_store_ps(&dstn+ 0, in10); - _mm_store_ps(&dstn+ 4, in11); - _mm_store_ps(&dstn+ 8, in12); - _mm_store_ps(&dstn+12, in13); - } - for (; n < n_samples; n++) { - in10 = _mm_load_ss(&dstn), - in20 = _mm_load_ss(&srcn), - in10 = _mm_add_ss(in10, in20); - _mm_store_ss(&dstn, in10); - } -} - void mix_f32_sse(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, uint32_t n_src, uint32_t n_samples) { - uint32_t i; + n_samples *= ops->n_channels; + + if (n_src == 0) { + memset(dst, 0, n_samples * sizeof(float)); + } else if (n_src == 1) { + if (dst != src0) + spa_memcpy(dst, src0, n_samples * sizeof(float)); + } else { + uint32_t n, i, unrolled; + __m128 in4; + const float **s = (const float **)src; + float *d = dst; + + if (SPA_LIKELY(SPA_IS_ALIGNED(dst, 16))) { + unrolled = n_samples & ~15; + for (i = 0; i < n_src; i++) { + if (SPA_UNLIKELY(!SPA_IS_ALIGNED(srci, 16))) { + unrolled = 0; + break; + } + } + } else + unrolled = 0; - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(float)); - else if (dst != src0) - spa_memcpy(dst, src0, n_samples * ops->n_channels * sizeof(float)); + for (n = 0; n < unrolled; n += 16) { + in0 = _mm_load_ps(&s0n+ 0); + in1 = _mm_load_ps(&s0n+ 4); + in2 = _mm_load_ps(&s0n+ 8); + in3 = _mm_load_ps(&s0n+12); - for (i = 1; i < n_src; i++) { - mix_2(dst, srci, n_samples * ops->n_channels); + for (i = 1; i < n_src; i++) { + in0 = _mm_add_ps(in0, _mm_load_ps(&sin+ 0)); + in1 = _mm_add_ps(in1, _mm_load_ps(&sin+ 4)); + in2 = _mm_add_ps(in2, _mm_load_ps(&sin+ 8)); + in3 = _mm_add_ps(in3, _mm_load_ps(&sin+12)); + } + _mm_store_ps(&dn+ 0, in0); + _mm_store_ps(&dn+ 4, in1); + _mm_store_ps(&dn+ 8, in2); + _mm_store_ps(&dn+12, in3); + } + for (; n < n_samples; n++) { + in0 = _mm_load_ss(&s0n); + for (i = 1; i < n_src; i++) + in0 = _mm_add_ss(in0, _mm_load_ss(&sin)); + _mm_store_ss(&dn, in0); + } } }
View file
pipewire-0.3.54.tar.gz/spa/plugins/audiomixer/mix-ops-sse2.c -> pipewire-0.3.56.tar.gz/spa/plugins/audiomixer/mix-ops-sse2.c
Changed
@@ -32,58 +32,56 @@ #include <emmintrin.h> -static inline void mix_2(double * dst, const double * SPA_RESTRICT src, uint32_t n_samples) -{ - uint32_t n, unrolled; - __m128d in14, in24; - - if (SPA_IS_ALIGNED(src, 16) && - SPA_IS_ALIGNED(dst, 16)) - unrolled = n_samples & ~7; - else - unrolled = 0; - - for (n = 0; n < unrolled; n += 8) { - in10 = _mm_load_pd(&dstn+ 0); - in11 = _mm_load_pd(&dstn+ 2); - in12 = _mm_load_pd(&dstn+ 4); - in13 = _mm_load_pd(&dstn+ 6); - - in20 = _mm_load_pd(&srcn+ 0); - in21 = _mm_load_pd(&srcn+ 2); - in22 = _mm_load_pd(&srcn+ 4); - in23 = _mm_load_pd(&srcn+ 6); - - in10 = _mm_add_pd(in10, in20); - in11 = _mm_add_pd(in11, in21); - in12 = _mm_add_pd(in12, in22); - in13 = _mm_add_pd(in13, in23); - - _mm_store_pd(&dstn+ 0, in10); - _mm_store_pd(&dstn+ 2, in11); - _mm_store_pd(&dstn+ 4, in12); - _mm_store_pd(&dstn+ 6, in13); - } - for (; n < n_samples; n++) { - in10 = _mm_load_sd(&dstn), - in20 = _mm_load_sd(&srcn), - in10 = _mm_add_sd(in10, in20); - _mm_store_sd(&dstn, in10); - } -} - void mix_f64_sse2(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, uint32_t n_src, uint32_t n_samples) { - uint32_t i; + n_samples *= ops->n_channels; + + if (n_src == 0) { + memset(dst, 0, n_samples * sizeof(double)); + } else if (n_src == 1) { + if (dst != src0) + spa_memcpy(dst, src0, n_samples * sizeof(double)); + } else { + uint32_t n, i, unrolled; + __m128d in4; + const double **s = (const double **)src; + double *d = dst; + + if (SPA_LIKELY(SPA_IS_ALIGNED(dst, 16))) { + unrolled = n_samples & ~15; + for (i = 0; i < n_src; i++) { + if (SPA_UNLIKELY(!SPA_IS_ALIGNED(srci, 16))) { + unrolled = 0; + break; + } + } + } else + unrolled = 0; - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(double)); - else if (dst != src0) - spa_memcpy(dst, src0, n_samples * ops->n_channels * sizeof(double)); + for (n = 0; n < unrolled; n += 8) { + in0 = _mm_load_pd(&s0n+0); + in1 = _mm_load_pd(&s0n+2); + in2 = _mm_load_pd(&s0n+4); + in3 = _mm_load_pd(&s0n+6); - for (i = 1; i < n_src; i++) { - mix_2(dst, srci, n_samples * ops->n_channels); + for (i = 1; i < n_src; i++) { + in0 = _mm_add_pd(in0, _mm_load_pd(&sin+0)); + in1 = _mm_add_pd(in1, _mm_load_pd(&sin+2)); + in2 = _mm_add_pd(in2, _mm_load_pd(&sin+4)); + in3 = _mm_add_pd(in3, _mm_load_pd(&sin+6)); + } + _mm_store_pd(&dn+0, in0); + _mm_store_pd(&dn+2, in1); + _mm_store_pd(&dn+4, in2); + _mm_store_pd(&dn+6, in3); + } + for (; n < n_samples; n++) { + in0 = _mm_load_sd(&s0n); + for (i = 1; i < n_src; i++) + in0 = _mm_add_sd(in0, _mm_load_sd(&sin)); + _mm_store_sd(&dn, in0); + } } }
View file
pipewire-0.3.54.tar.gz/spa/plugins/audiomixer/mix-ops.h -> pipewire-0.3.56.tar.gz/spa/plugins/audiomixer/mix-ops.h
Changed
@@ -24,80 +24,98 @@ #include <spa/utils/defs.h> -static inline uint32_t read_u24(const void *src) -{ - const uint8_t *s = src; +typedef struct { #if __BYTE_ORDER == __LITTLE_ENDIAN - return (((uint32_t)s2 << 16) | ((uint32_t)(uint8_t)s1 << 8) | (uint32_t)(uint8_t)s0); + uint8_t v3; + uint8_t v2; + uint8_t v1; #else - return (((uint32_t)s0 << 16) | ((uint32_t)(uint8_t)s1 << 8) | (uint32_t)(uint8_t)s2); + uint8_t v1; + uint8_t v2; + uint8_t v3; #endif -} +} __attribute__ ((packed)) uint24_t; -static inline int32_t read_s24(const void *src) -{ - const int8_t *s = src; +typedef struct { #if __BYTE_ORDER == __LITTLE_ENDIAN - return (((int32_t)s2 << 16) | ((uint32_t)(uint8_t)s1 << 8) | (uint32_t)(uint8_t)s0); + uint8_t v3; + uint8_t v2; + int8_t v1; #else - return (((int32_t)s0 << 16) | ((uint32_t)(uint8_t)s1 << 8) | (uint32_t)(uint8_t)s2); + int8_t v1; + uint8_t v2; + uint8_t v3; #endif -} +} __attribute__ ((packed)) int24_t; -static inline void write_u24(void *dst, uint32_t val) +static inline uint32_t u24_to_u32(uint24_t src) { - uint8_t *d = dst; -#if __BYTE_ORDER == __LITTLE_ENDIAN - d0 = (uint8_t) (val); - d1 = (uint8_t) (val >> 8); - d2 = (uint8_t) (val >> 16); -#else - d0 = (uint8_t) (val >> 16); - d1 = (uint8_t) (val >> 8); - d2 = (uint8_t) (val); -#endif + return ((uint32_t)src.v1 << 16) | ((uint32_t)src.v2 << 8) | (uint32_t)src.v3; } -static inline void write_s24(void *dst, int32_t val) +#define U32_TO_U24(s) (uint24_t) { .v1 = (uint8_t)(((uint32_t)s) >> 16), \ + .v2 = (uint8_t)(((uint32_t)s) >> 8), .v3 = (uint8_t)((uint32_t)s) } + +static inline uint24_t u32_to_u24(uint32_t src) { - uint8_t *d = dst; -#if __BYTE_ORDER == __LITTLE_ENDIAN - d0 = (uint8_t) (val); - d1 = (uint8_t) (val >> 8); - d2 = (uint8_t) (val >> 16); -#else - d0 = (uint8_t) (val >> 16); - d1 = (uint8_t) (val >> 8); - d2 = (uint8_t) (val); -#endif + return U32_TO_U24(src); } -#define S8_MIN -127 -#define S8_MAX 127 -#define S8_MIX(a, b) (int8_t)(SPA_CLAMP((int16_t)(a) + (int16_t)(b), S8_MIN, S8_MAX)) -#define U8_MIX(a, b) (uint8_t)((int16_t)S8_MIX((int16_t)(a) - S8_MAX, (int16_t)(b) - S8_MAX) + S8_MAX) - -#define S16_MIN -32767 -#define S16_MAX 32767 -#define S16_MIX(a, b) (int16_t)(SPA_CLAMP((int32_t)(a) + (int32_t)(b), S16_MIN, S16_MAX)) -#define U16_MIX(a, b) (uint16_t)((int32_t)S16_MIX((int32_t)(a) - S16_MAX, (int32_t)(b) - S16_MAX) + S16_MAX) - -#define S24_MIN -8388607 -#define S24_MAX 8388607 -#define S24_MIX(a, b) (int32_t)(SPA_CLAMP((int32_t)(a) + (int32_t)(b), S24_MIN, S24_MAX)) -#define U24_MIX(a, b) (uint32_t)((int32_t)S24_MIX((int32_t)(a) - S24_MAX, (int32_t)(b) - S24_MAX) + S24_MAX) +static inline int32_t s24_to_s32(int24_t src) +{ + return ((int32_t)src.v1 << 16) | ((uint32_t)src.v2 << 8) | (uint32_t)src.v3; +} -#define S32_MIN -2147483647 -#define S32_MAX 2147483647 -#define S32_MIX(a, b) (int32_t)(SPA_CLAMP((int64_t)(a) + (int64_t)(b), S32_MIN, S32_MAX)) -#define U32_MIX(a, b) (uint32_t)((int64_t)S32_MIX((int64_t)(a) - S32_MAX, (int64_t)(b) - S32_MAX) + S32_MAX) +#define S32_TO_S24(s) (int24_t) { .v1 = (int8_t)(((int32_t)s) >> 16), \ + .v2 = (uint8_t)(((uint32_t)s) >> 8), .v3 = (uint8_t)((uint32_t)s) } -#define S24_32_MIX(a, b) S24_MIX (a, b) -#define U24_32_MIX(a, b) U24_MIX (a, b) +static inline int24_t s32_to_s24(int32_t src) +{ + return S32_TO_S24(src); +} -#define F32_MIX(a, b) (float)((float)(a) + (float)(b)) -#define F64_MIX(a, b) (double)((double)(a) + (double)(b)) +#define S8_MIN -128 +#define S8_MAX 127 +#define S8_ACCUM(a,b) ((a) + (int16_t)(b)) +#define S8_CLAMP(a) (int8_t)(SPA_CLAMP((a), S8_MIN, S8_MAX)) +#define U8_OFFS 128 +#define U8_ACCUM(a,b) ((a) + ((int16_t)(b) - U8_OFFS)) +#define U8_CLAMP(a) (uint8_t)(SPA_CLAMP((a), S8_MIN, S8_MAX) + U8_OFFS) + +#define S16_MIN -32768 +#define S16_MAX 32767 +#define S16_ACCUM(a,b) ((a) + (int32_t)(b)) +#define S16_CLAMP(a) (int16_t)(SPA_CLAMP((a), S16_MIN, S16_MAX)) +#define U16_OFFS 32768 +#define U16_ACCUM(a,b) ((a) + ((int32_t)(b) - U16_OFFS)) +#define U16_CLAMP(a) (uint16_t)(SPA_CLAMP((a), S16_MIN, S16_MAX) + U16_OFFS) + +#define S24_32_MIN -8388608 +#define S24_32_MAX 8388607 +#define S24_32_ACCUM(a,b) ((a) + (int32_t)(b)) +#define S24_32_CLAMP(a) (int32_t)(SPA_CLAMP((a), S24_32_MIN, S24_32_MAX)) +#define U24_32_OFFS 8388608 +#define U24_32_ACCUM(a,b) ((a) + ((int32_t)(b) - U24_32_OFFS)) +#define U24_32_CLAMP(a) (uint32_t)(SPA_CLAMP((a), S24_32_MIN, S24_32_MAX) + U24_32_OFFS) + +#define S24_ACCUM(a,b) S24_32_ACCUM(a, s24_to_s32(b)) +#define S24_CLAMP(a) s32_to_s24(S24_32_CLAMP(a)) +#define U24_ACCUM(a,b) U24_32_ACCUM(a, u24_to_u32(b)) +#define U24_CLAMP(a) u32_to_u24(U24_32_CLAMP(a)) + +#define S32_MIN -2147483648 +#define S32_MAX 2147483647 +#define S32_ACCUM(a,b) ((a) + (int64_t)(b)) +#define S32_CLAMP(a) (int32_t)(SPA_CLAMP((a), S32_MIN, S32_MAX)) +#define U32_OFFS 2147483648 +#define U32_ACCUM(a,b) ((a) + ((int64_t)(b) - U32_OFFS)) +#define U32_CLAMP(a) (uint32_t)(SPA_CLAMP((a), S32_MIN, S32_MAX) + U32_OFFS) + +#define F32_ACCUM(a,b) ((a) + (b)) +#define F32_CLAMP(a) (a) +#define F64_ACCUM(a,b) ((a) + (b)) +#define F64_CLAMP(a) (a) struct mix_ops { uint32_t fmt;
View file
pipewire-0.3.54.tar.gz/spa/plugins/audiomixer/mixer-dsp.c -> pipewire-0.3.56.tar.gz/spa/plugins/audiomixer/mixer-dsp.c
Changed
@@ -105,7 +105,6 @@ uint32_t cpu_flags; uint32_t max_align; - struct spa_io_position *io_position; uint32_t quantum_limit; struct mix_ops ops; @@ -153,20 +152,7 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_log_debug(this->log, "%p: io %d %p/%zd", this, id, data, size); - - switch (id) { - case SPA_IO_Position: - this->io_position = data; - break; - default: - return -ENOENT; - } - return 0; + return -ENOTSUP; } static int impl_node_send_command(void *object, const struct spa_command *command) @@ -708,10 +694,7 @@ datas = alloca(MAX_PORTS * sizeof(void *)); n_buffers = 0; - if (SPA_LIKELY(this->io_position)) - maxsize = this->io_position->clock.duration * sizeof(float); - else - maxsize = this->quantum_limit; + maxsize = UINT32_MAX; for (i = 0; i < this->last_port; i++) { struct port *inport = GET_IN_PORT(this, i); @@ -867,7 +850,6 @@ this->max_align = SPA_MIN(MAX_ALIGN, spa_cpu_get_max_align(this->cpu)); } - this->quantum_limit = 8192; for (i = 0; info && i < info->n_items; i++) { const char *k = info->itemsi.key; const char *s = info->itemsi.value;
View file
pipewire-0.3.56.tar.gz/spa/plugins/audiomixer/test-helper.h
Added
@@ -0,0 +1,97 @@ +#include <dlfcn.h> + +#include <spa/support/plugin.h> +#include <spa/utils/type.h> +#include <spa/utils/result.h> +#include <spa/support/cpu.h> +#include <spa/utils/names.h> + +static inline const struct spa_handle_factory *get_factory(spa_handle_factory_enum_func_t enum_func, + const char *name, uint32_t version) +{ + uint32_t i; + int res; + const struct spa_handle_factory *factory; + + for (i = 0;;) { + if ((res = enum_func(&factory, &i)) <= 0) { + if (res < 0) + errno = -res; + break; + } + if (factory->version >= version && + !strcmp(factory->name, name)) + return factory; + } + return NULL; +} + +static inline struct spa_handle *load_handle(const struct spa_support *support, + uint32_t n_support, const char *lib, const char *name) +{ + int res, len; + void *hnd; + spa_handle_factory_enum_func_t enum_func; + const struct spa_handle_factory *factory; + struct spa_handle *handle; + const char *str; + char *path; + + if ((str = getenv("SPA_PLUGIN_DIR")) == NULL) + str = PLUGINDIR; + + len = strlen(str) + strlen(lib) + 2; + path = alloca(len); + snprintf(path, len, "%s/%s", str, lib); + + if ((hnd = dlopen(path, RTLD_NOW)) == NULL) { + fprintf(stderr, "can't load %s: %s\n", lib, dlerror()); + res = -ENOENT; + goto error; + } + if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { + fprintf(stderr, "can't find enum function\n"); + res = -ENXIO; + goto error_close; + } + + if ((factory = get_factory(enum_func, name, SPA_VERSION_HANDLE_FACTORY)) == NULL) { + fprintf(stderr, "can't find factory\n"); + res = -ENOENT; + goto error_close; + } + handle = calloc(1, spa_handle_factory_get_size(factory, NULL)); + if ((res = spa_handle_factory_init(factory, handle, + NULL, support, n_support)) < 0) { + fprintf(stderr, "can't make factory instance: %d\n", res); + goto error_close; + } + return handle; + +error_close: + dlclose(hnd); +error: + errno = -res; + return NULL; +} + +static inline uint32_t get_cpu_flags(void) +{ + struct spa_handle *handle; + uint32_t flags; + void *iface; + int res; + + handle = load_handle(NULL, 0, "support/libspa-support.so", SPA_NAME_SUPPORT_CPU); + if (handle == NULL) + return 0; + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_CPU, &iface)) < 0) { + fprintf(stderr, "can't get CPU interface %s\n", spa_strerror(res)); + return 0; + } + flags = spa_cpu_get_flags((struct spa_cpu*)iface); + + free(handle); + + return flags; +}
View file
pipewire-0.3.56.tar.gz/spa/plugins/audiomixer/test-mix-ops.c
Added
@@ -0,0 +1,293 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +#include <spa/debug/mem.h> + +#include "test-helper.h" +#include "mix-ops.c" + +static uint32_t cpu_flags; + +#define N_SAMPLES 1024 + +static uint8_t samp_outN_SAMPLES * 8; + +static void compare_mem(int i, int j, const void *m1, const void *m2, size_t size) +{ + int res = memcmp(m1, m2, size); + if (res != 0) { + fprintf(stderr, "%d %d %zd:\n", i, j, size); + spa_debug_mem(0, m1, size); + spa_debug_mem(0, m2, size); + } + spa_assert_se(res == 0); +} + +static int run_test(const char *name, const void *src, uint32_t n_src, const void *dst, + size_t dst_size, uint32_t n_samples, mix_func_t mix) +{ + struct mix_ops ops; + + ops.fmt = SPA_AUDIO_FORMAT_F32; + ops.n_channels = 1; + ops.cpu_flags = cpu_flags; + mix_ops_init(&ops); + + fprintf(stderr, "%s\n", name); + + mix(&ops, (void *)samp_out, src, n_src, n_samples); + compare_mem(0, 0, samp_out, dst, dst_size); + return 0; +} + +static void test_s8(void) +{ + int8_t out = { 0x00, 0x00, 0x00, 0x00 }; + int8_t in_1 = { 0x00, 0x00, 0x00, 0x00 }; + int8_t in_2 = { 0x7f, 0x80, 0x40, 0xc0 }; + int8_t in_3 = { 0x40, 0xc0, 0xc0, 0x40 }; + int8_t in_4 = { 0xc0, 0x40, 0x40, 0xc0 }; + int8_t out_4 = { 0x7f, 0x80, 0x40, 0xc0 }; + const void *src6 = { in_1, in_2, in_3, in_4 }; + + run_test("test_s8_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_s8_c); + run_test("test_s8_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_s8_c); + run_test("test_s8_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_s8_c); +} + +static void test_u8(void) +{ + uint8_t out = { 0x80, 0x80, 0x80, 0x80 }; + uint8_t in_1 = { 0x80, 0x80, 0x80, 0x80 }; + uint8_t in_2 = { 0xff, 0x00, 0xc0, 0x40 }; + uint8_t in_3 = { 0xc0, 0x40, 0x40, 0xc0 }; + uint8_t in_4 = { 0x40, 0xc0, 0xc0, 0x40 }; + uint8_t out_4 = { 0xff, 0x00, 0xc0, 0x40 }; + const void *src6 = { in_1, in_2, in_3, in_4 }; + + run_test("test_u8_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_u8_c); + run_test("test_u8_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_u8_c); + run_test("test_u8_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_u8_c); +} + +static void test_s16(void) +{ + int16_t out = { 0x0000, 0x0000, 0x0000, 0x0000 }; + int16_t in_1 = { 0x0000, 0x0000, 0x0000, 0x0000 }; + int16_t in_2 = { 0x7fff, 0x8000, 0x4000, 0xc000 }; + int16_t in_3 = { 0x4000, 0xc000, 0xc000, 0x4000 }; + int16_t in_4 = { 0xc000, 0x4000, 0x4000, 0xc000 }; + int16_t out_4 = { 0x7fff, 0x8000, 0x4000, 0xc000 }; + const void *src6 = { in_1, in_2, in_3, in_4 }; + + run_test("test_s16_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_s16_c); + run_test("test_s16_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_s16_c); + run_test("test_s16_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_s16_c); +} + +static void test_u16(void) +{ + uint16_t out = { 0x8000, 0x8000, 0x8000, 0x8000 }; + uint16_t in_1 = { 0x8000, 0x8000, 0x8000 , 0x8000}; + uint16_t in_2 = { 0xffff, 0x0000, 0xc000, 0x4000 }; + uint16_t in_3 = { 0xc000, 0x4000, 0x4000, 0xc000 }; + uint16_t in_4 = { 0x4000, 0xc000, 0xc000, 0x4000 }; + uint16_t out_4 = { 0xffff, 0x0000, 0xc000, 0x4000 }; + const void *src6 = { in_1, in_2, in_3, in_4 }; + + run_test("test_u16_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_u16_c); + run_test("test_u16_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_u16_c); + run_test("test_u16_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_u16_c); +} + +static void test_s24(void) +{ + int24_t out = { S32_TO_S24(0x000000), S32_TO_S24(0x000000), S32_TO_S24(0x000000) }; + int24_t in_1 = { S32_TO_S24(0x000000), S32_TO_S24(0x000000), S32_TO_S24(0x000000) }; + int24_t in_2 = { S32_TO_S24(0x7fffff), S32_TO_S24(0xff800000), S32_TO_S24(0x400000) }; + int24_t in_3 = { S32_TO_S24(0x400000), S32_TO_S24(0xffc00000), S32_TO_S24(0xffc00000) }; + int24_t in_4 = { S32_TO_S24(0xffc00000), S32_TO_S24(0x400000), S32_TO_S24(0x400000) }; + int24_t out_4 = { S32_TO_S24(0x7fffff), S32_TO_S24(0xff800000), S32_TO_S24(0x400000) }; + const void *src6 = { in_1, in_2, in_3, in_4 }; + + run_test("test_s24_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_s24_c); + run_test("test_s24_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_s24_c); + run_test("test_s24_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_s24_c); +} + +static void test_u24(void) +{ + uint24_t out = { U32_TO_U24(0x800000), U32_TO_U24(0x800000), U32_TO_U24(0x800000) }; + uint24_t in_1 = { U32_TO_U24(0x800000), U32_TO_U24(0x800000), U32_TO_U24(0x800000) }; + uint24_t in_2 = { U32_TO_U24(0xffffffff), U32_TO_U24(0x000000), U32_TO_U24(0xffc00000) }; + uint24_t in_3 = { U32_TO_U24(0xffc00000), U32_TO_U24(0x400000), U32_TO_U24(0x400000) }; + uint24_t in_4 = { U32_TO_U24(0x400000), U32_TO_U24(0xffc00000), U32_TO_U24(0xffc00000) }; + uint24_t out_4 = { U32_TO_U24(0xffffffff), U32_TO_U24(0x000000), U32_TO_U24(0xffc00000) }; + const void *src6 = { in_1, in_2, in_3, in_4 }; + + run_test("test_u24_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_u24_c); + run_test("test_u24_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_u24_c); + run_test("test_u24_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_u24_c); +} + +static void test_s32(void) +{ + int32_t out = { 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; + int32_t in_1 = { 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; + int32_t in_2 = { 0x7fffffff, 0x80000000, 0x40000000, 0xc0000000 }; + int32_t in_3 = { 0x40000000, 0xc0000000, 0xc0000000, 0x40000000 }; + int32_t in_4 = { 0xc0000000, 0x40000000, 0x40000000, 0xc0000000 }; + int32_t out_4 = { 0x7fffffff, 0x80000000, 0x40000000, 0xc0000000 }; + const void *src6 = { in_1, in_2, in_3, in_4 }; + + run_test("test_s32_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_s32_c); + run_test("test_s32_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_s32_c); + run_test("test_s32_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_s32_c); +} + +static void test_u32(void) +{ + uint32_t out = { 0x80000000, 0x80000000, 0x80000000, 0x80000000 }; + uint32_t in_1 = { 0x80000000, 0x80000000, 0x80000000, 0x80000000 }; + uint32_t in_2 = { 0xffffffff, 0x00000000, 0xc0000000, 0x40000000 }; + uint32_t in_3 = { 0xc0000000, 0x40000000, 0x40000000, 0xc0000000 }; + uint32_t in_4 = { 0x40000000, 0xc0000000, 0xc0000000, 0x40000000 }; + uint32_t out_4 = { 0xffffffff, 0x00000000, 0xc0000000, 0x40000000 }; + const void *src6 = { in_1, in_2, in_3, in_4 }; + + run_test("test_u32_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_u32_c); + run_test("test_u32_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_u32_c); + run_test("test_u32_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_u32_c); +} + +static void test_s24_32(void) +{ + int32_t out = { 0x000000, 0x000000, 0x000000, 0x000000 }; + int32_t in_1 = { 0x000000, 0x000000, 0x000000, 0x000000 }; + int32_t in_2 = { 0x7fffff, 0xff800000, 0x400000, 0xffc00000 }; + int32_t in_3 = { 0x400000, 0xffc00000, 0xffc00000, 0x400000 }; + int32_t in_4 = { 0xffc00000, 0x400000, 0x400000, 0xffc00000 }; + int32_t out_4 = { 0x7fffff, 0xff800000, 0x400000, 0xffc00000 }; + const void *src6 = { in_1, in_2, in_3, in_4 }; + + run_test("test_s24_32_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_s24_32_c); + run_test("test_s24_32_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_s24_32_c); + run_test("test_s24_32_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_s24_32_c); +} + +static void test_u24_32(void) +{ + uint32_t out = { 0x800000, 0x800000, 0x800000, 0x800000 }; + uint32_t in_1 = { 0x800000, 0x800000, 0x800000, 0x800000 }; + uint32_t in_2 = { 0xffffff, 0x000000, 0xc00000, 0x400000 }; + uint32_t in_3 = { 0xc00000, 0x400000, 0x400000, 0xc00000 }; + uint32_t in_4 = { 0x400000, 0xc00000, 0xc00000, 0x400000 }; + uint32_t out_4 = { 0xffffff, 0x000000, 0xc00000, 0x400000 }; + const void *src6 = { in_1, in_2, in_3, in_4 }; + + run_test("test_u24_32_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_u24_32_c); + run_test("test_u24_32_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_u24_32_c); + run_test("test_u24_32_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_u24_32_c); +} + +static void test_f32(void) +{ + float out = { 0.0f, 0.0f, 0.0f, 0.0f }; + float in_1 = { 0.0f, 0.0f, 0.0f, 0.0f }; + float in_2 = { 1.0f, -1.0f, 0.5f, -0.5f }; + float in_3 = { 0.5f, -0.5f, -0.5f, 0.5f }; + float in_4 = { -0.5f, 1.0f, 0.5f, -0.5f }; + float out_4 = { 1.0f, -0.5f, 0.5f, -0.5f }; + const void *src6 = { in_1, in_2, in_3, in_4 }; + + run_test("test_f32_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_f32_c); + run_test("test_f32_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_f32_c); + run_test("test_f32_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_f32_c); +#if defined(HAVE_SSE) + if (cpu_flags & SPA_CPU_FLAG_SSE) { + run_test("test_f32_0_sse", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_f32_sse); + run_test("test_f32_1_sse", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_f32_sse); + run_test("test_f32_4_sse", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_f32_sse); + } +#endif +#if defined(HAVE_AVX) + if (cpu_flags & SPA_CPU_FLAG_AVX) { + run_test("test_f32_0_avx", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_f32_avx); + run_test("test_f32_1_avx", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_f32_avx); + run_test("test_f32_4_avx", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_f32_avx); + } +#endif +} + +static void test_f64(void) +{ + double out = { 0.0, 0.0, 0.0, 0.0 }; + double in_1 = { 0.0, 0.0, 0.0, 0.0 }; + double in_2 = { 1.0, -1.0, 0.5, -0.5 }; + double in_3 = { 0.5, -0.5, -0.5, 0.5 }; + double in_4 = { -0.5, 1.0, 0.5, -0.5 }; + double out_4 = { 1.0, -0.5, 0.5, -0.5 }; + const void *src6 = { in_1, in_2, in_3, in_4 }; + + run_test("test_f64_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_f64_c); + run_test("test_f64_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_f64_c); + run_test("test_f64_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_f64_c); +#if defined(HAVE_SSE2) + if (cpu_flags & SPA_CPU_FLAG_SSE2) { + run_test("test_f64_0_sse2", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_f64_sse2); + run_test("test_f64_1_sse2", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_f64_sse2); + run_test("test_f64_4_sse2", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_f64_sse2); + } +#endif +} + +int main(int argc, char *argv) +{ + cpu_flags = get_cpu_flags(); + printf("got get CPU flags %d\n", cpu_flags); + + test_s8(); + test_u8(); + test_s16(); + test_u16(); + test_s24(); + test_u24(); + test_s32(); + test_u32(); + test_s24_32(); + test_u24_32(); + test_f32(); + test_f64(); + + return 0; +}
View file
pipewire-0.3.56.tar.gz/spa/plugins/avb
Added
+(directory)
View file
pipewire-0.3.56.tar.gz/spa/plugins/avb/avb-pcm-sink.c
Added
@@ -0,0 +1,912 @@ +/* Spa AVB PCM Sink + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <stddef.h> + +#include <spa/node/node.h> +#include <spa/node/utils.h> +#include <spa/node/keys.h> +#include <spa/monitor/device.h> +#include <spa/utils/keys.h> +#include <spa/utils/names.h> +#include <spa/utils/string.h> +#include <spa/param/audio/format.h> +#include <spa/pod/filter.h> +#include <spa/debug/pod.h> + +#include "avb-pcm.h" + +#define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0) +#define GET_PORT(this,d,p) (&this->portsp) + +static void reset_props(struct props *props) +{ + snprintf(props->ifname, sizeof(props->ifname), "%s", DEFAULT_IFNAME); + parse_addr(props->addr, DEFAULT_ADDR); + props->prio = DEFAULT_PRIO; + parse_streamid(&props->streamid, DEFAULT_STREAMID); + props->mtt = DEFAULT_MTT; + props->t_uncertainty = DEFAULT_TU; + props->frames_per_pdu = DEFAULT_FRAMES_PER_PDU; +} + +static void emit_node_info(struct state *this, bool full) +{ + uint64_t old = full ? this->info.change_mask : 0; + + if (full) + this->info.change_mask = this->info_all; + if (this->info.change_mask) { + struct spa_dict_item items4; + uint32_t i, n_items = 0; + + itemsn_items++ = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "avb"); + itemsn_items++ = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Sink"); + itemsn_items++ = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, "true"); + this->info.props = &SPA_DICT_INIT(items, n_items); + + if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { + for (i = 0; i < this->info.n_params; i++) { + if (this->paramsi.user > 0) { + this->paramsi.flags ^= SPA_PARAM_INFO_SERIAL; + this->paramsi.user = 0; + } + } + } + spa_node_emit_info(&this->hooks, &this->info); + + this->info.change_mask = old; + } +} + +static void emit_port_info(struct state *this, struct port *port, bool full) +{ + uint64_t old = full ? port->info.change_mask : 0; + + if (full) + port->info.change_mask = port->info_all; + if (port->info.change_mask) { + uint32_t i; + + if (port->info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { + for (i = 0; i < port->info.n_params; i++) { + if (port->paramsi.user > 0) { + port->paramsi.flags ^= SPA_PARAM_INFO_SERIAL; + port->paramsi.user = 0; + } + } + } + spa_node_emit_port_info(&this->hooks, + port->direction, port->id, &port->info); + port->info.change_mask = old; + } +} + +static int impl_node_enum_params(void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct state *this = object; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer4096; + struct spa_result_node_params result; + uint32_t count = 0; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + result.id = id; + result.next = start; + next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + switch (id) { + case SPA_PARAM_PropInfo: + { + switch (result.index) { + default: + param = spa_avb_enum_propinfo(this, result.index, &b); + if (param == NULL) + return 0; + } + break; + } + case SPA_PARAM_Props: + { + struct spa_pod_frame f; + + switch (result.index) { + case 0: + spa_pod_builder_push_object(&b, &f, + SPA_TYPE_OBJECT_Props, id); + spa_pod_builder_add(&b, + SPA_PROP_latencyOffsetNsec, SPA_POD_Long(this->process_latency.ns), + 0); + spa_avb_add_prop_params(this, &b); + param = spa_pod_builder_pop(&b, &f); + break; + default: + return 0; + } + break; + } + case SPA_PARAM_IO: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Position), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_position))); + break; + default: + return 0; + } + break; + + case SPA_PARAM_ProcessLatency: + switch (result.index) { + case 0: + param = spa_process_latency_build(&b, id, &this->process_latency); + break; + default: + return 0; + } + break; + + default: + return -ENOENT; + } + + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return 0; +} + +static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) +{ + struct state *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_IO_Clock: + this->clock = data; + break; + case SPA_IO_Position: + this->position = data; + break; + default: + return -ENOENT; + } + spa_avb_reassign_follower(this); + + return 0; +} + +static void handle_process_latency(struct state *this, + const struct spa_process_latency_info *info) +{ + bool ns_changed = this->process_latency.ns != info->ns; + struct port *port = &this->ports0; + + if (this->process_latency.quantum == info->quantum && + this->process_latency.rate == info->rate && + !ns_changed) + return; + + this->process_latency = *info; + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + if (ns_changed) + this->paramsNODE_Props.user++; + this->paramsNODE_ProcessLatency.user++; + + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->paramsPORT_Latency.user++; +} + +static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct state *this = object; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_PARAM_Props: + { + struct props *p = &this->props; + struct spa_pod *params = NULL; + int64_t lat_ns = -1; + + if (param == NULL) { + reset_props(p); + return 0; + } + + spa_pod_parse_object(param, + SPA_TYPE_OBJECT_Props, NULL, + SPA_PROP_latencyOffsetNsec, SPA_POD_OPT_Long(&lat_ns), + SPA_PROP_params, SPA_POD_OPT_Pod(¶ms)); + + spa_avb_parse_prop_params(this, params); + if (lat_ns != -1) { + struct spa_process_latency_info info; + info = this->process_latency; + info.ns = lat_ns; + handle_process_latency(this, &info); + } + emit_node_info(this, false); + emit_port_info(this, &this->ports0, false); + break; + } + case SPA_PARAM_ProcessLatency: + { + struct spa_process_latency_info info; + if ((res = spa_process_latency_parse(param, &info)) < 0) + return res; + + handle_process_latency(this, &info); + + emit_node_info(this, false); + emit_port_info(this, &this->ports0, false); + break; + } + default: + return -ENOENT; + } + return 0; +} + +static int impl_node_send_command(void *object, const struct spa_command *command) +{ + struct state *this = object; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(command != NULL, -EINVAL); + + switch (SPA_NODE_COMMAND_ID(command)) { + case SPA_NODE_COMMAND_ParamBegin: + break; + case SPA_NODE_COMMAND_ParamEnd: + break; + case SPA_NODE_COMMAND_Start: + if (!this->ports0.have_format) + return -EIO; + if (this->ports0.n_buffers == 0) + return -EIO; + if ((res = spa_avb_start(this)) < 0) + return res; + break; + case SPA_NODE_COMMAND_Suspend: + case SPA_NODE_COMMAND_Pause: + if ((res = spa_avb_pause(this)) < 0) + return res; + break; + default: + return -ENOTSUP; + } + return 0; +} + + +static int +impl_node_add_listener(void *object, + struct spa_hook *listener, + const struct spa_node_events *events, + void *data) +{ + struct state *this = object; + struct spa_hook_list save; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_hook_list_isolate(&this->hooks, &save, listener, events, data); + + emit_node_info(this, true); + emit_port_info(this, &this->ports0, true); + + spa_hook_list_join(&this->hooks, &save); + + return 0; +} + +static int +impl_node_set_callbacks(void *object, + const struct spa_node_callbacks *callbacks, + void *data) +{ + struct state *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); + + return 0; +} + +static int +impl_node_sync(void *object, int seq) +{ + struct state *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); + + return 0; +} + +static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, + const struct spa_dict *props) +{ + return -ENOTSUP; +} + +static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) +{ + return -ENOTSUP; +} + +static int +impl_node_port_enum_params(void *object, int seq, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + + struct state *this = object; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer1024; + struct spa_result_node_params result; + uint32_t count = 0; + struct port *port; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + result.id = id; + result.next = start; + next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + switch (id) { + case SPA_PARAM_EnumFormat: + return spa_avb_enum_format(this, seq, start, num, filter); + + case SPA_PARAM_Format: + if (!port->have_format) + return -EIO; + if (result.index > 0) + return 0; + + param = spa_format_audio_raw_build(&b, id, + &port->current_format.info.raw); + break; + + case SPA_PARAM_Buffers: + if (!port->have_format) + return -EIO; + if (result.index > 0) + return 0; + + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamBuffers, id, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(this->blocks), + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( + this->quantum_limit * this->stride, + 16 * this->stride, + INT32_MAX), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(this->stride)); + break; + + case SPA_PARAM_Meta: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamMeta, id, + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); + break; + default: + return 0; + } + break; + + case SPA_PARAM_IO: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_RateMatch), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_rate_match))); + break; + default: + return 0; + } + break; + + case SPA_PARAM_Latency: + switch (result.index) { + case 0: case 1: + { + struct spa_latency_info latency = this->latencyresult.index; + if (latency.direction == SPA_DIRECTION_INPUT) + spa_process_latency_info_add(&this->process_latency, &latency); + param = spa_latency_build(&b, id, &latency); + break; + } + default: + return 0; + } + break; + + default: + return -ENOENT; + } + + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return 0; +} + +static int clear_buffers(struct state *this, struct port *port) +{ + if (port->n_buffers > 0) { + spa_list_init(&port->ready); + port->n_buffers = 0; + } + return 0; +} + +static int port_set_format(void *object, struct port *port, + uint32_t flags, const struct spa_pod *format) +{ + struct state *this = object; + int err; + + if (format == NULL) { + if (!port->have_format) + return 0; + + spa_log_debug(this->log, "clear format"); + port->have_format = false; + spa_avb_clear_format(this); + clear_buffers(this, port); + } else { + struct spa_audio_info info = { 0 }; + + if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) + return err; + + if (info.media_type != SPA_MEDIA_TYPE_audio || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -EINVAL; + + if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) + return -EINVAL; + + if ((err = spa_avb_set_format(this, &info, flags)) < 0) + return err; + + port->current_format = info; + port->have_format = true; + } + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; + emit_node_info(this, false); + + port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; + port->info.rate = SPA_FRACTION(1, this->rate); + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + if (port->have_format) { + port->paramsPORT_Format = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); + port->paramsPORT_Buffers = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); + port->paramsPORT_Latency.user++; + } else { + port->paramsPORT_Format = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->paramsPORT_Buffers = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + } + emit_port_info(this, port, false); + + return 0; +} + +static int +impl_node_port_set_param(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct state *this = object; + struct port *port; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + switch (id) { + case SPA_PARAM_Format: + res = port_set_format(this, port, flags, param); + break; + case SPA_PARAM_Latency: + { + struct spa_latency_info info; + if ((res = spa_latency_parse(param, &info)) < 0) + return res; + if (direction == info.direction) + return -EINVAL; + + this->latencyinfo.direction = info; + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->paramsPORT_Latency.user++; + emit_port_info(this, port, false); + break; + } + default: + res = -ENOENT; + break; + } + return res; +} + +static int +impl_node_port_use_buffers(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t flags, + struct spa_buffer **buffers, uint32_t n_buffers) +{ + struct state *this = object; + struct port *port; + uint32_t i; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + spa_log_debug(this->log, "%p: use %d buffers", this, n_buffers); + + if (!port->have_format) + return -EIO; + + if (n_buffers == 0) { + spa_avb_pause(this); + clear_buffers(this, port); + return 0; + } + + for (i = 0; i < n_buffers; i++) { + struct buffer *b = &port->buffersi; + struct spa_data *d = buffersi->datas; + + b->buf = buffersi; + b->id = i; + b->flags = BUFFER_FLAG_OUT; + + b->h = spa_buffer_find_meta_data(b->buf, SPA_META_Header, sizeof(*b->h)); + + if (d0.data == NULL) { + spa_log_error(this->log, "%p: need mapped memory", this); + return -EINVAL; + } + spa_log_debug(this->log, "%p: %d %p data:%p", this, i, b->buf, d0.data); + } + port->n_buffers = n_buffers; + + return 0; +} + +static int +impl_node_port_set_io(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t id, + void *data, size_t size) +{ + struct state *this = object; + struct port *port; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + spa_log_debug(this->log, "%p: io %d %p %zd", this, id, data, size); + + switch (id) { + case SPA_IO_Buffers: + port->io = data; + break; + case SPA_IO_RateMatch: + port->rate_match = data; + break; + default: + return -ENOENT; + } + return 0; +} + +static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) +{ + return -ENOTSUP; +} + +static int impl_node_process(void *object) +{ + struct state *this = object; + struct port *port; + struct spa_io_buffers *input; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + port = GET_PORT(this, SPA_DIRECTION_INPUT, 0); + + input = port->io; + spa_return_val_if_fail(input != NULL, -EIO); + + spa_log_trace_fp(this->log, "%p: process %d %d/%d", this, input->status, + input->buffer_id, port->n_buffers); + + if (this->position && this->position->clock.flags & SPA_IO_CLOCK_FLAG_FREEWHEEL) { + input->status = SPA_STATUS_NEED_DATA; + return SPA_STATUS_HAVE_DATA; + } + if (input->status == SPA_STATUS_HAVE_DATA && + input->buffer_id < port->n_buffers) { + struct buffer *b = &port->buffersinput->buffer_id; + + if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { + spa_log_warn(this->log, "%p: buffer %u in use", + this, input->buffer_id); + input->status = -EINVAL; + return -EINVAL; + } + spa_log_trace_fp(this->log, "%p: queue buffer %u", this, input->buffer_id); + spa_list_append(&port->ready, &b->link); + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); + input->buffer_id = SPA_ID_INVALID; + + spa_avb_write(this); + + input->status = SPA_STATUS_OK; + } + return SPA_STATUS_HAVE_DATA; +} + +static const struct spa_node_methods impl_node = { + SPA_VERSION_NODE_METHODS, + .add_listener = impl_node_add_listener, + .set_callbacks = impl_node_set_callbacks, + .sync = impl_node_sync, + .enum_params = impl_node_enum_params, + .set_param = impl_node_set_param, + .set_io = impl_node_set_io, + .send_command = impl_node_send_command, + .add_port = impl_node_add_port, + .remove_port = impl_node_remove_port, + .port_enum_params = impl_node_port_enum_params, + .port_set_param = impl_node_port_set_param, + .port_use_buffers = impl_node_port_use_buffers, + .port_set_io = impl_node_port_set_io, + .port_reuse_buffer = impl_node_port_reuse_buffer, + .process = impl_node_process, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct state *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + this = (struct state *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) + *interface = &this->node; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + struct state *this; + spa_return_val_if_fail(handle != NULL, -EINVAL); + this = (struct state *) handle; + spa_avb_clear(this); + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct state); +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) +{ + struct state *this; + struct port *port; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + this = (struct state *) handle; + + this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + avb_log_topic_init(this->log); + + this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); + this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); + + if (this->data_loop == NULL) { + spa_log_error(this->log, "a data loop is needed"); + return -EINVAL; + } + if (this->data_system == NULL) { + spa_log_error(this->log, "a data system is needed"); + return -EINVAL; + } + + this->node.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Node, + SPA_VERSION_NODE, + &impl_node, this); + + spa_hook_list_init(&this->hooks); + + + this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | + SPA_NODE_CHANGE_MASK_PROPS | + SPA_NODE_CHANGE_MASK_PARAMS; + this->info = SPA_NODE_INFO_INIT(); + this->info.max_input_ports = 1; + this->info.flags = SPA_NODE_FLAG_RT; + this->paramsNODE_PropInfo = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); + this->paramsNODE_Props = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); + this->paramsNODE_IO = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); + this->paramsNODE_ProcessLatency = SPA_PARAM_INFO(SPA_PARAM_ProcessLatency, SPA_PARAM_INFO_READWRITE); + this->info.params = this->params; + this->info.n_params = N_NODE_PARAMS; + + reset_props(&this->props); + + port = GET_PORT(this, SPA_DIRECTION_INPUT, 0); + port->direction = SPA_DIRECTION_INPUT; + + port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | + SPA_PORT_CHANGE_MASK_PARAMS; + port->info = SPA_PORT_INFO_INIT(); + port->info.flags = SPA_PORT_FLAG_LIVE | + SPA_PORT_FLAG_PHYSICAL | + SPA_PORT_FLAG_TERMINAL; + port->paramsPORT_EnumFormat = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); + port->paramsPORT_Meta = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); + port->paramsPORT_IO = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); + port->paramsPORT_Format = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->paramsPORT_Buffers = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + port->paramsPORT_Latency = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); + port->info.params = port->params; + port->info.n_params = N_PORT_PARAMS; + + spa_list_init(&port->ready); + + this->latencyport->direction = SPA_LATENCY_INFO( + port->direction, + .min_quantum = 1.0f, + .max_quantum = 1.0f); + this->latencySPA_DIRECTION_OUTPUT = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); + + return spa_avb_init(this, info); +} + +static const struct spa_interface_info impl_interfaces = { + {SPA_TYPE_INTERFACE_Node,}, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *info = &impl_interfaces*index; + break; + default: + return 0; + } + (*index)++; + return 1; +} + +static const struct spa_dict_item info_items = { + { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { SPA_KEY_FACTORY_DESCRIPTION, "Play audio with AVB" }, + { SPA_KEY_FACTORY_USAGE, "" }, +}; + +static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); + +const struct spa_handle_factory spa_avb_sink_factory = { + SPA_VERSION_HANDLE_FACTORY, + "avb.pcm.sink", + &info, + impl_get_size, + impl_init, + impl_enum_interface_info, +};
View file
pipewire-0.3.56.tar.gz/spa/plugins/avb/avb-pcm-source.c
Added
@@ -0,0 +1,912 @@ +/* Spa AVB PCM Source + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <stddef.h> + +#include <spa/node/node.h> +#include <spa/node/utils.h> +#include <spa/node/keys.h> +#include <spa/monitor/device.h> +#include <spa/utils/keys.h> +#include <spa/utils/names.h> +#include <spa/utils/string.h> +#include <spa/param/audio/format.h> +#include <spa/pod/filter.h> +#include <spa/debug/pod.h> + +#include "avb-pcm.h" + +#define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) +#define GET_PORT(this,d,p) (&this->portsp) + +static void reset_props(struct props *props) +{ + snprintf(props->ifname, sizeof(props->ifname), "%s", DEFAULT_IFNAME); + parse_addr(props->addr, DEFAULT_ADDR); + props->prio = DEFAULT_PRIO; + parse_streamid(&props->streamid, DEFAULT_STREAMID); + props->mtt = DEFAULT_MTT; + props->t_uncertainty = DEFAULT_TU; + props->frames_per_pdu = DEFAULT_FRAMES_PER_PDU; +} + +static void emit_node_info(struct state *this, bool full) +{ + uint64_t old = full ? this->info.change_mask : 0; + + if (full) + this->info.change_mask = this->info_all; + if (this->info.change_mask) { + struct spa_dict_item items4; + uint32_t i, n_items = 0; + + itemsn_items++ = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "avb"); + itemsn_items++ = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Source"); + itemsn_items++ = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, "true"); + this->info.props = &SPA_DICT_INIT(items, n_items); + + if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { + for (i = 0; i < this->info.n_params; i++) { + if (this->paramsi.user > 0) { + this->paramsi.flags ^= SPA_PARAM_INFO_SERIAL; + this->paramsi.user = 0; + } + } + } + spa_node_emit_info(&this->hooks, &this->info); + + this->info.change_mask = old; + } +} + +static void emit_port_info(struct state *this, struct port *port, bool full) +{ + uint64_t old = full ? port->info.change_mask : 0; + + if (full) + port->info.change_mask = port->info_all; + if (port->info.change_mask) { + uint32_t i; + + if (port->info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { + for (i = 0; i < port->info.n_params; i++) { + if (port->paramsi.user > 0) { + port->paramsi.flags ^= SPA_PARAM_INFO_SERIAL; + port->paramsi.user = 0; + } + } + } + spa_node_emit_port_info(&this->hooks, + port->direction, port->id, &port->info); + port->info.change_mask = old; + } +} + +static int impl_node_enum_params(void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct state *this = object; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer4096; + struct spa_result_node_params result; + uint32_t count = 0; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + result.id = id; + result.next = start; + next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + switch (id) { + case SPA_PARAM_PropInfo: + { + switch (result.index) { + default: + param = spa_avb_enum_propinfo(this, result.index, &b); + if (param == NULL) + return 0; + } + break; + } + case SPA_PARAM_Props: + { + struct spa_pod_frame f; + + switch (result.index) { + case 0: + spa_pod_builder_push_object(&b, &f, + SPA_TYPE_OBJECT_Props, id); + spa_pod_builder_add(&b, + SPA_PROP_latencyOffsetNsec, SPA_POD_Long(this->process_latency.ns), + 0); + spa_avb_add_prop_params(this, &b); + param = spa_pod_builder_pop(&b, &f); + break; + default: + return 0; + } + break; + } + case SPA_PARAM_IO: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Position), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_position))); + break; + default: + return 0; + } + break; + + case SPA_PARAM_ProcessLatency: + switch (result.index) { + case 0: + param = spa_process_latency_build(&b, id, &this->process_latency); + break; + default: + return 0; + } + break; + + default: + return -ENOENT; + } + + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return 0; +} + +static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) +{ + struct state *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_IO_Clock: + this->clock = data; + break; + case SPA_IO_Position: + this->position = data; + break; + default: + return -ENOENT; + } + spa_avb_reassign_follower(this); + + return 0; +} + +static void handle_process_latency(struct state *this, + const struct spa_process_latency_info *info) +{ + bool ns_changed = this->process_latency.ns != info->ns; + struct port *port = &this->ports0; + + if (this->process_latency.quantum == info->quantum && + this->process_latency.rate == info->rate && + !ns_changed) + return; + + this->process_latency = *info; + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + if (ns_changed) + this->paramsNODE_Props.user++; + this->paramsNODE_ProcessLatency.user++; + + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->paramsPORT_Latency.user++; +} + +static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct state *this = object; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_PARAM_Props: + { + struct props *p = &this->props; + struct spa_pod *params = NULL; + int64_t lat_ns = -1; + + if (param == NULL) { + reset_props(p); + return 0; + } + + spa_pod_parse_object(param, + SPA_TYPE_OBJECT_Props, NULL, + SPA_PROP_latencyOffsetNsec, SPA_POD_OPT_Long(&lat_ns), + SPA_PROP_params, SPA_POD_OPT_Pod(¶ms)); + + spa_avb_parse_prop_params(this, params); + if (lat_ns != -1) { + struct spa_process_latency_info info; + info = this->process_latency; + info.ns = lat_ns; + handle_process_latency(this, &info); + } + emit_node_info(this, false); + emit_port_info(this, &this->ports0, false); + break; + } + case SPA_PARAM_ProcessLatency: + { + struct spa_process_latency_info info; + if ((res = spa_process_latency_parse(param, &info)) < 0) + return res; + + handle_process_latency(this, &info); + + emit_node_info(this, false); + emit_port_info(this, &this->ports0, false); + break; + } + default: + return -ENOENT; + } + return 0; +} + +static int impl_node_send_command(void *object, const struct spa_command *command) +{ + struct state *this = object; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(command != NULL, -EINVAL); + + switch (SPA_NODE_COMMAND_ID(command)) { + case SPA_NODE_COMMAND_ParamBegin: + break; + case SPA_NODE_COMMAND_ParamEnd: + break; + case SPA_NODE_COMMAND_Start: + if (!this->ports0.have_format) + return -EIO; + if (this->ports0.n_buffers == 0) + return -EIO; + if ((res = spa_avb_start(this)) < 0) + return res; + break; + case SPA_NODE_COMMAND_Suspend: + case SPA_NODE_COMMAND_Pause: + if ((res = spa_avb_pause(this)) < 0) + return res; + break; + default: + return -ENOTSUP; + } + return 0; +} + + +static int +impl_node_add_listener(void *object, + struct spa_hook *listener, + const struct spa_node_events *events, + void *data) +{ + struct state *this = object; + struct spa_hook_list save; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_hook_list_isolate(&this->hooks, &save, listener, events, data); + + emit_node_info(this, true); + emit_port_info(this, &this->ports0, true); + + spa_hook_list_join(&this->hooks, &save); + + return 0; +} + +static int +impl_node_set_callbacks(void *object, + const struct spa_node_callbacks *callbacks, + void *data) +{ + struct state *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); + + return 0; +} + +static int +impl_node_sync(void *object, int seq) +{ + struct state *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); + + return 0; +} + +static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, + const struct spa_dict *props) +{ + return -ENOTSUP; +} + +static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) +{ + return -ENOTSUP; +} + +static int +impl_node_port_enum_params(void *object, int seq, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + + struct state *this = object; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer1024; + struct spa_result_node_params result; + uint32_t count = 0; + struct port *port; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + result.id = id; + result.next = start; + next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + switch (id) { + case SPA_PARAM_EnumFormat: + return spa_avb_enum_format(this, seq, start, num, filter); + + case SPA_PARAM_Format: + if (!port->have_format) + return -EIO; + if (result.index > 0) + return 0; + + param = spa_format_audio_raw_build(&b, id, + &port->current_format.info.raw); + break; + + case SPA_PARAM_Buffers: + if (!port->have_format) + return -EIO; + if (result.index > 0) + return 0; + + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamBuffers, id, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(this->blocks), + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( + this->quantum_limit * this->stride, + 16 * this->stride, + INT32_MAX), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(this->stride)); + break; + + case SPA_PARAM_Meta: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamMeta, id, + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); + break; + default: + return 0; + } + break; + + case SPA_PARAM_IO: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_RateMatch), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_rate_match))); + break; + default: + return 0; + } + break; + + case SPA_PARAM_Latency: + switch (result.index) { + case 0: case 1: + { + struct spa_latency_info latency = this->latencyresult.index; + if (latency.direction == SPA_DIRECTION_OUTPUT) + spa_process_latency_info_add(&this->process_latency, &latency); + param = spa_latency_build(&b, id, &latency); + break; + } + default: + return 0; + } + break; + + default: + return -ENOENT; + } + + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return 0; +} + +static int clear_buffers(struct state *this, struct port *port) +{ + if (port->n_buffers > 0) { + spa_list_init(&port->ready); + port->n_buffers = 0; + } + return 0; +} + +static int port_set_format(void *object, struct port *port, + uint32_t flags, const struct spa_pod *format) +{ + struct state *this = object; + int err; + + if (format == NULL) { + if (!port->have_format) + return 0; + + spa_log_debug(this->log, "clear format"); + port->have_format = false; + spa_avb_clear_format(this); + clear_buffers(this, port); + } else { + struct spa_audio_info info = { 0 }; + + if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) + return err; + + if (info.media_type != SPA_MEDIA_TYPE_audio || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -EINVAL; + + if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) + return -EINVAL; + + if ((err = spa_avb_set_format(this, &info, flags)) < 0) + return err; + + port->current_format = info; + port->have_format = true; + } + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; + emit_node_info(this, false); + + port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; + port->info.rate = SPA_FRACTION(1, this->rate); + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + if (port->have_format) { + port->paramsPORT_Format = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); + port->paramsPORT_Buffers = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); + port->paramsPORT_Latency.user++; + } else { + port->paramsPORT_Format = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->paramsPORT_Buffers = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + } + emit_port_info(this, port, false); + + return 0; +} + +static int +impl_node_port_set_param(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct state *this = object; + struct port *port; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + switch (id) { + case SPA_PARAM_Format: + res = port_set_format(this, port, flags, param); + break; + case SPA_PARAM_Latency: + { + struct spa_latency_info info; + if ((res = spa_latency_parse(param, &info)) < 0) + return res; + if (direction == info.direction) + return -EINVAL; + + this->latencyinfo.direction = info; + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->paramsPORT_Latency.user++; + emit_port_info(this, port, false); + break; + } + default: + res = -ENOENT; + break; + } + return res; +} + +static int +impl_node_port_use_buffers(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t flags, + struct spa_buffer **buffers, uint32_t n_buffers) +{ + struct state *this = object; + struct port *port; + uint32_t i; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + spa_log_debug(this->log, "%p: use %d buffers", this, n_buffers); + + if (!port->have_format) + return -EIO; + + if (n_buffers == 0) { + spa_avb_pause(this); + clear_buffers(this, port); + return 0; + } + + for (i = 0; i < n_buffers; i++) { + struct buffer *b = &port->buffersi; + struct spa_data *d = buffersi->datas; + + b->buf = buffersi; + b->id = i; + b->flags = BUFFER_FLAG_OUT; + + b->h = spa_buffer_find_meta_data(b->buf, SPA_META_Header, sizeof(*b->h)); + + if (d0.data == NULL) { + spa_log_error(this->log, "%p: need mapped memory", this); + return -EINVAL; + } + spa_log_debug(this->log, "%p: %d %p data:%p", this, i, b->buf, d0.data); + } + port->n_buffers = n_buffers; + + return 0; +} + +static int +impl_node_port_set_io(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t id, + void *data, size_t size) +{ + struct state *this = object; + struct port *port; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + spa_log_debug(this->log, "%p: io %d %p %zd", this, id, data, size); + + switch (id) { + case SPA_IO_Buffers: + port->io = data; + break; + case SPA_IO_RateMatch: + port->rate_match = data; + break; + default: + return -ENOENT; + } + return 0; +} + +static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) +{ + return -ENOTSUP; +} + +static int impl_node_process(void *object) +{ + struct state *this = object; + struct port *port; + struct spa_io_buffers *io; + struct buffer *b; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + port = GET_PORT(this, SPA_DIRECTION_OUTPUT, 0); + + io = port->io; + spa_return_val_if_fail(io != NULL, -EIO); + + spa_log_trace_fp(this->log, "%p: process %d %d/%d %d", this, io->status, + io->buffer_id, port->n_buffers, this->following); + + if (io->status == SPA_STATUS_HAVE_DATA) + return SPA_STATUS_HAVE_DATA; + + if (io->buffer_id < port->n_buffers) { + spa_avb_recycle_buffer(this, port, io->buffer_id); + io->buffer_id = SPA_ID_INVALID; + } + + if (spa_list_is_empty(&port->ready) && this->following) { + spa_avb_read(this); + } + if (spa_list_is_empty(&port->ready) || !this->following) + return SPA_STATUS_OK; + + b = spa_list_first(&port->ready, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); + + spa_log_trace_fp(this->log, "%p: dequeue buffer %d", this, b->id); + + io->buffer_id = b->id; + io->status = SPA_STATUS_HAVE_DATA; + + return SPA_STATUS_HAVE_DATA; +} + +static const struct spa_node_methods impl_node = { + SPA_VERSION_NODE_METHODS, + .add_listener = impl_node_add_listener, + .set_callbacks = impl_node_set_callbacks, + .sync = impl_node_sync, + .enum_params = impl_node_enum_params, + .set_param = impl_node_set_param, + .set_io = impl_node_set_io, + .send_command = impl_node_send_command, + .add_port = impl_node_add_port, + .remove_port = impl_node_remove_port, + .port_enum_params = impl_node_port_enum_params, + .port_set_param = impl_node_port_set_param, + .port_use_buffers = impl_node_port_use_buffers, + .port_set_io = impl_node_port_set_io, + .port_reuse_buffer = impl_node_port_reuse_buffer, + .process = impl_node_process, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct state *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + this = (struct state *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) + *interface = &this->node; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + struct state *this; + spa_return_val_if_fail(handle != NULL, -EINVAL); + this = (struct state *) handle; + spa_avb_clear(this); + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct state); +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) +{ + struct state *this; + struct port *port; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + this = (struct state *) handle; + + this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + avb_log_topic_init(this->log); + + this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); + this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); + + if (this->data_loop == NULL) { + spa_log_error(this->log, "a data loop is needed"); + return -EINVAL; + } + if (this->data_system == NULL) { + spa_log_error(this->log, "a data system is needed"); + return -EINVAL; + } + + this->node.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Node, + SPA_VERSION_NODE, + &impl_node, this); + + spa_hook_list_init(&this->hooks); + + this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | + SPA_NODE_CHANGE_MASK_PROPS | + SPA_NODE_CHANGE_MASK_PARAMS; + this->info = SPA_NODE_INFO_INIT(); + this->info.max_output_ports = 1; + this->info.flags = SPA_NODE_FLAG_RT; + this->paramsNODE_PropInfo = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); + this->paramsNODE_Props = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); + this->paramsNODE_IO = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); + this->paramsNODE_ProcessLatency = SPA_PARAM_INFO(SPA_PARAM_ProcessLatency, SPA_PARAM_INFO_READWRITE); + this->info.params = this->params; + this->info.n_params = N_NODE_PARAMS; + + reset_props(&this->props); + + port = GET_PORT(this, SPA_DIRECTION_OUTPUT, 0); + port->direction = SPA_DIRECTION_OUTPUT; + + port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | + SPA_PORT_CHANGE_MASK_PARAMS; + port->info = SPA_PORT_INFO_INIT(); + port->info.flags = SPA_PORT_FLAG_LIVE | + SPA_PORT_FLAG_PHYSICAL | + SPA_PORT_FLAG_TERMINAL; + port->paramsPORT_EnumFormat = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); + port->paramsPORT_Meta = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); + port->paramsPORT_IO = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); + port->paramsPORT_Format = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->paramsPORT_Buffers = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + port->paramsPORT_Latency = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); + port->info.params = port->params; + port->info.n_params = N_PORT_PARAMS; + + spa_list_init(&port->ready); + + this->latencyport->direction = SPA_LATENCY_INFO( + port->direction, + .min_quantum = 1.0f, + .max_quantum = 1.0f); + this->latencySPA_DIRECTION_INPUT = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); + + return spa_avb_init(this, info); +} + +static const struct spa_interface_info impl_interfaces = { + {SPA_TYPE_INTERFACE_Node,}, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *info = &impl_interfaces*index; + break; + default: + return 0; + } + (*index)++; + return 1; +} + +static const struct spa_dict_item info_items = { + { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { SPA_KEY_FACTORY_DESCRIPTION, "Play audio with AVB" }, + { SPA_KEY_FACTORY_USAGE, "" }, +}; + +static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); + +const struct spa_handle_factory spa_avb_source_factory = { + SPA_VERSION_HANDLE_FACTORY, + "avb.pcm.source", + &info, + impl_get_size, + impl_init, + impl_enum_interface_info, +};
View file
pipewire-0.3.56.tar.gz/spa/plugins/avb/avb-pcm.c
Added
@@ -0,0 +1,1217 @@ +/* Spa AVB PCM + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sched.h> +#include <errno.h> +#include <getopt.h> +#include <sys/time.h> +#include <math.h> +#include <limits.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> + +#include <spa/pod/filter.h> +#include <spa/utils/string.h> +#include <spa/support/system.h> +#include <spa/utils/keys.h> + +#include "avb-pcm.h" + +#define TAI_OFFSET (37ULL * SPA_NSEC_PER_SEC) +#define TAI_TO_UTC(t) (t - TAI_OFFSET) + +static int avb_set_param(struct state *state, const char *k, const char *s) +{ + struct props *p = &state->props; + int fmt_change = 0; + if (spa_streq(k, SPA_KEY_AUDIO_CHANNELS)) { + state->default_channels = atoi(s); + fmt_change++; + } else if (spa_streq(k, SPA_KEY_AUDIO_RATE)) { + state->default_rate = atoi(s); + fmt_change++; + } else if (spa_streq(k, SPA_KEY_AUDIO_FORMAT)) { + state->default_format = spa_avb_format_from_name(s, strlen(s)); + fmt_change++; + } else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) { + spa_avb_parse_position(&state->default_pos, s, strlen(s)); + fmt_change++; + } else if (spa_streq(k, SPA_KEY_AUDIO_ALLOWED_RATES)) { + state->n_allowed_rates = spa_avb_parse_rates(state->allowed_rates, + MAX_RATES, s, strlen(s)); + fmt_change++; + } else if (spa_streq(k, "avb.ifname")) { + snprintf(p->ifname, sizeof(p->ifname), "%s", s); + } else if (spa_streq(k, "avb.macaddr")) { + parse_addr(p->addr, s); + } else if (spa_streq(k, "avb.prio")) { + p->prio = atoi(s); + } else if (spa_streq(k, "avb.streamid")) { + parse_streamid(&p->streamid, s); + } else if (spa_streq(k, "avb.mtt")) { + p->mtt = atoi(s); + } else if (spa_streq(k, "avb.time-uncertainty")) { + p->t_uncertainty = atoi(s); + } else if (spa_streq(k, "avb.frames-per-pdu")) { + p->frames_per_pdu = atoi(s); + } else if (spa_streq(k, "avb.ptime-tolerance")) { + p->ptime_tolerance = atoi(s); + } else if (spa_streq(k, "latency.internal.rate")) { + state->process_latency.rate = atoi(s); + } else if (spa_streq(k, "latency.internal.ns")) { + state->process_latency.ns = atoi(s); + } else if (spa_streq(k, "clock.name")) { + spa_scnprintf(state->clock_name, + sizeof(state->clock_name), "%s", s); + } else + return 0; + + if (fmt_change > 0) { + struct port *port = &state->ports0; + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->paramsPORT_EnumFormat.user++; + } + return 1; +} + +static int position_to_string(struct channel_map *map, char *val, size_t len) +{ + uint32_t i, o = 0; + int r; + o += snprintf(val, len, " "); + for (i = 0; i < map->channels; i++) { + r = snprintf(val+o, len-o, "%s%s", i == 0 ? "" : ", ", + spa_debug_type_find_short_name(spa_type_audio_channel, + map->posi)); + if (r < 0 || o + r >= len) + return -ENOSPC; + o += r; + } + if (len > o) + o += snprintf(val+o, len-o, " "); + return 0; +} + +static int uint32_array_to_string(uint32_t *vals, uint32_t n_vals, char *val, size_t len) +{ + uint32_t i, o = 0; + int r; + o += snprintf(val, len, " "); + for (i = 0; i < n_vals; i++) { + r = snprintf(val+o, len-o, "%s%d", i == 0 ? "" : ", ", valsi); + if (r < 0 || o + r >= len) + return -ENOSPC; + o += r; + } + if (len > o) + o += snprintf(val+o, len-o, " "); + return 0; +} + +struct spa_pod *spa_avb_enum_propinfo(struct state *state, + uint32_t idx, struct spa_pod_builder *b) +{ + struct spa_pod *param; + struct props *p = &state->props; + char tmp128; + + switch (idx) { + case 0: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_CHANNELS), + SPA_PROP_INFO_description, SPA_POD_String("Audio Channels"), + SPA_PROP_INFO_type, SPA_POD_Int(state->default_channels), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 1: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_RATE), + SPA_PROP_INFO_description, SPA_POD_String("Audio Rate"), + SPA_PROP_INFO_type, SPA_POD_Int(state->default_rate), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 2: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_FORMAT), + SPA_PROP_INFO_description, SPA_POD_String("Audio Format"), + SPA_PROP_INFO_type, SPA_POD_String( + spa_debug_type_find_short_name(spa_type_audio_format, + state->default_format)), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 3: + { + char buf1024; + position_to_string(&state->default_pos, buf, sizeof(buf)); + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_POSITION), + SPA_PROP_INFO_description, SPA_POD_String("Audio Position"), + SPA_PROP_INFO_type, SPA_POD_String(buf), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + } + case 4: + { + char buf1024; + uint32_array_to_string(state->allowed_rates, state->n_allowed_rates, buf, sizeof(buf)); + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_ALLOWED_RATES), + SPA_PROP_INFO_description, SPA_POD_String("Audio Allowed Rates"), + SPA_PROP_INFO_type, SPA_POD_String(buf), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + } + case 5: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("avb.ifname"), + SPA_PROP_INFO_description, SPA_POD_String("The AVB interface name"), + SPA_PROP_INFO_type, SPA_POD_Stringn(p->ifname, sizeof(p->ifname)), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 6: + format_addr(tmp, sizeof(tmp), p->addr); + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("avb.macaddr"), + SPA_PROP_INFO_description, SPA_POD_String("The AVB MAC address"), + SPA_PROP_INFO_type, SPA_POD_String(tmp), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 7: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("avb.prio"), + SPA_PROP_INFO_description, SPA_POD_String("The AVB stream priority"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->prio, 0, INT32_MAX), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 8: + format_streamid(tmp, sizeof(tmp), p->streamid); + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("avb.streamid"), + SPA_PROP_INFO_description, SPA_POD_String("The AVB stream id"), + SPA_PROP_INFO_type, SPA_POD_String(tmp), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 9: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("avb.mtt"), + SPA_PROP_INFO_description, SPA_POD_String("The AVB mtt"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->mtt, 0, INT32_MAX), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 10: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("avb.time-uncertainty"), + SPA_PROP_INFO_description, SPA_POD_String("The AVB time uncertainty"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->t_uncertainty, 0, INT32_MAX), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 11: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("avb.frames-per-pdu"), + SPA_PROP_INFO_description, SPA_POD_String("The AVB frames per packet"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->frames_per_pdu, 0, INT32_MAX), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 12: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("avb.ptime-tolerance"), + SPA_PROP_INFO_description, SPA_POD_String("The AVB packet tolerance"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->ptime_tolerance, 0, INT32_MAX), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 13: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("latency.internal.rate"), + SPA_PROP_INFO_description, SPA_POD_String("Internal latency in samples"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(state->process_latency.rate, + 0, 65536), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 14: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("latency.internal.ns"), + SPA_PROP_INFO_description, SPA_POD_String("Internal latency in nanoseconds"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Long(state->process_latency.ns, + 0, 2 * SPA_NSEC_PER_SEC), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 15: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("clock.name"), + SPA_PROP_INFO_description, SPA_POD_String("The name of the clock"), + SPA_PROP_INFO_type, SPA_POD_String(state->clock_name), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + default: + return NULL; + } + return param; +} + +int spa_avb_add_prop_params(struct state *state, struct spa_pod_builder *b) +{ + struct props *p = &state->props; + struct spa_pod_frame f1; + char buf1024; + + spa_pod_builder_prop(b, SPA_PROP_params, 0); + spa_pod_builder_push_struct(b, &f0); + + spa_pod_builder_string(b, SPA_KEY_AUDIO_CHANNELS); + spa_pod_builder_int(b, state->default_channels); + + spa_pod_builder_string(b, SPA_KEY_AUDIO_RATE); + spa_pod_builder_int(b, state->default_rate); + + spa_pod_builder_string(b, SPA_KEY_AUDIO_FORMAT); + spa_pod_builder_string(b, + spa_debug_type_find_short_name(spa_type_audio_format, + state->default_format)); + + position_to_string(&state->default_pos, buf, sizeof(buf)); + spa_pod_builder_string(b, SPA_KEY_AUDIO_POSITION); + spa_pod_builder_string(b, buf); + + uint32_array_to_string(state->allowed_rates, state->n_allowed_rates, + buf, sizeof(buf)); + spa_pod_builder_string(b, SPA_KEY_AUDIO_ALLOWED_RATES); + spa_pod_builder_string(b, buf); + + spa_pod_builder_string(b, "avb.ifname"); + spa_pod_builder_string(b, p->ifname); + + format_addr(buf, sizeof(buf), p->addr); + spa_pod_builder_string(b, "avb.macadr"); + spa_pod_builder_string(b, buf); + + spa_pod_builder_string(b, "avb.prio"); + spa_pod_builder_int(b, p->prio); + + format_streamid(buf, sizeof(buf), p->streamid); + spa_pod_builder_string(b, "avb.streamid"); + spa_pod_builder_string(b, buf); + spa_pod_builder_string(b, "avb.mtt"); + spa_pod_builder_int(b, p->mtt); + spa_pod_builder_string(b, "avb.time-uncertainty"); + spa_pod_builder_int(b, p->t_uncertainty); + spa_pod_builder_string(b, "avb.frames-per-pdu"); + spa_pod_builder_int(b, p->frames_per_pdu); + spa_pod_builder_string(b, "avb.ptime-tolerance"); + spa_pod_builder_int(b, p->ptime_tolerance); + + spa_pod_builder_string(b, "latency.internal.rate"); + spa_pod_builder_int(b, state->process_latency.rate); + + spa_pod_builder_string(b, "latency.internal.ns"); + spa_pod_builder_long(b, state->process_latency.ns); + + spa_pod_builder_string(b, "clock.name"); + spa_pod_builder_string(b, state->clock_name); + + spa_pod_builder_pop(b, &f0); + return 0; +} + +int spa_avb_parse_prop_params(struct state *state, struct spa_pod *params) +{ + struct spa_pod_parser prs; + struct spa_pod_frame f; + int changed = 0; + + if (params == NULL) + return 0; + + spa_pod_parser_pod(&prs, params); + if (spa_pod_parser_push_struct(&prs, &f) < 0) + return 0; + + while (true) { + const char *name; + struct spa_pod *pod; + char value512; + + if (spa_pod_parser_get_string(&prs, &name) < 0) + break; + + if (spa_pod_parser_get_pod(&prs, &pod) < 0) + break; + if (spa_pod_is_string(pod)) { + spa_pod_copy_string(pod, sizeof(value), value); + } else if (spa_pod_is_int(pod)) { + snprintf(value, sizeof(value), "%d", + SPA_POD_VALUE(struct spa_pod_int, pod)); + } else if (spa_pod_is_long(pod)) { + snprintf(value, sizeof(value), "%"PRIi64, + SPA_POD_VALUE(struct spa_pod_long, pod)); + } else if (spa_pod_is_bool(pod)) { + snprintf(value, sizeof(value), "%s", + SPA_POD_VALUE(struct spa_pod_bool, pod) ? + "true" : "false"); + } else + continue; + + spa_log_info(state->log, "key:'%s' val:'%s'", name, value); + avb_set_param(state, name, value); + changed++; + } + if (changed > 0) { + state->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + state->paramsNODE_Props.user++; + } + return changed; +} + +int spa_avb_init(struct state *state, const struct spa_dict *info) +{ + uint32_t i; + + state->quantum_limit = 8192; + for (i = 0; info && i < info->n_items; i++) { + const char *k = info->itemsi.key; + const char *s = info->itemsi.value; + if (spa_streq(k, "clock.quantum-limit")) { + spa_atou32(s, &state->quantum_limit, 0); + } else { + avb_set_param(state, k, s); + } + } + + state->ringbuffer_size = state->quantum_limit * 64; + state->ringbuffer_data = calloc(1, state->ringbuffer_size * 4); + spa_ringbuffer_init(&state->ring); + return 0; +} + +int spa_avb_clear(struct state *state) +{ + return 0; +} + +static int spa_format_to_aaf(uint32_t format) +{ + switch(format) { + case SPA_AUDIO_FORMAT_F32_BE: return SPA_AVBTP_AAF_FORMAT_FLOAT_32BIT; + case SPA_AUDIO_FORMAT_S32_BE: return SPA_AVBTP_AAF_FORMAT_INT_32BIT; + case SPA_AUDIO_FORMAT_S24_BE: return SPA_AVBTP_AAF_FORMAT_INT_24BIT; + case SPA_AUDIO_FORMAT_S16_BE: return SPA_AVBTP_AAF_FORMAT_INT_16BIT; + default: return SPA_AVBTP_AAF_FORMAT_USER; + } +} + +static int frame_size(uint32_t format) +{ + switch(format) { + case SPA_AUDIO_FORMAT_F32_BE: + case SPA_AUDIO_FORMAT_S32_BE: return 4; + case SPA_AUDIO_FORMAT_S24_BE: return 3; + case SPA_AUDIO_FORMAT_S16_BE: return 2; + default: return 0; + } +} + +static int spa_rate_to_aaf(uint32_t rate) +{ + switch(rate) { + case 8000: return SPA_AVBTP_AAF_PCM_NSR_8KHZ; + case 16000: return SPA_AVBTP_AAF_PCM_NSR_16KHZ; + case 24000: return SPA_AVBTP_AAF_PCM_NSR_24KHZ; + case 32000: return SPA_AVBTP_AAF_PCM_NSR_32KHZ; + case 44100: return SPA_AVBTP_AAF_PCM_NSR_44_1KHZ; + case 48000: return SPA_AVBTP_AAF_PCM_NSR_48KHZ; + case 88200: return SPA_AVBTP_AAF_PCM_NSR_88_2KHZ; + case 96000: return SPA_AVBTP_AAF_PCM_NSR_96KHZ; + case 176400: return SPA_AVBTP_AAF_PCM_NSR_176_4KHZ; + case 192000: return SPA_AVBTP_AAF_PCM_NSR_192KHZ; + default: return SPA_AVBTP_AAF_PCM_NSR_USER; + } +} + +int +spa_avb_enum_format(struct state *state, int seq, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + uint8_t buffer4096; + struct spa_pod_builder b = { 0 }; + struct spa_pod_frame f2; + struct spa_pod *fmt; + int res = 0; + struct spa_result_node_params result; + uint32_t count = 0; + + result.id = SPA_PARAM_EnumFormat; + result.next = start; + +next: + result.index = result.next++; + + if (result.index > 0) + return 0; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + spa_pod_builder_push_object(&b, &f0, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); + spa_pod_builder_add(&b, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + 0); + + spa_pod_builder_prop(&b, SPA_FORMAT_AUDIO_format, 0); + if (state->default_format != 0) { + spa_pod_builder_id(&b, state->default_format); + } else { + spa_pod_builder_push_choice(&b, &f1, SPA_CHOICE_Enum, 0); + spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_F32_BE); + spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_F32_BE); + spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_S32_BE); + spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_S24_BE); + spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_S16_BE); + spa_pod_builder_pop(&b, &f1); + } + spa_pod_builder_prop(&b, SPA_FORMAT_AUDIO_rate, 0); + if (state->default_rate != 0) { + spa_pod_builder_int(&b, state->default_rate); + } else { + spa_pod_builder_push_choice(&b, &f1, SPA_CHOICE_Enum, 0); + spa_pod_builder_int(&b, 48000); + spa_pod_builder_int(&b, 8000); + spa_pod_builder_int(&b, 16000); + spa_pod_builder_int(&b, 24000); + spa_pod_builder_int(&b, 32000); + spa_pod_builder_int(&b, 44100); + spa_pod_builder_int(&b, 48000); + spa_pod_builder_int(&b, 88200); + spa_pod_builder_int(&b, 96000); + spa_pod_builder_int(&b, 176400); + spa_pod_builder_int(&b, 192000); + spa_pod_builder_pop(&b, &f1); + } + spa_pod_builder_prop(&b, SPA_FORMAT_AUDIO_channels, 0); + if (state->default_channels != 0) { + spa_pod_builder_int(&b, state->default_channels); + } else { + spa_pod_builder_push_choice(&b, &f1, SPA_CHOICE_Range, 0); + spa_pod_builder_int(&b, 8); + spa_pod_builder_int(&b, 2); + spa_pod_builder_int(&b, 32); + spa_pod_builder_pop(&b, &f1); + } + fmt = spa_pod_builder_pop(&b, &f0); + + if (spa_pod_filter(&b, &result.param, fmt, filter) < 0) + goto next; + + spa_node_emit_result(&state->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return res; +} + +static int setup_socket(struct state *state) +{ + int fd, res; + struct ifreq req; + struct props *p = &state->props; + + fd = socket(AF_PACKET, SOCK_DGRAM|SOCK_NONBLOCK, htons(ETH_P_TSN)); + if (fd < 0) { + spa_log_error(state->log, "socket() failed: %m"); + return -errno; + } + + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", p->ifname); + res = ioctl(fd, SIOCGIFINDEX, &req); + if (res < 0) { + spa_log_error(state->log, "SIOCGIFINDEX %s failed: %m", p->ifname); + res = -errno; + goto error_close; + } + + state->sock_addr.sll_family = AF_PACKET; + state->sock_addr.sll_protocol = htons(ETH_P_TSN); + state->sock_addr.sll_halen = ETH_ALEN; + state->sock_addr.sll_ifindex = req.ifr_ifindex; + memcpy(&state->sock_addr.sll_addr, p->addr, ETH_ALEN); + + if (state->ports0.direction == SPA_DIRECTION_INPUT) { + struct sock_txtime txtime_cfg; + + res = setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &p->prio, + sizeof(p->prio)); + if (res < 0) { + spa_log_error(state->log, "setsockopt(SO_PRIORITY %d) failed: %m", p->prio); + res = -errno; + goto error_close; + } + + txtime_cfg.clockid = CLOCK_TAI; + txtime_cfg.flags = 0; + res = setsockopt(fd, SOL_SOCKET, SO_TXTIME, &txtime_cfg, + sizeof(txtime_cfg)); + if (res < 0) { + spa_log_error(state->log, "setsockopt(SO_TXTIME) failed: %m"); + res = -errno; + goto error_close; + } + } else { + struct packet_mreq mreq = { 0 }; + + res = bind(fd, (struct sockaddr *) &state->sock_addr, + sizeof(state->sock_addr)); + if (res < 0) { + spa_log_error(state->log, "bind() failed: %m"); + res = -errno; + goto error_close; + } + + mreq.mr_ifindex = req.ifr_ifindex; + mreq.mr_type = PACKET_MR_MULTICAST; + mreq.mr_alen = ETH_ALEN; + memcpy(&mreq.mr_address, p->addr, ETH_ALEN); + res = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, + &mreq, sizeof(struct packet_mreq)); + if (res < 0) { + spa_log_error(state->log, "setsockopt(ADD_MEMBERSHIP) failed: %m"); + res = -errno; + goto error_close; + } + } + state->sockfd = fd; + return 0; + +error_close: + close(fd); + return res; +} + +static int setup_packet(struct state *state, struct spa_audio_info *fmt) +{ + struct spa_avbtp_packet_aaf *pdu; + struct props *p = &state->props; + ssize_t payload_size, hdr_size, pdu_size; + + hdr_size = sizeof(*pdu); + payload_size = state->stride * p->frames_per_pdu; + pdu_size = hdr_size + payload_size; + if ((pdu = calloc(1, pdu_size)) == NULL) + return -errno; + + SPA_AVBTP_PACKET_AAF_SET_SUBTYPE(pdu, SPA_AVBTP_SUBTYPE_AAF); + + if (state->ports0.direction == SPA_DIRECTION_INPUT) { + SPA_AVBTP_PACKET_AAF_SET_SV(pdu, 1); + SPA_AVBTP_PACKET_AAF_SET_STREAM_ID(pdu, p->streamid); + SPA_AVBTP_PACKET_AAF_SET_TV(pdu, 1); + SPA_AVBTP_PACKET_AAF_SET_FORMAT(pdu, spa_format_to_aaf(state->format)); + SPA_AVBTP_PACKET_AAF_SET_NSR(pdu, spa_rate_to_aaf(state->rate)); + SPA_AVBTP_PACKET_AAF_SET_CHAN_PER_FRAME(pdu, state->channels); + SPA_AVBTP_PACKET_AAF_SET_BIT_DEPTH(pdu, frame_size(state->format)*8); + SPA_AVBTP_PACKET_AAF_SET_DATA_LEN(pdu, payload_size); + SPA_AVBTP_PACKET_AAF_SET_SP(pdu, SPA_AVBTP_AAF_PCM_SP_NORMAL); + } + state->pdu = pdu; + state->hdr_size = hdr_size; + state->payload_size = payload_size; + state->pdu_size = pdu_size; + return 0; +} + +static int setup_msg(struct state *state) +{ + state->iov0.iov_base = state->pdu; + state->iov0.iov_len = state->hdr_size; + state->iov1.iov_base = state->pdu->payload; + state->iov1.iov_len = state->payload_size; + state->iov2.iov_base = state->pdu->payload; + state->iov2.iov_len = 0; + state->msg.msg_name = &state->sock_addr; + state->msg.msg_namelen = sizeof(state->sock_addr); + state->msg.msg_iov = state->iov; + state->msg.msg_iovlen = 3; + state->msg.msg_control = state->control; + state->msg.msg_controllen = sizeof(state->control); + state->cmsg = CMSG_FIRSTHDR(&state->msg); + state->cmsg->cmsg_level = SOL_SOCKET; + state->cmsg->cmsg_type = SCM_TXTIME; + state->cmsg->cmsg_len = CMSG_LEN(sizeof(__u64)); + return 0; +} + +int spa_avb_clear_format(struct state *state) +{ + close(state->sockfd); + close(state->timerfd); + free(state->pdu); + + return 0; +} + +int spa_avb_set_format(struct state *state, struct spa_audio_info *fmt, uint32_t flags) +{ + int res; + struct props *p = &state->props; + + state->format = fmt->info.raw.format; + state->rate = fmt->info.raw.rate; + state->channels = fmt->info.raw.channels; + state->blocks = 1; + state->stride = state->channels * frame_size(state->format); + + if ((res = setup_socket(state)) < 0) + return res; + + if ((res = spa_system_timerfd_create(state->data_system, + CLOCK_REALTIME, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK)) < 0) + goto error_close_sockfd; + + state->timerfd = res; + + if ((res = setup_packet(state, fmt)) < 0) + return res; + + if ((res = setup_msg(state)) < 0) + return res; + + state->pdu_period = SPA_NSEC_PER_SEC * p->frames_per_pdu / + state->rate; + + return 0; + +error_close_sockfd: + close(state->sockfd); + return res; +} + +void spa_avb_recycle_buffer(struct state *this, struct port *port, uint32_t buffer_id) +{ + struct buffer *b = &port->buffersbuffer_id; + + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { + spa_log_trace_fp(this->log, "%p: recycle buffer %u", this, buffer_id); + spa_list_append(&port->free, &b->link); + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); + } +} + +static void reset_buffers(struct state *this, struct port *port) +{ + uint32_t i; + + spa_list_init(&port->free); + spa_list_init(&port->ready); + + for (i = 0; i < port->n_buffers; i++) { + struct buffer *b = &port->buffersi; + if (port->direction == SPA_DIRECTION_INPUT) { + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); + spa_node_call_reuse_buffer(&this->callbacks, 0, b->id); + } else { + spa_list_append(&port->free, &b->link); + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); + } + } +} + +static inline bool is_pdu_valid(struct state *state) +{ + uint8_t seq_num; + + seq_num = SPA_AVBTP_PACKET_AAF_GET_SEQ_NUM(state->pdu); + + if (state->prev_seq != 0 && (uint8_t)(state->prev_seq + 1) != seq_num) { + spa_log_warn(state->log, "dropped packets %d != %d", state->prev_seq + 1, seq_num); + } + state->prev_seq = seq_num; + return true; +} + +static inline void +set_iovec(struct spa_ringbuffer *rbuf, void *buffer, uint32_t size, + uint32_t offset, struct iovec *iov, uint32_t len) +{ + iov0.iov_len = SPA_MIN(len, size - offset); + iov0.iov_base = SPA_PTROFF(buffer, offset, void); + iov1.iov_len = len - iov0.iov_len; + iov1.iov_base = buffer; +} + +static void avb_on_socket_event(struct spa_source *source) +{ + struct state *state = source->data; + ssize_t n; + int32_t filled; + uint32_t subtype, index; + struct spa_avbtp_packet_aaf *pdu = state->pdu; + bool overrun = false; + + filled = spa_ringbuffer_get_write_index(&state->ring, &index); + overrun = filled > (int32_t) state->ringbuffer_size; + if (overrun) { + state->iov1.iov_base = state->pdu->payload; + state->iov1.iov_len = state->payload_size; + state->iov2.iov_len = 0; + } else { + set_iovec(&state->ring, + state->ringbuffer_data, + state->ringbuffer_size, + index % state->ringbuffer_size, + &state->iov1, state->payload_size); + } + + n = recvmsg(state->sockfd, &state->msg, 0); + if (n < 0) { + spa_log_error(state->log, "recv() failed: %m"); + return; + } + if (n != (ssize_t)state->pdu_size) { + spa_log_error(state->log, "AVB packet dropped: Invalid size"); + return; + } + + subtype = SPA_AVBTP_PACKET_AAF_GET_SUBTYPE(pdu); + if (subtype != SPA_AVBTP_SUBTYPE_AAF) { + spa_log_error(state->log, "non supported subtype %d", subtype); + return; + } + if (!is_pdu_valid(state)) { + spa_log_error(state->log, "AAF PDU invalid"); + return; + } + if (overrun) { + spa_log_warn(state->log, "overrun %d", filled); + return; + } + index += state->payload_size; + spa_ringbuffer_write_update(&state->ring, index); +} + +static void set_timeout(struct state *state, uint64_t next_time) +{ + struct itimerspec ts; + uint64_t time_utc; + + spa_log_trace(state->log, "set timeout %"PRIu64, next_time); + + time_utc = next_time > TAI_OFFSET ? TAI_TO_UTC(next_time) : 0; + ts.it_value.tv_sec = time_utc / SPA_NSEC_PER_SEC; + ts.it_value.tv_nsec = time_utc % SPA_NSEC_PER_SEC; + ts.it_interval.tv_sec = 0; + ts.it_interval.tv_nsec = 0; + spa_system_timerfd_settime(state->data_system, + state->timer_source.fd, SPA_FD_TIMER_ABSTIME, &ts, NULL); +} + +static int flush_write(struct state *state, uint64_t current_time) +{ + int32_t avail, wanted; + uint32_t index; + uint64_t ptime, txtime; + int pdu_count; + struct props *p = &state->props; + struct spa_avbtp_packet_aaf *pdu = state->pdu; + ssize_t n; + + avail = spa_ringbuffer_get_read_index(&state->ring, &index); + wanted = state->duration * state->stride; + if (avail < wanted) { + spa_log_warn(state->log, "underrun %d < %d", avail, wanted); + return -EPIPE; + } + + pdu_count = state->duration / p->frames_per_pdu; + + txtime = current_time + p->t_uncertainty; + ptime = txtime + p->mtt; + + while (pdu_count--) { + *(__u64 *)CMSG_DATA(state->cmsg) = txtime; + + set_iovec(&state->ring, + state->ringbuffer_data, + state->ringbuffer_size, + index % state->ringbuffer_size, + &state->iov1, state->payload_size); + + SPA_AVBTP_PACKET_AAF_SET_SEQ_NUM(pdu, state->pdu_seq++); + SPA_AVBTP_PACKET_AAF_SET_TIMESTAMP(pdu, ptime); + + n = sendmsg(state->sockfd, &state->msg, 0); + if (n < 0 || n != (ssize_t)state->pdu_size) { + spa_log_error(state->log, "sendmdg() failed: %m"); + } + txtime += state->pdu_period; + ptime += state->pdu_period; + index += state->payload_size; + } + spa_ringbuffer_read_update(&state->ring, index); + return 0; +} + +int spa_avb_write(struct state *state) +{ + int32_t filled; + uint32_t index, to_write; + struct port *port = &state->ports0; + + filled = spa_ringbuffer_get_write_index(&state->ring, &index); + if (filled < 0) { + spa_log_warn(state->log, "underrun %d", filled); + } else if (filled > (int32_t)state->ringbuffer_size) { + spa_log_warn(state->log, "overrun %d", filled); + } + to_write = state->ringbuffer_size - filled; + + while (!spa_list_is_empty(&port->ready) && to_write > 0) { + size_t n_bytes; + struct buffer *b; + struct spa_data *d; + uint32_t offs, avail, size; + + b = spa_list_first(&port->ready, struct buffer, link); + d = b->buf->datas; + + offs = SPA_MIN(d0.chunk->offset + port->ready_offset, d0.maxsize); + size = SPA_MIN(d0.chunk->size, d0.maxsize - offs); + avail = size - offs; + + n_bytes = SPA_MIN(avail, to_write); + if (n_bytes == 0) + break; + + spa_ringbuffer_write_data(&state->ring, + state->ringbuffer_data, + state->ringbuffer_size, + index % state->ringbuffer_size, + SPA_PTROFF(d0.data, offs, void), + n_bytes); + + port->ready_offset += n_bytes; + + if (port->ready_offset >= size || avail == 0) { + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); + port->io->buffer_id = b->id; + spa_log_trace_fp(state->log, "%p: reuse buffer %u", state, b->id); + + spa_node_call_reuse_buffer(&state->callbacks, 0, b->id); + + port->ready_offset = 0; + } + to_write -= n_bytes; + index += n_bytes; + } + spa_ringbuffer_write_update(&state->ring, index); + + if (state->following) { + flush_write(state, state->position->clock.nsec); + } + return 0; +} + +static int handle_play(struct state *state, uint64_t current_time) +{ + flush_write(state, current_time); + spa_node_call_ready(&state->callbacks, SPA_STATUS_NEED_DATA); + return 0; +} + +int spa_avb_read(struct state *state) +{ + int32_t avail, wanted; + uint32_t index; + struct port *port = &state->ports0; + struct buffer *b; + struct spa_data *d; + uint32_t n_bytes; + + if (state->position) + state->duration = state->position->clock.duration; + + avail = spa_ringbuffer_get_read_index(&state->ring, &index); + wanted = state->duration * state->stride; + + if (spa_list_is_empty(&port->free)) { + spa_log_warn(state->log, "out of buffers"); + return -EPIPE; + } + + b = spa_list_first(&port->free, struct buffer, link); + d = b->buf->datas; + + n_bytes = SPA_MIN(d0.maxsize, (uint32_t)wanted); + + if (avail < wanted) { + spa_log_warn(state->log, "capture underrun %d < %d", avail, wanted); + memset(d0.data, 0, n_bytes); + } else { + spa_ringbuffer_read_data(&state->ring, + state->ringbuffer_data, + state->ringbuffer_size, + index % state->ringbuffer_size, + d0.data, n_bytes); + index += n_bytes; + spa_ringbuffer_read_update(&state->ring, index); + } + + d0.chunk->offset = 0; + d0.chunk->size = n_bytes; + d0.chunk->stride = state->stride; + d0.chunk->flags = 0; + + spa_list_remove(&b->link); + spa_list_append(&port->ready, &b->link); + + return 0; +} + +static int handle_capture(struct state *state, uint64_t current_time) +{ + struct port *port = &state->ports0; + struct spa_io_buffers *io; + struct buffer *b; + + spa_avb_read(state); + + if (spa_list_is_empty(&port->ready)) + return 0; + + io = port->io; + if (io != NULL && + (io->status != SPA_STATUS_HAVE_DATA || port->rate_match != NULL)) { + if (io->buffer_id < port->n_buffers) + spa_avb_recycle_buffer(state, port, io->buffer_id); + + b = spa_list_first(&port->ready, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); + + io->buffer_id = b->id; + io->status = SPA_STATUS_HAVE_DATA; + spa_log_trace_fp(state->log, "%p: output buffer:%d", state, b->id); + } + spa_node_call_ready(&state->callbacks, SPA_STATUS_HAVE_DATA); + return 0; +} + +static void avb_on_timeout_event(struct spa_source *source) +{ + struct state *state = source->data; + uint64_t expirations, current_time, duration; + uint32_t rate; + + spa_log_trace(state->log, "timeout"); + + if (spa_system_timerfd_read(state->data_system, + state->timer_source.fd, &expirations) < 0) { + if (errno == EAGAIN) + return; + spa_log_error(state->log, "read timerfd: %m"); + } + + current_time = state->next_time; + if (SPA_LIKELY(state->position)) { + duration = state->position->clock.duration; + rate = state->position->clock.rate.denom; + } else { + duration = 1024; + rate = 48000; + } + state->duration = duration; + + if (state->ports0.direction == SPA_DIRECTION_INPUT) + handle_play(state, current_time); + else + handle_capture(state, current_time); + + state->next_time = current_time + duration * SPA_NSEC_PER_SEC / rate; + + if (SPA_LIKELY(state->clock)) { + state->clock->nsec = current_time; + state->clock->position += duration; + state->clock->duration = duration; + state->clock->delay = 0; + state->clock->rate_diff = 1.0; + state->clock->next_nsec = state->next_time; + } + + set_timeout(state, state->next_time); +} + +static int set_timers(struct state *state) +{ + struct timespec now; + int res; + + if ((res = spa_system_clock_gettime(state->data_system, CLOCK_TAI, &now)) < 0) + return res; + + state->next_time = SPA_TIMESPEC_TO_NSEC(&now); + + if (state->following) { + set_timeout(state, 0); + } else { + set_timeout(state, state->next_time); + } + return 0; +} + +static inline bool is_following(struct state *state) +{ + return state->position && state->clock && state->position->clock.id != state->clock->id; +} + +static int do_reassign_follower(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) +{ + struct state *state = user_data; + spa_dll_init(&state->dll); + set_timers(state); + return 0; +} + +int spa_avb_reassign_follower(struct state *state) +{ + bool following, freewheel; + + if (!state->started) + return 0; + + following = is_following(state); + if (following != state->following) { + spa_log_debug(state->log, "%p: reassign follower %d->%d", state, state->following, following); + state->following = following; + spa_loop_invoke(state->data_loop, do_reassign_follower, 0, NULL, 0, true, state); + } + + freewheel = state->position && + SPA_FLAG_IS_SET(state->position->clock.flags, SPA_IO_CLOCK_FLAG_FREEWHEEL); + + if (state->freewheel != freewheel) { + spa_log_debug(state->log, "%p: freewheel %d->%d", state, state->freewheel, freewheel); + state->freewheel = freewheel; + } + return 0; +} + +int spa_avb_start(struct state *state) +{ + if (state->started) + return 0; + + if (state->position) { + state->duration = state->position->clock.duration; + state->rate_denom = state->position->clock.rate.denom; + } else { + state->duration = 1024; + state->rate_denom = state->rate; + } + + spa_dll_init(&state->dll); + state->max_error = (256.0 * state->rate) / state->rate_denom; + + state->following = is_following(state); + + state->timer_source.func = avb_on_timeout_event; + state->timer_source.data = state; + state->timer_source.fd = state->timerfd; + state->timer_source.mask = SPA_IO_IN; + state->timer_source.rmask = 0; + spa_loop_add_source(state->data_loop, &state->timer_source); + + state->pdu_seq = 0; + + if (state->ports0.direction == SPA_DIRECTION_OUTPUT) { + state->sock_source.func = avb_on_socket_event; + state->sock_source.data = state; + state->sock_source.fd = state->sockfd; + state->sock_source.mask = SPA_IO_IN; + state->sock_source.rmask = 0; + spa_loop_add_source(state->data_loop, &state->sock_source); + } + + reset_buffers(state, &state->ports0); + + set_timers(state); + + state->started = true; + + return 0; +} + +static int do_remove_source(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) +{ + struct state *state = user_data; + + spa_loop_remove_source(state->data_loop, &state->timer_source); + + if (state->ports0.direction == SPA_DIRECTION_OUTPUT) { + spa_loop_remove_source(state->data_loop, &state->sock_source); + } + return 0; +} + +int spa_avb_pause(struct state *state) +{ + if (!state->started) + return 0; + + spa_log_debug(state->log, "%p: pause", state); + + spa_loop_invoke(state->data_loop, do_remove_source, 0, NULL, 0, true, state); + + state->started = false; + set_timeout(state, 0); + + return 0; +}
View file
pipewire-0.3.56.tar.gz/spa/plugins/avb/avb-pcm.h
Added
@@ -0,0 +1,343 @@ +/* Spa AVB PCM + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef SPA_AVB_PCM_H +#define SPA_AVB_PCM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stddef.h> +#include <math.h> +#include <linux/if_ether.h> +#include <linux/if_packet.h> +#include <linux/net_tstamp.h> +#include <limits.h> +#include <net/if.h> + +#include <avbtp/packets.h> + +#include <spa/support/plugin.h> +#include <spa/support/loop.h> +#include <spa/utils/list.h> +#include <spa/utils/json.h> +#include <spa/utils/dll.h> + +#include <spa/node/node.h> +#include <spa/node/utils.h> +#include <spa/node/io.h> +#include <spa/debug/types.h> +#include <spa/utils/ringbuffer.h> +#include <spa/param/param.h> +#include <spa/param/latency-utils.h> +#include <spa/param/audio/format-utils.h> + +#include "avb.h" + +#define MAX_RATES 16 + +#define DEFAULT_IFNAME "eth0" +#define DEFAULT_ADDR "01:AA:AA:AA:AA:AA" +#define DEFAULT_PRIO 0 +#define DEFAULT_STREAMID "AA:BB:CC:DD:EE:FF:0000" +#define DEFAULT_MTT 5000000 +#define DEFAULT_TU 1000000 +#define DEFAULT_FRAMES_PER_PDU 8 + +#define DEFAULT_PERIOD 1024u +#define DEFAULT_RATE 48000u +#define DEFAULT_CHANNELS 8u + +struct props { + char ifnameIFNAMSIZ; + unsigned char addrETH_ALEN; + int prio; + uint64_t streamid; + int mtt; + int t_uncertainty; + uint32_t frames_per_pdu; + int ptime_tolerance; +}; + +static inline int parse_addr(unsigned char addrETH_ALEN, const char *str) +{ + unsigned char adETH_ALEN; + if (sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &ad0, &ad1, &ad2, &ad3, &ad4, &ad5) != 6) + return -EINVAL; + memcpy(addr, ad, sizeof(ad)); + return 0; +} +static inline char *format_addr(char *str, size_t size, const unsigned char addrETH_ALEN) +{ + snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x", + addr0, addr1, addr2, + addr3, addr4, addr5); + return str; +} + +static inline int parse_streamid(uint64_t *streamid, const char *str) +{ + unsigned char addr6; + unsigned short unique_id; + if (sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx:%hx", + &addr0, &addr1, &addr2, &addr3, + &addr4, &addr5, &unique_id) != 7) + return -EINVAL; + *streamid = (uint64_t) addr0 << 56 | + (uint64_t) addr1 << 48 | + (uint64_t) addr2 << 40 | + (uint64_t) addr3 << 32 | + (uint64_t) addr4 << 24 | + (uint64_t) addr5 << 16 | + unique_id; + return 0; +} +static inline char *format_streamid(char *str, size_t size, const uint64_t streamid) +{ + snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x:%04x", + (uint8_t)(streamid >> 56), + (uint8_t)(streamid >> 48), + (uint8_t)(streamid >> 40), + (uint8_t)(streamid >> 32), + (uint8_t)(streamid >> 24), + (uint8_t)(streamid >> 16), + (uint16_t)(streamid)); + return str; +} + +#define MAX_BUFFERS 32 + +struct buffer { + uint32_t id; +#define BUFFER_FLAG_OUT (1<<0) + uint32_t flags; + struct spa_buffer *buf; + struct spa_meta_header *h; + struct spa_list link; +}; + +#define BW_MAX 0.128 +#define BW_MED 0.064 +#define BW_MIN 0.016 +#define BW_PERIOD (3 * SPA_NSEC_PER_SEC) + +struct channel_map { + uint32_t channels; + uint32_t posSPA_AUDIO_MAX_CHANNELS; +}; + +struct port { + enum spa_direction direction; + uint32_t id; + + uint64_t info_all; + struct spa_port_info info; +#define PORT_EnumFormat 0 +#define PORT_Meta 1 +#define PORT_IO 2 +#define PORT_Format 3 +#define PORT_Buffers 4 +#define PORT_Latency 5 +#define N_PORT_PARAMS 6 + struct spa_param_info paramsN_PORT_PARAMS; + + bool have_format; + struct spa_audio_info current_format; + + struct spa_io_buffers *io; + struct spa_io_rate_match *rate_match; + struct buffer buffersMAX_BUFFERS; + unsigned int n_buffers; + + struct spa_list free; + struct spa_list ready; + uint32_t ready_offset; +}; + +struct state { + struct spa_handle handle; + struct spa_node node; + + struct spa_log *log; + struct spa_system *data_system; + struct spa_loop *data_loop; + + struct spa_hook_list hooks; + struct spa_callbacks callbacks; + + uint64_t info_all; + struct spa_node_info info; +#define NODE_PropInfo 0 +#define NODE_Props 1 +#define NODE_IO 2 +#define NODE_ProcessLatency 3 +#define N_NODE_PARAMS 4 + struct spa_param_info paramsN_NODE_PARAMS; + struct props props; + + uint32_t default_period_size; + uint32_t default_format; + unsigned int default_channels; + unsigned int default_rate; + uint32_t allowed_ratesMAX_RATES; + uint32_t n_allowed_rates; + struct channel_map default_pos; + char clock_name64; + uint32_t quantum_limit; + + uint32_t format; + uint32_t rate; + uint32_t channels; + uint32_t stride; + uint32_t blocks; + uint32_t rate_denom; + + struct spa_io_clock *clock; + struct spa_io_position *position; + + struct port ports1; + + uint32_t duration; + unsigned int following:1; + unsigned int matching:1; + unsigned int resample:1; + unsigned int started:1; + unsigned int freewheel:1; + + int timerfd; + struct spa_source timer_source; + uint64_t next_time; + + int sockfd; + struct spa_source sock_source; + struct sockaddr_ll sock_addr; + + struct spa_avbtp_packet_aaf *pdu; + size_t hdr_size; + size_t payload_size; + size_t pdu_size; + int64_t pdu_period; + uint8_t pdu_seq; + uint8_t prev_seq; + + struct iovec iov3; + struct msghdr msg; + char controlCMSG_SPACE(sizeof(__u64)); + struct cmsghdr *cmsg; + + uint8_t *ringbuffer_data; + uint32_t ringbuffer_size; + struct spa_ringbuffer ring; + + struct spa_dll dll; + double max_error; + + struct spa_latency_info latency2; + struct spa_process_latency_info process_latency; +}; + +struct spa_pod *spa_avb_enum_propinfo(struct state *state, + uint32_t idx, struct spa_pod_builder *b); +int spa_avb_add_prop_params(struct state *state, struct spa_pod_builder *b); +int spa_avb_parse_prop_params(struct state *state, struct spa_pod *params); + +int spa_avb_enum_format(struct state *state, int seq, + uint32_t start, uint32_t num, + const struct spa_pod *filter); + +int spa_avb_clear_format(struct state *state); +int spa_avb_set_format(struct state *state, struct spa_audio_info *info, uint32_t flags); + +int spa_avb_init(struct state *state, const struct spa_dict *info); +int spa_avb_clear(struct state *state); + +int spa_avb_start(struct state *state); +int spa_avb_reassign_follower(struct state *state); +int spa_avb_pause(struct state *state); + +int spa_avb_write(struct state *state); +int spa_avb_read(struct state *state); +int spa_avb_skip(struct state *state); + +void spa_avb_recycle_buffer(struct state *state, struct port *port, uint32_t buffer_id); + +static inline uint32_t spa_avb_format_from_name(const char *name, size_t len) +{ + int i; + for (i = 0; spa_type_audio_formati.name; i++) { + if (strncmp(name, spa_debug_type_short_name(spa_type_audio_formati.name), len) == 0) + return spa_type_audio_formati.type; + } + return SPA_AUDIO_FORMAT_UNKNOWN; +} + +static inline uint32_t spa_avb_channel_from_name(const char *name) +{ + int i; + for (i = 0; spa_type_audio_channeli.name; i++) { + if (strcmp(name, spa_debug_type_short_name(spa_type_audio_channeli.name)) == 0) + return spa_type_audio_channeli.type; + } + return SPA_AUDIO_CHANNEL_UNKNOWN; +} + +static inline void spa_avb_parse_position(struct channel_map *map, const char *val, size_t len) +{ + struct spa_json it2; + char v256; + + spa_json_init(&it0, val, len); + if (spa_json_enter_array(&it0, &it1) <= 0) + spa_json_init(&it1, val, len); + + map->channels = 0; + while (spa_json_get_string(&it1, v, sizeof(v)) > 0 && + map->channels < SPA_AUDIO_MAX_CHANNELS) { + map->posmap->channels++ = spa_avb_channel_from_name(v); + } +} + +static inline uint32_t spa_avb_parse_rates(uint32_t *rates, uint32_t max, const char *val, size_t len) +{ + struct spa_json it2; + char v256; + uint32_t count; + + spa_json_init(&it0, val, len); + if (spa_json_enter_array(&it0, &it1) <= 0) + spa_json_init(&it1, val, len); + + count = 0; + while (spa_json_get_string(&it1, v, sizeof(v)) > 0 && count < max) + ratescount++ = atoi(v); + return count; +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_AVB_PCM_H */
View file
pipewire-0.3.56.tar.gz/spa/plugins/avb/avb.c
Added
@@ -0,0 +1,54 @@ +/* Spa AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <errno.h> + +#include <spa/support/plugin.h> +#include <spa/support/log.h> + +extern const struct spa_handle_factory spa_avb_sink_factory; +extern const struct spa_handle_factory spa_avb_source_factory; + +struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.avb"); +struct spa_log_topic *avb_log_topic = &log_topic; + +SPA_EXPORT +int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *factory = &spa_avb_sink_factory; + break; + case 1: + *factory = &spa_avb_source_factory; + break; + default: + return 0; + } + (*index)++; + return 1; +}
View file
pipewire-0.3.56.tar.gz/spa/plugins/avb/avb.h
Added
@@ -0,0 +1,39 @@ +/* Spa AVB + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef SPA_AVB_H +#define SPA_AVB_H + +#include <spa/support/log.h> + +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT avb_log_topic +extern struct spa_log_topic *avb_log_topic; + +static inline void avb_log_topic_init(struct spa_log *log) +{ + spa_log_topic_init(log, avb_log_topic); +} + +#endif /* SPA_AVB_H */
View file
pipewire-0.3.56.tar.gz/spa/plugins/avb/avbtp
Added
+(directory)
View file
pipewire-0.3.56.tar.gz/spa/plugins/avb/avbtp/packets.h
Added
@@ -0,0 +1,220 @@ +/* Spa AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef SPA_AVB_PACKETS_H +#define SPA_AVB_PACKETS_H + +#define SPA_AVBTP_SUBTYPE_61883_IIDC 0x00 +#define SPA_AVBTP_SUBTYPE_MMA_STREAM 0x01 +#define SPA_AVBTP_SUBTYPE_AAF 0x02 +#define SPA_AVBTP_SUBTYPE_CVF 0x03 +#define SPA_AVBTP_SUBTYPE_CRF 0x04 +#define SPA_AVBTP_SUBTYPE_TSCF 0x05 +#define SPA_AVBTP_SUBTYPE_SVF 0x06 +#define SPA_AVBTP_SUBTYPE_RVF 0x07 +#define SPA_AVBTP_SUBTYPE_AEF_CONTINUOUS 0x6E +#define SPA_AVBTP_SUBTYPE_VSF_STREAM 0x6F +#define SPA_AVBTP_SUBTYPE_EF_STREAM 0x7F +#define SPA_AVBTP_SUBTYPE_NTSCF 0x82 +#define SPA_AVBTP_SUBTYPE_ESCF 0xEC +#define SPA_AVBTP_SUBTYPE_EECF 0xED +#define SPA_AVBTP_SUBTYPE_AEF_DISCRETE 0xEE +#define SPA_AVBTP_SUBTYPE_ADP 0xFA +#define SPA_AVBTP_SUBTYPE_AECP 0xFB +#define SPA_AVBTP_SUBTYPE_ACMP 0xFC +#define SPA_AVBTP_SUBTYPE_MAAP 0xFE +#define SPA_AVBTP_SUBTYPE_EF_CONTROL 0xFF + +struct spa_avbtp_packet_common { + uint8_t subtype; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned sv:1; /* stream_id valid */ + unsigned version:3; + unsigned subtype_data1:4; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned subtype_data1:4; + unsigned version:3; + unsigned sv:1; +#elif +#error "Unknown byte order" +#endif + uint16_t subtype_data2; + uint64_t stream_id; + uint8_t payload0; +} __attribute__ ((__packed__)); + +#define SPA_AVBTP_PACKET_SET_SUBTYPE(p,v) ((p)->subtype = (v)) +#define SPA_AVBTP_PACKET_SET_SV(p,v) ((p)->sv = (v)) +#define SPA_AVBTP_PACKET_SET_VERSION(p,v) ((p)->version = (v)) +#define SPA_AVBTP_PACKET_SET_STREAM_ID(p,v) ((p)->stream_id = htobe64(v)) + +#define SPA_AVBTP_PACKET_GET_SUBTYPE(p) ((p)->subtype) +#define SPA_AVBTP_PACKET_GET_SV(p) ((p)->sv) +#define SPA_AVBTP_PACKET_GET_VERSION(p) ((p)->version) +#define SPA_AVBTP_PACKET_GET_STREAM_ID(p) be64toh((p)->stream_id) + +struct spa_avbtp_packet_cc { + uint8_t subtype; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned sv:1; + unsigned version:3; + unsigned control_data1:4; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned control_data1:4; + unsigned version:3; + unsigned sv:1; +#endif + uint8_t status; + uint16_t control_frame_length; + uint64_t stream_id; + uint8_t payload0; +} __attribute__ ((__packed__)); + +#define SPA_AVBTP_PACKET_CC_SET_SUBTYPE(p,v) ((p)->subtype = (v)) +#define SPA_AVBTP_PACKET_CC_SET_SV(p,v) ((p)->sv = (v)) +#define SPA_AVBTP_PACKET_CC_SET_VERSION(p,v) ((p)->version = (v)) +#define SPA_AVBTP_PACKET_CC_SET_STREAM_ID(p,v) ((p)->stream_id = htobe64(v)) +#define SPA_AVBTP_PACKET_CC_SET_STATUS(p,v) ((p)->status = (v)) +#define SPA_AVBTP_PACKET_CC_SET_LENGTH(p,v) ((p)->control_frame_length = htons(v)) + +#define SPA_AVBTP_PACKET_CC_GET_SUBTYPE(p) ((p)->subtype) +#define SPA_AVBTP_PACKET_CC_GET_SV(p) ((p)->sv) +#define SPA_AVBTP_PACKET_CC_GET_VERSION(p) ((p)->version) +#define SPA_AVBTP_PACKET_CC_GET_STREAM_ID(p) be64toh((p)->stream_id) +#define SPA_AVBTP_PACKET_CC_GET_STATUS(p) ((p)->status) +#define SPA_AVBTP_PACKET_CC_GET_LENGTH(p) ntohs((p)->control_frame_length) + +/* AAF */ +struct spa_avbtp_packet_aaf { + uint8_t subtype; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned sv:1; + unsigned version:3; + unsigned mr:1; + unsigned _r1:1; + unsigned gv:1; + unsigned tv:1; + + uint8_t seq_number; + + unsigned _r2:7; + unsigned tu:1; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned tv:1; + unsigned gv:1; + unsigned _r1:1; + unsigned mr:1; + unsigned version:3; + unsigned sv:1; + + uint8_t seq_num; + + unsigned tu:1; + unsigned _r2:7; +#endif + uint64_t stream_id; + uint32_t timestamp; +#define SPA_AVBTP_AAF_FORMAT_USER 0x00 +#define SPA_AVBTP_AAF_FORMAT_FLOAT_32BIT 0x01 +#define SPA_AVBTP_AAF_FORMAT_INT_32BIT 0x02 +#define SPA_AVBTP_AAF_FORMAT_INT_24BIT 0x03 +#define SPA_AVBTP_AAF_FORMAT_INT_16BIT 0x04 +#define SPA_AVBTP_AAF_FORMAT_AES3_32BIT 0x05 + uint8_t format; + +#define SPA_AVBTP_AAF_PCM_NSR_USER 0x00 +#define SPA_AVBTP_AAF_PCM_NSR_8KHZ 0x01 +#define SPA_AVBTP_AAF_PCM_NSR_16KHZ 0x02 +#define SPA_AVBTP_AAF_PCM_NSR_32KHZ 0x03 +#define SPA_AVBTP_AAF_PCM_NSR_44_1KHZ 0x04 +#define SPA_AVBTP_AAF_PCM_NSR_48KHZ 0x05 +#define SPA_AVBTP_AAF_PCM_NSR_88_2KHZ 0x06 +#define SPA_AVBTP_AAF_PCM_NSR_96KHZ 0x07 +#define SPA_AVBTP_AAF_PCM_NSR_176_4KHZ 0x08 +#define SPA_AVBTP_AAF_PCM_NSR_192KHZ 0x09 +#define SPA_AVBTP_AAF_PCM_NSR_24KHZ 0x0A +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned nsr:4; + unsigned _r3:4; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned _r3:4; + unsigned nsr:4; +#endif + uint8_t chan_per_frame; + uint8_t bit_depth; + uint16_t data_len; + +#define SPA_AVBTP_AAF_PCM_SP_NORMAL 0x00 +#define SPA_AVBTP_AAF_PCM_SP_SPARSE 0x01 +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned _r4:3; + unsigned sp:1; + unsigned event:4; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned event:4; + unsigned sp:1; + unsigned _r4:3; +#endif + uint8_t _r5; + uint8_t payload0; +} __attribute__ ((__packed__)); + +#define SPA_AVBTP_PACKET_AAF_SET_SUBTYPE(p,v) ((p)->subtype = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_SV(p,v) ((p)->sv = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_VERSION(p,v) ((p)->version = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_MR(p,v) ((p)->mr = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_GV(p,v) ((p)->gv = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_TV(p,v) ((p)->tv = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_SEQ_NUM(p,v) ((p)->seq_num = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_TU(p,v) ((p)->tu = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_STREAM_ID(p,v) ((p)->stream_id = htobe64(v)) +#define SPA_AVBTP_PACKET_AAF_SET_TIMESTAMP(p,v) ((p)->timestamp = htonl(v)) +#define SPA_AVBTP_PACKET_AAF_SET_DATA_LEN(p,v) ((p)->data_len = htons(v)) +#define SPA_AVBTP_PACKET_AAF_SET_FORMAT(p,v) ((p)->format = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_NSR(p,v) ((p)->nsr = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_CHAN_PER_FRAME(p,v) ((p)->chan_per_frame = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_BIT_DEPTH(p,v) ((p)->bit_depth = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_SP(p,v) ((p)->sp = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_EVENT(p,v) ((p)->event = (v)) + +#define SPA_AVBTP_PACKET_AAF_GET_SUBTYPE(p) ((p)->subtype) +#define SPA_AVBTP_PACKET_AAF_GET_SV(p) ((p)->sv) +#define SPA_AVBTP_PACKET_AAF_GET_VERSION(p) ((p)->version) +#define SPA_AVBTP_PACKET_AAF_GET_MR(p) ((p)->mr) +#define SPA_AVBTP_PACKET_AAF_GET_GV(p) ((p)->gv) +#define SPA_AVBTP_PACKET_AAF_GET_TV(p) ((p)->tv) +#define SPA_AVBTP_PACKET_AAF_GET_SEQ_NUM(p) ((p)->seq_num) +#define SPA_AVBTP_PACKET_AAF_GET_TU(p) ((p)->tu) +#define SPA_AVBTP_PACKET_AAF_GET_STREAM_ID(p) be64toh((p)->stream_id) +#define SPA_AVBTP_PACKET_AAF_GET_TIMESTAMP(p) ntohl((p)->timestamp) +#define SPA_AVBTP_PACKET_AAF_GET_DATA_LEN(p) ntohs((p)->data_len) +#define SPA_AVBTP_PACKET_AAF_GET_FORMAT(p) ((p)->format) +#define SPA_AVBTP_PACKET_AAF_GET_NSR(p) ((p)->nsr) +#define SPA_AVBTP_PACKET_AAF_GET_CHAN_PER_FRAME(p) ((p)->chan_per_frame) +#define SPA_AVBTP_PACKET_AAF_GET_BIT_DEPTH(p) ((p)->bit_depth) +#define SPA_AVBTP_PACKET_AAF_GET_SP(p) ((p)->sp) +#define SPA_AVBTP_PACKET_AAF_GET_EVENT(p) ((p)->event) + + +#endif /* SPA_AVB_PACKETS_H */
View file
pipewire-0.3.56.tar.gz/spa/plugins/avb/meson.build
Added
@@ -0,0 +1,14 @@ +spa_avb_sources = 'avb.c', + 'avb.h', + 'avb-pcm-sink.c', + 'avb-pcm-source.c', + 'avb-pcm.c' + +spa_avb = shared_library( + 'spa-avb', + spa_avb_sources , + include_directories : configinc, + dependencies : spa_dep, mathlib, epoll_shim_dep , + install : true, + install_dir : spa_plugindir / 'avb' +)
View file
pipewire-0.3.54.tar.gz/spa/plugins/bluez5/backend-native.c -> pipewire-0.3.56.tar.gz/spa/plugins/bluez5/backend-native.c
Changed
@@ -29,6 +29,7 @@ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> +#include <poll.h> #include <bluetooth/bluetooth.h> #include <bluetooth/sco.h> @@ -73,6 +74,7 @@ struct spa_log *log; struct spa_loop *main_loop; struct spa_system *main_system; + struct spa_loop_utils *loop_utils; struct spa_dbus *dbus; DBusConnection *conn; @@ -127,6 +129,7 @@ struct spa_hook transport_listener; enum spa_bt_profile profile; struct spa_source timer; + struct spa_source *volume_sync_timer; char* path; bool has_volume; struct rfcomm_volume volumesSPA_BT_VOLUME_ID_TERM; @@ -225,6 +228,8 @@ static int codec_switch_stop_timer(struct rfcomm *rfcomm); +static void volume_sync_stop_timer(struct rfcomm *rfcomm); + static void rfcomm_free(struct rfcomm *rfcomm) { codec_switch_stop_timer(rfcomm); @@ -247,6 +252,8 @@ close (rfcomm->source.fd); rfcomm->source.fd = -1; } + if (rfcomm->volume_sync_timer) + spa_loop_utils_destroy_source(rfcomm->backend->loop_utils, rfcomm->volume_sync_timer); free(rfcomm); } @@ -809,6 +816,7 @@ rfcomm->hfp_ag_switching_codec = false; rfcomm->hfp_ag_initial_codec_setup = HFP_AG_INITIAL_CODEC_SETUP_NONE; codec_switch_stop_timer(rfcomm); + volume_sync_stop_timer(rfcomm); if (selected_codec != HFP_AUDIO_CODEC_CVSD && selected_codec != HFP_AUDIO_CODEC_MSBC) { spa_log_warn(backend->log, "unsupported codec negotiation: %d", selected_codec); @@ -1203,6 +1211,18 @@ return -1; } +static int rfcomm_ag_sync_volume(struct rfcomm *rfcomm, bool later); + +static void wait_for_socket(int fd) +{ + struct pollfd fds1; + const int timeout_ms = 500; + + fds0.fd = fd; + fds0.events = POLLIN | POLLERR | POLLHUP; + poll(fds, 1, timeout_ms); +} + static int sco_acquire_cb(void *data, bool optional) { struct spa_bt_transport *t = data; @@ -1225,6 +1245,25 @@ rfcomm_hfp_ag_set_cind(td->rfcomm, true); #endif + /* + * Send RFCOMM volume after connection is ready, and also after + * a timeout. + * + * Some headsets adjust their HFP volume when in A2DP mode + * without reporting via RFCOMM to us, so the volume level can + * be out of sync, and we can't know what it is. Moreover, they may + * take the first +VGS command after connection only partially + * into account, and need a long enough timeout. + * + * E.g. with Sennheiser HD-250BT, the first +VGS changes the + * actual volume, but does not update the level in the hardware + * volume buttons, which is updated by an +VGS event only after + * sufficient time is elapsed from the connection. + */ + wait_for_socket(sock); + rfcomm_ag_sync_volume(td->rfcomm, false); + rfcomm_ag_sync_volume(td->rfcomm, true); + t->fd = sock; /* Fallback value */ @@ -1471,10 +1510,8 @@ return -1; } -static int sco_set_volume_cb(void *data, int id, float volume) +static int rfcomm_ag_set_volume(struct spa_bt_transport *t, int id) { - struct spa_bt_transport *t = data; - struct spa_bt_transport_volume *t_volume = &t->volumesid; struct transport_data *td = t->user_data; struct rfcomm *rfcomm = td->rfcomm; const char *format; @@ -1485,12 +1522,7 @@ || !(rfcomm->has_volume && rfcomm->volumesid.active)) return -ENOTSUP; - value = spa_bt_volume_linear_to_hw(volume, t_volume->hw_volume_max); - t_volume->volume = volume; - - if (rfcomm->volumesid.hw_volume == value) - return 0; - rfcomm->volumesid.hw_volume = value; + value = rfcomm->volumesid.hw_volume; if (id == SPA_BT_VOLUME_ID_RX) if (rfcomm->profile & SPA_BT_PROFILE_HFP_HF) @@ -1511,6 +1543,29 @@ return 0; } +static int sco_set_volume_cb(void *data, int id, float volume) +{ + struct spa_bt_transport *t = data; + struct spa_bt_transport_volume *t_volume = &t->volumesid; + struct transport_data *td = t->user_data; + struct rfcomm *rfcomm = td->rfcomm; + int value; + + if (!rfcomm_volume_enabled(rfcomm) + || !(rfcomm->profile & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) + || !(rfcomm->has_volume && rfcomm->volumesid.active)) + return -ENOTSUP; + + value = spa_bt_volume_linear_to_hw(volume, t_volume->hw_volume_max); + t_volume->volume = volume; + + if (rfcomm->volumesid.hw_volume == value) + return 0; + rfcomm->volumesid.hw_volume = value; + + return rfcomm_ag_set_volume(t, id); +} + static const struct spa_bt_transport_implementation sco_transport_impl = { SPA_VERSION_BT_TRANSPORT_IMPLEMENTATION, .acquire = sco_acquire_cb, @@ -1570,6 +1625,60 @@ return 0; } +static void volume_sync_stop_timer(struct rfcomm *rfcomm) +{ + if (rfcomm->volume_sync_timer) + spa_loop_utils_update_timer(rfcomm->backend->loop_utils, rfcomm->volume_sync_timer, + NULL, NULL, false); +} + +static void volume_sync_timer_event(void *data, uint64_t expirations) +{ + struct rfcomm *rfcomm = data; + + volume_sync_stop_timer(rfcomm); + + if (rfcomm->transport) { + rfcomm_ag_set_volume(rfcomm->transport, SPA_BT_VOLUME_ID_TX); + rfcomm_ag_set_volume(rfcomm->transport, SPA_BT_VOLUME_ID_RX); + } +} + +static int volume_sync_start_timer(struct rfcomm *rfcomm) +{ + struct timespec ts; + const uint64_t timeout = 1500 * SPA_NSEC_PER_MSEC; + + if (rfcomm->volume_sync_timer == NULL) + rfcomm->volume_sync_timer = spa_loop_utils_add_timer(rfcomm->backend->loop_utils, + volume_sync_timer_event, rfcomm); + + if (rfcomm->volume_sync_timer == NULL) + return -EIO; + + ts.tv_sec = timeout / SPA_NSEC_PER_SEC; + ts.tv_nsec = timeout % SPA_NSEC_PER_SEC; + spa_loop_utils_update_timer(rfcomm->backend->loop_utils, rfcomm->volume_sync_timer, + &ts, NULL, false); + + return 0; +} + +static int rfcomm_ag_sync_volume(struct rfcomm *rfcomm, bool later) +{ + if (rfcomm->transport == NULL) + return -ENOENT; + + if (!later) { + rfcomm_ag_set_volume(rfcomm->transport, SPA_BT_VOLUME_ID_TX); + rfcomm_ag_set_volume(rfcomm->transport, SPA_BT_VOLUME_ID_RX); + } else { + volume_sync_start_timer(rfcomm); + } + + return 0; +} + static void codec_switch_timer_event(struct spa_source *source) { struct rfcomm *rfcomm = source->data; @@ -2222,6 +2331,7 @@ backend->dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus); backend->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); backend->main_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System); + backend->loop_utils = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_LoopUtils); backend->conn = dbus_connection; backend->sco.fd = -1;
View file
pipewire-0.3.54.tar.gz/spa/plugins/bluez5/bluez5-device.c -> pipewire-0.3.56.tar.gz/spa/plugins/bluez5/bluez5-device.c
Changed
@@ -337,35 +337,57 @@ } } -static void volume_changed(void *userdata) +static bool node_update_volume_from_transport(struct node *node, bool reset) { - struct node *node = userdata; struct impl *impl = node->impl; struct spa_bt_transport_volume *t_volume; float prev_hw_volume; if (!node->transport || !spa_bt_transport_volume_enabled(node->transport)) - return; + return false; /* PW is the controller for remote device. */ if (impl->profile != DEVICE_PROFILE_A2DP && impl->profile != DEVICE_PROFILE_HSP_HFP) - return; + return false; t_volume = &node->transport->volumesnode->id; if (!t_volume->active) - return; + return false; prev_hw_volume = node_get_hw_volume(node); - for (uint32_t i = 0; i < node->n_channels; ++i) { - node->volumesi = prev_hw_volume > 0.0f - ? node->volumesi * t_volume->volume / prev_hw_volume - : t_volume->volume; + + if (!reset) { + for (uint32_t i = 0; i < node->n_channels; ++i) { + node->volumesi = prev_hw_volume > 0.0f + ? node->volumesi * t_volume->volume / prev_hw_volume + : t_volume->volume; + } + } else { + for (uint32_t i = 0; i < node->n_channels; ++i) + node->volumesi = t_volume->volume; } node_update_soft_volumes(node, t_volume->volume); + /* + * Consider volume changes from the headset as requested + * by the user, and to be saved by the SM. + */ + node->save = true; + + return true; +} + +static void volume_changed(void *userdata) +{ + struct node *node = userdata; + struct impl *impl = node->impl; + + if (!node_update_volume_from_transport(node, false)) + return; + emit_volume(impl, node); impl->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; @@ -469,6 +491,8 @@ this->nodesid.volumesi = this->nodesid.volumesi % prev_channels; } + node_update_volume_from_transport(&this->nodesid, true); + boost = get_soft_volume_boost(&this->nodesid); if (boost != 1.0f) { size_t i; @@ -1301,16 +1325,17 @@ } static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, - uint32_t id, uint32_t port, uint32_t dev, uint32_t profile) + uint32_t id, uint32_t port, uint32_t profile) { struct spa_bt_device *device = this->bt_dev; struct spa_pod_frame f2; enum spa_direction direction; - const char *name_prefix, *description, *port_type; + const char *name_prefix, *description, *hfp_description, *port_type; enum spa_bt_form_factor ff; enum spa_bluetooth_audio_codec codec; char name128; uint32_t i, j, mask, next; + uint32_t dev = SPA_ID_INVALID, enum_dev; ff = spa_bt_form_factor_from_class(device->bluetooth_class); @@ -1318,52 +1343,62 @@ case SPA_BT_FORM_FACTOR_HEADSET: name_prefix = "headset"; description = _("Headset"); + hfp_description = _("Handsfree"); port_type = "headset"; break; case SPA_BT_FORM_FACTOR_HANDSFREE: name_prefix = "handsfree"; description = _("Handsfree"); + hfp_description = _("Handsfree (HFP)"); port_type = "handsfree"; break; case SPA_BT_FORM_FACTOR_MICROPHONE: name_prefix = "microphone"; description = _("Microphone"); + hfp_description = _("Handsfree"); port_type = "mic"; break; case SPA_BT_FORM_FACTOR_SPEAKER: name_prefix = "speaker"; description = _("Speaker"); + hfp_description = _("Handsfree"); port_type = "speaker"; break; case SPA_BT_FORM_FACTOR_HEADPHONE: name_prefix = "headphone"; description = _("Headphone"); + hfp_description = _("Handsfree"); port_type = "headphones"; break; case SPA_BT_FORM_FACTOR_PORTABLE: name_prefix = "portable"; description = _("Portable"); + hfp_description = _("Handsfree"); port_type = "portable"; break; case SPA_BT_FORM_FACTOR_CAR: name_prefix = "car"; description = _("Car"); + hfp_description = _("Handsfree"); port_type = "car"; break; case SPA_BT_FORM_FACTOR_HIFI: name_prefix = "hifi"; description = _("HiFi"); + hfp_description = _("Handsfree"); port_type = "hifi"; break; case SPA_BT_FORM_FACTOR_PHONE: name_prefix = "phone"; description = _("Phone"); + hfp_description = _("Handsfree"); port_type = "phone"; break; case SPA_BT_FORM_FACTOR_UNKNOWN: default: name_prefix = "bluetooth"; description = _("Bluetooth"); + hfp_description = _("Bluetooth (HFP)"); port_type = "bluetooth"; break; } @@ -1372,16 +1407,51 @@ case 0: direction = SPA_DIRECTION_INPUT; snprintf(name, sizeof(name), "%s-input", name_prefix); + enum_dev = DEVICE_ID_SOURCE; + if (profile == DEVICE_PROFILE_A2DP) + dev = enum_dev; + else if (profile != SPA_ID_INVALID) + enum_dev = SPA_ID_INVALID; break; case 1: direction = SPA_DIRECTION_OUTPUT; snprintf(name, sizeof(name), "%s-output", name_prefix); + enum_dev = DEVICE_ID_SINK; + if (profile == DEVICE_PROFILE_A2DP) + dev = enum_dev; + else if (profile != SPA_ID_INVALID) + enum_dev = SPA_ID_INVALID; + break; + case 2: + direction = SPA_DIRECTION_INPUT; + snprintf(name, sizeof(name), "%s-hf-input", name_prefix); + description = hfp_description; + enum_dev = DEVICE_ID_SOURCE; + if (profile == DEVICE_PROFILE_HSP_HFP) + dev = enum_dev; + else if (profile != SPA_ID_INVALID) + enum_dev = SPA_ID_INVALID; + break; + case 3: + direction = SPA_DIRECTION_OUTPUT; + snprintf(name, sizeof(name), "%s-hf-output", name_prefix); + description = hfp_description; + enum_dev = DEVICE_ID_SINK; + if (profile == DEVICE_PROFILE_HSP_HFP) + dev = enum_dev; + else if (profile != SPA_ID_INVALID) + enum_dev = SPA_ID_INVALID; break; default: errno = EINVAL; return NULL; } + if (enum_dev == SPA_ID_INVALID) { + errno = EINVAL; + return NULL; + } + spa_pod_builder_push_object(b, &f0, SPA_TYPE_OBJECT_ParamRoute, id); spa_pod_builder_add(b, SPA_PARAM_ROUTE_index, SPA_POD_Int(port), @@ -1406,6 +1476,11 @@ for (i = 1; (j = get_profile_from_index(this, i, &next, &codec)) != SPA_ID_INVALID; i = next) { uint32_t profile_mask; + if (j == DEVICE_PROFILE_A2DP && !(port == 0 || port == 1)) + continue; + if (j == DEVICE_PROFILE_HSP_HFP && !(port == 2 || port == 3)) + continue; + profile_mask = profile_direction_mask(this, j, codec); if (!(profile_mask & (1 << direction))) continue; @@ -1472,8 +1547,7 @@ spa_pod_builder_prop(b, SPA_PARAM_ROUTE_devices, 0); spa_pod_builder_push_array(b, &f1); - /* port and device indexes are the same, 0=source, 1=sink */ - spa_pod_builder_int(b, port); + spa_pod_builder_int(b, enum_dev); spa_pod_builder_pop(b, &f1); if (profile != SPA_ID_INVALID) { @@ -1640,9 +1714,8 @@ case SPA_PARAM_EnumRoute: { switch (result.index) { - case 0: case 1: - param = build_route(this, &b, id, result.index, - SPA_ID_INVALID, SPA_ID_INVALID); + case 0: case 1: case 2: case 3: + param = build_route(this, &b, id, result.index, SPA_ID_INVALID); if (param == NULL) goto next; break; @@ -1654,9 +1727,8 @@ case SPA_PARAM_Route: { switch (result.index) { - case 0: case 1: - param = build_route(this, &b, id, result.index, - result.index, this->profile); + case 0: case 1: case 2: case 3: + param = build_route(this, &b, id, result.index, this->profile); if (param == NULL) goto next; break;
View file
pipewire-0.3.54.tar.gz/spa/plugins/bluez5/decode-buffer.h -> pipewire-0.3.56.tar.gz/spa/plugins/bluez5/decode-buffer.h
Changed
@@ -56,12 +56,10 @@ #include <stdlib.h> #include <spa/utils/defs.h> -#include <spa/utils/dll.h> #include <spa/support/log.h> -#define BUFFERING_LONG_MSEC 60000 +#define BUFFERING_LONG_MSEC (2*60000) #define BUFFERING_SHORT_MSEC 1000 -#define BUFFERING_DLL_BW 0.03 #define BUFFERING_RATE_DIFF_MAX 0.005 /** @@ -73,6 +71,132 @@ #define BUFFERING_TARGET(spike,packet_size) \ SPA_CLAMP((spike)*3/2, (packet_size), 6*(packet_size)) +/** + * Rate controller. + * + * It's here in a form, where it operates on the running average + * so it's compatible with the level spike determination, and + * clamping the rate to a range is easy. The impulse response + * is similar to spa_dll, and step response does not have sign changes. + * + * The controller iterates as + * + * avg(j+1) = (1 - beta) avg(j) + beta level(j) + * corr(j+1) = corr(j) + a avg(j+1) - avg(j) / duration + * + b avg(j) - target / duration + * + * with beta = duration/avg_period < 0.5 is the moving average parameter, + * and a = beta/3 + ..., b = beta^2/27 + .... + * + * This choice results to c(j) being low-pass filtered, and buffer level(j) + * converging towards target with stable damped evolution with eigenvalues + * real and close to each other around (1 - beta)^(1/3). + * + * Derivation: + * + * The deviation from the buffer level target evolves as + * + * delta(j) = level(j) - target + * delta(j+1) = delta(j) + r(j) - c(j+1) + * + * where r is samples received in one duration, and c corrected rate + * (samples per duration). + * + * The rate correction is in general determined by linear filter f + * + * c(j+1) = c(j) + \sum_{k=0}^\infty delta(j - k) f(k) + * + * If \sum_k f(k) is not zero, the only fixed point is c=r, delta=0, + * so this structure (if the filter is stable) rate matches and + * drives buffer level to target. + * + * The z-transform then is + * + * delta(z) = G(z) r(z) + * c(z) = F(z) delta(z) + * G(z) = (z - 1) / (z - 1)^2 + z f(z) + * F(z) = f(z) / (z - 1) + * + * We now want: poles of G(z) must be in |z|<1 for stability, F(z) + * should damp high frequencies, and f(z) is causal. + * + * To satisfy the conditions, take + * + * (z - 1)^2 + z f(z) = p(z) / q(z) + * + * where p(z) is polynomial with leading term z^n with wanted root + * structure, and q(z) is any polynomial with leading term z^{n-2}. + * This guarantees f(z) is causal, and G(z) = (z-1) q(z) / p(z). + * We can choose p(z) and q(z) to improve low-pass properties of F(z). + * + * Simplest choice is p(z)=(z-x)^2 and q(z)=1, but that gives flat + * high frequency response in F(z). Better choice is p(z) = (z-u)*(z-v)*(z-w) + * and q(z) = z - r. To make F(z) better lowpass, one can cancel + * a resulting 1/z pole in F(z) by setting r=u*v*w. Then, + * + * G(z) = (z - u*v*w)*(z - 1) / (z - u)*(z - v)*(z - w) + * F(z) = (a z + b - a) / (z - 1) * H(z) + * H(z) = beta / (z - 1 + beta) + * beta = 1 - u*v*w + * a = (1-u) + (1-v) + (1-w) - beta / beta + * b = (1-u)*(1-v)*(1-w) / beta + * + * which corresponds to iteration for c(j): + * + * avg(j+1) = (1 - beta) avg(j) + beta delta(j) + * c(j+1) = c(j) + a avg(j+1) - avg(j) + b avg(j) + * + * So the controller operates on the running average, + * which gives the low-pass property for c(j). + * + * The simplest filter is obtained by putting the poles at + * u=v=w=(1-beta)**(1/3). Since beta << 1, computing the root + * can be avoided by expanding in series. + * + * Overshoot in impulse response could be reduced by moving one of the + * poles closer to z=1, but this increases the step response time. + */ +struct spa_bt_rate_control +{ + double avg; + double corr; +}; + +static void spa_bt_rate_control_init(struct spa_bt_rate_control *this, double level) +{ + this->avg = level; + this->corr = 1.0; +} + +static double spa_bt_rate_control_update(struct spa_bt_rate_control *this, double level, + double target, double duration, double period) +{ + /* + * u = (1 - beta)^(1/3) + * x = a / beta + * y = b / beta + * a = (2 + u) * (1 - u)^2 / beta + * b = (1 - u)^3 / beta + * beta -> 0 + */ + const double beta = SPA_CLAMP(duration / period, 0, 0.5); + const double x = 1.0/3; + const double y = beta/27; + double avg; + + avg = beta * level + (1 - beta) * this->avg; + this->corr += x * (avg - this->avg) / period + + y * (this->avg - target) / period; + this->avg = avg; + + this->corr = SPA_CLAMP(this->corr, + 1 - BUFFERING_RATE_DIFF_MAX, + 1 + BUFFERING_RATE_DIFF_MAX); + + return this->corr; +} + + /** Windowed min/max */ struct spa_bt_ptp { @@ -104,11 +228,7 @@ struct spa_bt_ptp spike; /**< spikes (long window) */ struct spa_bt_ptp packet_size; /**< packet size (short window) */ - int32_t target; - int32_t level; - double level_avg; - - struct spa_dll dll; + struct spa_bt_rate_control ctl; double corr; uint32_t prev_consumed; @@ -168,7 +288,7 @@ this->corr = 1.0; this->buffering = true; - spa_dll_init(&this->dll); + spa_bt_rate_control_init(&this->ctl, 0); spa_bt_ptp_init(&this->spike, (uint64_t)this->rate * BUFFERING_LONG_MSEC / 1000); spa_bt_ptp_init(&this->packet_size, (uint64_t)this->rate * BUFFERING_SHORT_MSEC / 1000); @@ -254,16 +374,16 @@ static void spa_bt_decode_buffer_recover(struct spa_bt_decode_buffer *this) { int32_t size = (this->write_index - this->read_index) / this->frame_size; + int32_t level; this->prev_avail = size * this->frame_size; this->prev_consumed = this->prev_duration; - this->level = (int32_t)this->prev_avail/this->frame_size + + level = (int32_t)this->prev_avail/this->frame_size - (int32_t)this->prev_duration; - this->level_avg = this->level; - this->target = this->level; this->corr = 1.0; - spa_dll_init(&this->dll); + spa_bt_rate_control_init(&this->ctl, level); } static void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *this, uint32_t samples, uint32_t duration) @@ -294,12 +414,6 @@ spa_bt_decode_buffer_recover(this); } - if (SPA_UNLIKELY(this->dll.bw == 0.0)) { - spa_log_trace(this->log, "%p dll reset duration:%d rate:%d", this, - (int)duration, (int)this->rate); - spa_dll_set_bw(&this->dll, BUFFERING_DLL_BW, duration, (uint64_t)this->rate); - } - spa_bt_decode_buffer_get_read(this, &avail); if (this->received) { @@ -311,9 +425,7 @@ level = SPA_MAX(level, -max_level); this->prev_consumed = SPA_MIN(this->prev_consumed, avg_period); - this->level_avg = ((double)this->prev_consumed*level - + ((double)avg_period - this->prev_consumed)*this->level_avg) / avg_period; - spa_bt_ptp_update(&this->spike, this->level_avg - level, this->prev_consumed); + spa_bt_ptp_update(&this->spike, this->ctl.avg - level, this->prev_consumed); /* Update target level */ target = BUFFERING_TARGET(this->spike.max, this->packet_size.max); @@ -337,7 +449,7 @@ spa_log_debug(this->log, "%p avg:%d target:%d level:%d buffer:%d spike:%d corr:%f", this, - (int)this->level_avg, + (int)this->ctl.avg, (int)target, (int)level, (int)(avail / this->frame_size), @@ -346,23 +458,15 @@ this->pos = 0; } + this->corr = spa_bt_rate_control_update(&this->ctl, + level, target, this->prev_consumed, avg_period); + spa_bt_decode_buffer_get_read(this, &avail); this->prev_consumed = 0; this->prev_avail = avail; this->underrun = 0; this->received = false; - this->level = level; - this->target = target; - } - - this->corr = spa_dll_update(&this->dll, this->target - this->level); - - if (SPA_ABS(this->corr - 1.0) > BUFFERING_RATE_DIFF_MAX) { - spa_log_trace(this->log, "%p too big rate difference: clamp + reset", this); - spa_dll_init(&this->dll); - this->corr = SPA_CLAMP(this->corr, 1.0 - BUFFERING_RATE_DIFF_MAX, - 1.0 + BUFFERING_RATE_DIFF_MAX); } if (avail < data_size) {
View file
pipewire-0.3.54.tar.gz/spa/plugins/bluez5/sco-sink.c -> pipewire-0.3.56.tar.gz/spa/plugins/bluez5/sco-sink.c
Changed
@@ -759,6 +759,7 @@ { SPA_KEY_MEDIA_CLASS, "Stream/Input/Audio" }, { "media.name", ((this->transport && this->transport->device->name) ? this->transport->device->name : "HSP/HFP") }, + { SPA_KEY_MEDIA_ROLE, "Communication" }, }; bool is_ag = this->transport && (this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY);
View file
pipewire-0.3.54.tar.gz/spa/plugins/bluez5/sco-source.c -> pipewire-0.3.56.tar.gz/spa/plugins/bluez5/sco-source.c
Changed
@@ -805,6 +805,7 @@ { SPA_KEY_MEDIA_CLASS, "Stream/Output/Audio" }, { "media.name", ((this->transport && this->transport->device->name) ? this->transport->device->name : "HSP/HFP") }, + { SPA_KEY_MEDIA_ROLE, "Communication" }, }; bool is_ag = this->transport && (this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY); uint64_t old = full ? this->info.change_mask : 0;
View file
pipewire-0.3.54.tar.gz/spa/plugins/meson.build -> pipewire-0.3.56.tar.gz/spa/plugins/meson.build
Changed
@@ -1,6 +1,9 @@ if alsa_dep.found() subdir('alsa') endif +if get_option('avb').allowed() + subdir('avb') +endif if get_option('audioconvert').allowed() subdir('audioconvert') endif
View file
pipewire-0.3.54.tar.gz/spa/plugins/v4l2/v4l2-udev.c -> pipewire-0.3.56.tar.gz/spa/plugins/v4l2/v4l2-udev.c
Changed
@@ -278,9 +278,14 @@ if ((str = udev_device_get_property_value(dev, "SUBSYSTEM")) && *str) { itemsn_items++ = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_SUBSYSTEM, str); } - if ((str = udev_device_get_property_value(dev, "ID_VENDOR_ID")) && *str) - itemsn_items++ = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_ID, str); - + if ((str = udev_device_get_property_value(dev, "ID_VENDOR_ID")) && *str) { + int32_t val; + if (spa_atoi32(str, &val, 16)) { + char *dec = alloca(12); /* 0xffff is max */ + snprintf(dec, 12, "0x%04x", val); + itemsn_items++ = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_ID, dec); + } + } str = udev_device_get_property_value(dev, "ID_VENDOR_FROM_DATABASE"); if (!(str && *str)) { str = udev_device_get_property_value(dev, "ID_VENDOR_ENC"); @@ -295,8 +300,14 @@ if (str && *str) { itemsn_items++ = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_NAME, str); } - if ((str = udev_device_get_property_value(dev, "ID_MODEL_ID")) && *str) - itemsn_items++ = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PRODUCT_ID, str); + if ((str = udev_device_get_property_value(dev, "ID_MODEL_ID")) && *str) { + int32_t val; + if (spa_atoi32(str, &val, 16)) { + char *dec = alloca(12); /* 0xffff is max */ + snprintf(dec, 12, "0x%04x", val); + itemsn_items++ = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PRODUCT_ID, dec); + } + } str = udev_device_get_property_value(dev, "ID_MODEL_FROM_DATABASE"); if (!(str && *str)) {
View file
pipewire-0.3.54.tar.gz/spa/plugins/vulkan/vulkan-utils.c -> pipewire-0.3.56.tar.gz/spa/plugins/vulkan/vulkan-utils.c
Changed
@@ -6,7 +6,7 @@ #include <sys/mman.h> #include <fcntl.h> #include <string.h> -#if defined(__FreeBSD__) || defined(__MidnightBSD__) +#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) #include <alloca.h> #endif #include <errno.h>
View file
pipewire-0.3.54.tar.gz/src/daemon/client.conf.in -> pipewire-0.3.56.tar.gz/src/daemon/client.conf.in
Changed
@@ -7,6 +7,7 @@ # @PIPEWIRE_CONFIG_DIR@/client.conf.d/ for system-wide changes or in # ~/.config/pipewire/client.conf.d/ for local changes. # + context.properties = { ## Configure properties in the system. #mem.warn-mlock = false
View file
pipewire-0.3.56.tar.gz/src/daemon/filter-chain.conf.in
Added
@@ -0,0 +1,62 @@ +# Filter-chain config file for PipeWire version @VERSION@ # +# +# This is a base config file for running filters. +# +# Place filter fragments in @PIPEWIRE_CONFIG_DIR@/filter-chain.conf.d/ +# for system-wide changes or in ~/.config/pipewire/filter-chain.conf.d/ +# for local changes. +# +# Run the filters with pipewire -c filter-chain.conf +# + +context.properties = { + ## Configure properties in the system. + #mem.warn-mlock = false + #mem.allow-mlock = true + #mem.mlock-all = false + log.level = 0 +} + +context.spa-libs = { + #<factory-name regex> = <library-name> + # + # Used to find spa factory names. It maps an spa factory name + # regular expression to a library name that should contain + # that factory. + # + audio.convert.* = audioconvert/libspa-audioconvert + support.* = support/libspa-support +} + +context.modules = + #{ name = <module-name> + # args = { <key> = <value> ... } + # flags = ifexists nofail + #} + # + # Loads a module with the given parameters. + # If ifexists is given, the module is ignored when it is not found. + # If nofail is given, module initialization failures are ignored. + # + # Uses realtime scheduling to boost the audio thread priorities + { name = libpipewire-module-rt + args = { + #rt.prio = 88 + #rt.time.soft = -1 + #rt.time.hard = -1 + } + flags = ifexists nofail + } + + # The native communication protocol. + { name = libpipewire-module-protocol-native } + + # Allows creating nodes that run in the context of the + # client. Is used by all clients that want to provide + # data to PipeWire. + { name = libpipewire-module-client-node } + + # Makes a factory for wrapping nodes in an adapter with a + # converter and resampler. + { name = libpipewire-module-adapter } +
View file
pipewire-0.3.54.tar.gz/src/daemon/filter-chain/demonic.conf -> pipewire-0.3.56.tar.gz/src/daemon/filter-chain/demonic.conf
Changed
@@ -1,46 +1,9 @@ # filter-chain example config file for PipeWire version @VERSION@ # -context.properties = { - ## Configure properties in the system. - #mem.warn-mlock = false - #mem.allow-mlock = true - #mem.mlock-all = false - log.level = 0 -} - -context.spa-libs = { - #<factory-name regex> = <library-name> - # - # Used to find spa factory names. It maps an spa factory name - # regular expression to a library name that should contain - # that factory. - # - audio.convert.* = audioconvert/libspa-audioconvert - support.* = support/libspa-support -} - +# +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ +# context.modules = - # Uses realtime scheduling to boost the audio thread priorities - { name = libpipewire-module-rt - args = { - #rt.prio = 88 - #rt.time.soft = -1 - #rt.time.hard = -1 - } - flags = ifexists nofail - } - - # The native communication protocol. - { name = libpipewire-module-protocol-native } - - # Allows creating nodes that run in the context of the - # client. Is used by all clients that want to provide - # data to PipeWire. - { name = libpipewire-module-client-node } - - # Makes a factory for wrapping nodes in an adapter with a - # converter and resampler. - { name = libpipewire-module-adapter } - { name = libpipewire-module-filter-chain args = { #audio.format = F32
View file
pipewire-0.3.54.tar.gz/src/daemon/filter-chain/meson.build -> pipewire-0.3.56.tar.gz/src/daemon/filter-chain/meson.build
Changed
@@ -1,6 +1,8 @@ conf_files = 'demonic.conf', 'demonic.conf' , - 'duplicate-FL.conf', 'duplicate-FL.conf' , + 'source-duplicate-FL.conf', 'source-duplicate-FL.conf' , + 'sink-mix-FL-FR.conf', 'sink-mix-FL-FR.conf' , + 'sink-make-LFE.conf', 'sink-make-LFE.conf' , 'sink-virtual-surround-5.1-kemar.conf', 'sink-virtual-surround-5.1-kemar.conf' , 'sink-virtual-surround-7.1-hesuvi.conf', 'sink-virtual-surround-7.1-hesuvi.conf' , 'sink-dolby-surround.conf', 'sink-dolby-surround.conf' ,
View file
pipewire-0.3.54.tar.gz/src/daemon/filter-chain/sink-dolby-surround.conf -> pipewire-0.3.56.tar.gz/src/daemon/filter-chain/sink-dolby-surround.conf
Changed
@@ -1,29 +1,9 @@ # Dolby Surround encoder sink # -# start with pipewire -c filter-chain/sink-dolby-surround.conf +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ # -context.properties = { - log.level = 0 -} - -context.spa-libs = { - audio.convert.* = audioconvert/libspa-audioconvert - support.* = support/libspa-support -} - context.modules = - { name = libpipewire-module-rt - args = { - #rt.prio = 88 - #rt.time.soft = -1 - #rt.time.hard = -1 - } - flags = ifexists nofail - } - { name = libpipewire-module-protocol-native } - { name = libpipewire-module-client-node } - { name = libpipewire-module-adapter } - { name = libpipewire-module-filter-chain args = { node.description = "Dolby Surround Sink"
View file
pipewire-0.3.54.tar.gz/src/daemon/filter-chain/sink-eq6.conf -> pipewire-0.3.56.tar.gz/src/daemon/filter-chain/sink-eq6.conf
Changed
@@ -1,29 +1,9 @@ # 6 band sink equalizer # -# start with pipewire -c filter-chain/sink-eq6.conf +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ # -context.properties = { - log.level = 0 -} - -context.spa-libs = { - audio.convert.* = audioconvert/libspa-audioconvert - support.* = support/libspa-support -} - context.modules = - { name = libpipewire-module-rt - args = { - #rt.prio = 88 - #rt.time.soft = -1 - #rt.time.hard = -1 - } - flags = ifexists nofail - } - { name = libpipewire-module-protocol-native } - { name = libpipewire-module-client-node } - { name = libpipewire-module-adapter } - { name = libpipewire-module-filter-chain args = { node.description = "Equalizer Sink"
View file
pipewire-0.3.56.tar.gz/src/daemon/filter-chain/sink-make-LFE.conf
Added
@@ -0,0 +1,56 @@ +# An example filter chain that makes a stereo sink that mixes +# the FL and FR channels to FL, FR, LFE +# +# Copy this file into a conf.d/ directory +# +context.modules = + { name = libpipewire-module-filter-chain + args = { + node.description = "LFE example" + media.name = "LFE example" + filter.graph = { + nodes = + { name = copyIL type = builtin label = copy } + { name = copyOL type = builtin label = copy } + { name = copyIR type = builtin label = copy } + { name = copyOR type = builtin label = copy } + { + name = mix + type = builtin + label = mixer + control = { + "Gain 1" = 0.5 + "Gain 2" = 0.5 + } + } + { + type = builtin + name = lpLFE + label = bq_lowpass + control = { "Freq" = 150.0 } + } + + links = + { output = "copyIL:Out" input = "copyOL:In" } + { output = "copyIR:Out" input = "copyOR:In" } + { output = "copyIL:Out" input = "mix:In 1" } + { output = "copyIR:Out" input = "mix:In 2" } + { output = "mix:Out" input = "lpLFE:In" } + + inputs = "copyIL:In" "copyIR:In" + outputs = "copyOL:Out" "copyOR:Out" "lpLFE:Out" + } + capture.props = { + node.name = "input_lfe" + audio.position = FL FR + media.class = "Audio/Sink" + } + playback.props = { + node.name = "output_lfe" + audio.position = FL FR LFE + stream.dont-remix = true + node.passive = true + } + } + } +
View file
pipewire-0.3.54.tar.gz/src/daemon/filter-chain/sink-matrix-spatialiser.conf -> pipewire-0.3.56.tar.gz/src/daemon/filter-chain/sink-matrix-spatialiser.conf
Changed
@@ -1,30 +1,12 @@ # Matrix Spatialiser sink # -# start with pipewire -c filter-chain/sink-matrix-spatialiser.conf +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ +# # ( Jean-Philippe Guillemin <hyp3ri0n@sfr.fr> ) - -context.properties = { - log.level = 0 -} - -context.spa-libs = { - audio.convert.* = audioconvert/libspa-audioconvert - support.* = support/libspa-support -} +# context.modules = - { name = libpipewire-module-rt - args = { - #rt.prio = 88 - #rt.time.soft = -1 - #rt.time.hard = -1 - } - flags = ifexists nofail - } - { name = libpipewire-module-protocol-native } - { name = libpipewire-module-client-node } - { name = libpipewire-module-adapter } - { name = libpipewire-module-filter-chain args = { node.description = "Matrix Spatialiser"
View file
pipewire-0.3.56.tar.gz/src/daemon/filter-chain/sink-mix-FL-FR.conf
Added
@@ -0,0 +1,40 @@ +# An example filter chain that makes a stereo sink that mixes +# the FL and FR channels to a single FL channel +# +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ +# +context.modules = + { name = libpipewire-module-filter-chain + args = { + node.description = "Mix example" + media.name = "Mix example" + filter.graph = { + nodes = + { + name = mix + type = builtin + label = mixer + control = { + "Gain 1" = 0.5 + "Gain 2" = 0.5 + } + } + + inputs = "mix:In 1" "mix:In 2" + outputs = "mix:Out" + } + capture.props = { + node.name = "mix_input.mix-FL-FR-to-FL" + audio.position = FL FR + media.class = "Audio/Sink" + } + playback.props = { + node.name = "mix_output.mix-FL-FR-to-FL" + audio.position = FL + stream.dont-remix = true + node.passive = true + } + } + } +
View file
pipewire-0.3.54.tar.gz/src/daemon/filter-chain/sink-virtual-surround-5.1-kemar.conf -> pipewire-0.3.56.tar.gz/src/daemon/filter-chain/sink-virtual-surround-5.1-kemar.conf
Changed
@@ -1,29 +1,9 @@ # Convolver sink # -# start with pipewire -c filter-chain/sink-virtual-surround-5.1-kemar.conf +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ # -context.properties = { - log.level = 0 -} - -context.spa-libs = { - audio.convert.* = audioconvert/libspa-audioconvert - support.* = support/libspa-support -} - context.modules = - { name = libpipewire-module-rt - args = { - #rt.prio = 88 - #rt.time.soft = -1 - #rt.time.hard = -1 - } - flags = ifexists nofail - } - { name = libpipewire-module-protocol-native } - { name = libpipewire-module-client-node } - { name = libpipewire-module-adapter } - { name = libpipewire-module-filter-chain args = { node.description = "Virtual Surround Sink"
View file
pipewire-0.3.54.tar.gz/src/daemon/filter-chain/sink-virtual-surround-7.1-hesuvi.conf -> pipewire-0.3.56.tar.gz/src/daemon/filter-chain/sink-virtual-surround-7.1-hesuvi.conf
Changed
@@ -1,29 +1,9 @@ # Convolver sink # -# start with pipewire -c filter-chain/sink-virtual-surround-7.1-hesuvi.conf +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ # -context.properties = { - log.level = 0 -} - -context.spa-libs = { - audio.convert.* = audioconvert/libspa-audioconvert - support.* = support/libspa-support -} - context.modules = - { name = libpipewire-module-rt - args = { - #rt.prio = 88 - #rt.time.soft = -1 - #rt.time.hard = -1 - } - flags = ifexists nofail - } - { name = libpipewire-module-protocol-native } - { name = libpipewire-module-client-node } - { name = libpipewire-module-adapter } - { name = libpipewire-module-filter-chain args = { node.description = "Virtual Surround Sink"
View file
pipewire-0.3.56.tar.gz/src/daemon/filter-chain/source-duplicate-FL.conf
Added
@@ -0,0 +1,52 @@ +# An example filter chain that makes a source that duplicates the FL channel +# to FL and FR. +# +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ +# +context.modules = + { name = libpipewire-module-filter-chain + args = { + node.description = "Remap example" + media.name = "Remap example" + filter.graph = { + nodes = + { + name = copyIL + type = builtin + label = copy + } + { + name = copyOL + type = builtin + label = copy + } + { + name = copyOR + type = builtin + label = copy + } + + links = + # we can only tee from nodes, not inputs so we need + # to copy the inputs and then tee. + { output = "copyIL:Out" input = "copyOL:In" } + { output = "copyIL:Out" input = "copyOR:In" } + + inputs = "copyIL:In" + outputs = "copyOL:Out" "copyOR:Out" + } + capture.props = { + node.name = "remap_input.remap-FL-to-FL-FR" + audio.position = FL + stream.dont-remix = true + node.passive = true + } + playback.props = { + node.name = "remap_output.remap-FL-to-FL-FR" + audio.position = FL FR + media.class = "Audio/Source" + } + } + } +
View file
pipewire-0.3.54.tar.gz/src/daemon/filter-chain/source-rnnoise.conf -> pipewire-0.3.56.tar.gz/src/daemon/filter-chain/source-rnnoise.conf
Changed
@@ -1,29 +1,9 @@ # Noise canceling source # -# start with pipewire -c filter-chain/source-rnnoise.conf +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ # -context.properties = { - log.level = 0 -} - -context.spa-libs = { - audio.convert.* = audioconvert/libspa-audioconvert - support.* = support/libspa-support -} - context.modules = - { name = libpipewire-module-rt - args = { - #rt.prio = 88 - #rt.time.soft = -1 - #rt.time.hard = -1 - } - flags = ifexists nofail - } - { name = libpipewire-module-protocol-native } - { name = libpipewire-module-client-node } - { name = libpipewire-module-adapter } - { name = libpipewire-module-filter-chain args = { node.description = "Noise Canceling source"
View file
pipewire-0.3.54.tar.gz/src/daemon/meson.build -> pipewire-0.3.56.tar.gz/src/daemon/meson.build
Changed
@@ -67,9 +67,11 @@ 'pipewire.conf', 'client.conf', 'client-rt.conf', + 'filter-chain.conf', 'jack.conf', 'minimal.conf', 'pipewire-pulse.conf', + 'pipewire-avb.conf', foreach c : conf_files @@ -99,6 +101,14 @@ dependencies : spa_dep, pipewire_dep, , ) +executable('pipewire-avb', + pipewire_daemon_sources, + install: true, + c_args : pipewire_c_args, + include_directories : configinc , + dependencies : spa_dep, pipewire_dep, , +) + ln = find_program('ln') custom_target('pipewire-uninstalled',
View file
pipewire-0.3.56.tar.gz/src/daemon/pipewire-avb.conf.in
Added
@@ -0,0 +1,73 @@ +# PulseAudio config file for PipeWire version @VERSION@ # +# +# Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes +# or in ~/.config/pipewire for local changes. +# +# It is also possible to place a file with an updated section in +# @PIPEWIRE_CONFIG_DIR@/pipewire-pulse.conf.d/ for system-wide changes or in +# ~/.config/pipewire/pipewire-pulse.conf.d/ for local changes. +# + +context.properties = { + ## Configure properties in the system. + #mem.warn-mlock = false + #mem.allow-mlock = true + #mem.mlock-all = false + #log.level = 2 + + #default.clock.quantum-limit = 8192 +} + +context.spa-libs = { + audio.convert.* = audioconvert/libspa-audioconvert + support.* = support/libspa-support +} + +context.modules = + { name = libpipewire-module-rt + args = { + nice.level = -11 + #rt.prio = 88 + #rt.time.soft = -1 + #rt.time.hard = -1 + } + flags = ifexists nofail + } + { name = libpipewire-module-protocol-native } + { name = libpipewire-module-client-node } + { name = libpipewire-module-adapter } + { name = libpipewire-module-avb + args = { + # contents of avb.properties can also be placed here + # to have config per server. + } + } + + +# Extra modules can be loaded here. Setup in default.pa can be moved here +context.exec = + #{ path = "pactl" args = "load-module module-always-sink" } + + +stream.properties = { + #node.latency = 1024/48000 + #node.autoconnect = true + #resample.quality = 4 + #channelmix.normalize = false + #channelmix.mix-lfe = false + #channelmix.upmix = true + #channelmix.lfe-cutoff = 120 + #channelmix.fc-cutoff = 6000 + #channelmix.rear-delay = 12.0 + #channelmix.stereo-widen = 0.1 + #channelmix.hilbert-taps = 0 +} + +avb.properties = { + # the addresses this server listens on + #ifname = "eth0.2" + ifname = "enp3s0" + # These overrides are only applied when running in a vm. + vm.overrides = { + } +}
View file
pipewire-0.3.54.tar.gz/src/daemon/pipewire.conf.in -> pipewire-0.3.56.tar.gz/src/daemon/pipewire.conf.in
Changed
@@ -54,6 +54,7 @@ # that factory. # audio.convert.* = audioconvert/libspa-audioconvert + avb.* = avb/libspa-avb api.alsa.* = alsa/libspa-alsa api.v4l2.* = v4l2/libspa-v4l2 api.libcamera.* = libcamera/libspa-libcamera
View file
pipewire-0.3.54.tar.gz/src/examples/audio-src.c -> pipewire-0.3.56.tar.gz/src/examples/audio-src.c
Changed
@@ -120,6 +120,7 @@ struct data data = { 0, }; const struct spa_pod *params1; uint8_t buffer1024; + struct pw_properties *props; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); pw_init(&argc, &argv); @@ -142,14 +143,17 @@ * you need to listen to is the process event where you need to produce * the data. */ + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Playback", + PW_KEY_MEDIA_ROLE, "Music", + NULL); + if (argc > 1) + /* Set stream target if given on command line */ + pw_properties_set(props, PW_KEY_TARGET_OBJECT, argv1); data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "audio-src", - pw_properties_new( - PW_KEY_MEDIA_TYPE, "Audio", - PW_KEY_MEDIA_CATEGORY, "Playback", - PW_KEY_MEDIA_ROLE, "Music", - NULL), + props, &stream_events, &data); @@ -165,7 +169,7 @@ * called in a realtime thread. */ pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, - argc > 1 ? (uint32_t)atoi(argv1) : PW_ID_ANY, + PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS,
View file
pipewire-0.3.54.tar.gz/src/examples/export-sink.c -> pipewire-0.3.56.tar.gz/src/examples/export-sink.c
Changed
@@ -472,7 +472,7 @@ props = pw_properties_new(PW_KEY_NODE_AUTOCONNECT, "true", NULL); if (data->path) - pw_properties_set(props, PW_KEY_NODE_TARGET, data->path); + pw_properties_set(props, PW_KEY_TARGET_OBJECT, data->path); pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Stream/Input/Video"); pw_properties_set(props, PW_KEY_MEDIA_TYPE, "Video"); pw_properties_set(props, PW_KEY_MEDIA_CATEGORY, "Capture");
View file
pipewire-0.3.54.tar.gz/src/examples/export-source.c -> pipewire-0.3.56.tar.gz/src/examples/export-source.c
Changed
@@ -483,7 +483,7 @@ PW_KEY_MEDIA_ROLE, "Music", NULL); if (data->path) - pw_properties_set(props, PW_KEY_NODE_TARGET, data->path); + pw_properties_set(props, PW_KEY_TARGET_OBJECT, data->path); data->impl_node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node,
View file
pipewire-0.3.54.tar.gz/src/examples/export-spa.c -> pipewire-0.3.56.tar.gz/src/examples/export-spa.c
Changed
@@ -93,7 +93,7 @@ if (data->path) { pw_properties_set(props, PW_KEY_NODE_AUTOCONNECT, "true"); - pw_properties_set(props, PW_KEY_NODE_TARGET, data->path); + pw_properties_set(props, PW_KEY_TARGET_OBJECT, data->path); } data->proxy = pw_core_export(data->core,
View file
pipewire-0.3.54.tar.gz/src/examples/video-dsp-play.c -> pipewire-0.3.56.tar.gz/src/examples/video-dsp-play.c
Changed
@@ -263,7 +263,7 @@ PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "DSP", PW_KEY_NODE_AUTOCONNECT, data.target ? "true" : "false", - PW_KEY_NODE_TARGET, data.target, + PW_KEY_TARGET_OBJECT, data.target, PW_KEY_MEDIA_CLASS, "Stream/Input/Video", NULL), &filter_events,
View file
pipewire-0.3.54.tar.gz/src/examples/video-play-fixate.c -> pipewire-0.3.56.tar.gz/src/examples/video-play-fixate.c
Changed
@@ -419,6 +419,7 @@ const struct spa_pod *params2; uint8_t buffer1024; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + struct pw_properties *props; int res, n_params; pw_init(&argc, &argv); @@ -440,19 +441,22 @@ * you need to listen to is the process event where you need to consume * the data provided to you. */ + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Camera", + NULL), + data.path = argc > 1 ? argv1 : NULL; + if (data.path) + /* Set stream target if given on command line */ + pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); + data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), - "video-play-reneg", - pw_properties_new( - PW_KEY_MEDIA_TYPE, "Video", - PW_KEY_MEDIA_CATEGORY, "Capture", - PW_KEY_MEDIA_ROLE, "Camera", - NULL), + "video-play-fixate", + props, &stream_events, &data); - data.path = argc > 1 ? argv1 : NULL; - if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); return -1; @@ -477,7 +481,7 @@ */ if ((res = pw_stream_connect(data.stream, PW_DIRECTION_INPUT, - data.path ? (uint32_t)atoi(data.path) : PW_ID_ANY, + PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ params, n_params)) /* extra parameters, see above */ < 0) {
View file
pipewire-0.3.54.tar.gz/src/examples/video-play-pull.c -> pipewire-0.3.56.tar.gz/src/examples/video-play-pull.c
Changed
@@ -493,6 +493,7 @@ const struct spa_pod *params2; uint8_t buffer1024; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + struct pw_properties *props; int res, n_params; pw_init(&argc, &argv); @@ -517,20 +518,23 @@ * you need to listen to is the process event where you need to consume * the data provided to you. */ + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Camera", + PW_KEY_PRIORITY_DRIVER, "10000", + NULL), + data.path = argc > 1 ? argv1 : NULL; + if (data.path) + /* Set stream target if given on command line */ + pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); + data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "video-play", - pw_properties_new( - PW_KEY_MEDIA_TYPE, "Video", - PW_KEY_MEDIA_CATEGORY, "Capture", - PW_KEY_MEDIA_ROLE, "Camera", - PW_KEY_PRIORITY_DRIVER, "10000", - NULL), + props, &stream_events, &data); - data.path = argc > 1 ? argv1 : NULL; - if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); return -1; @@ -552,7 +556,7 @@ */ if ((res = pw_stream_connect(data.stream, PW_DIRECTION_INPUT, - data.path ? (uint32_t)atoi(data.path) : PW_ID_ANY, + PW_ID_ANY, PW_STREAM_FLAG_DRIVER | /* we're driver, we pull */ PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */
View file
pipewire-0.3.54.tar.gz/src/examples/video-play-reneg.c -> pipewire-0.3.56.tar.gz/src/examples/video-play-reneg.c
Changed
@@ -346,6 +346,7 @@ const struct spa_pod *params2; uint8_t buffer1024; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + struct pw_properties *props; int res, n_params; pw_init(&argc, &argv); @@ -367,19 +368,22 @@ * you need to listen to is the process event where you need to consume * the data provided to you. */ + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Camera", + NULL), + data.path = argc > 1 ? argv1 : NULL; + if (data.path) + /* Set stream target if given on command line */ + pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); + data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "video-play-reneg", - pw_properties_new( - PW_KEY_MEDIA_TYPE, "Video", - PW_KEY_MEDIA_CATEGORY, "Capture", - PW_KEY_MEDIA_ROLE, "Camera", - NULL), + props, &stream_events, &data); - data.path = argc > 1 ? argv1 : NULL; - if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); return -1; @@ -401,7 +405,7 @@ */ if ((res = pw_stream_connect(data.stream, PW_DIRECTION_INPUT, - data.path ? (uint32_t)atoi(data.path) : PW_ID_ANY, + PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ params, n_params)) /* extra parameters, see above */ < 0) {
View file
pipewire-0.3.54.tar.gz/src/examples/video-play.c -> pipewire-0.3.56.tar.gz/src/examples/video-play.c
Changed
@@ -439,6 +439,7 @@ const struct spa_pod *params2; uint8_t buffer1024; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + struct pw_properties *props; int res, n_params; pw_init(&argc, &argv); @@ -460,19 +461,21 @@ * you need to listen to is the process event where you need to consume * the data provided to you. */ + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Camera", + NULL), + data.path = argc > 1 ? argv1 : NULL; + if (data.path) + pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); + data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "video-play", - pw_properties_new( - PW_KEY_MEDIA_TYPE, "Video", - PW_KEY_MEDIA_CATEGORY, "Capture", - PW_KEY_MEDIA_ROLE, "Camera", - NULL), + props, &stream_events, &data); - data.path = argc > 1 ? argv1 : NULL; - if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); return -1; @@ -494,7 +497,7 @@ */ if ((res = pw_stream_connect(data.stream, PW_DIRECTION_INPUT, - data.path ? (uint32_t)atoi(data.path) : PW_ID_ANY, + PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ PW_STREAM_FLAG_INACTIVE | /* we will activate ourselves */ PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */
View file
pipewire-0.3.54.tar.gz/src/examples/video-src-alloc.c -> pipewire-0.3.56.tar.gz/src/examples/video-src-alloc.c
Changed
@@ -442,7 +442,7 @@ * the server. */ pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, - PW_ID_ANY, /* link to any node */ + PW_ID_ANY, PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS, params, 1);
View file
pipewire-0.3.54.tar.gz/src/examples/video-src-fixate.c -> pipewire-0.3.56.tar.gz/src/examples/video-src-fixate.c
Changed
@@ -578,7 +578,7 @@ * the server. */ pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, - PW_ID_ANY, /* link to any node */ + PW_ID_ANY, PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS, params, 2);
View file
pipewire-0.3.54.tar.gz/src/examples/video-src-reneg.c -> pipewire-0.3.56.tar.gz/src/examples/video-src-reneg.c
Changed
@@ -487,7 +487,7 @@ * the server. */ pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, - PW_ID_ANY, /* link to any node */ + PW_ID_ANY, PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS, params, 1);
View file
pipewire-0.3.54.tar.gz/src/modules/meson.build -> pipewire-0.3.56.tar.gz/src/modules/meson.build
Changed
@@ -5,6 +5,7 @@ module_sources = 'module-access.c', 'module-adapter.c', + 'module-avb.c', 'module-client-device.c', 'module-client-node.c', 'module-echo-cancel.c', @@ -516,3 +517,30 @@ install_rpath: modules_install_dir, dependencies : mathlib, dl_lib, rt_lib, pipewire_dep, ) + +build_module_avb = get_option('avb').allowed() +if build_module_avb +pipewire_module_avb = shared_library('pipewire-module-avb', + 'module-avb.c', + 'module-avb/avb.c', + 'module-avb/adp.c', + 'module-avb/acmp.c', + 'module-avb/aecp.c', + 'module-avb/aecp-aem.c', + 'module-avb/avdecc.c', + 'module-avb/maap.c', + 'module-avb/mmrp.c', + 'module-avb/mrp.c', + 'module-avb/msrp.c', + 'module-avb/mvrp.c', + 'module-avb/srp.c', + 'module-avb/stream.c' + , + include_directories : configinc, + install : true, + install_dir : modules_install_dir, + install_rpath: modules_install_dir, + dependencies : mathlib, dl_lib, rt_lib, pipewire_dep, +) +endif +summary({'avb': build_module_avb}, bool_yn: true, section: 'Optional Modules')
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb
Added
+(directory)
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb.c
Added
@@ -0,0 +1,130 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include "config.h" + +#include <spa/utils/result.h> +#include <spa/utils/string.h> +#include <spa/utils/json.h> + +#include <pipewire/impl.h> +#include <pipewire/private.h> +#include <pipewire/i18n.h> + +#include "module-avb/avb.h" + +/** \page page_module_avb PipeWire Module: AVB + */ + +#define NAME "avb" + +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); +#define PW_LOG_TOPIC_DEFAULT mod_topic + +#define MODULE_USAGE " " + +static const struct spa_dict_item module_props = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { PW_KEY_MODULE_DESCRIPTION, "Manage an AVB network" }, + { PW_KEY_MODULE_USAGE, MODULE_USAGE }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + + +struct impl { + struct pw_context *context; + + struct pw_impl_module *module; + struct spa_hook module_listener; + + struct pw_avb *avb; +}; + +static void impl_free(struct impl *impl) +{ + free(impl); +} + +static void module_destroy(void *data) +{ + struct impl *impl = data; + spa_hook_remove(&impl->module_listener); + impl_free(impl); +} + +static const struct pw_impl_module_events module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = module_destroy, +}; + +SPA_EXPORT +int pipewire__module_init(struct pw_impl_module *module, const char *args) +{ + struct pw_context *context = pw_impl_module_get_context(module); + struct pw_properties *props; + struct impl *impl; + int res; + + PW_LOG_TOPIC_INIT(mod_topic); + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + goto error_errno; + + pw_log_debug("module %p: new %s", impl, args); + + if (args == NULL) + args = ""; + + props = pw_properties_new_string(args); + if (props == NULL) + goto error_errno; + + impl->module = module; + impl->context = context; + + impl->avb = pw_avb_new(context, props, 0); + if (impl->avb == NULL) + goto error_errno; + + pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); + + pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); + + return 0; + +error_errno: + res = -errno; + if (impl) + impl_free(impl); + return res; +}
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/aaf.h
Added
@@ -0,0 +1,102 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_AAF_H +#define AVB_AAF_H + +struct avb_packet_aaf { + uint8_t subtype; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned sv:1; + unsigned version:3; + unsigned mr:1; + unsigned _r1:1; + unsigned gv:1; + unsigned tv:1; + + uint8_t seq_number; + + unsigned _r2:7; + unsigned tu:1; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned tv:1; + unsigned gv:1; + unsigned _r1:1; + unsigned mr:1; + unsigned version:3; + unsigned sv:1; + + uint8_t seq_num; + + unsigned tu:1; + unsigned _r2:7; +#endif + uint64_t stream_id; + uint32_t timestamp; +#define AVB_AAF_FORMAT_USER 0x00 +#define AVB_AAF_FORMAT_FLOAT_32BIT 0x01 +#define AVB_AAF_FORMAT_INT_32BIT 0x02 +#define AVB_AAF_FORMAT_INT_24BIT 0x03 +#define AVB_AAF_FORMAT_INT_16BIT 0x04 +#define AVB_AAF_FORMAT_AES3_32BIT 0x05 + uint8_t format; + +#define AVB_AAF_PCM_NSR_USER 0x00 +#define AVB_AAF_PCM_NSR_8KHZ 0x01 +#define AVB_AAF_PCM_NSR_16KHZ 0x02 +#define AVB_AAF_PCM_NSR_32KHZ 0x03 +#define AVB_AAF_PCM_NSR_44_1KHZ 0x04 +#define AVB_AAF_PCM_NSR_48KHZ 0x05 +#define AVB_AAF_PCM_NSR_88_2KHZ 0x06 +#define AVB_AAF_PCM_NSR_96KHZ 0x07 +#define AVB_AAF_PCM_NSR_176_4KHZ 0x08 +#define AVB_AAF_PCM_NSR_192KHZ 0x09 +#define AVB_AAF_PCM_NSR_24KHZ 0x0A +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned nsr:4; + unsigned _r3:4; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned _r3:4; + unsigned nsr:4; +#endif + uint8_t chan_per_frame; + uint8_t bit_depth; + uint16_t data_len; + +#define AVB_AAF_PCM_SP_NORMAL 0x00 +#define AVB_AAF_PCM_SP_SPARSE 0x01 +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned _r4:3; + unsigned sp:1; + unsigned event:4; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned event:4; + unsigned sp:1; + unsigned _r4:3; +#endif + uint8_t _r5; + uint8_t payload0; +} __attribute__ ((__packed__)); + +#endif /* AVB_AAF_H */
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/acmp.c
Added
@@ -0,0 +1,477 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <spa/utils/json.h> +#include <spa/debug/mem.h> + +#include <pipewire/pipewire.h> + +#include "acmp.h" +#include "msrp.h" +#include "internal.h" +#include "stream.h" + +static const uint8_t mac6 = AVB_BROADCAST_MAC; + +struct pending { + struct spa_list link; + uint64_t last_time; + uint64_t timeout; + uint16_t old_sequence_id; + uint16_t sequence_id; + uint16_t retry; + size_t size; + void *ptr; +}; + +struct acmp { + struct server *server; + struct spa_hook server_listener; + +#define PENDING_TALKER 0 +#define PENDING_LISTENER 1 +#define PENDING_CONTROLLER 2 + struct spa_list pending3; + uint16_t sequence_id3; +}; + +static void *pending_new(struct acmp *acmp, uint32_t type, uint64_t now, uint32_t timeout_ms, + const void *m, size_t size) +{ + struct pending *p; + struct avb_ethernet_header *h; + struct avb_packet_acmp *pm; + + p = calloc(1, sizeof(*p) + size); + if (p == NULL) + return NULL; + p->last_time = now; + p->timeout = timeout_ms * SPA_NSEC_PER_MSEC; + p->sequence_id = acmp->sequence_idtype++; + p->size = size; + p->ptr = SPA_PTROFF(p, sizeof(*p), void); + memcpy(p->ptr, m, size); + + h = p->ptr; + pm = SPA_PTROFF(h, sizeof(*h), void); + p->old_sequence_id = ntohs(pm->sequence_id); + pm->sequence_id = htons(p->sequence_id); + spa_list_append(&acmp->pendingtype, &p->link); + + return p->ptr; +} + +static struct pending *pending_find(struct acmp *acmp, uint32_t type, uint16_t sequence_id) +{ + struct pending *p; + spa_list_for_each(p, &acmp->pendingtype, link) + if (p->sequence_id == sequence_id) + return p; + return NULL; +} + +static void pending_free(struct acmp *acmp, struct pending *p) +{ + spa_list_remove(&p->link); + free(p); +} + +struct msg_info { + uint16_t type; + const char *name; + int (*handle) (struct acmp *acmp, uint64_t now, const void *m, int len); +}; + +static int reply_not_supported(struct acmp *acmp, uint8_t type, const void *m, int len) +{ + struct server *server = acmp->server; + uint8_t buflen; + struct avb_ethernet_header *h = (void*)buf; + struct avb_packet_acmp *reply = SPA_PTROFF(h, sizeof(*h), void); + + memcpy(h, m, len); + AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, type); + AVB_PACKET_ACMP_SET_STATUS(reply, AVB_ACMP_STATUS_NOT_SUPPORTED); + + return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, len); +} + +static int retry_pending(struct acmp *acmp, uint64_t now, struct pending *p) +{ + struct server *server = acmp->server; + struct avb_ethernet_header *h = p->ptr; + p->retry++; + p->last_time = now; + return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, p->ptr, p->size); +} + +static int handle_connect_tx_command(struct acmp *acmp, uint64_t now, const void *m, int len) +{ + struct server *server = acmp->server; + uint8_t buflen; + struct avb_ethernet_header *h = (void*)buf; + struct avb_packet_acmp *reply = SPA_PTROFF(h, sizeof(*h), void); + const struct avb_packet_acmp *p = SPA_PTROFF(m, sizeof(*h), void); + int status = AVB_ACMP_STATUS_SUCCESS; + struct stream *stream; + + if (be64toh(p->talker_guid) != server->entity_id) + return 0; + + memcpy(buf, m, len); + stream = server_find_stream(server, SPA_DIRECTION_OUTPUT, + reply->talker_unique_id); + if (stream == NULL) { + status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX; + goto done; + } + + AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE); + reply->stream_id = htobe64(stream->id); + + stream_activate(stream, now); + + memcpy(reply->stream_dest_mac, stream->addr, 6); + reply->connection_count = htons(1); + reply->stream_vlan_id = htons(stream->vlan_id); + +done: + AVB_PACKET_ACMP_SET_STATUS(reply, status); + return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, buf, len); +} + +static int handle_connect_tx_response(struct acmp *acmp, uint64_t now, const void *m, int len) +{ + struct server *server = acmp->server; + struct avb_ethernet_header *h; + const struct avb_packet_acmp *resp = SPA_PTROFF(m, sizeof(*h), void); + struct avb_packet_acmp *reply; + struct pending *pending; + uint16_t sequence_id; + struct stream *stream; + int res; + + if (be64toh(resp->listener_guid) != server->entity_id) + return 0; + + sequence_id = ntohs(resp->sequence_id); + + pending = pending_find(acmp, PENDING_TALKER, sequence_id); + if (pending == NULL) + return 0; + + h = pending->ptr; + pending->size = SPA_MIN((int)pending->size, len); + memcpy(h, m, pending->size); + + reply = SPA_PTROFF(h, sizeof(*h), void); + reply->sequence_id = htons(pending->old_sequence_id); + AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_RESPONSE); + + stream = server_find_stream(server, SPA_DIRECTION_INPUT, + ntohs(reply->listener_unique_id)); + if (stream == NULL) + return 0; + + stream->peer_id = be64toh(reply->stream_id); + memcpy(stream->addr, reply->stream_dest_mac, 6); + stream_activate(stream, now); + + res = avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, pending->size); + + pending_free(acmp, pending); + + return res; +} + +static int handle_disconnect_tx_command(struct acmp *acmp, uint64_t now, const void *m, int len) +{ + struct server *server = acmp->server; + uint8_t buflen; + struct avb_ethernet_header *h = (void*)buf; + struct avb_packet_acmp *reply = SPA_PTROFF(h, sizeof(*h), void); + const struct avb_packet_acmp *p = SPA_PTROFF(m, sizeof(*h), void); + int status = AVB_ACMP_STATUS_SUCCESS; + struct stream *stream; + + if (be64toh(p->talker_guid) != server->entity_id) + return 0; + + memcpy(buf, m, len); + stream = server_find_stream(server, SPA_DIRECTION_OUTPUT, + reply->talker_unique_id); + if (stream == NULL) { + status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX; + goto done; + } + + AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_RESPONSE); + + stream_deactivate(stream, now); + +done: + AVB_PACKET_ACMP_SET_STATUS(reply, status); + return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, buf, len); +} + +static int handle_disconnect_tx_response(struct acmp *acmp, uint64_t now, const void *m, int len) +{ + struct server *server = acmp->server; + struct avb_ethernet_header *h; + struct avb_packet_acmp *reply; + const struct avb_packet_acmp *resp = SPA_PTROFF(m, sizeof(*h), void); + struct pending *pending; + uint16_t sequence_id; + struct stream *stream; + int res; + + if (be64toh(resp->listener_guid) != server->entity_id) + return 0; + + sequence_id = ntohs(resp->sequence_id); + + pending = pending_find(acmp, PENDING_TALKER, sequence_id); + if (pending == NULL) + return 0; + + h = pending->ptr; + pending->size = SPA_MIN((int)pending->size, len); + memcpy(h, m, pending->size); + + reply = SPA_PTROFF(h, sizeof(*h), void); + reply->sequence_id = htons(pending->old_sequence_id); + AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_RESPONSE); + + stream = server_find_stream(server, SPA_DIRECTION_INPUT, + reply->listener_unique_id); + if (stream == NULL) + return 0; + + stream_deactivate(stream, now); + + res = avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, pending->size); + + pending_free(acmp, pending); + + return res; +} + +static int handle_connect_rx_command(struct acmp *acmp, uint64_t now, const void *m, int len) +{ + struct server *server = acmp->server; + struct avb_ethernet_header *h; + const struct avb_packet_acmp *p = SPA_PTROFF(m, sizeof(*h), void); + struct avb_packet_acmp *cmd; + + if (be64toh(p->listener_guid) != server->entity_id) + return 0; + + h = pending_new(acmp, PENDING_TALKER, now, + AVB_ACMP_TIMEOUT_CONNECT_TX_COMMAND_MS, m, len); + if (h == NULL) + return -errno; + + cmd = SPA_PTROFF(h, sizeof(*h), void); + AVB_PACKET_ACMP_SET_MESSAGE_TYPE(cmd, AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_COMMAND); + AVB_PACKET_ACMP_SET_STATUS(cmd, AVB_ACMP_STATUS_SUCCESS); + + return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, len); +} + +static int handle_ignore(struct acmp *acmp, uint64_t now, const void *m, int len) +{ + return 0; +} + +static int handle_disconnect_rx_command(struct acmp *acmp, uint64_t now, const void *m, int len) +{ + struct server *server = acmp->server; + struct avb_ethernet_header *h; + const struct avb_packet_acmp *p = SPA_PTROFF(m, sizeof(*h), void); + struct avb_packet_acmp *cmd; + + if (be64toh(p->listener_guid) != server->entity_id) + return 0; + + h = pending_new(acmp, PENDING_TALKER, now, + AVB_ACMP_TIMEOUT_DISCONNECT_TX_COMMAND_MS, m, len); + if (h == NULL) + return -errno; + + cmd = SPA_PTROFF(h, sizeof(*h), void); + AVB_PACKET_ACMP_SET_MESSAGE_TYPE(cmd, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_COMMAND); + AVB_PACKET_ACMP_SET_STATUS(cmd, AVB_ACMP_STATUS_SUCCESS); + + return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, len); +} + +static const struct msg_info msg_info = { + { AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_COMMAND, "connect-tx-command", handle_connect_tx_command, }, + { AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE, "connect-tx-response", handle_connect_tx_response, }, + { AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_COMMAND, "disconnect-tx-command", handle_disconnect_tx_command, }, + { AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_RESPONSE, "disconnect-tx-response", handle_disconnect_tx_response, }, + { AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_COMMAND, "get-tx-state-command", NULL, }, + { AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_RESPONSE, "get-tx-state-response", handle_ignore, }, + { AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_COMMAND, "connect-rx-command", handle_connect_rx_command, }, + { AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_RESPONSE, "connect-rx-response", handle_ignore, }, + { AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_COMMAND, "disconnect-rx-command", handle_disconnect_rx_command, }, + { AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_RESPONSE, "disconnect-rx-response", handle_ignore, }, + { AVB_ACMP_MESSAGE_TYPE_GET_RX_STATE_COMMAND, "get-rx-state-command", NULL, }, + { AVB_ACMP_MESSAGE_TYPE_GET_RX_STATE_RESPONSE, "get-rx-state-response", handle_ignore, }, + { AVB_ACMP_MESSAGE_TYPE_GET_TX_CONNECTION_COMMAND, "get-tx-connection-command", NULL, }, + { AVB_ACMP_MESSAGE_TYPE_GET_TX_CONNECTION_RESPONSE, "get-tx-connection-response", handle_ignore, }, +}; + +static inline const struct msg_info *find_msg_info(uint16_t type, const char *name) +{ + uint32_t i; + for (i = 0; i < SPA_N_ELEMENTS(msg_info); i++) { + if ((name == NULL && type == msg_infoi.type) || + (name != NULL && spa_streq(name, msg_infoi.name))) + return &msg_infoi; + } + return NULL; +} + +static int acmp_message(void *data, uint64_t now, const void *message, int len) +{ + struct acmp *acmp = data; + struct server *server = acmp->server; + const struct avb_ethernet_header *h = message; + const struct avb_packet_acmp *p = SPA_PTROFF(h, sizeof(*h), void); + const struct msg_info *info; + int message_type; + + if (ntohs(h->type) != AVB_TSN_ETH) + return 0; + if (memcmp(h->dest, mac, 6) != 0 && + memcmp(h->dest, server->mac_addr, 6) != 0) + return 0; + + if (AVB_PACKET_GET_SUBTYPE(&p->hdr) != AVB_SUBTYPE_ACMP) + return 0; + + message_type = AVB_PACKET_ACMP_GET_MESSAGE_TYPE(p); + + info = find_msg_info(message_type, NULL); + if (info == NULL) + return 0; + + pw_log_info("got ACMP message %s", info->name); + + if (info->handle == NULL) + return reply_not_supported(acmp, message_type | 1, message, len); + + return info->handle(acmp, now, message, len); +} + +static void acmp_destroy(void *data) +{ + struct acmp *acmp = data; + spa_hook_remove(&acmp->server_listener); + free(acmp); +} + +static void check_timeout(struct acmp *acmp, uint64_t now, uint16_t type) +{ + struct pending *p, *t; + + spa_list_for_each_safe(p, t, &acmp->pendingtype, link) { + if (p->last_time + p->timeout > now) + continue; + + if (p->retry == 0) { + pw_log_info("%p: pending timeout, retry", p); + retry_pending(acmp, now, p); + } else { + pw_log_info("%p: pending timeout, fail", p); + pending_free(acmp, p); + } + } +} +static void acmp_periodic(void *data, uint64_t now) +{ + struct acmp *acmp = data; + check_timeout(acmp, now, PENDING_TALKER); + check_timeout(acmp, now, PENDING_LISTENER); + check_timeout(acmp, now, PENDING_CONTROLLER); +} + +static int do_help(struct acmp *acmp, const char *args, FILE *out) +{ + fprintf(out, "{ \"type\": \"help\"," + "\"text\": \"" + "/adp/help: this help \\n" + "\" }"); + return 0; +} + +static int acmp_command(void *data, uint64_t now, const char *command, const char *args, FILE *out) +{ + struct acmp *acmp = data; + int res; + + if (!spa_strstartswith(command, "/acmp/")) + return 0; + + command += strlen("/acmp/"); + + if (spa_streq(command, "help")) + res = do_help(acmp, args, out); + else + res = -ENOTSUP; + + return res; +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = acmp_destroy, + .message = acmp_message, + .periodic = acmp_periodic, + .command = acmp_command +}; + +struct avb_acmp *avb_acmp_register(struct server *server) +{ + struct acmp *acmp; + + acmp = calloc(1, sizeof(*acmp)); + if (acmp == NULL) + return NULL; + + acmp->server = server; + spa_list_init(&acmp->pendingPENDING_TALKER); + spa_list_init(&acmp->pendingPENDING_LISTENER); + spa_list_init(&acmp->pendingPENDING_CONTROLLER); + + avdecc_server_add_listener(server, &acmp->server_listener, &server_events, acmp); + + return (struct avb_acmp*)acmp; +} + +void avb_acmp_unregister(struct avb_acmp *acmp) +{ + acmp_destroy(acmp); +}
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/acmp.h
Added
@@ -0,0 +1,99 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_ACMP_H +#define AVB_ACMP_H + +#include "packets.h" +#include "internal.h" + +#define AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_COMMAND 0 +#define AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE 1 +#define AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_COMMAND 2 +#define AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_RESPONSE 3 +#define AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_COMMAND 4 +#define AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_RESPONSE 5 +#define AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_COMMAND 6 +#define AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_RESPONSE 7 +#define AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_COMMAND 8 +#define AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_RESPONSE 9 +#define AVB_ACMP_MESSAGE_TYPE_GET_RX_STATE_COMMAND 10 +#define AVB_ACMP_MESSAGE_TYPE_GET_RX_STATE_RESPONSE 11 +#define AVB_ACMP_MESSAGE_TYPE_GET_TX_CONNECTION_COMMAND 12 +#define AVB_ACMP_MESSAGE_TYPE_GET_TX_CONNECTION_RESPONSE 13 + +#define AVB_ACMP_STATUS_SUCCESS 0 +#define AVB_ACMP_STATUS_LISTENER_UNKNOWN_ID 1 +#define AVB_ACMP_STATUS_TALKER_UNKNOWN_ID 2 +#define AVB_ACMP_STATUS_TALKER_DEST_MAC_FAIL 3 +#define AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX 4 +#define AVB_ACMP_STATUS_TALKER_NO_BANDWIDTH 5 +#define AVB_ACMP_STATUS_TALKER_EXCLUSIVE 6 +#define AVB_ACMP_STATUS_LISTENER_TALKER_TIMEOUT 7 +#define AVB_ACMP_STATUS_LISTENER_EXCLUSIVE 8 +#define AVB_ACMP_STATUS_STATE_UNAVAILABLE 9 +#define AVB_ACMP_STATUS_NOT_CONNECTED 10 +#define AVB_ACMP_STATUS_NO_SUCH_CONNECTION 11 +#define AVB_ACMP_STATUS_COULD_NOT_SEND_MESSAGE 12 +#define AVB_ACMP_STATUS_TALKER_MISBEHAVING 13 +#define AVB_ACMP_STATUS_LISTENER_MISBEHAVING 14 +#define AVB_ACMP_STATUS_RESERVED 15 +#define AVB_ACMP_STATUS_CONTROLLER_NOT_AUTHORIZED 16 +#define AVB_ACMP_STATUS_INCOMPATIBLE_REQUEST 17 +#define AVB_ACMP_STATUS_LISTENER_INVALID_CONNECTION 18 +#define AVB_ACMP_STATUS_NOT_SUPPORTED 31 + +#define AVB_ACMP_TIMEOUT_CONNECT_TX_COMMAND_MS 2000 +#define AVB_ACMP_TIMEOUT_DISCONNECT_TX_COMMAND_MS 200 +#define AVB_ACMP_TIMEOUT_GET_TX_STATE_COMMAND 200 +#define AVB_ACMP_TIMEOUT_CONNECT_RX_COMMAND_MS 4500 +#define AVB_ACMP_TIMEOUT_DISCONNECT_RX_COMMAND_MS 500 +#define AVB_ACMP_TIMEOUT_GET_RX_STATE_COMMAND_MS 200 +#define AVB_ACMP_TIMEOUT_GET_TX_CONNECTION_COMMAND 200 + +struct avb_packet_acmp { + struct avb_packet_header hdr; + uint64_t stream_id; + uint64_t controller_guid; + uint64_t talker_guid; + uint64_t listener_guid; + uint16_t talker_unique_id; + uint16_t listener_unique_id; + char stream_dest_mac6; + uint16_t connection_count; + uint16_t sequence_id; + uint16_t flags; + uint16_t stream_vlan_id; + uint16_t reserved; +} __attribute__ ((__packed__)); + +#define AVB_PACKET_ACMP_SET_MESSAGE_TYPE(p,v) AVB_PACKET_SET_SUB1(&(p)->hdr, v) +#define AVB_PACKET_ACMP_SET_STATUS(p,v) AVB_PACKET_SET_SUB2(&(p)->hdr, v) + +#define AVB_PACKET_ACMP_GET_MESSAGE_TYPE(p) AVB_PACKET_GET_SUB1(&(p)->hdr) +#define AVB_PACKET_ACMP_GET_STATUS(p) AVB_PACKET_GET_SUB2(&(p)->hdr) + +struct avb_acmp *avb_acmp_register(struct server *server); + +#endif /* AVB_ACMP_H */
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/adp.c
Added
@@ -0,0 +1,381 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <spa/utils/json.h> + +#include <pipewire/pipewire.h> + +#include "adp.h" +#include "aecp-aem-descriptors.h" +#include "internal.h" +#include "utils.h" + +static const uint8_t mac6 = AVB_BROADCAST_MAC; + +struct entity { + struct spa_list link; + uint64_t entity_id; + uint64_t last_time; + int valid_time; + unsigned advertise:1; + size_t len; + uint8_t buf128; +}; + +struct adp { + struct server *server; + struct spa_hook server_listener; + + struct spa_list entities; + uint32_t available_index; +}; + +static struct entity *find_entity_by_id(struct adp *adp, uint64_t id) +{ + struct entity *e; + spa_list_for_each(e, &adp->entities, link) + if (e->entity_id == id) + return e; + return NULL; +} +static void entity_free(struct entity *e) +{ + spa_list_remove(&e->link); + free(e); +} + +static int send_departing(struct adp *adp, uint64_t now, struct entity *e) +{ + struct avb_ethernet_header *h = (void*)e->buf; + struct avb_packet_adp *p = SPA_PTROFF(h, sizeof(*h), void); + + AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_DEPARTING); + p->available_index = htonl(adp->available_index++); + avb_server_send_packet(adp->server, mac, AVB_TSN_ETH, e->buf, e->len); + e->last_time = now; + return 0; +} + +static int send_advertise(struct adp *adp, uint64_t now, struct entity *e) +{ + struct avb_ethernet_header *h = (void*)e->buf; + struct avb_packet_adp *p = SPA_PTROFF(h, sizeof(*h), void); + + AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE); + p->available_index = htonl(adp->available_index++); + avb_server_send_packet(adp->server, mac, AVB_TSN_ETH, e->buf, e->len); + e->last_time = now; + return 0; +} + +static int send_discover(struct adp *adp, uint64_t entity_id) +{ + uint8_t buf128; + struct avb_ethernet_header *h = (void*)buf; + struct avb_packet_adp *p = SPA_PTROFF(h, sizeof(*h), void); + size_t len = sizeof(*h) + sizeof(*p); + + spa_memzero(buf, sizeof(buf)); + AVB_PACKET_SET_SUBTYPE(&p->hdr, AVB_SUBTYPE_ADP); + AVB_PACKET_SET_LENGTH(&p->hdr, AVB_ADP_CONTROL_DATA_LENGTH); + AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_DISCOVER); + p->entity_id = htonl(entity_id); + avb_server_send_packet(adp->server, mac, AVB_TSN_ETH, buf, len); + return 0; +} + +static int adp_message(void *data, uint64_t now, const void *message, int len) +{ + struct adp *adp = data; + struct server *server = adp->server; + const struct avb_ethernet_header *h = message; + const struct avb_packet_adp *p = SPA_PTROFF(h, sizeof(*h), void); + struct entity *e; + int message_type; + char buf128; + uint64_t entity_id; + + if (ntohs(h->type) != AVB_TSN_ETH) + return 0; + if (memcmp(h->dest, mac, 6) != 0 && + memcmp(h->dest, server->mac_addr, 6) != 0) + return 0; + + if (AVB_PACKET_GET_SUBTYPE(&p->hdr) != AVB_SUBTYPE_ADP || + AVB_PACKET_GET_LENGTH(&p->hdr) < AVB_ADP_CONTROL_DATA_LENGTH) + return 0; + + message_type = AVB_PACKET_ADP_GET_MESSAGE_TYPE(p); + entity_id = be64toh(p->entity_id); + + e = find_entity_by_id(adp, entity_id); + + switch (message_type) { + case AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE: + if (e == NULL) { + e = calloc(1, sizeof(*e)); + if (e == NULL) + return -errno; + + memcpy(e->buf, message, len); + e->len = len; + e->valid_time = AVB_PACKET_ADP_GET_VALID_TIME(p); + e->entity_id = entity_id; + spa_list_append(&adp->entities, &e->link); + pw_log_info("entity %s available", + avb_utils_format_id(buf, sizeof(buf), entity_id)); + } + e->last_time = now; + break; + case AVB_ADP_MESSAGE_TYPE_ENTITY_DEPARTING: + if (e != NULL) { + pw_log_info("entity %s departing", + avb_utils_format_id(buf, sizeof(buf), entity_id)); + entity_free(e); + } + break; + case AVB_ADP_MESSAGE_TYPE_ENTITY_DISCOVER: + pw_log_info("entity %s advertise", + avb_utils_format_id(buf, sizeof(buf), entity_id)); + if (entity_id == 0UL) { + spa_list_for_each(e, &adp->entities, link) + if (e->advertise) + send_advertise(adp, now, e); + } else if (e != NULL && + e->advertise && e->entity_id == entity_id) { + send_advertise(adp, now, e); + } + break; + default: + return -EINVAL; + } + return 0; +} + +static void adp_destroy(void *data) +{ + struct adp *adp = data; + spa_hook_remove(&adp->server_listener); + free(adp); +} + +static void check_timeout(struct adp *adp, uint64_t now) +{ + struct entity *e, *t; + char buf128; + + spa_list_for_each_safe(e, t, &adp->entities, link) { + if (e->last_time + (e->valid_time + 2) * SPA_NSEC_PER_SEC > now) + continue; + + pw_log_info("entity %s timeout", + avb_utils_format_id(buf, sizeof(buf), e->entity_id)); + + if (e->advertise) + send_departing(adp, now, e); + + entity_free(e); + } +} +static void check_readvertize(struct adp *adp, uint64_t now, struct entity *e) +{ + char buf128; + + if (!e->advertise) + return; + + if (e->last_time + (e->valid_time / 2) * SPA_NSEC_PER_SEC > now) + return; + + pw_log_debug("entity %s readvertise", + avb_utils_format_id(buf, sizeof(buf), e->entity_id)); + + send_advertise(adp, now, e); +} + +static int check_advertise(struct adp *adp, uint64_t now) +{ + struct server *server = adp->server; + const struct descriptor *d; + struct avb_aem_desc_entity *entity; + struct avb_aem_desc_avb_interface *avb_interface; + struct entity *e; + uint64_t entity_id; + struct avb_ethernet_header *h; + struct avb_packet_adp *p; + char buf128; + + d = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 0); + if (d == NULL) + return 0; + + entity = d->ptr; + entity_id = be64toh(entity->entity_id); + + if ((e = find_entity_by_id(adp, entity_id)) != NULL) { + if (e->advertise) + check_readvertize(adp, now, e); + return 0; + } + + d = server_find_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0); + avb_interface = d ? d->ptr : NULL; + + pw_log_info("entity %s advertise", + avb_utils_format_id(buf, sizeof(buf), entity_id)); + + e = calloc(1, sizeof(*e)); + if (e == NULL) + return -errno; + + e->advertise = true; + e->valid_time = 10; + e->last_time = now; + e->entity_id = entity_id; + e->len = sizeof(*h) + sizeof(*p); + + h = (void*)e->buf; + p = SPA_PTROFF(h, sizeof(*h), void); + AVB_PACKET_SET_LENGTH(&p->hdr, AVB_ADP_CONTROL_DATA_LENGTH); + AVB_PACKET_SET_SUBTYPE(&p->hdr, AVB_SUBTYPE_ADP); + AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE); + AVB_PACKET_ADP_SET_VALID_TIME(p, e->valid_time); + + p->entity_id = entity->entity_id; + p->entity_model_id = entity->entity_model_id; + p->entity_capabilities = entity->entity_capabilities; + p->talker_stream_sources = entity->talker_stream_sources; + p->talker_capabilities = entity->talker_capabilities; + p->listener_stream_sinks = entity->listener_stream_sinks; + p->listener_capabilities = entity->listener_capabilities; + p->controller_capabilities = entity->controller_capabilities; + p->available_index = entity->available_index; + if (avb_interface) { + p->gptp_grandmaster_id = avb_interface->clock_identity; + p->gptp_domain_number = avb_interface->domain_number; + } + p->identify_control_index = 0; + p->interface_index = 0; + p->association_id = entity->association_id; + + spa_list_append(&adp->entities, &e->link); + + return 0; +} + +static void adp_periodic(void *data, uint64_t now) +{ + struct adp *adp = data; + check_timeout(adp, now); + check_advertise(adp, now); +} + +static int do_help(struct adp *adp, const char *args, FILE *out) +{ + fprintf(out, "{ \"type\": \"help\"," + "\"text\": \"" + "/adp/help: this help \\n" + "/adp/discover { \"entity-id\": <id> } : trigger discover\\n" + "\" }"); + return 0; +} + +static int do_discover(struct adp *adp, const char *args, FILE *out) +{ + struct spa_json it2; + char key128; + uint64_t entity_id = 0ULL; + + spa_json_init(&it0, args, strlen(args)); + if (spa_json_enter_object(&it0, &it1) <= 0) + return -EINVAL; + + while (spa_json_get_string(&it1, key, sizeof(key)) > 0) { + int len; + const char *value; + uint64_t id_val; + + if ((len = spa_json_next(&it1, &value)) <= 0) + break; + + if (spa_json_is_null(value, len)) + continue; + + if (spa_streq(key, "entity-id")) { + if (avb_utils_parse_id(value, len, &id_val) >= 0) + entity_id = id_val; + } + } + send_discover(adp, entity_id); + return 0; +} + +static int adp_command(void *data, uint64_t now, const char *command, const char *args, FILE *out) +{ + struct adp *adp = data; + int res; + + if (!spa_strstartswith(command, "/adp/")) + return 0; + + command += strlen("/adp/"); + + if (spa_streq(command, "help")) + res = do_help(adp, args, out); + else if (spa_streq(command, "discover")) + res = do_discover(adp, args, out); + else + res = -ENOTSUP; + + return res; +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = adp_destroy, + .message = adp_message, + .periodic = adp_periodic, + .command = adp_command +}; + +struct avb_adp *avb_adp_register(struct server *server) +{ + struct adp *adp; + + adp = calloc(1, sizeof(*adp)); + if (adp == NULL) + return NULL; + + adp->server = server; + spa_list_init(&adp->entities); + + avdecc_server_add_listener(server, &adp->server_listener, &server_events, adp); + + return (struct avb_adp*)adp; +} + +void avb_adp_unregister(struct avb_adp *adp) +{ + adp_destroy(adp); +}
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/adp.h
Added
@@ -0,0 +1,105 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_ADP_H +#define AVB_ADP_H + +#include "packets.h" +#include "internal.h" + +#define AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE 0 +#define AVB_ADP_MESSAGE_TYPE_ENTITY_DEPARTING 1 +#define AVB_ADP_MESSAGE_TYPE_ENTITY_DISCOVER 2 + +#define AVB_ADP_ENTITY_CAPABILITY_EFU_MODE (1u<<0) +#define AVB_ADP_ENTITY_CAPABILITY_ADDRESS_ACCESS_SUPPORTED (1u<<1) +#define AVB_ADP_ENTITY_CAPABILITY_GATEWAY_ENTITY (1u<<2) +#define AVB_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED (1u<<3) +#define AVB_ADP_ENTITY_CAPABILITY_LEGACY_AVC (1u<<4) +#define AVB_ADP_ENTITY_CAPABILITY_ASSOCIATION_ID_SUPPORTED (1u<<5) +#define AVB_ADP_ENTITY_CAPABILITY_ASSOCIATION_ID_VALID (1u<<6) +#define AVB_ADP_ENTITY_CAPABILITY_VENDOR_UNIQUE_SUPPORTED (1u<<7) +#define AVB_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED (1u<<8) +#define AVB_ADP_ENTITY_CAPABILITY_CLASS_B_SUPPORTED (1u<<9) +#define AVB_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED (1u<<10) +#define AVB_ADP_ENTITY_CAPABILITY_AEM_AUTHENTICATION_SUPPORTED (1u<<11) +#define AVB_ADP_ENTITY_CAPABILITY_AEM_AUTHENTICATION_REQUIRED (1u<<12) +#define AVB_ADP_ENTITY_CAPABILITY_AEM_PERSISTENT_ACQUIRE_SUPPORTED (1u<<13) +#define AVB_ADP_ENTITY_CAPABILITY_AEM_IDENTIFY_CONTROL_INDEX_VALID (1u<<14) +#define AVB_ADP_ENTITY_CAPABILITY_AEM_INTERFACE_INDEX_VALID (1u<<15) +#define AVB_ADP_ENTITY_CAPABILITY_GENERAL_CONTROLLER_IGNORE (1u<<16) +#define AVB_ADP_ENTITY_CAPABILITY_ENTITY_NOT_READY (1u<<17) + +#define AVB_ADP_TALKER_CAPABILITY_IMPLEMENTED (1u<<0) +#define AVB_ADP_TALKER_CAPABILITY_OTHER_SOURCE (1u<<9) +#define AVB_ADP_TALKER_CAPABILITY_CONTROL_SOURCE (1u<<10) +#define AVB_ADP_TALKER_CAPABILITY_MEDIA_CLOCK_SOURCE (1u<<11) +#define AVB_ADP_TALKER_CAPABILITY_SMPTE_SOURCE (1u<<12) +#define AVB_ADP_TALKER_CAPABILITY_MIDI_SOURCE (1u<<13) +#define AVB_ADP_TALKER_CAPABILITY_AUDIO_SOURCE (1u<<14) +#define AVB_ADP_TALKER_CAPABILITY_VIDEO_SOURCE (1u<<15) + +#define AVB_ADP_LISTENER_CAPABILITY_IMPLEMENTED (1u<<0) +#define AVB_ADP_LISTENER_CAPABILITY_OTHER_SINK (1u<<9) +#define AVB_ADP_LISTENER_CAPABILITY_CONTROL_SINK (1u<<10) +#define AVB_ADP_LISTENER_CAPABILITY_MEDIA_CLOCK_SINK (1u<<11) +#define AVB_ADP_LISTENER_CAPABILITY_SMPTE_SINK (1u<<12) +#define AVB_ADP_LISTENER_CAPABILITY_MIDI_SINK (1u<<13) +#define AVB_ADP_LISTENER_CAPABILITY_AUDIO_SINK (1u<<14) +#define AVB_ADP_LISTENER_CAPABILITY_VIDEO_SINK (1u<<15) + +#define AVB_ADP_CONTROLLER_CAPABILITY_IMPLEMENTED (1u<<0) +#define AVB_ADP_CONTROLLER_CAPABILITY_LAYER3_PROXY (1u<<1) + +#define AVB_ADP_CONTROL_DATA_LENGTH 56 + +struct avb_packet_adp { + struct avb_packet_header hdr; + uint64_t entity_id; + uint64_t entity_model_id; + uint32_t entity_capabilities; + uint16_t talker_stream_sources; + uint16_t talker_capabilities; + uint16_t listener_stream_sinks; + uint16_t listener_capabilities; + uint32_t controller_capabilities; + uint32_t available_index; + uint64_t gptp_grandmaster_id; + uint8_t gptp_domain_number; + uint8_t reserved03; + uint16_t identify_control_index; + uint16_t interface_index; + uint64_t association_id; + uint32_t reserved1; +} __attribute__ ((__packed__)); + +#define AVB_PACKET_ADP_SET_MESSAGE_TYPE(p,v) AVB_PACKET_SET_SUB1(&(p)->hdr, v) +#define AVB_PACKET_ADP_SET_VALID_TIME(p,v) AVB_PACKET_SET_SUB2(&(p)->hdr, v) + +#define AVB_PACKET_ADP_GET_MESSAGE_TYPE(p) AVB_PACKET_GET_SUB1(&(p)->hdr) +#define AVB_PACKET_ADP_GET_VALID_TIME(p) AVB_PACKET_GET_SUB2(&(p)->hdr) + +struct avb_adp *avb_adp_register(struct server *server); + +#endif /* AVB_ADP_H */
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/aecp-aem-descriptors.h
Added
@@ -0,0 +1,247 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_AECP_AEM_DESCRIPTORS_H +#define AVB_AECP_AEM_DESCRIPTORS_H + +#include "internal.h" + +#define AVB_AEM_DESC_ENTITY 0x0000 +#define AVB_AEM_DESC_CONFIGURATION 0x0001 +#define AVB_AEM_DESC_AUDIO_UNIT 0x0002 +#define AVB_AEM_DESC_VIDEO_UNIT 0x0003 +#define AVB_AEM_DESC_SENSOR_UNIT 0x0004 +#define AVB_AEM_DESC_STREAM_INPUT 0x0005 +#define AVB_AEM_DESC_STREAM_OUTPUT 0x0006 +#define AVB_AEM_DESC_JACK_INPUT 0x0007 +#define AVB_AEM_DESC_JACK_OUTPUT 0x0008 +#define AVB_AEM_DESC_AVB_INTERFACE 0x0009 +#define AVB_AEM_DESC_CLOCK_SOURCE 0x000a +#define AVB_AEM_DESC_MEMORY_OBJECT 0x000b +#define AVB_AEM_DESC_LOCALE 0x000c +#define AVB_AEM_DESC_STRINGS 0x000d +#define AVB_AEM_DESC_STREAM_PORT_INPUT 0x000e +#define AVB_AEM_DESC_STREAM_PORT_OUTPUT 0x000f +#define AVB_AEM_DESC_EXTERNAL_PORT_INPUT 0x0010 +#define AVB_AEM_DESC_EXTERNAL_PORT_OUTPUT 0x0011 +#define AVB_AEM_DESC_INTERNAL_PORT_INPUT 0x0012 +#define AVB_AEM_DESC_INTERNAL_PORT_OUTPUT 0x0013 +#define AVB_AEM_DESC_AUDIO_CLUSTER 0x0014 +#define AVB_AEM_DESC_VIDEO_CLUSTER 0x0015 +#define AVB_AEM_DESC_SENSOR_CLUSTER 0x0016 +#define AVB_AEM_DESC_AUDIO_MAP 0x0017 +#define AVB_AEM_DESC_VIDEO_MAP 0x0018 +#define AVB_AEM_DESC_SENSOR_MAP 0x0019 +#define AVB_AEM_DESC_CONTROL 0x001a +#define AVB_AEM_DESC_SIGNAL_SELECTOR 0x001b +#define AVB_AEM_DESC_MIXER 0x001c +#define AVB_AEM_DESC_MATRIX 0x001d +#define AVB_AEM_DESC_MATRIX_SIGNAL 0x001e +#define AVB_AEM_DESC_SIGNAL_SPLITTER 0x001f +#define AVB_AEM_DESC_SIGNAL_COMBINER 0x0020 +#define AVB_AEM_DESC_SIGNAL_DEMULTIPLEXER 0x0021 +#define AVB_AEM_DESC_SIGNAL_MULTIPLEXER 0x0022 +#define AVB_AEM_DESC_SIGNAL_TRANSCODER 0x0023 +#define AVB_AEM_DESC_CLOCK_DOMAIN 0x0024 +#define AVB_AEM_DESC_CONTROL_BLOCK 0x0025 +#define AVB_AEM_DESC_INVALID 0xffff + +struct avb_aem_desc_entity { + uint64_t entity_id; + uint64_t entity_model_id; + uint32_t entity_capabilities; + uint16_t talker_stream_sources; + uint16_t talker_capabilities; + uint16_t listener_stream_sinks; + uint16_t listener_capabilities; + uint32_t controller_capabilities; + uint32_t available_index; + uint64_t association_id; + char entity_name64; + uint16_t vendor_name_string; + uint16_t model_name_string; + char firmware_version64; + char group_name64; + char serial_number64; + uint16_t configurations_count; + uint16_t current_configuration; +} __attribute__ ((__packed__)); + +struct avb_aem_desc_descriptor_count { + uint16_t descriptor_type; + uint16_t descriptor_count; +} __attribute__ ((__packed__)); + +struct avb_aem_desc_configuration { + char object_name64; + uint16_t localized_description; + uint16_t descriptor_counts_count; + uint16_t descriptor_counts_offset; + struct avb_aem_desc_descriptor_count descriptor_counts0; +} __attribute__ ((__packed__)); + +struct avb_aem_desc_sampling_rate { + uint32_t pull_frequency; +} __attribute__ ((__packed__)); + +struct avb_aem_desc_audio_unit { + char object_name64; + uint16_t localized_description; + uint16_t clock_domain_index; + uint16_t number_of_stream_input_ports; + uint16_t base_stream_input_port; + uint16_t number_of_stream_output_ports; + uint16_t base_stream_output_port; + uint16_t number_of_external_input_ports; + uint16_t base_external_input_port; + uint16_t number_of_external_output_ports; + uint16_t base_external_output_port; + uint16_t number_of_internal_input_ports; + uint16_t base_internal_input_port; + uint16_t number_of_internal_output_ports; + uint16_t base_internal_output_port; + uint16_t number_of_controls; + uint16_t base_control; + uint16_t number_of_signal_selectors; + uint16_t base_signal_selector; + uint16_t number_of_mixers; + uint16_t base_mixer; + uint16_t number_of_matrices; + uint16_t base_matrix; + uint16_t number_of_splitters; + uint16_t base_splitter; + uint16_t number_of_combiners; + uint16_t base_combiner; + uint16_t number_of_demultiplexers; + uint16_t base_demultiplexer; + uint16_t number_of_multiplexers; + uint16_t base_multiplexer; + uint16_t number_of_transcoders; + uint16_t base_transcoder; + uint16_t number_of_control_blocks; + uint16_t base_control_block; + uint32_t current_sampling_rate; + uint16_t sampling_rates_offset; + uint16_t sampling_rates_count; + struct avb_aem_desc_sampling_rate sampling_rates0; +} __attribute__ ((__packed__)); + +#define AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE (1u<<0) +#define AVB_AEM_DESC_STREAM_FLAG_CLASS_A (1u<<1) +#define AVB_AEM_DESC_STREAM_FLAG_CLASS_B (1u<<2) +#define AVB_AEM_DESC_STREAM_FLAG_SUPPORTS_ENCRYPTED (1u<<3) +#define AVB_AEM_DESC_STREAM_FLAG_PRIMARY_BACKUP_SUPPORTED (1u<<4) +#define AVB_AEM_DESC_STREAM_FLAG_PRIMARY_BACKUP_VALID (1u<<5) +#define AVB_AEM_DESC_STREAM_FLAG_SECONDARY_BACKUP_SUPPORTED (1u<<6) +#define AVB_AEM_DESC_STREAM_FLAG_SECONDARY_BACKUP_VALID (1u<<7) +#define AVB_AEM_DESC_STREAM_FLAG_TERTIARY_BACKUP_SUPPORTED (1u<<8) +#define AVB_AEM_DESC_STREAM_FLAG_TERTIARY_BACKUP_VALID (1u<<9) + +struct avb_aem_desc_stream { + char object_name64; + uint16_t localized_description; + uint16_t clock_domain_index; + uint16_t stream_flags; + uint64_t current_format; + uint16_t formats_offset; + uint16_t number_of_formats; + uint64_t backup_talker_entity_id_0; + uint16_t backup_talker_unique_id_0; + uint64_t backup_talker_entity_id_1; + uint16_t backup_talker_unique_id_1; + uint64_t backup_talker_entity_id_2; + uint16_t backup_talker_unique_id_2; + uint64_t backedup_talker_entity_id; + uint16_t backedup_talker_unique; + uint16_t avb_interface_index; + uint32_t buffer_length; + uint64_t stream_formats0; +} __attribute__ ((__packed__)); + +#define AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_GRANDMASTER_SUPPORTED (1<<0) +#define AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_SUPPORTED (1<<1) +#define AVB_AEM_DESC_AVB_INTERFACE_FLAG_SRP_SUPPORTED (1<<2) + +struct avb_aem_desc_avb_interface { + char object_name64; + uint16_t localized_description; + uint8_t mac_address6; + uint16_t interface_flags; + uint64_t clock_identity; + uint8_t priority1; + uint8_t clock_class; + uint16_t offset_scaled_log_variance; + uint8_t clock_accuracy; + uint8_t priority2; + uint8_t domain_number; + int8_t log_sync_interval; + int8_t log_announce_interval; + int8_t log_pdelay_interval; + uint16_t port_number; +} __attribute__ ((__packed__)); + +#define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INTERNAL 0x0000 +#define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_EXTERNAL 0x0001 +#define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INPUT_STREAM 0x0002 +#define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_MEDIA_CLOCK_STREAM 0x0003 +#define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_EXPANSION 0xffff + +struct avb_aem_desc_clock_source { + char object_name64; + uint16_t localized_description; + uint16_t clock_source_flags; + uint16_t clock_source_type; + uint64_t clock_source_identifier; + uint16_t clock_source_location_type; + uint16_t clock_source_location_index; +} __attribute__ ((__packed__)); + +struct avb_aem_desc_locale { + char locale_identifier64; + uint16_t number_of_strings; + uint16_t base_strings; +} __attribute__ ((__packed__)); + +struct avb_aem_desc_strings { + char string_064; + char string_164; + char string_264; + char string_364; + char string_464; + char string_564; + char string_664; +} __attribute__ ((__packed__)); + +struct avb_aem_desc_stream_port { + uint16_t clock_domain_index; + uint16_t port_flags; + uint16_t number_of_controls; + uint16_t base_control; + uint16_t number_of_clusters; + uint16_t base_cluster; + uint16_t number_of_maps; + uint16_t base_map; +} __attribute__ ((__packed__)); + +#endif /* AVB_AECP_AEM_DESCRIPTORS_H */
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/aecp-aem.c
Added
@@ -0,0 +1,286 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "aecp-aem.h" +#include "aecp-aem-descriptors.h" + +static int reply_status(struct aecp *aecp, int status, const void *m, int len) +{ + struct server *server = aecp->server; + uint8_t buflen; + struct avb_ethernet_header *h = (void*)buf; + struct avb_packet_aecp_header *reply = SPA_PTROFF(h, sizeof(*h), void); + + memcpy(buf, m, len); + AVB_PACKET_AECP_SET_MESSAGE_TYPE(reply, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); + AVB_PACKET_AECP_SET_STATUS(reply, status); + + return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, len); +} + +static int reply_not_implemented(struct aecp *aecp, const void *m, int len) +{ + return reply_status(aecp, AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED, m, len); +} + +static int reply_success(struct aecp *aecp, const void *m, int len) +{ + return reply_status(aecp, AVB_AECP_AEM_STATUS_SUCCESS, m, len); +} + +/* ACQUIRE_ENTITY */ +static int handle_acquire_entity(struct aecp *aecp, const void *m, int len) +{ + struct server *server = aecp->server; + const struct avb_packet_aecp_aem *p = m; + const struct avb_packet_aecp_aem_acquire *ae; + const struct descriptor *desc; + uint16_t desc_type, desc_id; + + ae = (const struct avb_packet_aecp_aem_acquire*)p->payload; + + desc_type = ntohs(ae->descriptor_type); + desc_id = ntohs(ae->descriptor_id); + + desc = server_find_descriptor(server, desc_type, desc_id); + if (desc == NULL) + return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len); + + if (desc_type != AVB_AEM_DESC_ENTITY || desc_id != 0) + return reply_not_implemented(aecp, m, len); + + return reply_success(aecp, m, len); +} + +/* LOCK_ENTITY */ +static int handle_lock_entity(struct aecp *aecp, const void *m, int len) +{ + struct server *server = aecp->server; + const struct avb_packet_aecp_aem *p = m; + const struct avb_packet_aecp_aem_acquire *ae; + const struct descriptor *desc; + uint16_t desc_type, desc_id; + + ae = (const struct avb_packet_aecp_aem_acquire*)p->payload; + + desc_type = ntohs(ae->descriptor_type); + desc_id = ntohs(ae->descriptor_id); + + desc = server_find_descriptor(server, desc_type, desc_id); + if (desc == NULL) + return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len); + + if (desc_type != AVB_AEM_DESC_ENTITY || desc_id != 0) + return reply_not_implemented(aecp, m, len); + + return reply_success(aecp, m, len); +} + +/* READ_DESCRIPTOR */ +static int handle_read_descriptor(struct aecp *aecp, const void *m, int len) +{ + struct server *server = aecp->server; + const struct avb_ethernet_header *h = m; + const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void); + struct avb_packet_aecp_aem *reply; + const struct avb_packet_aecp_aem_read_descriptor *rd; + uint16_t desc_type, desc_id; + const struct descriptor *desc; + uint8_t buf2048; + size_t size, psize; + + rd = (struct avb_packet_aecp_aem_read_descriptor*)p->payload; + + desc_type = ntohs(rd->descriptor_type); + desc_id = ntohs(rd->descriptor_id); + + pw_log_info("descriptor type:%04x index:%d", desc_type, desc_id); + + desc = server_find_descriptor(server, desc_type, desc_id); + if (desc == NULL) + return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len); + + memcpy(buf, m, len); + + psize = sizeof(*rd); + size = sizeof(*h) + sizeof(*reply) + psize; + + memcpy(buf + size, desc->ptr, desc->size); + size += desc->size; + psize += desc->size; + + h = (void*)buf; + reply = SPA_PTROFF(h, sizeof(*h), void); + AVB_PACKET_AECP_SET_MESSAGE_TYPE(&reply->aecp, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); + AVB_PACKET_AECP_SET_STATUS(&reply->aecp, AVB_AECP_AEM_STATUS_SUCCESS); + AVB_PACKET_SET_LENGTH(&reply->aecp.hdr, psize + 12); + + return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, size); +} + +/* GET_AVB_INFO */ +static int handle_get_avb_info(struct aecp *aecp, const void *m, int len) +{ + struct server *server = aecp->server; + const struct avb_ethernet_header *h = m; + const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void); + struct avb_packet_aecp_aem *reply; + struct avb_packet_aecp_aem_get_avb_info *i; + struct avb_aem_desc_avb_interface *avb_interface; + uint16_t desc_type, desc_id; + const struct descriptor *desc; + uint8_t buf2048; + size_t size, psize; + + i = (struct avb_packet_aecp_aem_get_avb_info*)p->payload; + + desc_type = ntohs(i->descriptor_type); + desc_id = ntohs(i->descriptor_id); + + desc = server_find_descriptor(server, desc_type, desc_id); + if (desc == NULL) + return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len); + + if (desc_type != AVB_AEM_DESC_AVB_INTERFACE || desc_id != 0) + return reply_not_implemented(aecp, m, len); + + avb_interface = desc->ptr; + + memcpy(buf, m, len); + + psize = sizeof(*i); + size = sizeof(*h) + sizeof(*reply) + psize; + + h = (void*)buf; + reply = SPA_PTROFF(h, sizeof(*h), void); + AVB_PACKET_AECP_SET_MESSAGE_TYPE(&reply->aecp, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); + AVB_PACKET_AECP_SET_STATUS(&reply->aecp, AVB_AECP_AEM_STATUS_SUCCESS); + AVB_PACKET_SET_LENGTH(&reply->aecp.hdr, psize + 12); + + i = (struct avb_packet_aecp_aem_get_avb_info*)reply->payload; + i->gptp_grandmaster_id = avb_interface->clock_identity; + i->propagation_delay = htonl(0); + i->gptp_domain_number = avb_interface->domain_number; + i->flags = 0; + i->msrp_mappings_count = htons(0); + + return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, size); +} + +/* AEM_COMMAND */ +struct cmd_info { + uint16_t type; + const char *name; + int (*handle) (struct aecp *aecp, const void *p, int len); +}; + +static const struct cmd_info cmd_info = { + { AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, "acquire-entity", handle_acquire_entity, }, + { AVB_AECP_AEM_CMD_LOCK_ENTITY, "lock-entity", handle_lock_entity, }, + { AVB_AECP_AEM_CMD_ENTITY_AVAILABLE, "entity-available", NULL, }, + { AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE, "controller-available", NULL, }, + { AVB_AECP_AEM_CMD_READ_DESCRIPTOR, "read-descriptor", handle_read_descriptor, }, + { AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR, "write-descriptor", NULL, }, + { AVB_AECP_AEM_CMD_SET_CONFIGURATION, "set-configuration", NULL, }, + { AVB_AECP_AEM_CMD_GET_CONFIGURATION, "get-configuration", NULL, }, + { AVB_AECP_AEM_CMD_SET_STREAM_FORMAT, "set-stream-format", NULL, }, + { AVB_AECP_AEM_CMD_GET_STREAM_FORMAT, "get-stream-format", NULL, }, + { AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT, "set-video-format", NULL, }, + { AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT, "get-video-format", NULL, }, + { AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT, "set-sensor-format", NULL, }, + { AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT, "get-sensor-format", NULL, }, + { AVB_AECP_AEM_CMD_SET_STREAM_INFO, "set-stream-info", NULL, }, + { AVB_AECP_AEM_CMD_GET_STREAM_INFO, "get-stream-info", NULL, }, + { AVB_AECP_AEM_CMD_SET_NAME, "set-name", NULL, }, + { AVB_AECP_AEM_CMD_GET_NAME, "get-name", NULL, }, + { AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID, "set-association-id", NULL, }, + { AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID, "get-association-id", NULL, }, + { AVB_AECP_AEM_CMD_SET_SAMPLING_RATE, "set-sampling-rate", NULL, }, + { AVB_AECP_AEM_CMD_GET_SAMPLING_RATE, "get-sampling-rate", NULL, }, + { AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE, "set-clock-source", NULL, }, + { AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE, "get-clock-source", NULL, }, + { AVB_AECP_AEM_CMD_SET_CONTROL, "set-control", NULL, }, + { AVB_AECP_AEM_CMD_GET_CONTROL, "get-control", NULL, }, + { AVB_AECP_AEM_CMD_INCREMENT_CONTROL, "increment-control", NULL, }, + { AVB_AECP_AEM_CMD_DECREMENT_CONTROL, "decrement-control", NULL, }, + { AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR, "set-signal-selector", NULL, }, + { AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR, "get-signal-selector", NULL, }, + { AVB_AECP_AEM_CMD_SET_MIXER, "set-mixer", NULL, }, + { AVB_AECP_AEM_CMD_GET_MIXER, "get-mixer", NULL, }, + { AVB_AECP_AEM_CMD_SET_MATRIX, "set-matrix", NULL, }, + { AVB_AECP_AEM_CMD_GET_MATRIX, "get-matrix", NULL, }, + { AVB_AECP_AEM_CMD_START_STREAMING, "start-streaming", NULL, }, + { AVB_AECP_AEM_CMD_STOP_STREAMING, "stop-streaming", NULL, }, + { AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION, "register-unsolicited-notification", NULL, }, + { AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION, "deregister-unsolicited-notification", NULL, }, + { AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION, "identify-notification", NULL, }, + { AVB_AECP_AEM_CMD_GET_AVB_INFO, "get-avb-info", handle_get_avb_info, }, + { AVB_AECP_AEM_CMD_GET_AS_PATH, "get-as-path", NULL, }, + { AVB_AECP_AEM_CMD_GET_COUNTERS, "get-counters", NULL, }, + { AVB_AECP_AEM_CMD_REBOOT, "reboot", NULL, }, + { AVB_AECP_AEM_CMD_GET_AUDIO_MAP, "get-audio-map", NULL, }, + { AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS, "add-audio-mappings", NULL, }, + { AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS, "remove-audio-mappings", NULL, }, + { AVB_AECP_AEM_CMD_GET_VIDEO_MAP, "get-video-map", NULL, }, + { AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS, "add-video-mappings", NULL, }, + { AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS, "remove-video-mappings", NULL, }, + { AVB_AECP_AEM_CMD_GET_SENSOR_MAP, "get-sensor-map", NULL, } +}; + +static inline const struct cmd_info *find_cmd_info(uint16_t type, const char *name) +{ + uint32_t i; + for (i = 0; i < SPA_N_ELEMENTS(cmd_info); i++) { + if ((name == NULL && type == cmd_infoi.type) || + (name != NULL && spa_streq(name, cmd_infoi.name))) + return &cmd_infoi; + } + return NULL; +} + +int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len) +{ + const struct avb_ethernet_header *h = m; + const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void); + uint16_t cmd_type; + const struct cmd_info *info; + + cmd_type = AVB_PACKET_AEM_GET_COMMAND_TYPE(p); + + info = find_cmd_info(cmd_type, NULL); + if (info == NULL) + return reply_not_implemented(aecp, m, len); + + pw_log_info("aem command %s", info->name); + + if (info->handle == NULL) + return reply_not_implemented(aecp, m, len); + + return info->handle(aecp, m, len); +} + +int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len) +{ + return 0; +}
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/aecp-aem.h
Added
@@ -0,0 +1,345 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_AEM_H +#define AVB_AEM_H + +#include "aecp.h" + +#define AVB_AECP_AEM_STATUS_SUCCESS 0 +#define AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED 1 +#define AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR 2 +#define AVB_AECP_AEM_STATUS_ENTITY_LOCKED 3 +#define AVB_AECP_AEM_STATUS_ENTITY_ACQUIRED 4 +#define AVB_AECP_AEM_STATUS_NOT_AUTHENTICATED 5 +#define AVB_AECP_AEM_STATUS_AUTHENTICATION_DISABLED 6 +#define AVB_AECP_AEM_STATUS_BAD_ARGUMENTS 7 +#define AVB_AECP_AEM_STATUS_NO_RESOURCES 8 +#define AVB_AECP_AEM_STATUS_IN_PROGRESS 9 +#define AVB_AECP_AEM_STATUS_ENTITY_MISBEHAVING 10 +#define AVB_AECP_AEM_STATUS_NOT_SUPPORTED 11 +#define AVB_AECP_AEM_STATUS_STREAM_IS_RUNNING 12 + +#define AVB_AECP_AEM_CMD_ACQUIRE_ENTITY 0x0000 +#define AVB_AECP_AEM_CMD_LOCK_ENTITY 0x0001 +#define AVB_AECP_AEM_CMD_ENTITY_AVAILABLE 0x0002 +#define AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE 0x0003 +#define AVB_AECP_AEM_CMD_READ_DESCRIPTOR 0x0004 +#define AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR 0x0005 +#define AVB_AECP_AEM_CMD_SET_CONFIGURATION 0x0006 +#define AVB_AECP_AEM_CMD_GET_CONFIGURATION 0x0007 +#define AVB_AECP_AEM_CMD_SET_STREAM_FORMAT 0x0008 +#define AVB_AECP_AEM_CMD_GET_STREAM_FORMAT 0x0009 +#define AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT 0x000a +#define AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT 0x000b +#define AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT 0x000c +#define AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT 0x000d +#define AVB_AECP_AEM_CMD_SET_STREAM_INFO 0x000e +#define AVB_AECP_AEM_CMD_GET_STREAM_INFO 0x000f +#define AVB_AECP_AEM_CMD_SET_NAME 0x0010 +#define AVB_AECP_AEM_CMD_GET_NAME 0x0011 +#define AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID 0x0012 +#define AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID 0x0013 +#define AVB_AECP_AEM_CMD_SET_SAMPLING_RATE 0x0014 +#define AVB_AECP_AEM_CMD_GET_SAMPLING_RATE 0x0015 +#define AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE 0x0016 +#define AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE 0x0017 +#define AVB_AECP_AEM_CMD_SET_CONTROL 0x0018 +#define AVB_AECP_AEM_CMD_GET_CONTROL 0x0019 +#define AVB_AECP_AEM_CMD_INCREMENT_CONTROL 0x001a +#define AVB_AECP_AEM_CMD_DECREMENT_CONTROL 0x001b +#define AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR 0x001c +#define AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR 0x001d +#define AVB_AECP_AEM_CMD_SET_MIXER 0x001e +#define AVB_AECP_AEM_CMD_GET_MIXER 0x001f +#define AVB_AECP_AEM_CMD_SET_MATRIX 0x0020 +#define AVB_AECP_AEM_CMD_GET_MATRIX 0x0021 +#define AVB_AECP_AEM_CMD_START_STREAMING 0x0022 +#define AVB_AECP_AEM_CMD_STOP_STREAMING 0x0023 +#define AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION 0x0024 +#define AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION 0x0025 +#define AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION 0x0026 +#define AVB_AECP_AEM_CMD_GET_AVB_INFO 0x0027 +#define AVB_AECP_AEM_CMD_GET_AS_PATH 0x0028 +#define AVB_AECP_AEM_CMD_GET_COUNTERS 0x0029 +#define AVB_AECP_AEM_CMD_REBOOT 0x002a +#define AVB_AECP_AEM_CMD_GET_AUDIO_MAP 0x002b +#define AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS 0x002c +#define AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS 0x002d +#define AVB_AECP_AEM_CMD_GET_VIDEO_MAP 0x002e +#define AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS 0x002f +#define AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS 0x0030 +#define AVB_AECP_AEM_CMD_GET_SENSOR_MAP 0x0031 +#define AVB_AECP_AEM_CMD_ADD_SENSOR_MAPPINGS 0x0032 +#define AVB_AECP_AEM_CMD_REMOVE_SENSOR_MAPPINGS 0x0033 +#define AVB_AECP_AEM_CMD_START_OPERATION 0x0034 +#define AVB_AECP_AEM_CMD_ABORT_OPERATION 0x0035 +#define AVB_AECP_AEM_CMD_OPERATION_STATUS 0x0036 +#define AVB_AECP_AEM_CMD_AUTH_ADD_KEY 0x0037 +#define AVB_AECP_AEM_CMD_AUTH_DELETE_KEY 0x0038 +#define AVB_AECP_AEM_CMD_AUTH_GET_KEY_LIST 0x0039 +#define AVB_AECP_AEM_CMD_AUTH_GET_KEY 0x003a +#define AVB_AECP_AEM_CMD_AUTH_ADD_KEY_TO_CHAIN 0x003b +#define AVB_AECP_AEM_CMD_AUTH_DELETE_KEY_FROM_CHAIN 0x003c +#define AVB_AECP_AEM_CMD_AUTH_GET_KEYCHAIN_LIST 0x003d +#define AVB_AECP_AEM_CMD_AUTH_GET_IDENTITY 0x003e +#define AVB_AECP_AEM_CMD_AUTH_ADD_TOKEN 0x003f +#define AVB_AECP_AEM_CMD_AUTH_DELETE_TOKEN 0x0040 +#define AVB_AECP_AEM_CMD_AUTHENTICATE 0x0041 +#define AVB_AECP_AEM_CMD_DEAUTHENTICATE 0x0042 +#define AVB_AECP_AEM_CMD_ENABLE_TRANSPORT_SECURITY 0x0043 +#define AVB_AECP_AEM_CMD_DISABLE_TRANSPORT_SECURITY 0x0044 +#define AVB_AECP_AEM_CMD_ENABLE_STREAM_ENCRYPTION 0x0045 +#define AVB_AECP_AEM_CMD_DISABLE_STREAM_ENCRYPTION 0x0046 +#define AVB_AECP_AEM_CMD_SET_MEMORY_OBJECT_LENGTH 0x0047 +#define AVB_AECP_AEM_CMD_GET_MEMORY_OBJECT_LENGTH 0x0048 +#define AVB_AECP_AEM_CMD_SET_STREAM_BACKUP 0x0049 +#define AVB_AECP_AEM_CMD_GET_STREAM_BACKUP 0x004a +#define AVB_AECP_AEM_CMD_EXPANSION 0x7fff + +#define AVB_AEM_ACQUIRE_ENTITY_PERSISTENT_FLAG (1<<0) + +struct avb_packet_aecp_aem_acquire { + uint32_t flags; + uint64_t owner_guid; + uint16_t descriptor_type; + uint16_t descriptor_id; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_lock { + uint32_t flags; + uint64_t locked_guid; + uint16_t descriptor_type; + uint16_t descriptor_id; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_read_descriptor { + uint16_t configuration; + uint8_t reserved2; + uint16_t descriptor_type; + uint16_t descriptor_id; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_configuration { + uint16_t reserved; + uint16_t configuration_index; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_stream_format { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint64_t stream_format; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_video_format { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint32_t format_specific; + uint16_t aspect_ratio; + uint16_t color_space; + uint32_t frame_size; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_sensor_format { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint64_t sensor_format; +} __attribute__ ((__packed__)); + + +#define AVB_AEM_STREAM_INFO_FLAG_CLASS_B (1u<<0) +#define AVB_AEM_STREAM_INFO_FLAG_FAST_CONNECT (1u<<1) +#define AVB_AEM_STREAM_INFO_FLAG_SAVED_STATE (1u<<2) +#define AVB_AEM_STREAM_INFO_FLAG_STREAMING_WAIT (1u<<3) +#define AVB_AEM_STREAM_INFO_FLAG_ENCRYPTED_PDU (1u<<4) +#define AVB_AEM_STREAM_INFO_FLAG_STREAM_VLAN_ID_VALID (1u<<25) +#define AVB_AEM_STREAM_INFO_FLAG_CONNECTED (1u<<26) +#define AVB_AEM_STREAM_INFO_FLAG_MSRP_FAILURE_VALID (1u<<27) +#define AVB_AEM_STREAM_INFO_FLAG_STREAM_DEST_MAC_VALID (1u<<28) +#define AVB_AEM_STREAM_INFO_FLAG_MSRP_ACC_LAT_VALID (1u<<29) +#define AVB_AEM_STREAM_INFO_FLAG_STREAM_ID_VALID (1u<<30) +#define AVB_AEM_STREAM_INFO_FLAG_STREAM_FORMAT_VALID (1u<<31) + +struct avb_packet_aecp_aem_setget_stream_info { + uint16_t descriptor_type; + uint16_t descriptor_index; + uint32_t aem_stream_info_flags; + uint64_t stream_format; + uint64_t stream_id; + uint32_t msrp_accumulated_latency; + uint8_t stream_dest_mac6; + uint8_t msrp_failure_code; + uint8_t reserved; + uint64_t msrp_failure_bridge_id; + uint16_t stream_vlan_id; + uint16_t reserved2; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_name { + uint16_t descriptor_type; + uint16_t descriptor_index; + uint16_t name_index; + uint16_t configuration_index; + char name64; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_association_id { + uint16_t descriptor_type; + uint16_t descriptor_index; + uint64_t association_id; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_sampling_rate { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint32_t sampling_rate; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_clock_source { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint16_t clock_source_index; + uint16_t reserved; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_control { + uint16_t descriptor_type; + uint16_t descriptor_id; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_incdec_control { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint16_t index_count; + uint16_t reserved; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_signal_selector { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint16_t signal_type; + uint16_t signal_index; + uint16_t signal_output; + uint16_t reserved; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_mixer { + uint16_t descriptor_type; + uint16_t descriptor_id; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_matrix { + uint16_t descriptor_type; + uint16_t descriptor_index; + uint16_t matrix_column; + uint16_t matrix_row; + uint16_t region_width; + uint16_t region_height; + uint16_t rep_direction_value_count; + uint16_t item_offset; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_startstop_streaming { + uint16_t descriptor_type; + uint16_t descriptor_id; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_identify_notification { + uint16_t descriptor_type; + uint16_t descriptor_id; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_msrp_mapping { + uint8_t traffic_class; + uint8_t priority; + uint16_t vlan_id; +} __attribute__ ((__packed__)); + +#define AVB_AEM_AVB_INFO_FLAG_GPTP_GRANDMASTER_SUPPORTED (1u<<0) +#define AVB_AEM_AVB_INFO_FLAG_GPTP_ENABLED (1u<<1) +#define AVB_AEM_AVB_INFO_FLAG_SRP_ENABLED (1u<<2) + +struct avb_packet_aecp_aem_get_avb_info { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint64_t gptp_grandmaster_id; + uint32_t propagation_delay; + uint8_t gptp_domain_number; + uint8_t flags; + uint16_t msrp_mappings_count; + uint8_t msrp_mappings0; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_get_as_path { + uint16_t descriptor_index; + uint16_t reserved; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_get_counters { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint32_t counters_valid; + uint8_t counters_block0; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_reboot { + uint16_t descriptor_type; + uint16_t descriptor_id; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_start_operation { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint16_t operation_id; + uint16_t operation_type; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_operation_status { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint16_t operation_id; + uint16_t percent_complete; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem { + struct avb_packet_aecp_header aecp; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned u:1; + unsigned cmd1:7; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned cmd1:7; + unsigned u:1; +#endif + uint8_t cmd2; + uint8_t payload0; +} __attribute__ ((__packed__)); + +#define AVB_PACKET_AEM_SET_COMMAND_TYPE(p,v) ((p)->cmd1 = ((v) >> 8),(p)->cmd2 = (v)) + +#define AVB_PACKET_AEM_GET_COMMAND_TYPE(p) ((p)->cmd1 << 8 | (p)->cmd2) + +int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len); +int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len); + +#endif /* AVB_AEM_H */
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/aecp.c
Added
@@ -0,0 +1,169 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <spa/utils/json.h> +#include <spa/debug/mem.h> + +#include <pipewire/pipewire.h> + +#include "aecp.h" +#include "aecp-aem.h" +#include "internal.h" + +static const uint8_t mac6 = AVB_BROADCAST_MAC; + +struct msg_info { + uint16_t type; + const char *name; + int (*handle) (struct aecp *aecp, const void *p, int len); +}; + +static int reply_not_implemented(struct aecp *aecp, const void *p, int len) +{ + struct server *server = aecp->server; + uint8_t buflen; + struct avb_ethernet_header *h = (void*)buf; + struct avb_packet_aecp_header *reply = SPA_PTROFF(h, sizeof(*h), void); + + memcpy(h, p, len); + AVB_PACKET_AECP_SET_STATUS(reply, AVB_AECP_STATUS_NOT_IMPLEMENTED); + + return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, len); +} + +static const struct msg_info msg_info = { + { AVB_AECP_MESSAGE_TYPE_AEM_COMMAND, "aem-command", avb_aecp_aem_handle_command, }, + { AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE, "aem-response", avb_aecp_aem_handle_response, }, + { AVB_AECP_MESSAGE_TYPE_ADDRESS_ACCESS_COMMAND, "address-access-command", NULL, }, + { AVB_AECP_MESSAGE_TYPE_ADDRESS_ACCESS_RESPONSE, "address-access-response", NULL, }, + { AVB_AECP_MESSAGE_TYPE_AVC_COMMAND, "avc-command", NULL, }, + { AVB_AECP_MESSAGE_TYPE_AVC_RESPONSE, "avc-response", NULL, }, + { AVB_AECP_MESSAGE_TYPE_VENDOR_UNIQUE_COMMAND, "vendor-unique-command", NULL, }, + { AVB_AECP_MESSAGE_TYPE_VENDOR_UNIQUE_RESPONSE, "vendor-unique-response", NULL, }, + { AVB_AECP_MESSAGE_TYPE_EXTENDED_COMMAND, "extended-command", NULL, }, + { AVB_AECP_MESSAGE_TYPE_EXTENDED_RESPONSE, "extended-response", NULL, }, +}; + +static inline const struct msg_info *find_msg_info(uint16_t type, const char *name) +{ + uint32_t i; + for (i = 0; i < SPA_N_ELEMENTS(msg_info); i++) { + if ((name == NULL && type == msg_infoi.type) || + (name != NULL && spa_streq(name, msg_infoi.name))) + return &msg_infoi; + } + return NULL; +} + +static int aecp_message(void *data, uint64_t now, const void *message, int len) +{ + struct aecp *aecp = data; + struct server *server = aecp->server; + const struct avb_ethernet_header *h = message; + const struct avb_packet_aecp_header *p = SPA_PTROFF(h, sizeof(*h), void); + const struct msg_info *info; + int message_type; + + if (ntohs(h->type) != AVB_TSN_ETH) + return 0; + if (memcmp(h->dest, mac, 6) != 0 && + memcmp(h->dest, server->mac_addr, 6) != 0) + return 0; + if (AVB_PACKET_GET_SUBTYPE(&p->hdr) != AVB_SUBTYPE_AECP) + return 0; + + message_type = AVB_PACKET_AECP_GET_MESSAGE_TYPE(p); + + info = find_msg_info(message_type, NULL); + if (info == NULL) + return reply_not_implemented(aecp, message, len); + + pw_log_debug("got AECP message %s", info->name); + + if (info->handle == NULL) + return reply_not_implemented(aecp, message, len); + + return info->handle(aecp, message, len); +} + +static void aecp_destroy(void *data) +{ + struct aecp *aecp = data; + spa_hook_remove(&aecp->server_listener); + free(aecp); +} + +static int do_help(struct aecp *aecp, const char *args, FILE *out) +{ + fprintf(out, "{ \"type\": \"help\"," + "\"text\": \"" + "/adp/help: this help \\n" + "\" }"); + return 0; +} + +static int aecp_command(void *data, uint64_t now, const char *command, const char *args, FILE *out) +{ + struct aecp *aecp = data; + int res; + + if (!spa_strstartswith(command, "/aecp/")) + return 0; + + command += strlen("/aecp/"); + + if (spa_streq(command, "help")) + res = do_help(aecp, args, out); + else + res = -ENOTSUP; + + return res; +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = aecp_destroy, + .message = aecp_message, + .command = aecp_command +}; + +struct avb_aecp *avb_aecp_register(struct server *server) +{ + struct aecp *aecp; + + aecp = calloc(1, sizeof(*aecp)); + if (aecp == NULL) + return NULL; + + aecp->server = server; + + avdecc_server_add_listener(server, &aecp->server_listener, &server_events, aecp); + + return (struct avb_aecp*)aecp; +} + +void avb_aecp_unregister(struct avb_aecp *aecp) +{ + aecp_destroy(aecp); +}
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/aecp.h
Added
@@ -0,0 +1,60 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_AECP_H +#define AVB_AECP_H + +#include "packets.h" +#include "internal.h" + +#define AVB_AECP_MESSAGE_TYPE_AEM_COMMAND 0 +#define AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE 1 +#define AVB_AECP_MESSAGE_TYPE_ADDRESS_ACCESS_COMMAND 2 +#define AVB_AECP_MESSAGE_TYPE_ADDRESS_ACCESS_RESPONSE 3 +#define AVB_AECP_MESSAGE_TYPE_AVC_COMMAND 4 +#define AVB_AECP_MESSAGE_TYPE_AVC_RESPONSE 5 +#define AVB_AECP_MESSAGE_TYPE_VENDOR_UNIQUE_COMMAND 6 +#define AVB_AECP_MESSAGE_TYPE_VENDOR_UNIQUE_RESPONSE 7 +#define AVB_AECP_MESSAGE_TYPE_EXTENDED_COMMAND 14 +#define AVB_AECP_MESSAGE_TYPE_EXTENDED_RESPONSE 15 + +#define AVB_AECP_STATUS_SUCCESS 0 +#define AVB_AECP_STATUS_NOT_IMPLEMENTED 1 + +struct avb_packet_aecp_header { + struct avb_packet_header hdr; + uint64_t target_guid; + uint64_t controller_guid; + uint16_t sequence_id; +} __attribute__ ((__packed__)); + +#define AVB_PACKET_AECP_SET_MESSAGE_TYPE(p,v) AVB_PACKET_SET_SUB1(&(p)->hdr, v) +#define AVB_PACKET_AECP_SET_STATUS(p,v) AVB_PACKET_SET_SUB2(&(p)->hdr, v) + +#define AVB_PACKET_AECP_GET_MESSAGE_TYPE(p) AVB_PACKET_GET_SUB1(&(p)->hdr) +#define AVB_PACKET_AECP_GET_STATUS(p) AVB_PACKET_GET_SUB2(&(p)->hdr) + +struct avb_aecp *avb_aecp_register(struct server *server); + +#endif /* AVB_AECP_H */
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/avb.c
Added
@@ -0,0 +1,108 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "internal.h" + +#include <spa/support/cpu.h> + +struct pw_avb *pw_avb_new(struct pw_context *context, + struct pw_properties *props, size_t user_data_size) +{ + struct impl *impl; + const struct spa_support *support; + uint32_t n_support; + struct spa_cpu *cpu; + const char *str; + int res = 0; + + impl = calloc(1, sizeof(*impl) + user_data_size); + if (impl == NULL) + goto error_exit; + + if (props == NULL) + props = pw_properties_new(NULL, NULL); + if (props == NULL) + goto error_free; + + support = pw_context_get_support(context, &n_support); + cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); + + pw_context_conf_update_props(context, "avb.properties", props); + + if ((str = pw_properties_get(props, "vm.overrides")) != NULL) { + if (cpu != NULL && spa_cpu_get_vm_type(cpu) != SPA_CPU_VM_NONE) + pw_properties_update_string(props, str, strlen(str)); + pw_properties_set(props, "vm.overrides", NULL); + } + + impl->context = context; + impl->loop = pw_context_get_main_loop(context); + impl->props = props; + impl->core = pw_context_get_object(context, PW_TYPE_INTERFACE_Core); + if (impl->core == NULL) { + str = pw_properties_get(props, PW_KEY_REMOTE_NAME); + impl->core = pw_context_connect(context, + pw_properties_new( + PW_KEY_REMOTE_NAME, str, + NULL), + 0); + impl->do_disconnect = true; + } + if (impl->core == NULL) { + res = -errno; + pw_log_error("can't connect: %m"); + goto error_free; + } + + impl->work_queue = pw_context_get_work_queue(context); + + spa_list_init(&impl->servers); + + avdecc_server_new(impl, &props->dict); + + return (struct pw_avb*)impl; + +error_free: + free(impl); +error_exit: + pw_properties_free(props); + if (res < 0) + errno = -res; + return NULL; +} + +static void impl_free(struct impl *impl) +{ + struct server *s; + + spa_list_consume(s, &impl->servers, link) + avdecc_server_free(s); + free(impl); +} + +void pw_avb_destroy(struct pw_avb *avb) +{ + struct impl *impl = (struct impl*)avb; + impl_free(impl); +}
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/avb.h
Added
@@ -0,0 +1,44 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef PIPEWIRE_AVB_H +#define PIPEWIRE_AVB_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct pw_context; +struct pw_properties; +struct pw_avb; + +struct pw_avb *pw_avb_new(struct pw_context *context, + struct pw_properties *props, size_t user_data_size); +void pw_avb_destroy(struct pw_avb *avb); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_AVB_H */
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/avdecc.c
Added
@@ -0,0 +1,335 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <linux/if_ether.h> +#include <linux/if_packet.h> +#include <linux/filter.h> +#include <linux/net_tstamp.h> +#include <limits.h> +#include <net/if.h> +#include <arpa/inet.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#include <spa/support/cpu.h> +#include <spa/debug/mem.h> + +#include <pipewire/pipewire.h> + +#include "avb.h" +#include "packets.h" +#include "internal.h" +#include "stream.h" +#include "acmp.h" +#include "adp.h" +#include "aecp.h" +#include "maap.h" +#include "mmrp.h" +#include "msrp.h" +#include "mvrp.h" +#include "descriptors.h" +#include "utils.h" + +#define DEFAULT_INTERVAL 1 + +#define server_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct server_events, m, v, ##__VA_ARGS__) +#define server_emit_destroy(s) server_emit(s, destroy, 0) +#define server_emit_message(s,n,m,l) server_emit(s, message, 0, n, m, l) +#define server_emit_periodic(s,n) server_emit(s, periodic, 0, n) +#define server_emit_command(s,n,c,a,f) server_emit(s, command, 0, n, c, a, f) + +static void on_timer_event(void *data, uint64_t expirations) +{ + struct server *server = data; + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + server_emit_periodic(server, SPA_TIMESPEC_TO_NSEC(&now)); +} + +static void on_socket_data(void *data, int fd, uint32_t mask) +{ + struct server *server = data; + struct timespec now; + + if (mask & SPA_IO_IN) { + int len; + uint8_t buffer2048; + + len = recv(fd, buffer, sizeof(buffer), 0); + + if (len < 0) { + pw_log_warn("got recv error: %m"); + } + else if (len < (int)sizeof(struct avb_packet_header)) { + pw_log_warn("short packet received (%d < %d)", len, + (int)sizeof(struct avb_packet_header)); + } else { + clock_gettime(CLOCK_REALTIME, &now); + server_emit_message(server, SPA_TIMESPEC_TO_NSEC(&now), buffer, len); + } + } +} + +int avb_server_send_packet(struct server *server, const uint8_t dest6, + uint16_t type, void *data, size_t size) +{ + struct avb_ethernet_header *hdr = (struct avb_ethernet_header*)data; + int res = 0; + + memcpy(hdr->dest, dest, ETH_ALEN); + memcpy(hdr->src, server->mac_addr, ETH_ALEN); + hdr->type = htons(type); + + if (send(server->source->fd, data, size, 0) < 0) { + res = -errno; + pw_log_warn("got send error: %m"); + } + return res; +} + +static int load_filter(int fd, uint16_t eth, const uint8_t dest6, const uint8_t mac6) +{ + struct sock_fprog filter; + struct sock_filter bpf_code = { + BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 12), + BPF_JUMP(BPF_JMP|BPF_JEQ, eth, 0, 8), + BPF_STMT(BPF_LD|BPF_W|BPF_ABS, 2), + BPF_JUMP(BPF_JMP|BPF_JEQ, (dest2 << 24) | + (dest3 << 16) | + (dest4 << 8) | + (dest5), 0, 2), + BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 0), + BPF_JUMP(BPF_JMP|BPF_JEQ, (dest0 << 8) | + (dest1), 3, 4), + BPF_JUMP(BPF_JMP|BPF_JEQ, (mac2 << 24) | + (mac3 << 16) | + (mac4 << 8) | + (mac5), 0, 3), + BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 0), + BPF_JUMP(BPF_JMP|BPF_JEQ, (mac0 << 8) | + (mac1), 0, 1), + BPF_STMT(BPF_RET, 0x00040000), + BPF_STMT(BPF_RET, 0x00000000), + }; + filter.len = sizeof(bpf_code) / 8; + filter.filter = bpf_code; + + if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, + &filter, sizeof(filter)) < 0) { + pw_log_error("setsockopt(ATTACH_FILTER) failed: %m"); + return -errno; + } + return 0; +} + +int avb_server_make_socket(struct server *server, uint16_t type, const uint8_t mac6) +{ + int fd, res; + struct ifreq req; + struct packet_mreq mreq; + struct sockaddr_ll sll; + + fd = socket(AF_PACKET, SOCK_RAW|SOCK_NONBLOCK, htons(ETH_P_ALL)); + if (fd < 0) { + pw_log_error("socket() failed: %m"); + return -errno; + } + + spa_zero(req); + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", server->ifname); + if (ioctl(fd, SIOCGIFINDEX, &req) < 0) { + res = -errno; + pw_log_error("SIOCGIFINDEX %s failed: %m", server->ifname); + goto error_close; + } + server->ifindex = req.ifr_ifindex; + + spa_zero(req); + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", server->ifname); + if (ioctl(fd, SIOCGIFHWADDR, &req) < 0) { + res = -errno; + pw_log_error("SIOCGIFHWADDR %s failed: %m", server->ifname); + goto error_close; + } + memcpy(server->mac_addr, req.ifr_hwaddr.sa_data, sizeof(server->mac_addr)); + + server->entity_id = (uint64_t)server->mac_addr0 << 56 | + (uint64_t)server->mac_addr1 << 48 | + (uint64_t)server->mac_addr2 << 40 | + (uint64_t)0xff << 32 | + (uint64_t)0xfe << 24 | + (uint64_t)server->mac_addr3 << 16 | + (uint64_t)server->mac_addr4 << 8 | + (uint64_t)server->mac_addr5; + + spa_zero(sll); + sll.sll_family = AF_PACKET; + sll.sll_protocol = htons(ETH_P_ALL); + sll.sll_ifindex = server->ifindex; + if (bind(fd, (struct sockaddr *) &sll, sizeof(sll)) < 0) { + res = -errno; + pw_log_error("bind() failed: %m"); + goto error_close; + } + + spa_zero(mreq); + mreq.mr_ifindex = server->ifindex; + mreq.mr_type = PACKET_MR_MULTICAST; + mreq.mr_alen = ETH_ALEN; + memcpy(mreq.mr_address, mac, ETH_ALEN); + + if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, + &mreq, sizeof(mreq)) < 0) { + res = -errno; + pw_log_error("setsockopt(ADD_MEMBERSHIP) failed: %m"); + goto error_close; + } + + if ((res = load_filter(fd, type, mac, server->mac_addr)) < 0) + goto error_close; + + return fd; + +error_close: + close(fd); + return res; +} + +static int setup_socket(struct server *server) +{ + struct impl *impl = server->impl; + int fd, res; + static const uint8_t bmac6 = AVB_BROADCAST_MAC; + struct timespec value, interval; + + fd = avb_server_make_socket(server, AVB_TSN_ETH, bmac); + if (fd < 0) + return fd; + + pw_log_info("0x%"PRIx64" %d", server->entity_id, server->ifindex); + + server->source = pw_loop_add_io(impl->loop, fd, SPA_IO_IN, true, on_socket_data, server); + if (server->source == NULL) { + res = -errno; + pw_log_error("server %p: can't create server source: %m", impl); + goto error_no_source; + } + server->timer = pw_loop_add_timer(impl->loop, on_timer_event, server); + if (server->timer == NULL) { + res = -errno; + pw_log_error("server %p: can't create timer source: %m", impl); + goto error_no_timer; + } + value.tv_sec = 0; + value.tv_nsec = 1; + interval.tv_sec = DEFAULT_INTERVAL; + interval.tv_nsec = 0; + pw_loop_update_timer(impl->loop, server->timer, &value, &interval, false); + + return 0; + +error_no_timer: + pw_loop_destroy_source(impl->loop, server->source); + server->source = NULL; +error_no_source: + close(fd); + return res; +} + +struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props) +{ + struct server *server; + int res = 0; + + server = calloc(1, sizeof(*server)); + if (server == NULL) + return NULL; + + server->impl = impl; + spa_list_append(&impl->servers, &server->link); + server->ifname = strdup(spa_dict_lookup(props, "ifname")); + spa_hook_list_init(&server->listener_list); + spa_list_init(&server->descriptors); + spa_list_init(&server->streams); + + server->debug_messages = false; + + if ((res = setup_socket(server)) < 0) + goto error_free; + + init_descriptors(server); + + server->mrp = avb_mrp_new(server); + if (server->mrp == NULL) + goto error_free; + + avb_aecp_register(server); + server->maap = avb_maap_register(server); + server->mmrp = avb_mmrp_register(server); + server->msrp = avb_msrp_register(server); + server->mvrp = avb_mvrp_register(server); + avb_adp_register(server); + avb_acmp_register(server); + + server->domain_attr = avb_msrp_attribute_new(server->msrp, + AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN); + server->domain_attr->attr.domain.sr_class_id = AVB_MSRP_CLASS_ID_DEFAULT; + server->domain_attr->attr.domain.sr_class_priority = AVB_MSRP_PRIORITY_DEFAULT; + server->domain_attr->attr.domain.sr_class_vid = htons(AVB_DEFAULT_VLAN); + + avb_mrp_attribute_begin(server->domain_attr->mrp, 0); + avb_mrp_attribute_join(server->domain_attr->mrp, 0, true); + + server_create_stream(server, SPA_DIRECTION_INPUT, 0); + server_create_stream(server, SPA_DIRECTION_OUTPUT, 0); + + avb_maap_reserve(server->maap, 1); + + return server; + +error_free: + free(server); + if (res < 0) + errno = -res; + return NULL; +} + +void avdecc_server_add_listener(struct server *server, struct spa_hook *listener, + const struct server_events *events, void *data) +{ + spa_hook_list_append(&server->listener_list, listener, events, data); +} + +void avdecc_server_free(struct server *server) +{ + struct impl *impl = server->impl; + + spa_list_remove(&server->link); + if (server->source) + pw_loop_destroy_source(impl->loop, server->source); + if (server->timer) + pw_loop_destroy_source(impl->loop, server->source); + spa_hook_list_clean(&server->listener_list); + free(server); +}
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/descriptors.h
Added
@@ -0,0 +1,274 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "aecp-aem.h" +#include "aecp-aem-descriptors.h" +#include "internal.h" + +void init_descriptors(struct server *server) +{ + server_add_descriptor(server, AVB_AEM_DESC_STRINGS, 0, + sizeof(struct avb_aem_desc_strings), + &(struct avb_aem_desc_strings) + { + .string_0 = "PipeWire", + .string_1 = "Configuration 1", + .string_2 = "Wim Taymans", + }); + server_add_descriptor(server, AVB_AEM_DESC_LOCALE, 0, + sizeof(struct avb_aem_desc_locale), + &(struct avb_aem_desc_locale) + { + .locale_identifier = "en-EN", + .number_of_strings = htons(1), + .base_strings = htons(0) + }); + server_add_descriptor(server, AVB_AEM_DESC_ENTITY, 0, + sizeof(struct avb_aem_desc_entity), + &(struct avb_aem_desc_entity) + { + .entity_id = htobe64(server->entity_id), + .entity_model_id = htobe64(0), + .entity_capabilities = htonl( + AVB_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED | + AVB_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED | + AVB_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED | + AVB_ADP_ENTITY_CAPABILITY_AEM_IDENTIFY_CONTROL_INDEX_VALID | + AVB_ADP_ENTITY_CAPABILITY_AEM_INTERFACE_INDEX_VALID), + + .talker_stream_sources = htons(8), + .talker_capabilities = htons( + AVB_ADP_TALKER_CAPABILITY_IMPLEMENTED | + AVB_ADP_TALKER_CAPABILITY_AUDIO_SOURCE), + .listener_stream_sinks = htons(8), + .listener_capabilities = htons( + AVB_ADP_LISTENER_CAPABILITY_IMPLEMENTED | + AVB_ADP_LISTENER_CAPABILITY_AUDIO_SINK), + .controller_capabilities = htons(0), + .available_index = htonl(0), + .association_id = htobe64(0), + .entity_name = "PipeWire", + .vendor_name_string = htons(2), + .model_name_string = htons(0), + .firmware_version = "0.3.48", + .group_name = "", + .serial_number = "", + .configurations_count = htons(1), + .current_configuration = htons(0) + }); + struct { + struct avb_aem_desc_configuration desc; + struct avb_aem_desc_descriptor_count descriptor_counts8; + } __attribute__ ((__packed__)) config = + { + { + .object_name = "Configuration 1", + .localized_description = htons(1), + .descriptor_counts_count = htons(8), + .descriptor_counts_offset = htons( + 4 + sizeof(struct avb_aem_desc_configuration)), + }, + .descriptor_counts = { + { htons(AVB_AEM_DESC_AUDIO_UNIT), htons(1) }, + { htons(AVB_AEM_DESC_STREAM_INPUT), htons(1) }, + { htons(AVB_AEM_DESC_STREAM_OUTPUT), htons(1) }, + { htons(AVB_AEM_DESC_AVB_INTERFACE), htons(1) }, + { htons(AVB_AEM_DESC_CLOCK_SOURCE), htons(1) }, + { htons(AVB_AEM_DESC_CONTROL), htons(2) }, + { htons(AVB_AEM_DESC_LOCALE), htons(1) }, + { htons(AVB_AEM_DESC_CLOCK_DOMAIN), htons(1) } + } + }; + server_add_descriptor(server, AVB_AEM_DESC_CONFIGURATION, 0, + sizeof(config), &config); + + struct { + struct avb_aem_desc_audio_unit desc; + struct avb_aem_desc_sampling_rate sampling_rates6; + } __attribute__ ((__packed__)) audio_unit = + { + { + .object_name = "PipeWire", + .localized_description = htons(0), + .clock_domain_index = htons(0), + .number_of_stream_input_ports = htons(1), + .base_stream_input_port = htons(0), + .number_of_stream_output_ports = htons(1), + .base_stream_output_port = htons(0), + .number_of_external_input_ports = htons(8), + .base_external_input_port = htons(0), + .number_of_external_output_ports = htons(8), + .base_external_output_port = htons(0), + .number_of_internal_input_ports = htons(0), + .base_internal_input_port = htons(0), + .number_of_internal_output_ports = htons(0), + .base_internal_output_port = htons(0), + .number_of_controls = htons(0), + .base_control = htons(0), + .number_of_signal_selectors = htons(0), + .base_signal_selector = htons(0), + .number_of_mixers = htons(0), + .base_mixer = htons(0), + .number_of_matrices = htons(0), + .base_matrix = htons(0), + .number_of_splitters = htons(0), + .base_splitter = htons(0), + .number_of_combiners = htons(0), + .base_combiner = htons(0), + .number_of_demultiplexers = htons(0), + .base_demultiplexer = htons(0), + .number_of_multiplexers = htons(0), + .base_multiplexer = htons(0), + .number_of_transcoders = htons(0), + .base_transcoder = htons(0), + .number_of_control_blocks = htons(0), + .base_control_block = htons(0), + .current_sampling_rate = htonl(48000), + .sampling_rates_offset = htons( + 4 + sizeof(struct avb_aem_desc_audio_unit)), + .sampling_rates_count = htons(6), + }, + .sampling_rates = { + { .pull_frequency = htonl(44100) }, + { .pull_frequency = htonl(48000) }, + { .pull_frequency = htonl(88200) }, + { .pull_frequency = htonl(96000) }, + { .pull_frequency = htonl(176400) }, + { .pull_frequency = htonl(192000) }, + } + }; + server_add_descriptor(server, AVB_AEM_DESC_AUDIO_UNIT, 0, + sizeof(audio_unit), &audio_unit); + + struct { + struct avb_aem_desc_stream desc; + uint64_t stream_formats6; + } __attribute__ ((__packed__)) stream_input_0 = + { + { + .object_name = "Stream Input 1", + .localized_description = htons(0xffff), + .clock_domain_index = htons(0), + .stream_flags = htons( + AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE | + AVB_AEM_DESC_STREAM_FLAG_CLASS_A), + .current_format = htobe64(0x00a0020840000800ULL), + .formats_offset = htons( + 4 + sizeof(struct avb_aem_desc_stream)), + .number_of_formats = htons(6), + .backup_talker_entity_id_0 = htobe64(0), + .backup_talker_unique_id_0 = htons(0), + .backup_talker_entity_id_1 = htobe64(0), + .backup_talker_unique_id_1 = htons(0), + .backup_talker_entity_id_2 = htobe64(0), + .backup_talker_unique_id_2 = htons(0), + .backedup_talker_entity_id = htobe64(0), + .backedup_talker_unique = htons(0), + .avb_interface_index = htons(0), + .buffer_length = htons(8) + }, + .stream_formats = { + htobe64(0x00a0010860000800ULL), + htobe64(0x00a0020860000800ULL), + htobe64(0x00a0030860000800ULL), + htobe64(0x00a0040860000800ULL), + htobe64(0x00a0050860000800ULL), + htobe64(0x00a0060860000800ULL), + }, + }; + server_add_descriptor(server, AVB_AEM_DESC_STREAM_INPUT, 0, + sizeof(stream_input_0), &stream_input_0); + + struct { + struct avb_aem_desc_stream desc; + uint64_t stream_formats6; + } __attribute__ ((__packed__)) stream_output_0 = + { + { + .object_name = "Stream Output 1", + .localized_description = htons(0xffff), + .clock_domain_index = htons(0), + .stream_flags = htons( + AVB_AEM_DESC_STREAM_FLAG_CLASS_A), + .current_format = htobe64(0x00a0020840000800ULL), + .formats_offset = htons( + 4 + sizeof(struct avb_aem_desc_stream)), + .number_of_formats = htons(6), + .backup_talker_entity_id_0 = htobe64(0), + .backup_talker_unique_id_0 = htons(0), + .backup_talker_entity_id_1 = htobe64(0), + .backup_talker_unique_id_1 = htons(0), + .backup_talker_entity_id_2 = htobe64(0), + .backup_talker_unique_id_2 = htons(0), + .backedup_talker_entity_id = htobe64(0), + .backedup_talker_unique = htons(0), + .avb_interface_index = htons(0), + .buffer_length = htons(8) + }, + .stream_formats = { + htobe64(0x00a0010860000800ULL), + htobe64(0x00a0020860000800ULL), + htobe64(0x00a0030860000800ULL), + htobe64(0x00a0040860000800ULL), + htobe64(0x00a0050860000800ULL), + htobe64(0x00a0060860000800ULL), + }, + }; + server_add_descriptor(server, AVB_AEM_DESC_STREAM_OUTPUT, 0, + sizeof(stream_output_0), &stream_output_0); + + struct avb_aem_desc_avb_interface avb_interface = { + .localized_description = htons(0xffff), + .interface_flags = htons( + AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_GRANDMASTER_SUPPORTED), + .clock_identity = htobe64(0), + .priority1 = 0, + .clock_class = 0, + .offset_scaled_log_variance = htons(0), + .clock_accuracy = 0, + .priority2 = 0, + .domain_number = 0, + .log_sync_interval = 0, + .log_announce_interval = 0, + .log_pdelay_interval = 0, + .port_number = 0, + }; + strncpy(avb_interface.object_name, server->ifname, 63); + memcpy(avb_interface.mac_address, server->mac_addr, 6); + server_add_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0, + sizeof(avb_interface), &avb_interface); + + struct avb_aem_desc_clock_source clock_source = { + .object_name = "Stream Clock", + .localized_description = htons(0xffff), + .clock_source_flags = htons(0), + .clock_source_type = htons( + AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INPUT_STREAM), + .clock_source_identifier = htobe64(0), + .clock_source_location_type = htons(AVB_AEM_DESC_STREAM_INPUT), + .clock_source_location_index = htons(0), + }; + server_add_descriptor(server, AVB_AEM_DESC_CLOCK_SOURCE, 0, + sizeof(clock_source), &clock_source); +}
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/iec61883.h
Added
@@ -0,0 +1,110 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_IEC61883_H +#define AVB_IEC61883_H + +#include "packets.h" + +struct avb_packet_iec61883 { + uint8_t subtype; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned sv:1; + unsigned version:3; + unsigned mr:1; + unsigned _r1:1; + unsigned gv:1; + unsigned tv:1; + + uint8_t seq_number; + + unsigned _r2:7; + unsigned tu:1; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned tv:1; + unsigned gv:1; + unsigned _r1:1; + unsigned mr:1; + unsigned version:3; + unsigned sv:1; + + uint8_t seq_num; + + unsigned tu:1; + unsigned _r2:7; +#endif + uint64_t stream_id; + uint32_t timestamp; + uint32_t gateway_info; + uint16_t data_len; +#if __BYTE_ORDER == __BIG_ENDIAN + uint8_t tag:2; + uint8_t channel:6; + + uint8_t tcode:4; + uint8_t app:4; + + uint8_t qi1:2; /* CIP Quadlet Indicator 1 */ + uint8_t sid:6; /* CIP Source ID */ + + uint8_t dbs; /* CIP Data Block Size */ + + uint8_t fn:2; /* CIP Fraction Number */ + uint8_t qpc:3; /* CIP Quadlet Padding Count */ + uint8_t sph:1; /* CIP Source Packet Header */ + uint8_t _r3:2; + + uint8_t dbc; /* CIP Data Block Continuity */ + + uint8_t qi2:2; /* CIP Quadlet Indicator 2 */ + uint8_t format_id:6; /* CIP Format ID */ +#elif __BYTE_ORDER == __LITTLE_ENDIAN + uint8_t channel:6; + uint8_t tag:2; + + uint8_t app:4; + uint8_t tcode:4; + + uint8_t sid:6; /* CIP Source ID */ + uint8_t qi1:2; /* CIP Quadlet Indicator 1 */ + + uint8_t dbs; /* CIP Data Block Size */ + + uint8_t _r3:2; + uint8_t sph:1; /* CIP Source Packet Header */ + uint8_t qpc:3; /* CIP Quadlet Padding Count */ + uint8_t fn:2; /* CIP Fraction Number */ + + uint8_t dbc; /* CIP Data Block Continuity */ + + uint8_t format_id:6; /* CIP Format ID */ + uint8_t qi2:2; /* CIP Quadlet Indicator 2 */ +#endif + uint8_t fdf; /* CIP Format Dependent Field */ + uint16_t syt; + + uint8_t payload0; +} __attribute__ ((__packed__)); + +#endif /* AVB_IEC61883_H */
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/internal.h
Added
@@ -0,0 +1,167 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_INTERNAL_H +#define AVB_INTERNAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <pipewire/pipewire.h> + +struct server; +struct avb_mrp; + +#define AVB_TSN_ETH 0x22f0 +#define AVB_BROADCAST_MAC { 0x91, 0xe0, 0xf0, 0x01, 0x00, 0x00 }; + +struct impl { + struct pw_loop *loop; + struct pw_context *context; + struct spa_hook context_listener; + struct pw_core *core; + unsigned do_disconnect:1; + + struct pw_properties *props; + struct pw_work_queue *work_queue; + + struct spa_list servers; +}; + +struct server_events { +#define AVB_VERSION_SERVER_EVENTS 0 + uint32_t version; + + /** the server is destroyed */ + void (*destroy) (void *data); + + int (*message) (void *data, uint64_t now, const void *message, int len); + + void (*periodic) (void *data, uint64_t now); + + int (*command) (void *data, uint64_t now, const char *command, const char *args, FILE *out); +}; + +struct descriptor { + struct spa_list link; + uint16_t type; + uint16_t index; + uint32_t size; + void *ptr; +}; + +struct server { + struct spa_list link; + struct impl *impl; + + char *ifname; + uint8_t mac_addr6; + uint64_t entity_id; + int ifindex; + + struct spa_source *source; + struct spa_source *timer; + + struct spa_hook_list listener_list; + + struct spa_list descriptors; + struct spa_list streams; + + unsigned debug_messages:1; + + struct avb_mrp *mrp; + struct avb_mmrp *mmrp; + struct avb_mvrp *mvrp; + struct avb_msrp *msrp; + struct avb_maap *maap; + + struct avb_msrp_attribute *domain_attr; +}; + +#include "stream.h" + +static inline const struct descriptor *server_find_descriptor(struct server *server, + uint16_t type, uint16_t index) +{ + struct descriptor *d; + spa_list_for_each(d, &server->descriptors, link) { + if (d->type == type && + d->index == index) + return d; + } + return NULL; +} +static inline void *server_add_descriptor(struct server *server, + uint16_t type, uint16_t index, size_t size, void *ptr) +{ + struct descriptor *d; + + if ((d = calloc(1, sizeof(struct descriptor) + size)) == NULL) + return NULL; + + d->type = type; + d->index = index; + d->size = size; + d->ptr = SPA_PTROFF(d, sizeof(struct descriptor), void); + if (ptr) + memcpy(d->ptr, ptr, size); + spa_list_append(&server->descriptors, &d->link); + return d->ptr; +} + +static inline struct stream *server_find_stream(struct server *server, + enum spa_direction direction, uint16_t index) +{ + struct stream *s; + spa_list_for_each(s, &server->streams, link) { + if (s->direction == direction && + s->index == index) + return s; + } + return NULL; +} + +struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props); +void avdecc_server_free(struct server *server); + +void avdecc_server_add_listener(struct server *server, struct spa_hook *listener, + const struct server_events *events, void *data); + +int avb_server_make_socket(struct server *server, uint16_t type, const uint8_t mac6); + +int avb_server_send_packet(struct server *server, const uint8_t dest6, + uint16_t type, void *data, size_t size); + +struct aecp { + struct server *server; + struct spa_hook server_listener; +}; + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AVB_INTERNAL_H */
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/maap.c
Added
@@ -0,0 +1,467 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <unistd.h> + +#include <spa/utils/json.h> + +#include <pipewire/pipewire.h> +#include <pipewire/conf.h> + +#include "utils.h" +#include "maap.h" + +#define MAAP_ALLOCATION_POOL_SIZE 0xFE00 +#define MAAP_ALLOCATION_POOL_BASE { 0x91, 0xe0, 0xf0, 0x00, 0x00, 0x00 } +static uint8_t maap_base6 = MAAP_ALLOCATION_POOL_BASE; + +#define MAAP_PROBE_RETRANSMITS 3 + +#define MAAP_PROBE_INTERVAL_MS 500 +#define MAAP_PROBE_INTERVAL_VAR_MS 100 + +#define MAAP_ANNOUNCE_INTERVAL_MS 3000 +#define MAAP_ANNOUNCE_INTERVAL_VAR_MS 2000 + +struct maap { + struct server *server; + struct spa_hook server_listener; + + struct pw_properties *props; + + struct spa_source *source; + +#define STATE_IDLE 0 +#define STATE_PROBE 1 +#define STATE_ANNOUNCE 2 + uint32_t state; + uint64_t timeout; + uint32_t probe_count; + + unsigned short xsubi3; + + uint16_t offset; + uint16_t count; +}; + +static const char *message_type_as_string(uint8_t message_type) +{ + switch (message_type) { + case AVB_MAAP_MESSAGE_TYPE_PROBE: + return "PROBE"; + case AVB_MAAP_MESSAGE_TYPE_DEFEND: + return "DEFEND"; + case AVB_MAAP_MESSAGE_TYPE_ANNOUNCE: + return "ANNOUNCE"; + } + return "INVALID"; +} + +static void maap_message_debug(struct maap *maap, const struct avb_packet_maap *p) +{ + uint32_t v; + const uint8_t *addr; + + v = AVB_PACKET_MAAP_GET_MESSAGE_TYPE(p); + pw_log_info("message-type: %d (%s)", v, message_type_as_string(v)); + pw_log_info(" maap-version: %d", AVB_PACKET_MAAP_GET_MAAP_VERSION(p)); + pw_log_info(" length: %d", AVB_PACKET_GET_LENGTH(&p->hdr)); + + pw_log_info(" stream-id: 0x%"PRIx64, AVB_PACKET_MAAP_GET_STREAM_ID(p)); + addr = AVB_PACKET_MAAP_GET_REQUEST_START(p); + pw_log_info(" request-start: %02x:%02x:%02x:%02x:%02x:%02x", + addr0, addr1, addr2, addr3, addr4, addr5); + pw_log_info(" request-count: %d", AVB_PACKET_MAAP_GET_REQUEST_COUNT(p)); + addr = AVB_PACKET_MAAP_GET_CONFLICT_START(p); + pw_log_info(" conflict-start: %02x:%02x:%02x:%02x:%02x:%02x", + addr0, addr1, addr2, addr3, addr4, addr5); + pw_log_info(" conflict-count: %d", AVB_PACKET_MAAP_GET_CONFLICT_COUNT(p)); +} + +#define PROBE_TIMEOUT(n) ((n) + (MAAP_PROBE_INTERVAL_MS + \ + drand48() * MAAP_PROBE_INTERVAL_VAR_MS) * SPA_NSEC_PER_MSEC) +#define ANNOUNCE_TIMEOUT(n) ((n) + (MAAP_ANNOUNCE_INTERVAL_MS + \ + drand48() * MAAP_ANNOUNCE_INTERVAL_VAR_MS) * SPA_NSEC_PER_MSEC) + +static int make_new_address(struct maap *maap, uint64_t now, int range) +{ + maap->offset = nrand48(maap->xsubi) % (MAAP_ALLOCATION_POOL_SIZE - range); + maap->count = range; + maap->state = STATE_PROBE; + maap->probe_count = MAAP_PROBE_RETRANSMITS; + maap->timeout = PROBE_TIMEOUT(now); + return 0; +} + +static uint16_t maap_check_conflict(struct maap *maap, const uint8_t request_start6, + uint16_t request_count, uint8_t conflict_start6) +{ + uint16_t our_start, our_end; + uint16_t req_start, req_end; + uint16_t conf_start, conf_count = 0; + + if (memcmp(request_start, maap_base, 4) != 0) + return 0; + + our_start = maap->offset; + our_end = our_start + maap->count; + req_start = request_start4 << 8 | request_start5; + req_end = req_start + request_count; + + if (our_start >= req_start && our_start <= req_end) { + conf_start = our_start; + conf_count = SPA_MIN(our_end, req_end) - our_start; + } + else if (req_start >= our_start && req_start <= our_end) { + conf_start = req_start; + conf_count = SPA_MIN(req_end, our_end) - req_start; + } + if (conf_count == 0) + return 0; + + conflict_start4 = conf_start >> 8; + conflict_start5 = conf_start; + return conf_count; +} + +static int send_packet(struct maap *maap, uint64_t now, + uint8_t type, const uint8_t conflict_start6, uint16_t conflict_count) +{ + struct avb_ethernet_header *h; + struct avb_packet_maap *p; + uint8_t buf1024; + uint8_t bmac6 = AVB_MAAP_MAC; + int res = 0; + uint8_t start6; + + spa_memzero(buf, sizeof(buf)); + h = (void*)buf; + p = SPA_PTROFF(h, sizeof(*h), void); + + memcpy(h->dest, bmac, 6); + memcpy(h->src, maap->server->mac_addr, 6); + h->type = htons(AVB_TSN_ETH); + + p->hdr.subtype = AVB_SUBTYPE_MAAP; + AVB_PACKET_SET_LENGTH(&p->hdr, sizeof(*p)); + + AVB_PACKET_MAAP_SET_MAAP_VERSION(p, 1); + AVB_PACKET_MAAP_SET_MESSAGE_TYPE(p, type); + + memcpy(start, maap_base, 4); + start4 = maap->offset >> 8; + start5 = maap->offset; + AVB_PACKET_MAAP_SET_REQUEST_START(p, start); + AVB_PACKET_MAAP_SET_REQUEST_COUNT(p, maap->count); + if (conflict_count) { + AVB_PACKET_MAAP_SET_CONFLICT_START(p, conflict_start); + AVB_PACKET_MAAP_SET_CONFLICT_COUNT(p, conflict_count); + } + + if (maap->server->debug_messages) { + pw_log_info("send: %d (%s)", type, message_type_as_string(type)); + maap_message_debug(maap, p); + } + + if (send(maap->source->fd, p, sizeof(*h) + sizeof(*p), 0) < 0) { + res = -errno; + pw_log_warn("got send error: %m"); + } + return res; +} + +static int handle_probe(struct maap *maap, uint64_t now, const struct avb_packet_maap *p) +{ + uint8_t conflict_start6; + uint16_t conflict_count; + + conflict_count = maap_check_conflict(maap, p->request_start, ntohs(p->request_count), + conflict_start); + if (conflict_count == 0) + return 0; + + switch (maap->state) { + case STATE_PROBE: + make_new_address(maap, now, 8); + break; + case STATE_ANNOUNCE: + send_packet(maap, now, AVB_MAAP_MESSAGE_TYPE_DEFEND, conflict_start, conflict_count); + break; + } + return 0; +} + +static int handle_defend(struct maap *maap, uint64_t now, const struct avb_packet_maap *p) +{ + uint8_t conflict_start6; + uint16_t conflict_count; + + conflict_count = maap_check_conflict(maap, p->conflict_start, ntohs(p->conflict_count), + conflict_start); + if (conflict_count != 0) + make_new_address(maap, now, 8); + return 0; +} + +static int maap_message(struct maap *maap, uint64_t now, const void *message, int len) +{ + const struct avb_packet_maap *p = message; + + if (AVB_PACKET_GET_SUBTYPE(&p->hdr) != AVB_SUBTYPE_MAAP) + return 0; + + if (maap->server->debug_messages) + maap_message_debug(maap, p); + + switch (AVB_PACKET_MAAP_GET_MESSAGE_TYPE(p)) { + case AVB_MAAP_MESSAGE_TYPE_PROBE: + handle_probe(maap, now, p); + break; + case AVB_MAAP_MESSAGE_TYPE_DEFEND: + case AVB_MAAP_MESSAGE_TYPE_ANNOUNCE: + handle_defend(maap, now, p); + break; + } + return 0; +} + +static void on_socket_data(void *data, int fd, uint32_t mask) +{ + struct maap *maap = data; + struct timespec now; + + if (mask & SPA_IO_IN) { + int len; + uint8_t buffer2048; + + len = recv(fd, buffer, sizeof(buffer), 0); + + if (len < 0) { + pw_log_warn("got recv error: %m"); + } + else if (len < (int)sizeof(struct avb_packet_header)) { + pw_log_warn("short packet received (%d < %d)", len, + (int)sizeof(struct avb_packet_header)); + } else { + clock_gettime(CLOCK_REALTIME, &now); + maap_message(maap, SPA_TIMESPEC_TO_NSEC(&now), buffer, len); + } + } +} + +static int load_state(struct maap *maap) +{ + const char *str; + char key512; + struct spa_json it3; + bool have_offset = false; + int count = 0, offset = 0; + + snprintf(key, sizeof(key), "maap.%s", maap->server->ifname); + pw_conf_load_state("module-avb", key, maap->props); + + if ((str = pw_properties_get(maap->props, "maap.addresses")) == NULL) + return 0; + + spa_json_init(&it0, str, strlen(str)); + if (spa_json_enter_array(&it0, &it1) <= 0) + return 0; + + if (spa_json_enter_object(&it1, &it2) <= 0) + return 0; + + while (spa_json_get_string(&it2, key, sizeof(key)) > 0) { + const char *val; + int len; + + if ((len = spa_json_next(&it2, &val)) <= 0) + break; + + if (spa_streq(key, "start")) { + uint8_t addr6; + if (avb_utils_parse_addr(val, len, addr) >= 0 && + memcmp(addr, maap_base, 4) == 0) { + offset = addr4 << 8 | addr5; + have_offset = true; + } + } + else if (spa_streq(key, "count")) { + spa_json_parse_int(val, len, &count); + } + } + if (count > 0 && have_offset) { + maap->count = count; + maap->offset = offset; + maap->state = STATE_PROBE; + maap->probe_count = MAAP_PROBE_RETRANSMITS; + maap->timeout = PROBE_TIMEOUT(0); + } + return 0; +} + +static int save_state(struct maap *maap) +{ + char *ptr; + size_t size; + FILE *f; + char key512; + uint32_t count; + + if ((f = open_memstream(&ptr, &size)) == NULL) + return -errno; + + fprintf(f, " "); + fprintf(f, "{ \"start\": \"%02x:%02x:%02x:%02x:%02x:%02x\", ", + maap_base0, maap_base1, maap_base2, + maap_base3, (maap->offset >> 8) & 0xff, + maap->offset & 0xff); + fprintf(f, " \"count\": %u } ", maap->count); + fprintf(f, ""); + fclose(f); + + count = pw_properties_set(maap->props, "maap.addresses", ptr); + free(ptr); + + if (count > 0) { + snprintf(key, sizeof(key), "maap.%s", maap->server->ifname); + pw_conf_save_state("module-avb", key, maap->props); + } + return 0; +} + +static void maap_periodic(void *data, uint64_t now) +{ + struct maap *maap = data; + + if (now < maap->timeout) + return; + + switch(maap->state) { + case STATE_IDLE: + break; + case STATE_PROBE: + send_packet(maap, now, AVB_MAAP_MESSAGE_TYPE_PROBE, NULL, 0); + if (--maap->probe_count == 0) { + maap->state = STATE_ANNOUNCE; + save_state(maap); + } + maap->timeout = PROBE_TIMEOUT(now); + break; + case STATE_ANNOUNCE: + send_packet(maap, now, AVB_MAAP_MESSAGE_TYPE_ANNOUNCE, NULL, 0); + maap->timeout = ANNOUNCE_TIMEOUT(now); + break; + } +} + +static void maap_free(struct maap *maap) +{ + pw_loop_destroy_source(maap->server->impl->loop, maap->source); + spa_hook_remove(&maap->server_listener); + pw_properties_free(maap->props); + free(maap); +} + +static void maap_destroy(void *data) +{ + struct maap *maap = data; + maap_free(maap); +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = maap_destroy, + .periodic = maap_periodic, +}; + +struct avb_maap *avb_maap_register(struct server *server) +{ + struct maap *maap; + uint8_t bmac6 = AVB_MAAP_MAC; + int fd, res; + + fd = avb_server_make_socket(server, AVB_TSN_ETH, bmac); + if (fd < 0) { + res = fd; + goto error; + } + + maap = calloc(1, sizeof(*maap)); + if (maap == NULL) { + res = -errno; + goto error_close; + } + maap->props = pw_properties_new(NULL, NULL); + if (maap->props == NULL) { + res = -errno; + goto error_free; + } + + maap->server = server; + pw_log_info("0x%"PRIx64" %d", server->entity_id, server->ifindex); + + pw_getrandom(maap->xsubi, sizeof(maap->xsubi), 0); + load_state(maap); + + maap->source = pw_loop_add_io(server->impl->loop, fd, SPA_IO_IN, true, on_socket_data, maap); + if (maap->source == NULL) { + res = -errno; + pw_log_error("maap %p: can't create maap source: %m", maap); + goto error_free; + } + avdecc_server_add_listener(server, &maap->server_listener, &server_events, maap); + + return (struct avb_maap *)maap; + +error_free: + free(maap); +error_close: + close(fd); +error: + errno = -res; + return NULL; +} + +int avb_maap_reserve(struct avb_maap *m, uint32_t count) +{ + struct maap *maap = (struct maap*)m; + if (count > maap->count) + make_new_address(maap, 0, count); + return 0; +} + +int avb_maap_get_address(struct avb_maap *m, uint8_t addr6, uint32_t index) +{ + struct maap *maap = (struct maap*)m; + uint16_t offset; + + if (maap->state != STATE_ANNOUNCE) + return -EAGAIN; + + memcpy(addr, maap_base, 6); + offset = maap->offset + index; + addr4 = offset >> 8; + addr5 = offset; + return 0; +}
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/maap.h
Added
@@ -0,0 +1,70 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_MAAP_H +#define AVB_MAAP_H + +#include "packets.h" +#include "internal.h" + +#define AVB_TSN_ETH 0x22f0 +#define AVB_MAAP_MAC { 0x91, 0xe0, 0xf0, 0x00, 0xff, 0x00 }; + +#define AVB_MAAP_MESSAGE_TYPE_PROBE 1 +#define AVB_MAAP_MESSAGE_TYPE_DEFEND 2 +#define AVB_MAAP_MESSAGE_TYPE_ANNOUNCE 3 + +struct avb_packet_maap { + struct avb_packet_header hdr; + uint64_t stream_id; + uint8_t request_start6; + uint16_t request_count; + uint8_t conflict_start6; + uint16_t conflict_count; +} __attribute__ ((__packed__)); + +#define AVB_PACKET_MAAP_SET_MESSAGE_TYPE(p,v) AVB_PACKET_SET_SUB1(&(p)->hdr, v) +#define AVB_PACKET_MAAP_SET_MAAP_VERSION(p,v) AVB_PACKET_SET_SUB2(&(p)->hdr, v) +#define AVB_PACKET_MAAP_SET_STREAM_ID(p,v) ((p)->stream_id = htobe64(v)) +#define AVB_PACKET_MAAP_SET_REQUEST_START(p,v) memcpy((p)->request_start, (v), 6) +#define AVB_PACKET_MAAP_SET_REQUEST_COUNT(p,v) ((p)->request_count = htons(v)) +#define AVB_PACKET_MAAP_SET_CONFLICT_START(p,v) memcpy((p)->conflict_start, (v), 6) +#define AVB_PACKET_MAAP_SET_CONFLICT_COUNT(p,v) ((p)->conflict_count = htons(v)) + +#define AVB_PACKET_MAAP_GET_MESSAGE_TYPE(p) AVB_PACKET_GET_SUB1(&(p)->hdr) +#define AVB_PACKET_MAAP_GET_MAAP_VERSION(p) AVB_PACKET_GET_SUB2(&(p)->hdr) +#define AVB_PACKET_MAAP_GET_STREAM_ID(p) be64toh((p)->stream_id) +#define AVB_PACKET_MAAP_GET_REQUEST_START(p) ((p)->request_start) +#define AVB_PACKET_MAAP_GET_REQUEST_COUNT(p) ntohs((p)->request_count) +#define AVB_PACKET_MAAP_GET_CONFLICT_START(p) ((p)->conflict_start) +#define AVB_PACKET_MAAP_GET_CONFLICT_COUNT(p) ntohs((p)->conflict_count) + +struct avb_maap; + +struct avb_maap *avb_maap_register(struct server *server); + +int avb_maap_reserve(struct avb_maap *maap, uint32_t count); +int avb_maap_get_address(struct avb_maap *maap, uint8_t addr6, uint32_t index); + +#endif /* AVB_MAAP_H */
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/mmrp.c
Added
@@ -0,0 +1,233 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <unistd.h> + +#include <pipewire/pipewire.h> + +#include "utils.h" +#include "mmrp.h" + +static const uint8_t mmrp_mac6 = AVB_MMRP_MAC; + +struct attr { + struct avb_mmrp_attribute attr; + struct spa_list link; +}; + +struct mmrp { + struct server *server; + struct spa_hook server_listener; + + struct spa_source *source; + + struct spa_list attributes; +}; + +static bool mmrp_check_header(void *data, const void *hdr, size_t *hdr_size, bool *has_params) +{ + const struct avb_packet_mmrp_msg *msg = hdr; + uint8_t attr_type = msg->attribute_type; + + if (!AVB_MMRP_ATTRIBUTE_TYPE_VALID(attr_type)) + return false; + + *hdr_size = sizeof(*msg); + *has_params = false; + return true; +} + +static int mmrp_attr_event(void *data, uint64_t now, uint8_t attribute_type, uint8_t event) +{ + struct mmrp *mmrp = data; + struct attr *a; + spa_list_for_each(a, &mmrp->attributes, link) + if (a->attr.type == attribute_type) + avb_mrp_attribute_update_state(a->attr.mrp, now, event); + return 0; +} + +static void debug_service_requirement(const struct avb_packet_mmrp_service_requirement *t) +{ + char buf128; + pw_log_info("service requirement"); + pw_log_info(" %s", avb_utils_format_addr(buf, sizeof(buf), t->addr)); +} + +static int process_service_requirement(struct mmrp *mmrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num) +{ + const struct avb_packet_mmrp_service_requirement *t = m; + struct attr *a; + + debug_service_requirement(t); + + spa_list_for_each(a, &mmrp->attributes, link) + if (a->attr.type == attr_type && + memcmp(a->attr.attr.service_requirement.addr, t->addr, 6) == 0) + avb_mrp_attribute_rx_event(a->attr.mrp, now, event); + return 0; +} + +static void debug_process_mac(const struct avb_packet_mmrp_mac *t) +{ + char buf128; + pw_log_info("mac"); + pw_log_info(" %s", avb_utils_format_addr(buf, sizeof(buf), t->addr)); +} + +static int process_mac(struct mmrp *mmrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num) +{ + const struct avb_packet_mmrp_mac *t = m; + struct attr *a; + + debug_process_mac(t); + + spa_list_for_each(a, &mmrp->attributes, link) + if (a->attr.type == attr_type && + memcmp(a->attr.attr.mac.addr, t->addr, 6) == 0) + avb_mrp_attribute_rx_event(a->attr.mrp, now, event); + return 0; +} + +static const struct { + int (*dispatch) (struct mmrp *mmrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num); +} dispatch = { + AVB_MMRP_ATTRIBUTE_TYPE_SERVICE_REQUIREMENT = { process_service_requirement, }, + AVB_MMRP_ATTRIBUTE_TYPE_MAC = { process_mac, }, +}; + +static int mmrp_process(void *data, uint64_t now, uint8_t attribute_type, const void *value, + uint8_t event, uint8_t param, int index) +{ + struct mmrp *mmrp = data; + return dispatchattribute_type.dispatch(mmrp, now, + attribute_type, value, event, param, index); +} + +static const struct avb_mrp_parse_info info = { + AVB_VERSION_MRP_PARSE_INFO, + .check_header = mmrp_check_header, + .attr_event = mmrp_attr_event, + .process = mmrp_process, +}; + +static int mmrp_message(struct mmrp *mmrp, uint64_t now, const void *message, int len) +{ + pw_log_debug("MMRP"); + return avb_mrp_parse_packet(mmrp->server->mrp, + now, message, len, &info, mmrp); +} + +static void on_socket_data(void *data, int fd, uint32_t mask) +{ + struct mmrp *mmrp = data; + struct timespec now; + + if (mask & SPA_IO_IN) { + int len; + uint8_t buffer2048; + + len = recv(fd, buffer, sizeof(buffer), 0); + + if (len < 0) { + pw_log_warn("got recv error: %m"); + } + else if (len < (int)sizeof(struct avb_packet_header)) { + pw_log_warn("short packet received (%d < %d)", len, + (int)sizeof(struct avb_packet_header)); + } else { + clock_gettime(CLOCK_REALTIME, &now); + mmrp_message(mmrp, SPA_TIMESPEC_TO_NSEC(&now), buffer, len); + } + } +} +static void mmrp_destroy(void *data) +{ + struct mmrp *mmrp = data; + spa_hook_remove(&mmrp->server_listener); + pw_loop_destroy_source(mmrp->server->impl->loop, mmrp->source); + free(mmrp); +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = mmrp_destroy, +}; + +struct avb_mmrp_attribute *avb_mmrp_attribute_new(struct avb_mmrp *m, + uint8_t type) +{ + struct mmrp *mmrp = (struct mmrp*)m; + struct avb_mrp_attribute *attr; + struct attr *a; + + attr = avb_mrp_attribute_new(mmrp->server->mrp, sizeof(struct attr)); + + a = attr->user_data; + a->attr.mrp = attr; + a->attr.type = type; + spa_list_append(&mmrp->attributes, &a->link); + + return &a->attr; +} + +struct avb_mmrp *avb_mmrp_register(struct server *server) +{ + struct mmrp *mmrp; + int fd, res; + + fd = avb_server_make_socket(server, AVB_MMRP_ETH, mmrp_mac); + if (fd < 0) { + errno = -fd; + return NULL; + } + mmrp = calloc(1, sizeof(*mmrp)); + if (mmrp == NULL) { + res = -errno; + goto error_close; + } + + mmrp->server = server; + spa_list_init(&mmrp->attributes); + + mmrp->source = pw_loop_add_io(server->impl->loop, fd, SPA_IO_IN, true, on_socket_data, mmrp); + if (mmrp->source == NULL) { + res = -errno; + pw_log_error("mmrp %p: can't create mmrp source: %m", mmrp); + goto error_no_source; + } + avdecc_server_add_listener(server, &mmrp->server_listener, &server_events, mmrp); + + return (struct avb_mmrp*)mmrp; + +error_no_source: + free(mmrp); +error_close: + close(fd); + errno = -res; + return NULL; +}
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/mmrp.h
Added
@@ -0,0 +1,68 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_MMRP_H +#define AVB_MMRP_H + +#include "mrp.h" +#include "internal.h" + +#define AVB_MMRP_ETH 0x88f6 +#define AVB_MMRP_MAC { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x20 } + +#define AVB_MMRP_ATTRIBUTE_TYPE_SERVICE_REQUIREMENT 1 +#define AVB_MMRP_ATTRIBUTE_TYPE_MAC 2 +#define AVB_MMRP_ATTRIBUTE_TYPE_VALID(t) ((t)>=1 && (t)<=2) + +struct avb_packet_mmrp_msg { + uint8_t attribute_type; + uint8_t attribute_length; + uint8_t attribute_list0; +} __attribute__ ((__packed__)); + +struct avb_packet_mmrp_service_requirement { + unsigned char addr6; +} __attribute__ ((__packed__)); + +struct avb_packet_mmrp_mac { + unsigned char addr6; +} __attribute__ ((__packed__)); + +struct avb_mmrp; + +struct avb_mmrp_attribute { + struct avb_mrp_attribute *mrp; + uint8_t type; + union { + struct avb_packet_mmrp_service_requirement service_requirement; + struct avb_packet_mmrp_mac mac; + } attr; +}; + +struct avb_mmrp_attribute *avb_mmrp_attribute_new(struct avb_mmrp *mmrp, + uint8_t type); + +struct avb_mmrp *avb_mmrp_register(struct server *server); + +#endif /* AVB_MMRP_H */
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/mrp.c
Added
@@ -0,0 +1,612 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <pipewire/pipewire.h> + +#include "mrp.h" + +#define MRP_JOINTIMER_MS 100 +#define MRP_LVTIMER_MS 1000 +#define MRP_LVATIMER_MS 10000 +#define MRP_PERIODTIMER_MS 1000 + +#define mrp_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct avb_mrp_events, m, v, ##__VA_ARGS__) +#define mrp_emit_event(s,n,e) mrp_emit(s,event,0,n,e) +#define mrp_emit_notify(s,n,a,e) mrp_emit(s,notify,0,n,a,e) + +#define mrp_attribute_emit(a,m,v,...) spa_hook_list_call(&a->listener_list, struct avb_mrp_attribute_events, m, v, ##__VA_ARGS__) +#define mrp_attribute_emit_notify(a,n,e) mrp_attribute_emit(a,notify,0,n,e) + + +struct mrp; + +struct attribute { + struct avb_mrp_attribute attr; + struct mrp *mrp; + struct spa_list link; + uint8_t applicant_state; + uint8_t registrar_state; + uint64_t leave_timeout; + unsigned joined:1; + struct spa_hook_list listener_list; +}; + +struct mrp { + struct server *server; + struct spa_hook server_listener; + + struct spa_hook_list listener_list; + + struct spa_list attributes; + + uint64_t periodic_timeout; + uint64_t leave_all_timeout; + uint64_t join_timeout; +}; + +static void mrp_destroy(void *data) +{ + struct mrp *mrp = data; + spa_hook_remove(&mrp->server_listener); + free(mrp); +} + +static void global_event(struct mrp *mrp, uint64_t now, uint8_t event) +{ + struct attribute *a; + spa_list_for_each(a, &mrp->attributes, link) + avb_mrp_attribute_update_state(&a->attr, now, event); + mrp_emit_event(mrp, now, event); +} + +static void mrp_periodic(void *data, uint64_t now) +{ + struct mrp *mrp = data; + bool leave_all = false; + struct attribute *a; + + if (now > mrp->periodic_timeout) { + if (mrp->periodic_timeout > 0) + global_event(mrp, now, AVB_MRP_EVENT_PERIODIC); + mrp->periodic_timeout = now + MRP_PERIODTIMER_MS * SPA_NSEC_PER_MSEC; + } + if (now > mrp->leave_all_timeout) { + if (mrp->leave_all_timeout > 0) { + global_event(mrp, now, AVB_MRP_EVENT_RX_LVA); + leave_all = true; + } + mrp->leave_all_timeout = now + (MRP_LVATIMER_MS + (random() % (MRP_LVATIMER_MS / 2))) + * SPA_NSEC_PER_MSEC; + } + + if (now > mrp->join_timeout) { + if (mrp->join_timeout > 0) { + uint8_t event = leave_all ? AVB_MRP_EVENT_TX_LVA : AVB_MRP_EVENT_TX; + global_event(mrp, now, event); + } + mrp->join_timeout = now + MRP_JOINTIMER_MS * SPA_NSEC_PER_MSEC; + } + + spa_list_for_each(a, &mrp->attributes, link) { + if (a->leave_timeout > 0 && now > a->leave_timeout) { + a->leave_timeout = 0; + avb_mrp_attribute_update_state(&a->attr, now, AVB_MRP_EVENT_LV_TIMER); + } + } +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = mrp_destroy, + .periodic = mrp_periodic, +}; + +int avb_mrp_parse_packet(struct avb_mrp *mrp, uint64_t now, const void *pkt, int len, + const struct avb_mrp_parse_info *info, void *data) +{ + uint8_t *e = SPA_PTROFF(pkt, len, uint8_t); + uint8_t *m = SPA_PTROFF(pkt, sizeof(struct avb_packet_mrp), uint8_t); + + while (m < e && (m0 != 0 || m1 != 0)) { + const struct avb_packet_mrp_hdr *hdr = (const struct avb_packet_mrp_hdr*)m; + uint8_t attr_type = hdr->attribute_type; + uint8_t attr_len = hdr->attribute_length; + size_t hdr_size; + bool has_param; + + if (!info->check_header(data, hdr, &hdr_size, &has_param)) + return -EINVAL; + + m += hdr_size; + + while (m < e && (m0 != 0 || m1 != 0)) { + const struct avb_packet_mrp_vector *v = + (const struct avb_packet_mrp_vector*)m; + uint16_t i, num_values = AVB_MRP_VECTOR_GET_NUM_VALUES(v); + uint8_t event_len = (num_values+2)/3; + uint8_t param_len = has_param ? (num_values+3)/4 : 0; + int plen = sizeof(*v) + attr_len + event_len + param_len; + const uint8_t *first = v->first_value; + uint8_t event3, param4 = { 0, }; + + if (m + plen > e) + return -EPROTO; + + if (v->lva) + info->attr_event(data, now, attr_type, AVB_MRP_EVENT_RX_LVA); + + for (i = 0; i < num_values; i++) { + if (i % 3 == 0) { + uint8_t ep = firstattr_len + i/3; + event2 = ep % 6; ep /= 6; + event1 = ep % 6; ep /= 6; + event0 = ep % 6; + } + if (has_param && (i % 4 == 0)) { + uint8_t ep = firstattr_len + event_len + i/4; + param3 = ep % 4; ep /= 4; + param2 = ep % 4; ep /= 4; + param1 = ep % 4; ep /= 4; + param0 = ep % 4; + } + info->process(data, now, attr_type, first, + eventi%3, parami%4, i); + } + m += plen; + } + m += 2; + } + return 0; +} + +const char *avb_mrp_notify_name(uint8_t notify) +{ + switch(notify) { + case AVB_MRP_NOTIFY_NEW: + return "new"; + case AVB_MRP_NOTIFY_JOIN: + return "join"; + case AVB_MRP_NOTIFY_LEAVE: + return "leave"; + } + return "unknown"; +} + +const char *avb_mrp_send_name(uint8_t send) +{ + switch(send) { + case AVB_MRP_SEND_NEW: + return "new"; + case AVB_MRP_SEND_JOININ: + return "joinin"; + case AVB_MRP_SEND_IN: + return "in"; + case AVB_MRP_SEND_JOINMT: + return "joinmt"; + case AVB_MRP_SEND_MT: + return "mt"; + case AVB_MRP_SEND_LV: + return "leave"; + } + return "unknown"; +} + +struct avb_mrp_attribute *avb_mrp_attribute_new(struct avb_mrp *m, + size_t user_size) +{ + struct mrp *mrp = (struct mrp*)m; + struct attribute *a; + + a = calloc(1, sizeof(*a) + user_size); + if (a == NULL) + return NULL; + + a->mrp = mrp; + a->attr.user_data = SPA_PTROFF(a, sizeof(*a), void); + spa_hook_list_init(&a->listener_list); + spa_list_append(&mrp->attributes, &a->link); + + return &a->attr; +} + +void avb_mrp_attribute_destroy(struct avb_mrp_attribute *attr) +{ + struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr); + spa_list_remove(&a->link); + free(a); +} + +void avb_mrp_attribute_add_listener(struct avb_mrp_attribute *attr, struct spa_hook *listener, + const struct avb_mrp_attribute_events *events, void *data) +{ + struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr); + spa_hook_list_append(&a->listener_list, listener, events, data); +} + +void avb_mrp_attribute_update_state(struct avb_mrp_attribute *attr, uint64_t now, + int event) +{ + struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr); + struct mrp *mrp = a->mrp; + uint8_t notify = 0, state; + uint8_t send = 0; + + state = a->registrar_state; + + switch (event) { + case AVB_MRP_EVENT_BEGIN: + state = AVB_MRP_MT; + break; + case AVB_MRP_EVENT_RX_NEW: + notify = AVB_MRP_NOTIFY_NEW; + switch (state) { + case AVB_MRP_LV: + a->leave_timeout = 0; + break; + } + state = AVB_MRP_IN; + break; + case AVB_MRP_EVENT_RX_JOININ: + case AVB_MRP_EVENT_RX_JOINMT: + switch (state) { + case AVB_MRP_LV: + a->leave_timeout = 0; + break; + case AVB_MRP_MT: + notify = AVB_MRP_NOTIFY_JOIN; + break; + } + state = AVB_MRP_IN; + break; + case AVB_MRP_EVENT_RX_LV: + case AVB_MRP_EVENT_RX_LVA: + case AVB_MRP_EVENT_TX_LVA: + case AVB_MRP_EVENT_REDECLARE: + switch (state) { + case AVB_MRP_IN: + a->leave_timeout = now + MRP_LVTIMER_MS * SPA_NSEC_PER_MSEC; + //state = AVB_MRP_LV; + break; + } + break; + case AVB_MRP_EVENT_FLUSH: + switch (state) { + case AVB_MRP_LV: + notify = AVB_MRP_NOTIFY_LEAVE; + break; + } + state = AVB_MRP_MT; + break; + case AVB_MRP_EVENT_LV_TIMER: + switch (state) { + case AVB_MRP_LV: + notify = AVB_MRP_NOTIFY_LEAVE; + state = AVB_MRP_MT; + break; + } + break; + default: + break; + } + if (notify) { + mrp_attribute_emit_notify(a, now, notify); + mrp_emit_notify(mrp, now, &a->attr, notify); + } + + if (a->registrar_state != state || notify) { + pw_log_debug("attr %p: %d %d -> %d %d", a, event, a->registrar_state, state, notify); + a->registrar_state = state; + } + + state = a->applicant_state; + + switch (event) { + case AVB_MRP_EVENT_BEGIN: + state = AVB_MRP_VO; + break; + case AVB_MRP_EVENT_NEW: + switch (state) { + case AVB_MRP_VN: + case AVB_MRP_AN: + break; + default: + state = AVB_MRP_VN; + break; + } + break; + case AVB_MRP_EVENT_JOIN: + switch (state) { + case AVB_MRP_VO: + case AVB_MRP_LO: + state = AVB_MRP_VP; + break; + case AVB_MRP_LA: + state = AVB_MRP_AA; + break; + case AVB_MRP_AO: + state = AVB_MRP_AP; + break; + case AVB_MRP_QO: + state = AVB_MRP_QP; + break; + } + break; + case AVB_MRP_EVENT_LV: + switch (state) { + case AVB_MRP_VP: + state = AVB_MRP_VO; + break; + case AVB_MRP_VN: + case AVB_MRP_AN: + case AVB_MRP_AA: + case AVB_MRP_QA: + state = AVB_MRP_LA; + break; + case AVB_MRP_AP: + state = AVB_MRP_AO; + break; + case AVB_MRP_QP: + state = AVB_MRP_QO; + break; + } + break; + case AVB_MRP_EVENT_RX_JOININ: + switch (state) { + case AVB_MRP_VO: + state = AVB_MRP_AO; + break; + case AVB_MRP_VP: + state = AVB_MRP_AP; + break; + case AVB_MRP_AA: + state = AVB_MRP_QA; + break; + case AVB_MRP_AO: + state = AVB_MRP_QO; + break; + case AVB_MRP_AP: + state = AVB_MRP_QP; + break; + } + SPA_FALLTHROUGH; + case AVB_MRP_EVENT_RX_IN: + switch (state) { + case AVB_MRP_AA: + state = AVB_MRP_QA; + break; + } + break; + case AVB_MRP_EVENT_RX_JOINMT: + case AVB_MRP_EVENT_RX_MT: + switch (state) { + case AVB_MRP_QA: + state = AVB_MRP_AA; + break; + case AVB_MRP_QO: + state = AVB_MRP_AO; + break; + case AVB_MRP_QP: + state = AVB_MRP_AP; + break; + case AVB_MRP_LO: + state = AVB_MRP_VO; + break; + } + break; + case AVB_MRP_EVENT_RX_LV: + case AVB_MRP_EVENT_RX_LVA: + case AVB_MRP_EVENT_REDECLARE: + switch (state) { + case AVB_MRP_VO: + case AVB_MRP_AO: + case AVB_MRP_QO: + state = AVB_MRP_LO; + break; + case AVB_MRP_AN: + state = AVB_MRP_VN; + break; + case AVB_MRP_AA: + case AVB_MRP_QA: + case AVB_MRP_AP: + case AVB_MRP_QP: + state = AVB_MRP_VP; + break; + } + break; + case AVB_MRP_EVENT_PERIODIC: + switch (state) { + case AVB_MRP_QA: + state = AVB_MRP_AA; + break; + case AVB_MRP_QP: + state = AVB_MRP_AP; + break; + } + break; + case AVB_MRP_EVENT_TX: + switch (state) { + case AVB_MRP_VP: + case AVB_MRP_AA: + case AVB_MRP_AP: + if (a->registrar_state == AVB_MRP_IN) + send = AVB_MRP_SEND_JOININ; + else + send = AVB_MRP_SEND_JOINMT; + break; + case AVB_MRP_VN: + case AVB_MRP_AN: + send = AVB_MRP_SEND_NEW; + break; + case AVB_MRP_LA: + send = AVB_MRP_SEND_LV; + break; + case AVB_MRP_LO: + if (a->registrar_state == AVB_MRP_IN) + send = AVB_MRP_SEND_IN; + else + send = AVB_MRP_SEND_MT; + break; + } + switch (state) { + case AVB_MRP_VP: + state = AVB_MRP_AA; + break; + case AVB_MRP_VN: + state = AVB_MRP_AN; + break; + case AVB_MRP_AN: + if(a->registrar_state == AVB_MRP_IN) + state = AVB_MRP_QA; + else + state = AVB_MRP_AA; + break; + case AVB_MRP_AA: + case AVB_MRP_AP: + state = AVB_MRP_QA; + break; + case AVB_MRP_LA: + case AVB_MRP_LO: + state = AVB_MRP_VO; + break; + } + break; + case AVB_MRP_EVENT_TX_LVA: + { + switch (state) { + case AVB_MRP_VP: + if (a->registrar_state == AVB_MRP_IN) + send = AVB_MRP_SEND_IN; + else + send = AVB_MRP_SEND_MT; + break; + case AVB_MRP_VN: + case AVB_MRP_AN: + send = AVB_MRP_SEND_NEW; + break; + case AVB_MRP_AA: + case AVB_MRP_QA: + case AVB_MRP_AP: + case AVB_MRP_QP: + if (a->registrar_state == AVB_MRP_IN) + send = AVB_MRP_SEND_JOININ; + else + send = AVB_MRP_SEND_JOINMT; + break; + } + switch (state) { + case AVB_MRP_VO: + case AVB_MRP_LA: + case AVB_MRP_AO: + case AVB_MRP_QO: + state = AVB_MRP_LO; + break; + case AVB_MRP_VP: + state = AVB_MRP_AA; + break; + case AVB_MRP_VN: + state = AVB_MRP_AN; + break; + case AVB_MRP_AN: + case AVB_MRP_AA: + case AVB_MRP_AP: + case AVB_MRP_QP: + state = AVB_MRP_QA; + break; + } + break; + } + default: + break; + } + if (a->applicant_state != state || send) { + pw_log_debug("attr %p: %d %d -> %d %d", a, event, a->applicant_state, state, send); + a->applicant_state = state; + } + if (a->joined) + a->attr.pending_send = send; +} + +void avb_mrp_attribute_rx_event(struct avb_mrp_attribute *attr, uint64_t now, uint8_t event) +{ + static const int map = { + AVB_MRP_ATTRIBUTE_EVENT_NEW = AVB_MRP_EVENT_RX_NEW, + AVB_MRP_ATTRIBUTE_EVENT_JOININ = AVB_MRP_EVENT_RX_JOININ, + AVB_MRP_ATTRIBUTE_EVENT_IN = AVB_MRP_EVENT_RX_IN, + AVB_MRP_ATTRIBUTE_EVENT_JOINMT = AVB_MRP_EVENT_RX_JOINMT, + AVB_MRP_ATTRIBUTE_EVENT_MT = AVB_MRP_EVENT_RX_MT, + AVB_MRP_ATTRIBUTE_EVENT_LV = AVB_MRP_EVENT_RX_LV, + }; + avb_mrp_attribute_update_state(attr, now, mapevent); +} + +void avb_mrp_attribute_begin(struct avb_mrp_attribute *attr, uint64_t now) +{ + struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr); + a->leave_timeout = 0; + avb_mrp_attribute_update_state(attr, now, AVB_MRP_EVENT_BEGIN); +} + +void avb_mrp_attribute_join(struct avb_mrp_attribute *attr, uint64_t now, bool is_new) +{ + struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr); + a->joined = true; + int event = is_new ? AVB_MRP_EVENT_NEW : AVB_MRP_EVENT_JOIN; + avb_mrp_attribute_update_state(attr, now, event); +} + +void avb_mrp_attribute_leave(struct avb_mrp_attribute *attr, uint64_t now) +{ + struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr); + avb_mrp_attribute_update_state(attr, now, AVB_MRP_EVENT_LV); + a->joined = false; +} + +void avb_mrp_destroy(struct avb_mrp *mrp) +{ + mrp_destroy(mrp); +} + +struct avb_mrp *avb_mrp_new(struct server *server) +{ + struct mrp *mrp; + + mrp = calloc(1, sizeof(*mrp)); + if (mrp == NULL) + return NULL; + + mrp->server = server; + spa_list_init(&mrp->attributes); + spa_hook_list_init(&mrp->listener_list); + + avdecc_server_add_listener(server, &mrp->server_listener, &server_events, mrp); + + return (struct avb_mrp*)mrp; +} + +void avb_mrp_add_listener(struct avb_mrp *m, struct spa_hook *listener, + const struct avb_mrp_events *events, void *data) +{ + struct mrp *mrp = (struct mrp*)m; + spa_hook_list_append(&mrp->listener_list, listener, events, data); +}
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/mrp.h
Added
@@ -0,0 +1,181 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_MRP_H +#define AVB_MRP_H + +#include "packets.h" +#include "internal.h" + +#define AVB_MRP_PROTOCOL_VERSION 0 + +struct avb_packet_mrp { + struct avb_ethernet_header eth; + uint8_t version; +} __attribute__ ((__packed__)); + +struct avb_packet_mrp_hdr { + uint8_t attribute_type; + uint8_t attribute_length; +} __attribute__ ((__packed__)); + +struct avb_packet_mrp_vector { +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned lva:3; + unsigned nv1:5; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned nv1:5; + unsigned lva:3; +#endif + uint8_t nv2; + uint8_t first_value0; +} __attribute__ ((__packed__)); + +#define AVB_MRP_VECTOR_SET_NUM_VALUES(a,v) ((a)->nv1 = ((v) >> 8),(a)->nv2 = (v)) +#define AVB_MRP_VECTOR_GET_NUM_VALUES(a) ((a)->nv1 << 8 | (a)->nv2) + +struct avb_packet_mrp_footer { + uint16_t end_mark; +} __attribute__ ((__packed__)); + +/* applicant states */ +#define AVB_MRP_VO 0 /* Very anxious Observer */ +#define AVB_MRP_VP 1 /* Very anxious Passive */ +#define AVB_MRP_VN 2 /* Very anxious New */ +#define AVB_MRP_AN 3 /* Anxious New */ +#define AVB_MRP_AA 4 /* Anxious Active */ +#define AVB_MRP_QA 5 /* Quiet Active */ +#define AVB_MRP_LA 6 /* Leaving Active */ +#define AVB_MRP_AO 7 /* Anxious Observer */ +#define AVB_MRP_QO 8 /* Quiet Observer */ +#define AVB_MRP_AP 9 /* Anxious Passive */ +#define AVB_MRP_QP 10 /* Quiet Passive */ +#define AVB_MRP_LO 11 /* Leaving Observer */ + +/* registrar states */ +#define AVB_MRP_IN 16 +#define AVB_MRP_LV 17 +#define AVB_MRP_MT 18 + +/* events */ +#define AVB_MRP_EVENT_BEGIN 0 +#define AVB_MRP_EVENT_NEW 1 +#define AVB_MRP_EVENT_JOIN 2 +#define AVB_MRP_EVENT_LV 3 +#define AVB_MRP_EVENT_TX 4 +#define AVB_MRP_EVENT_TX_LVA 5 +#define AVB_MRP_EVENT_TX_LVAF 6 +#define AVB_MRP_EVENT_RX_NEW 7 +#define AVB_MRP_EVENT_RX_JOININ 8 +#define AVB_MRP_EVENT_RX_IN 9 +#define AVB_MRP_EVENT_RX_JOINMT 10 +#define AVB_MRP_EVENT_RX_MT 11 +#define AVB_MRP_EVENT_RX_LV 12 +#define AVB_MRP_EVENT_RX_LVA 13 +#define AVB_MRP_EVENT_FLUSH 14 +#define AVB_MRP_EVENT_REDECLARE 15 +#define AVB_MRP_EVENT_PERIODIC 16 +#define AVB_MRP_EVENT_LV_TIMER 17 +#define AVB_MRP_EVENT_LVA_TIMER 18 + +/* attribute events */ +#define AVB_MRP_ATTRIBUTE_EVENT_NEW 0 +#define AVB_MRP_ATTRIBUTE_EVENT_JOININ 1 +#define AVB_MRP_ATTRIBUTE_EVENT_IN 2 +#define AVB_MRP_ATTRIBUTE_EVENT_JOINMT 3 +#define AVB_MRP_ATTRIBUTE_EVENT_MT 4 +#define AVB_MRP_ATTRIBUTE_EVENT_LV 5 + +#define AVB_MRP_SEND_NEW 1 +#define AVB_MRP_SEND_JOININ 2 +#define AVB_MRP_SEND_IN 3 +#define AVB_MRP_SEND_JOINMT 4 +#define AVB_MRP_SEND_MT 5 +#define AVB_MRP_SEND_LV 6 + +#define AVB_MRP_NOTIFY_NEW 1 +#define AVB_MRP_NOTIFY_JOIN 2 +#define AVB_MRP_NOTIFY_LEAVE 3 + +const char *avb_mrp_notify_name(uint8_t notify); +const char *avb_mrp_send_name(uint8_t send); + +struct avb_mrp_attribute { + uint8_t pending_send; + void *user_data; +}; + +struct avb_mrp_attribute_events { +#define AVB_VERSION_MRP_ATTRIBUTE_EVENTS 0 + uint32_t version; + + void (*notify) (void *data, uint64_t now, uint8_t notify); +}; + +struct avb_mrp_attribute *avb_mrp_attribute_new(struct avb_mrp *mrp, + size_t user_size); +void avb_mrp_attribute_destroy(struct avb_mrp_attribute *attr); + +void avb_mrp_attribute_update_state(struct avb_mrp_attribute *attr, uint64_t now, int event); + +void avb_mrp_attribute_rx_event(struct avb_mrp_attribute *attr, uint64_t now, uint8_t event); + +void avb_mrp_attribute_begin(struct avb_mrp_attribute *attr, uint64_t now); +void avb_mrp_attribute_join(struct avb_mrp_attribute *attr, uint64_t now, bool is_new); +void avb_mrp_attribute_leave(struct avb_mrp_attribute *attr, uint64_t now); + +void avb_mrp_attribute_add_listener(struct avb_mrp_attribute *attr, struct spa_hook *listener, + const struct avb_mrp_attribute_events *events, void *data); + +struct avb_mrp_parse_info { +#define AVB_VERSION_MRP_PARSE_INFO 0 + uint32_t version; + + bool (*check_header) (void *data, const void *hdr, size_t *hdr_size, bool *has_params); + + int (*attr_event) (void *data, uint64_t now, uint8_t attribute_type, uint8_t event); + + int (*process) (void *data, uint64_t now, uint8_t attribute_type, const void *value, + uint8_t event, uint8_t param, int index); +}; + +int avb_mrp_parse_packet(struct avb_mrp *mrp, uint64_t now, const void *pkt, int size, + const struct avb_mrp_parse_info *cb, void *data); + +struct avb_mrp_events { +#define AVB_VERSION_MRP_EVENTS 0 + uint32_t version; + + void (*event) (void *data, uint64_t now, uint8_t event); + + void (*notify) (void *data, uint64_t now, struct avb_mrp_attribute *attr, uint8_t notify); +}; + +struct avb_mrp *avb_mrp_new(struct server *server); +void avb_mrp_destroy(struct avb_mrp *mrp); + +void avb_mrp_add_listener(struct avb_mrp *mrp, struct spa_hook *listener, + const struct avb_mrp_events *events, void *data); + +#endif /* AVB_MRP_H */
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/msrp.c
Added
@@ -0,0 +1,459 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <unistd.h> + +#include <spa/debug/mem.h> + +#include <pipewire/pipewire.h> + +#include "utils.h" +#include "msrp.h" + +static const uint8_t msrp_mac6 = AVB_MSRP_MAC; + +struct attr { + struct avb_msrp_attribute attr; + struct msrp *msrp; + struct spa_hook listener; + struct spa_list link; +}; + +struct msrp { + struct server *server; + struct spa_hook server_listener; + struct spa_hook mrp_listener; + + struct spa_source *source; + + struct spa_list attributes; +}; + +static void debug_msrp_talker_common(const struct avb_packet_msrp_talker *t) +{ + char buf128; + pw_log_info(" stream-id: %s", avb_utils_format_id(buf, sizeof(buf), be64toh(t->stream_id))); + pw_log_info(" dest-addr: %s", avb_utils_format_addr(buf, sizeof(buf), t->dest_addr)); + pw_log_info(" vlan-id: %d", ntohs(t->vlan_id)); + pw_log_info(" tspec-max-frame-size: %d", ntohs(t->tspec_max_frame_size)); + pw_log_info(" tspec-max-interval-frames: %d", ntohs(t->tspec_max_interval_frames)); + pw_log_info(" priority: %d", t->priority); + pw_log_info(" rank: %d", t->rank); + pw_log_info(" accumulated-latency: %d", ntohl(t->accumulated_latency)); +} + +static void debug_msrp_talker(const struct avb_packet_msrp_talker *t) +{ + pw_log_info("talker"); + debug_msrp_talker_common(t); +} + +static void notify_talker(struct msrp *msrp, uint64_t now, struct attr *attr, uint8_t notify) +{ + pw_log_info("> notify talker: %s", avb_mrp_notify_name(notify)); + debug_msrp_talker(&attr->attr.attr.talker); +} + +static int process_talker(struct msrp *msrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num) +{ + const struct avb_packet_msrp_talker *t = m; + struct attr *a; + spa_list_for_each(a, &msrp->attributes, link) + if (a->attr.type == attr_type && + a->attr.attr.talker.stream_id == t->stream_id) { + a->attr.attr.talker = *t; + avb_mrp_attribute_rx_event(a->attr.mrp, now, event); + } + return 0; +} +static int encode_talker(struct msrp *msrp, struct attr *a, void *m) +{ + struct avb_packet_msrp_msg *msg = m; + struct avb_packet_mrp_vector *v; + struct avb_packet_msrp_talker *t; + struct avb_packet_mrp_footer *f; + uint8_t *ev; + size_t attr_list_length = sizeof(*v) + sizeof(*t) + sizeof(*f) + 1; + + msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE; + msg->attribute_length = sizeof(*t); + msg->attribute_list_length = htons(attr_list_length); + + v = (struct avb_packet_mrp_vector *)msg->attribute_list; + v->lva = 0; + AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1); + + t = (struct avb_packet_msrp_talker *)v->first_value; + *t = a->attr.attr.talker; + + ev = SPA_PTROFF(t, sizeof(*t), uint8_t); + *ev = a->attr.mrp->pending_send * 6 * 6; + + f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer); + f->end_mark = 0; + + return attr_list_length + sizeof(*msg); +} + + +static void debug_msrp_talker_fail(const struct avb_packet_msrp_talker_fail *t) +{ + char buf128; + pw_log_info("talker fail"); + debug_msrp_talker_common(&t->talker); + pw_log_info(" bridge-id: %s", avb_utils_format_id(buf, sizeof(buf), be64toh(t->bridge_id))); + pw_log_info(" failure-code: %d", t->failure_code); +} + +static int process_talker_fail(struct msrp *msrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num) +{ + const struct avb_packet_msrp_talker_fail *t = m; + struct attr *a; + + debug_msrp_talker_fail(t); + + spa_list_for_each(a, &msrp->attributes, link) + if (a->attr.type == attr_type && + a->attr.attr.talker_fail.talker.stream_id == t->talker.stream_id) + avb_mrp_attribute_rx_event(a->attr.mrp, now, event); + return 0; +} + +static void debug_msrp_listener(const struct avb_packet_msrp_listener *l, uint8_t param) +{ + char buf128; + pw_log_info("listener"); + pw_log_info(" %s", avb_utils_format_id(buf, sizeof(buf), be64toh(l->stream_id))); + pw_log_info(" %d", param); +} + +static void notify_listener(struct msrp *msrp, uint64_t now, struct attr *attr, uint8_t notify) +{ + pw_log_info("> notify listener: %s", avb_mrp_notify_name(notify)); + debug_msrp_listener(&attr->attr.attr.listener, attr->attr.param); +} + +static int process_listener(struct msrp *msrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num) +{ + const struct avb_packet_msrp_listener *l = m; + struct attr *a; + spa_list_for_each(a, &msrp->attributes, link) + if (a->attr.type == attr_type && + a->attr.attr.listener.stream_id == l->stream_id) + avb_mrp_attribute_rx_event(a->attr.mrp, now, event); + return 0; +} +static int encode_listener(struct msrp *msrp, struct attr *a, void *m) +{ + struct avb_packet_msrp_msg *msg = m; + struct avb_packet_mrp_vector *v; + struct avb_packet_msrp_listener *l; + struct avb_packet_mrp_footer *f; + uint8_t *ev; + size_t attr_list_length = sizeof(*v) + sizeof(*l) + sizeof(*f) + 1 + 1; + + msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_LISTENER; + msg->attribute_length = sizeof(*l); + msg->attribute_list_length = htons(attr_list_length); + + v = (struct avb_packet_mrp_vector *)msg->attribute_list; + v->lva = 0; + AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1); + + l = (struct avb_packet_msrp_listener *)v->first_value; + *l = a->attr.attr.listener; + + ev = SPA_PTROFF(l, sizeof(*l), uint8_t); + *ev = a->attr.mrp->pending_send * 6 * 6; + + ev = SPA_PTROFF(ev, sizeof(*ev), uint8_t); + *ev = a->attr.param * 4 * 4 * 4; + + f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer); + f->end_mark = 0; + + return attr_list_length + sizeof(*msg); +} + +static void debug_msrp_domain(const struct avb_packet_msrp_domain *d) +{ + pw_log_info("domain"); + pw_log_info(" id: %d", d->sr_class_id); + pw_log_info(" prio: %d", d->sr_class_priority); + pw_log_info(" vid: %d", ntohs(d->sr_class_vid)); +} + +static void notify_domain(struct msrp *msrp, uint64_t now, struct attr *attr, uint8_t notify) +{ + pw_log_info("> notify domain: %s", avb_mrp_notify_name(notify)); + debug_msrp_domain(&attr->attr.attr.domain); +} + +static int process_domain(struct msrp *msrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num) +{ + struct attr *a; + spa_list_for_each(a, &msrp->attributes, link) + if (a->attr.type == attr_type) + avb_mrp_attribute_rx_event(a->attr.mrp, now, event); + return 0; +} + +static int encode_domain(struct msrp *msrp, struct attr *a, void *m) +{ + struct avb_packet_msrp_msg *msg = m; + struct avb_packet_mrp_vector *v; + struct avb_packet_msrp_domain *d; + struct avb_packet_mrp_footer *f; + uint8_t *ev; + size_t attr_list_length = sizeof(*v) + sizeof(*d) + sizeof(*f) + 1; + + msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN; + msg->attribute_length = sizeof(*d); + msg->attribute_list_length = htons(attr_list_length); + + v = (struct avb_packet_mrp_vector *)msg->attribute_list; + v->lva = 0; + AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1); + + d = (struct avb_packet_msrp_domain *)v->first_value; + *d = a->attr.attr.domain; + + ev = SPA_PTROFF(d, sizeof(*d), uint8_t); + *ev = a->attr.mrp->pending_send * 36; + + f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer); + f->end_mark = 0; + + return attr_list_length + sizeof(*msg); +} + +static const struct { + const char *name; + int (*process) (struct msrp *msrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num); + int (*encode) (struct msrp *msrp, struct attr *attr, void *m); + void (*notify) (struct msrp *msrp, uint64_t now, struct attr *attr, uint8_t notify); +} dispatch = { + AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE = { "talker", process_talker, encode_talker, notify_talker, }, + AVB_MSRP_ATTRIBUTE_TYPE_TALKER_FAILED = { "talker-fail", process_talker_fail, NULL, NULL }, + AVB_MSRP_ATTRIBUTE_TYPE_LISTENER = { "listener", process_listener, encode_listener, notify_listener }, + AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN = { "domain", process_domain, encode_domain, notify_domain, }, +}; + +static bool msrp_check_header(void *data, const void *hdr, size_t *hdr_size, bool *has_params) +{ + const struct avb_packet_msrp_msg *msg = hdr; + uint8_t attr_type = msg->attribute_type; + + if (!AVB_MSRP_ATTRIBUTE_TYPE_VALID(attr_type)) + return false; + + *hdr_size = sizeof(*msg); + *has_params = attr_type == AVB_MSRP_ATTRIBUTE_TYPE_LISTENER; + return true; +} + +static int msrp_attr_event(void *data, uint64_t now, uint8_t attribute_type, uint8_t event) +{ + struct msrp *msrp = data; + struct attr *a; + spa_list_for_each(a, &msrp->attributes, link) + if (a->attr.type == attribute_type) + avb_mrp_attribute_update_state(a->attr.mrp, now, event); + return 0; +} + +static int msrp_process(void *data, uint64_t now, uint8_t attribute_type, const void *value, + uint8_t event, uint8_t param, int index) +{ + struct msrp *msrp = data; + return dispatchattribute_type.process(msrp, now, + attribute_type, value, event, param, index); +} + +static const struct avb_mrp_parse_info info = { + AVB_VERSION_MRP_PARSE_INFO, + .check_header = msrp_check_header, + .attr_event = msrp_attr_event, + .process = msrp_process, +}; + + +static int msrp_message(struct msrp *msrp, uint64_t now, const void *message, int len) +{ + return avb_mrp_parse_packet(msrp->server->mrp, + now, message, len, &info, msrp); +} +static void on_socket_data(void *data, int fd, uint32_t mask) +{ + struct msrp *msrp = data; + struct timespec now; + + if (mask & SPA_IO_IN) { + int len; + uint8_t buffer2048; + + len = recv(fd, buffer, sizeof(buffer), 0); + + if (len < 0) { + pw_log_warn("got recv error: %m"); + } + else if (len < (int)sizeof(struct avb_packet_header)) { + pw_log_warn("short packet received (%d < %d)", len, + (int)sizeof(struct avb_packet_header)); + } else { + clock_gettime(CLOCK_REALTIME, &now); + msrp_message(msrp, SPA_TIMESPEC_TO_NSEC(&now), buffer, len); + } + } +} + +static void msrp_destroy(void *data) +{ + struct msrp *msrp = data; + spa_hook_remove(&msrp->server_listener); + pw_loop_destroy_source(msrp->server->impl->loop, msrp->source); + free(msrp); +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = msrp_destroy, +}; + +static void msrp_notify(void *data, uint64_t now, uint8_t notify) +{ + struct attr *a = data; + struct msrp *msrp = a->msrp; + return dispatcha->attr.type.notify(msrp, now, a, notify); +} + +static const struct avb_mrp_attribute_events mrp_attr_events = { + AVB_VERSION_MRP_ATTRIBUTE_EVENTS, + .notify = msrp_notify, +}; + +struct avb_msrp_attribute *avb_msrp_attribute_new(struct avb_msrp *m, + uint8_t type) +{ + struct msrp *msrp = (struct msrp*)m; + struct avb_mrp_attribute *attr; + struct attr *a; + + attr = avb_mrp_attribute_new(msrp->server->mrp, sizeof(struct attr)); + + a = attr->user_data; + a->msrp = msrp; + a->attr.mrp = attr; + a->attr.type = type; + spa_list_append(&msrp->attributes, &a->link); + avb_mrp_attribute_add_listener(attr, &a->listener, &mrp_attr_events, a); + + return &a->attr; +} + +static void msrp_event(void *data, uint64_t now, uint8_t event) +{ + struct msrp *msrp = data; + uint8_t buffer2048; + struct avb_packet_mrp *p = (struct avb_packet_mrp*)buffer; + struct avb_packet_mrp_footer *f; + void *msg = SPA_PTROFF(buffer, sizeof(*p), void); + struct attr *a; + int len, count = 0; + size_t total = sizeof(*p) + 2; + + p->version = AVB_MRP_PROTOCOL_VERSION; + + spa_list_for_each(a, &msrp->attributes, link) { + if (!a->attr.mrp->pending_send) + continue; + if (dispatcha->attr.type.encode == NULL) + continue; + + pw_log_debug("send %s %s", dispatcha->attr.type.name, + avb_mrp_send_name(a->attr.mrp->pending_send)); + + len = dispatcha->attr.type.encode(msrp, a, msg); + if (len < 0) + break; + + count++; + msg = SPA_PTROFF(msg, len, void); + total += len; + } + f = (struct avb_packet_mrp_footer *)msg; + f->end_mark = 0; + + if (count > 0) + avb_server_send_packet(msrp->server, msrp_mac, AVB_MSRP_ETH, + buffer, total); +} + +static const struct avb_mrp_events mrp_events = { + AVB_VERSION_MRP_EVENTS, + .event = msrp_event, +}; + +struct avb_msrp *avb_msrp_register(struct server *server) +{ + struct msrp *msrp; + int fd, res; + + fd = avb_server_make_socket(server, AVB_MSRP_ETH, msrp_mac); + if (fd < 0) { + errno = -fd; + return NULL; + } + msrp = calloc(1, sizeof(*msrp)); + if (msrp == NULL) { + res = -errno; + goto error_close; + } + + msrp->server = server; + spa_list_init(&msrp->attributes); + + msrp->source = pw_loop_add_io(server->impl->loop, fd, SPA_IO_IN, true, on_socket_data, msrp); + if (msrp->source == NULL) { + res = -errno; + pw_log_error("msrp %p: can't create msrp source: %m", msrp); + goto error_no_source; + } + avdecc_server_add_listener(server, &msrp->server_listener, &server_events, msrp); + avb_mrp_add_listener(server->mrp, &msrp->mrp_listener, &mrp_events, msrp); + + return (struct avb_msrp*)msrp; + +error_no_source: + free(msrp); +error_close: + close(fd); + errno = -res; + return NULL; +}
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/msrp.h
Added
@@ -0,0 +1,134 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_MSRP_H +#define AVB_MSRP_H + +#include "internal.h" +#include "mrp.h" + +#define AVB_MSRP_ETH 0x22ea +#define AVB_MSRP_MAC { 0x01, 0x80, 0xc2, 0x00, 0x00, 0xe }; + +#define AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE 1 +#define AVB_MSRP_ATTRIBUTE_TYPE_TALKER_FAILED 2 +#define AVB_MSRP_ATTRIBUTE_TYPE_LISTENER 3 +#define AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN 4 +#define AVB_MSRP_ATTRIBUTE_TYPE_VALID(t) ((t)>=1 && (t)<=4) + +struct avb_packet_msrp_msg { + uint8_t attribute_type; + uint8_t attribute_length; + uint16_t attribute_list_length; + uint8_t attribute_list0; +} __attribute__ ((__packed__)); + +#define AVB_MSRP_TSPEC_MAX_INTERVAL_FRAMES_DEFAULT 1 +#define AVB_MSRP_RANK_DEFAULT 1 +#define AVB_MSRP_PRIORITY_DEFAULT 3 + +struct avb_packet_msrp_talker { + uint64_t stream_id; + uint8_t dest_addr6; + uint16_t vlan_id; + uint16_t tspec_max_frame_size; + uint16_t tspec_max_interval_frames; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned priority:3; + unsigned rank:1; + unsigned reserved:4; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned reserved:4; + unsigned rank:1; + unsigned priority:3; +#endif + uint32_t accumulated_latency; +} __attribute__ ((__packed__)); + +/* failure codes */ +#define AVB_MRP_FAIL_BANDWIDTH 1 +#define AVB_MRP_FAIL_BRIDGE 2 +#define AVB_MRP_FAIL_TC_BANDWIDTH 3 +#define AVB_MRP_FAIL_ID_BUSY 4 +#define AVB_MRP_FAIL_DSTADDR_BUSY 5 +#define AVB_MRP_FAIL_PREEMPTED 6 +#define AVB_MRP_FAIL_LATENCY_CHNG 7 +#define AVB_MRP_FAIL_PORT_NOT_AVB 8 +#define AVB_MRP_FAIL_DSTADDR_FULL 9 +#define AVB_MRP_FAIL_AVB_MRP_RESOURCE 10 +#define AVB_MRP_FAIL_MMRP_RESOURCE 11 +#define AVB_MRP_FAIL_DSTADDR_FAIL 12 +#define AVB_MRP_FAIL_PRIO_NOT_SR 13 +#define AVB_MRP_FAIL_FRAME_SIZE 14 +#define AVB_MRP_FAIL_FANIN_EXCEED 15 +#define AVB_MRP_FAIL_STREAM_CHANGE 16 +#define AVB_MRP_FAIL_VLAN_BLOCKED 17 +#define AVB_MRP_FAIL_VLAN_DISABLED 18 +#define AVB_MRP_FAIL_SR_PRIO_ERR 19 + +struct avb_packet_msrp_talker_fail { + struct avb_packet_msrp_talker talker; + uint64_t bridge_id; + uint8_t failure_code; +} __attribute__ ((__packed__)); + +struct avb_packet_msrp_listener { + uint64_t stream_id; +} __attribute__ ((__packed__)); + +/* domain discovery */ +#define AVB_MSRP_CLASS_ID_DEFAULT 6 +#define AVB_DEFAULT_VLAN 2 + +struct avb_packet_msrp_domain { + uint8_t sr_class_id; + uint8_t sr_class_priority; + uint16_t sr_class_vid; +} __attribute__ ((__packed__)); + +#define AVB_MSRP_LISTENER_PARAM_IGNORE 0 +#define AVB_MSRP_LISTENER_PARAM_ASKING_FAILED 1 +#define AVB_MSRP_LISTENER_PARAM_READY 2 +#define AVB_MSRP_LISTENER_PARAM_READY_FAILED 3 + +struct avb_msrp_attribute { + struct avb_mrp_attribute *mrp; + uint8_t type; + uint8_t param; + union { + struct avb_packet_msrp_talker talker; + struct avb_packet_msrp_talker_fail talker_fail; + struct avb_packet_msrp_listener listener; + struct avb_packet_msrp_domain domain; + } attr; +}; + +struct avb_msrp; + +struct avb_msrp_attribute *avb_msrp_attribute_new(struct avb_msrp *msrp, + uint8_t type); + +struct avb_msrp *avb_msrp_register(struct server *server); + +#endif /* AVB_MSRP_H */
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/mvrp.c
Added
@@ -0,0 +1,297 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <unistd.h> + +#include <pipewire/pipewire.h> + +#include "mvrp.h" + +static const uint8_t mvrp_mac6 = AVB_MVRP_MAC; + +struct attr { + struct avb_mvrp_attribute attr; + struct spa_hook listener; + struct spa_list link; + struct mvrp *mvrp; +}; + +struct mvrp { + struct server *server; + struct spa_hook server_listener; + struct spa_hook mrp_listener; + + struct spa_source *source; + + struct spa_list attributes; +}; + +static bool mvrp_check_header(void *data, const void *hdr, size_t *hdr_size, bool *has_params) +{ + const struct avb_packet_mvrp_msg *msg = hdr; + uint8_t attr_type = msg->attribute_type; + + if (!AVB_MVRP_ATTRIBUTE_TYPE_VALID(attr_type)) + return false; + + *hdr_size = sizeof(*msg); + *has_params = false; + return true; +} + +static int mvrp_attr_event(void *data, uint64_t now, uint8_t attribute_type, uint8_t event) +{ + struct mvrp *mvrp = data; + struct attr *a; + spa_list_for_each(a, &mvrp->attributes, link) + if (a->attr.type == attribute_type) + avb_mrp_attribute_rx_event(a->attr.mrp, now, event); + return 0; +} + +static void debug_vid(const struct avb_packet_mvrp_vid *t) +{ + pw_log_info("vid"); + pw_log_info(" %d", ntohs(t->vlan)); +} + +static int process_vid(struct mvrp *mvrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num) +{ + return mvrp_attr_event(mvrp, now, attr_type, event); +} + +static int encode_vid(struct mvrp *mvrp, struct attr *a, void *m) +{ + struct avb_packet_mvrp_msg *msg = m; + struct avb_packet_mrp_vector *v; + struct avb_packet_mvrp_vid *d; + struct avb_packet_mrp_footer *f; + uint8_t *ev; + size_t attr_list_length = sizeof(*v) + sizeof(*d) + sizeof(*f) + 1; + + msg->attribute_type = AVB_MVRP_ATTRIBUTE_TYPE_VID; + msg->attribute_length = sizeof(*d); + + v = (struct avb_packet_mrp_vector *)msg->attribute_list; + v->lva = 0; + AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1); + + d = (struct avb_packet_mvrp_vid *)v->first_value; + *d = a->attr.attr.vid; + + ev = SPA_PTROFF(d, sizeof(*d), uint8_t); + *ev = a->attr.mrp->pending_send * 36; + + f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer); + f->end_mark = 0; + + return attr_list_length + sizeof(*msg); +} + +static void notify_vid(struct mvrp *mvrp, uint64_t now, struct attr *attr, uint8_t notify) +{ + pw_log_info("> notify vid: %s", avb_mrp_notify_name(notify)); + debug_vid(&attr->attr.attr.vid); +} + +static const struct { + const char *name; + int (*process) (struct mvrp *mvrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num); + int (*encode) (struct mvrp *mvrp, struct attr *attr, void *m); + void (*notify) (struct mvrp *mvrp, uint64_t now, struct attr *attr, uint8_t notify); +} dispatch = { + AVB_MVRP_ATTRIBUTE_TYPE_VID = { "vid", process_vid, encode_vid, notify_vid }, +}; + +static int mvrp_process(void *data, uint64_t now, uint8_t attribute_type, const void *value, + uint8_t event, uint8_t param, int index) +{ + struct mvrp *mvrp = data; + return dispatchattribute_type.process(mvrp, now, + attribute_type, value, event, param, index); +} + +static const struct avb_mrp_parse_info info = { + AVB_VERSION_MRP_PARSE_INFO, + .check_header = mvrp_check_header, + .attr_event = mvrp_attr_event, + .process = mvrp_process, +}; + +static int mvrp_message(struct mvrp *mvrp, uint64_t now, const void *message, int len) +{ + pw_log_debug("MVRP"); + return avb_mrp_parse_packet(mvrp->server->mrp, + now, message, len, &info, mvrp); +} + +static void on_socket_data(void *data, int fd, uint32_t mask) +{ + struct mvrp *mvrp = data; + struct timespec now; + + if (mask & SPA_IO_IN) { + int len; + uint8_t buffer2048; + + len = recv(fd, buffer, sizeof(buffer), 0); + + if (len < 0) { + pw_log_warn("got recv error: %m"); + } + else if (len < (int)sizeof(struct avb_packet_header)) { + pw_log_warn("short packet received (%d < %d)", len, + (int)sizeof(struct avb_packet_header)); + } else { + clock_gettime(CLOCK_REALTIME, &now); + mvrp_message(mvrp, SPA_TIMESPEC_TO_NSEC(&now), buffer, len); + } + } +} + +static void mvrp_destroy(void *data) +{ + struct mvrp *mvrp = data; + spa_hook_remove(&mvrp->server_listener); + pw_loop_destroy_source(mvrp->server->impl->loop, mvrp->source); + free(mvrp); +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = mvrp_destroy, +}; + +static void mvrp_notify(void *data, uint64_t now, uint8_t notify) +{ + struct attr *a = data; + struct mvrp *mvrp = a->mvrp; + return dispatcha->attr.type.notify(mvrp, now, a, notify); +} + +static const struct avb_mrp_attribute_events mrp_attr_events = { + AVB_VERSION_MRP_ATTRIBUTE_EVENTS, + .notify = mvrp_notify, +}; + +struct avb_mvrp_attribute *avb_mvrp_attribute_new(struct avb_mvrp *m, + uint8_t type) +{ + struct mvrp *mvrp = (struct mvrp*)m; + struct avb_mrp_attribute *attr; + struct attr *a; + + attr = avb_mrp_attribute_new(mvrp->server->mrp, sizeof(struct attr)); + + a = attr->user_data; + a->attr.mrp = attr; + a->attr.type = type; + spa_list_append(&mvrp->attributes, &a->link); + avb_mrp_attribute_add_listener(attr, &a->listener, &mrp_attr_events, a); + + return &a->attr; +} + +static void mvrp_event(void *data, uint64_t now, uint8_t event) +{ + struct mvrp *mvrp = data; + uint8_t buffer2048; + struct avb_packet_mrp *p = (struct avb_packet_mrp*)buffer; + struct avb_packet_mrp_footer *f; + void *msg = SPA_PTROFF(buffer, sizeof(*p), void); + struct attr *a; + int len, count = 0; + size_t total = sizeof(*p) + 2; + + p->version = AVB_MRP_PROTOCOL_VERSION; + + spa_list_for_each(a, &mvrp->attributes, link) { + if (!a->attr.mrp->pending_send) + continue; + if (dispatcha->attr.type.encode == NULL) + continue; + + pw_log_debug("send %s %s", dispatcha->attr.type.name, + avb_mrp_send_name(a->attr.mrp->pending_send)); + + len = dispatcha->attr.type.encode(mvrp, a, msg); + if (len < 0) + break; + + count++; + msg = SPA_PTROFF(msg, len, void); + total += len; + } + f = (struct avb_packet_mrp_footer *)msg; + f->end_mark = 0; + + if (count > 0) + avb_server_send_packet(mvrp->server, mvrp_mac, AVB_MVRP_ETH, + buffer, total); +} + +static const struct avb_mrp_events mrp_events = { + AVB_VERSION_MRP_EVENTS, + .event = mvrp_event, +}; + +struct avb_mvrp *avb_mvrp_register(struct server *server) +{ + struct mvrp *mvrp; + int fd, res; + + fd = avb_server_make_socket(server, AVB_MVRP_ETH, mvrp_mac); + if (fd < 0) { + errno = -fd; + return NULL; + } + mvrp = calloc(1, sizeof(*mvrp)); + if (mvrp == NULL) { + res = -errno; + goto error_close; + } + + mvrp->server = server; + spa_list_init(&mvrp->attributes); + + mvrp->source = pw_loop_add_io(server->impl->loop, fd, SPA_IO_IN, true, on_socket_data, mvrp); + if (mvrp->source == NULL) { + res = -errno; + pw_log_error("mvrp %p: can't create mvrp source: %m", mvrp); + goto error_no_source; + } + avdecc_server_add_listener(server, &mvrp->server_listener, &server_events, mvrp); + avb_mrp_add_listener(server->mrp, &mvrp->mrp_listener, &mrp_events, mvrp); + + return (struct avb_mvrp*)mvrp; + +error_no_source: + free(mvrp); +error_close: + close(fd); + errno = -res; + return NULL; +}
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/mvrp.h
Added
@@ -0,0 +1,62 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_MVRP_H +#define AVB_MVRP_H + +#include "mrp.h" +#include "internal.h" + +#define AVB_MVRP_ETH 0x88f5 +#define AVB_MVRP_MAC { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x21 }; + +struct avb_packet_mvrp_msg { + uint8_t attribute_type; + uint8_t attribute_length; + uint8_t attribute_list0; +} __attribute__ ((__packed__)); + +#define AVB_MVRP_ATTRIBUTE_TYPE_VID 1 +#define AVB_MVRP_ATTRIBUTE_TYPE_VALID(t) ((t)==1) + +struct avb_packet_mvrp_vid { + uint16_t vlan; +} __attribute__ ((__packed__)); + +struct avb_mvrp; + +struct avb_mvrp_attribute { + struct avb_mrp_attribute *mrp; + uint8_t type; + union { + struct avb_packet_mvrp_vid vid; + } attr; +}; + +struct avb_mvrp_attribute *avb_mvrp_attribute_new(struct avb_mvrp *mvrp, + uint8_t type); + +struct avb_mvrp *avb_mvrp_register(struct server *server); + +#endif /* AVB_MVRP_H */
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/packets.h
Added
@@ -0,0 +1,101 @@ +/* Spa AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_PACKETS_H +#define AVB_PACKETS_H + +#include <arpa/inet.h> + +#define AVB_SUBTYPE_61883_IIDC 0x00 +#define AVB_SUBTYPE_MMA_STREAM 0x01 +#define AVB_SUBTYPE_AAF 0x02 +#define AVB_SUBTYPE_CVF 0x03 +#define AVB_SUBTYPE_CRF 0x04 +#define AVB_SUBTYPE_TSCF 0x05 +#define AVB_SUBTYPE_SVF 0x06 +#define AVB_SUBTYPE_RVF 0x07 +#define AVB_SUBTYPE_AEF_CONTINUOUS 0x6E +#define AVB_SUBTYPE_VSF_STREAM 0x6F +#define AVB_SUBTYPE_EF_STREAM 0x7F +#define AVB_SUBTYPE_NTSCF 0x82 +#define AVB_SUBTYPE_ESCF 0xEC +#define AVB_SUBTYPE_EECF 0xED +#define AVB_SUBTYPE_AEF_DISCRETE 0xEE +#define AVB_SUBTYPE_ADP 0xFA +#define AVB_SUBTYPE_AECP 0xFB +#define AVB_SUBTYPE_ACMP 0xFC +#define AVB_SUBTYPE_MAAP 0xFE +#define AVB_SUBTYPE_EF_CONTROL 0xFF + +struct avb_ethernet_header { + uint8_t dest6; + uint8_t src6; + uint16_t type; +} __attribute__ ((__packed__)); + +struct avb_frame_header { + uint8_t dest6; + uint8_t src6; + uint16_t type; /* 802.1Q Virtual Lan 0x8100 */ + uint16_t prio_cfi_id; + uint16_t etype; +} __attribute__ ((__packed__)); + +struct avb_packet_header { + uint8_t subtype; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned sv:1; /* stream_id valid */ + unsigned version:3; + unsigned subtype_data1:4; + + unsigned subtype_data2:5; + unsigned len1:3; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned subtype_data1:4; + unsigned version:3; + unsigned sv:1; + + unsigned len1:3; + unsigned subtype_data2:5; +#elif +#error "Unknown byte order" +#endif + uint8_t len2:8; +} __attribute__ ((__packed__)); + +#define AVB_PACKET_SET_SUBTYPE(p,v) ((p)->subtype = (v)) +#define AVB_PACKET_SET_SV(p,v) ((p)->sv = (v)) +#define AVB_PACKET_SET_VERSION(p,v) ((p)->version = (v)) +#define AVB_PACKET_SET_SUB1(p,v) ((p)->subtype_data1 = (v)) +#define AVB_PACKET_SET_SUB2(p,v) ((p)->subtype_data2 = (v)) +#define AVB_PACKET_SET_LENGTH(p,v) ((p)->len1 = ((v) >> 8),(p)->len2 = (v)) + +#define AVB_PACKET_GET_SUBTYPE(p) ((p)->subtype) +#define AVB_PACKET_GET_SV(p) ((p)->sv) +#define AVB_PACKET_GET_VERSION(p) ((p)->version) +#define AVB_PACKET_GET_SUB1(p) ((p)->subtype_data1) +#define AVB_PACKET_GET_SUB2(p) ((p)->subtype_data2) +#define AVB_PACKET_GET_LENGTH(p) ((p)->len1 << 8 | (p)->len2) + +#endif /* AVB_PACKETS_H */
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/srp.c
Added
@@ -0,0 +1,59 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <pipewire/pipewire.h> + +#include "srp.h" + +struct srp { + struct server *server; + struct spa_hook server_listener; +}; + +static void srp_destroy(void *data) +{ + struct srp *srp = data; + spa_hook_remove(&srp->server_listener); + free(srp); +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = srp_destroy, +}; + +int avb_srp_register(struct server *server) +{ + struct srp *srp; + + srp = calloc(1, sizeof(*srp)); + if (srp == NULL) + return -errno; + + srp->server = server; + + avdecc_server_add_listener(server, &srp->server_listener, &server_events, srp); + + return 0; +}
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/srp.h
Added
@@ -0,0 +1,32 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_SRP_H +#define AVB_SRP_H + +#include "internal.h" + +int avb_srp_register(struct server *server); + +#endif /* AVB_SRP_H */
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/stream.c
Added
@@ -0,0 +1,589 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <unistd.h> +#include <linux/if_ether.h> +#include <linux/if_packet.h> +#include <linux/net_tstamp.h> +#include <net/if.h> +#include <sys/ioctl.h> + +#include <spa/debug/mem.h> +#include <spa/pod/builder.h> +#include <spa/param/audio/format-utils.h> + +#include "iec61883.h" +#include "stream.h" +#include "utils.h" +#include "aecp-aem-descriptors.h" + +static void on_stream_destroy(void *d) +{ + struct stream *stream = d; + spa_hook_remove(&stream->stream_listener); + stream->stream = NULL; +} + +static void on_source_stream_process(void *data) +{ + struct stream *stream = data; + struct pw_buffer *buf; + struct spa_data *d; + uint32_t index, n_bytes; + int32_t avail, wanted; + + if ((buf = pw_stream_dequeue_buffer(stream->stream)) == NULL) { + pw_log_debug("out of buffers: %m"); + return; + } + + d = buf->buffer->datas; + + wanted = buf->requested ? buf->requested * stream->stride : d0.maxsize; + + n_bytes = SPA_MIN(d0.maxsize, (uint32_t)wanted); + + avail = spa_ringbuffer_get_read_index(&stream->ring, &index); + + if (avail < wanted) { + pw_log_debug("capture underrun %d < %d", avail, wanted); + memset(d0.data, 0, n_bytes); + } else { + spa_ringbuffer_read_data(&stream->ring, + stream->buffer_data, + stream->buffer_size, + index % stream->buffer_size, + d0.data, n_bytes); + index += n_bytes; + spa_ringbuffer_read_update(&stream->ring, index); + } + + d0.chunk->size = n_bytes; + d0.chunk->stride = stream->stride; + d0.chunk->offset = 0; + buf->size = n_bytes / stream->stride; + + pw_stream_queue_buffer(stream->stream, buf); +} + +static const struct pw_stream_events source_stream_events = { + PW_VERSION_STREAM_EVENTS, + .destroy = on_stream_destroy, + .process = on_source_stream_process +}; + +static inline void +set_iovec(struct spa_ringbuffer *rbuf, void *buffer, uint32_t size, + uint32_t offset, struct iovec *iov, uint32_t len) +{ + iov0.iov_len = SPA_MIN(len, size - offset); + iov0.iov_base = SPA_PTROFF(buffer, offset, void); + iov1.iov_len = len - iov0.iov_len; + iov1.iov_base = buffer; +} + +static int flush_write(struct stream *stream, uint64_t current_time) +{ + int32_t avail; + uint32_t index; + uint64_t ptime, txtime; + int pdu_count; + ssize_t n; + struct avb_frame_header *h = (void*)stream->pdu; + struct avb_packet_iec61883 *p = SPA_PTROFF(h, sizeof(*h), void); + uint8_t dbc; + + avail = spa_ringbuffer_get_read_index(&stream->ring, &index); + + pdu_count = (avail / stream->stride) / stream->frames_per_pdu; + + txtime = current_time + stream->t_uncertainty; + ptime = txtime + stream->mtt; + dbc = stream->dbc; + + while (pdu_count--) { + *(uint64_t*)CMSG_DATA(stream->cmsg) = txtime; + + set_iovec(&stream->ring, + stream->buffer_data, + stream->buffer_size, + index % stream->buffer_size, + &stream->iov1, stream->payload_size); + + p->seq_num = stream->pdu_seq++; + p->tv = 1; + p->timestamp = ptime; + p->dbc = dbc; + + n = sendmsg(stream->source->fd, &stream->msg, 0); + if (n < 0 || n != (ssize_t)stream->pdu_size) { + pw_log_error("sendmsg() failed %zd != %zd: %m", + n, stream->pdu_size); + } + txtime += stream->pdu_period; + ptime += stream->pdu_period; + index += stream->payload_size; + dbc += stream->frames_per_pdu; + } + stream->dbc = dbc; + spa_ringbuffer_read_update(&stream->ring, index); + return 0; +} + +static void on_sink_stream_process(void *data) +{ + struct stream *stream = data; + struct pw_buffer *buf; + struct spa_data *d; + int32_t filled; + uint32_t index, offs, avail, size; + struct timespec now; + + if ((buf = pw_stream_dequeue_buffer(stream->stream)) == NULL) { + pw_log_debug("out of buffers: %m"); + return; + } + + d = buf->buffer->datas; + + offs = SPA_MIN(d0.chunk->offset, d0.maxsize); + size = SPA_MIN(d0.chunk->size, d0.maxsize - offs); + avail = size - offs; + + filled = spa_ringbuffer_get_write_index(&stream->ring, &index); + + if (filled >= (int32_t)stream->buffer_size) { + pw_log_warn("playback overrun %d >= %zd", filled, stream->buffer_size); + } else { + spa_ringbuffer_write_data(&stream->ring, + stream->buffer_data, + stream->buffer_size, + index % stream->buffer_size, + SPA_PTROFF(d0.data, offs, void), avail); + index += avail; + spa_ringbuffer_write_update(&stream->ring, index); + } + pw_stream_queue_buffer(stream->stream, buf); + + clock_gettime(CLOCK_TAI, &now); + flush_write(stream, SPA_TIMESPEC_TO_NSEC(&now)); +} + +static void setup_pdu(struct stream *stream) +{ + struct avb_frame_header *h; + struct avb_packet_iec61883 *p; + ssize_t payload_size, hdr_size, pdu_size; + + spa_memzero(stream->pdu, sizeof(stream->pdu)); + h = (struct avb_frame_header*)stream->pdu; + p = SPA_PTROFF(h, sizeof(*h), void); + + hdr_size = sizeof(*h) + sizeof(*p); + payload_size = stream->stride * stream->frames_per_pdu; + pdu_size = hdr_size + payload_size; + + h->type = htons(0x8100); + h->prio_cfi_id = htons((stream->prio << 13) | stream->vlan_id); + h->etype = htons(0x22f0); + + if (stream->direction == SPA_DIRECTION_OUTPUT) { + p->subtype = AVB_SUBTYPE_61883_IIDC; + p->sv = 1; + p->stream_id = htobe64(stream->id); + p->data_len = htons(payload_size+8); + p->tag = 0x1; + p->channel = 0x1f; + p->tcode = 0xa; + p->sid = 0x3f; + p->dbs = stream->info.info.raw.channels; + p->qi2 = 0x2; + p->format_id = 0x10; + p->fdf = 0x2; + p->syt = htons(0x0008); + } + stream->hdr_size = hdr_size; + stream->payload_size = payload_size; + stream->pdu_size = pdu_size; +} + +static int setup_msg(struct stream *stream) +{ + stream->iov0.iov_base = stream->pdu; + stream->iov0.iov_len = stream->hdr_size; + stream->iov1.iov_base = SPA_PTROFF(stream->pdu, stream->hdr_size, void); + stream->iov1.iov_len = stream->payload_size; + stream->iov2.iov_base = SPA_PTROFF(stream->pdu, stream->hdr_size, void); + stream->iov2.iov_len = 0; + stream->msg.msg_name = &stream->sock_addr; + stream->msg.msg_namelen = sizeof(stream->sock_addr); + stream->msg.msg_iov = stream->iov; + stream->msg.msg_iovlen = 3; + stream->msg.msg_control = stream->control; + stream->msg.msg_controllen = sizeof(stream->control); + stream->cmsg = CMSG_FIRSTHDR(&stream->msg); + stream->cmsg->cmsg_level = SOL_SOCKET; + stream->cmsg->cmsg_type = SCM_TXTIME; + stream->cmsg->cmsg_len = CMSG_LEN(sizeof(__u64)); + return 0; +} + +static const struct pw_stream_events sink_stream_events = { + PW_VERSION_STREAM_EVENTS, + .destroy = on_stream_destroy, + .process = on_sink_stream_process +}; + +struct stream *server_create_stream(struct server *server, + enum spa_direction direction, uint16_t index) +{ + struct stream *stream; + const struct descriptor *desc; + uint32_t n_params; + const struct spa_pod *params1; + uint8_t buffer1024; + struct spa_pod_builder b; + int res; + + desc = server_find_descriptor(server, + direction == SPA_DIRECTION_INPUT ? + AVB_AEM_DESC_STREAM_INPUT : + AVB_AEM_DESC_STREAM_OUTPUT, index); + if (desc == NULL) + return NULL; + + stream = calloc(1, sizeof(*stream)); + if (stream == NULL) + return NULL; + + stream->server = server; + stream->direction = direction; + stream->index = index; + stream->desc = desc; + spa_list_append(&server->streams, &stream->link); + + stream->prio = AVB_MSRP_PRIORITY_DEFAULT; + stream->vlan_id = AVB_DEFAULT_VLAN; + + stream->id = (uint64_t)server->mac_addr0 << 56 | + (uint64_t)server->mac_addr1 << 48 | + (uint64_t)server->mac_addr2 << 40 | + (uint64_t)server->mac_addr3 << 32 | + (uint64_t)server->mac_addr4 << 24 | + (uint64_t)server->mac_addr5 << 16 | + htons(index); + + stream->vlan_attr = avb_mvrp_attribute_new(server->mvrp, + AVB_MVRP_ATTRIBUTE_TYPE_VID); + stream->vlan_attr->attr.vid.vlan = htons(stream->vlan_id); + + stream->buffer_data = calloc(1, BUFFER_SIZE); + stream->buffer_size = BUFFER_SIZE; + spa_ringbuffer_init(&stream->ring); + + if (direction == SPA_DIRECTION_INPUT) { + stream->stream = pw_stream_new(server->impl->core, "source", + pw_properties_new( + PW_KEY_MEDIA_CLASS, "Audio/Source", + PW_KEY_NODE_NAME, "avb.source", + PW_KEY_NODE_DESCRIPTION, "AVB Source", + PW_KEY_NODE_WANT_DRIVER, "true", + NULL)); + } else { + stream->stream = pw_stream_new(server->impl->core, "sink", + pw_properties_new( + PW_KEY_MEDIA_CLASS, "Audio/Sink", + PW_KEY_NODE_NAME, "avb.sink", + PW_KEY_NODE_DESCRIPTION, "AVB Sink", + PW_KEY_NODE_WANT_DRIVER, "true", + NULL)); + } + if (stream->stream == NULL) + goto error_free; + + pw_stream_add_listener(stream->stream, + &stream->stream_listener, + direction == SPA_DIRECTION_INPUT ? + &source_stream_events : + &sink_stream_events, + stream); + + stream->info.info.raw.format = SPA_AUDIO_FORMAT_S24_32_BE; + stream->info.info.raw.flags = SPA_AUDIO_FLAG_UNPOSITIONED; + stream->info.info.raw.rate = 48000; + stream->info.info.raw.channels = 8; + stream->stride = stream->info.info.raw.channels * 4; + + n_params = 0; + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + paramsn_params++ = spa_format_audio_raw_build(&b, + SPA_PARAM_EnumFormat, &stream->info.info.raw); + + if ((res = pw_stream_connect(stream->stream, + pw_direction_reverse(direction), + PW_ID_ANY, + PW_STREAM_FLAG_MAP_BUFFERS | + PW_STREAM_FLAG_INACTIVE | + PW_STREAM_FLAG_RT_PROCESS, + params, n_params)) < 0) + goto error_free_stream; + + stream->frames_per_pdu = 6; + stream->pdu_period = SPA_NSEC_PER_SEC * stream->frames_per_pdu / + stream->info.info.raw.rate; + + setup_pdu(stream); + setup_msg(stream); + + stream->listener_attr = avb_msrp_attribute_new(server->msrp, + AVB_MSRP_ATTRIBUTE_TYPE_LISTENER); + stream->talker_attr = avb_msrp_attribute_new(server->msrp, + AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE); + stream->talker_attr->attr.talker.vlan_id = htons(stream->vlan_id); + stream->talker_attr->attr.talker.tspec_max_frame_size = htons(32 + stream->frames_per_pdu * stream->stride); + stream->talker_attr->attr.talker.tspec_max_interval_frames = + htons(AVB_MSRP_TSPEC_MAX_INTERVAL_FRAMES_DEFAULT); + stream->talker_attr->attr.talker.priority = stream->prio; + stream->talker_attr->attr.talker.rank = AVB_MSRP_RANK_DEFAULT; + stream->talker_attr->attr.talker.accumulated_latency = htonl(95); + + return stream; + +error_free_stream: + pw_stream_destroy(stream->stream); + errno = -res; +error_free: + free(stream); + return NULL; +} + +void stream_destroy(struct stream *stream) +{ + avb_mrp_attribute_destroy(stream->listener_attr->mrp); + spa_list_remove(&stream->link); + free(stream); +} + +static int setup_socket(struct stream *stream) +{ + struct server *server = stream->server; + int fd, res; + char buf128; + struct ifreq req; + + fd = socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, htons(ETH_P_ALL)); + if (fd < 0) { + pw_log_error("socket() failed: %m"); + return -errno; + } + + spa_zero(req); + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", server->ifname); + res = ioctl(fd, SIOCGIFINDEX, &req); + if (res < 0) { + pw_log_error("SIOCGIFINDEX %s failed: %m", server->ifname); + res = -errno; + goto error_close; + } + + spa_zero(stream->sock_addr); + stream->sock_addr.sll_family = AF_PACKET; + stream->sock_addr.sll_protocol = htons(ETH_P_TSN); + stream->sock_addr.sll_ifindex = req.ifr_ifindex; + + if (stream->direction == SPA_DIRECTION_OUTPUT) { + struct sock_txtime txtime_cfg; + + res = setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &stream->prio, + sizeof(stream->prio)); + if (res < 0) { + pw_log_error("setsockopt(SO_PRIORITY %d) failed: %m", stream->prio); + res = -errno; + goto error_close; + } + + txtime_cfg.clockid = CLOCK_TAI; + txtime_cfg.flags = 0; + res = setsockopt(fd, SOL_SOCKET, SO_TXTIME, &txtime_cfg, + sizeof(txtime_cfg)); + if (res < 0) { + pw_log_error("setsockopt(SO_TXTIME) failed: %m"); + res = -errno; + goto error_close; + } + } else { + struct packet_mreq mreq; + + res = bind(fd, (struct sockaddr *) &stream->sock_addr, sizeof(stream->sock_addr)); + if (res < 0) { + pw_log_error("bind() failed: %m"); + res = -errno; + goto error_close; + } + + spa_zero(mreq); + mreq.mr_ifindex = req.ifr_ifindex; + mreq.mr_type = PACKET_MR_MULTICAST; + mreq.mr_alen = ETH_ALEN; + memcpy(&mreq.mr_address, stream->addr, ETH_ALEN); + res = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, + &mreq, sizeof(struct packet_mreq)); + + pw_log_info("join %s", avb_utils_format_addr(buf, 128, stream->addr)); + + if (res < 0) { + pw_log_error("setsockopt(ADD_MEMBERSHIP) failed: %m"); + res = -errno; + goto error_close; + } + } + return fd; + +error_close: + close(fd); + return res; +} + +static void handle_iec61883_packet(struct stream *stream, + struct avb_packet_iec61883 *p, int len) +{ + uint32_t index, n_bytes; + int32_t filled; + + filled = spa_ringbuffer_get_write_index(&stream->ring, &index); + n_bytes = ntohs(p->data_len) - 8; + + if (filled + n_bytes > stream->buffer_size) { + pw_log_debug("capture overrun"); + } else { + spa_ringbuffer_write_data(&stream->ring, + stream->buffer_data, + stream->buffer_size, + index % stream->buffer_size, + p->payload, n_bytes); + index += n_bytes; + spa_ringbuffer_write_update(&stream->ring, index); + } +} + +static void on_socket_data(void *data, int fd, uint32_t mask) +{ + struct stream *stream = data; + + if (mask & SPA_IO_IN) { + int len; + uint8_t buffer2048; + + len = recv(fd, buffer, sizeof(buffer), 0); + + if (len < 0) { + pw_log_warn("got recv error: %m"); + } + else if (len < (int)sizeof(struct avb_packet_header)) { + pw_log_warn("short packet received (%d < %d)", len, + (int)sizeof(struct avb_packet_header)); + } else { + struct avb_frame_header *h = (void*)buffer; + struct avb_packet_iec61883 *p = SPA_PTROFF(h, sizeof(*h), void); + + if (memcmp(h->dest, stream->addr, 6) != 0 || + p->subtype != AVB_SUBTYPE_61883_IIDC) + return; + + handle_iec61883_packet(stream, p, len - sizeof(*h)); + } + } +} + +int stream_activate(struct stream *stream, uint64_t now) +{ + struct server *server = stream->server; + struct avb_frame_header *h = (void*)stream->pdu; + int fd, res; + + if (stream->source == NULL) { + if ((fd = setup_socket(stream)) < 0) + return fd; + + stream->source = pw_loop_add_io(server->impl->loop, fd, + SPA_IO_IN, true, on_socket_data, stream); + if (stream->source == NULL) { + res = -errno; + pw_log_error("stream %p: can't create source: %m", stream); + close(fd); + return res; + } + } + + avb_mrp_attribute_begin(stream->vlan_attr->mrp, now); + avb_mrp_attribute_join(stream->vlan_attr->mrp, now, true); + + if (stream->direction == SPA_DIRECTION_INPUT) { + stream->listener_attr->attr.listener.stream_id = htobe64(stream->peer_id); + stream->listener_attr->param = AVB_MSRP_LISTENER_PARAM_READY; + avb_mrp_attribute_begin(stream->listener_attr->mrp, now); + avb_mrp_attribute_join(stream->listener_attr->mrp, now, true); + + stream->talker_attr->attr.talker.stream_id = htobe64(stream->peer_id); + avb_mrp_attribute_begin(stream->talker_attr->mrp, now); + } else { + if ((res = avb_maap_get_address(server->maap, stream->addr, stream->index)) < 0) + return res; + + stream->listener_attr->attr.listener.stream_id = htobe64(stream->id); + stream->listener_attr->param = AVB_MSRP_LISTENER_PARAM_IGNORE; + avb_mrp_attribute_begin(stream->listener_attr->mrp, now); + + stream->talker_attr->attr.talker.stream_id = htobe64(stream->id); + memcpy(stream->talker_attr->attr.talker.dest_addr, stream->addr, 6); + + stream->sock_addr.sll_halen = ETH_ALEN; + memcpy(&stream->sock_addr.sll_addr, stream->addr, ETH_ALEN); + memcpy(h->dest, stream->addr, 6); + memcpy(h->src, server->mac_addr, 6); + avb_mrp_attribute_begin(stream->talker_attr->mrp, now); + avb_mrp_attribute_join(stream->talker_attr->mrp, now, true); + } + pw_stream_set_active(stream->stream, true); + return 0; +} + +int stream_deactivate(struct stream *stream, uint64_t now) +{ + pw_stream_set_active(stream->stream, false); + + if (stream->source != NULL) { + pw_loop_destroy_source(stream->server->impl->loop, stream->source); + stream->source = NULL; + } + + avb_mrp_attribute_leave(stream->vlan_attr->mrp, now); + + if (stream->direction == SPA_DIRECTION_INPUT) { + avb_mrp_attribute_leave(stream->listener_attr->mrp, now); + } else { + avb_mrp_attribute_leave(stream->talker_attr->mrp, now); + } + return 0; +}
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/stream.h
Added
@@ -0,0 +1,104 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_STREAM_H +#define AVB_STREAM_H + +#include <sys/socket.h> +#include <sys/types.h> +#include <linux/if_packet.h> +#include <net/if.h> + +#include <spa/utils/ringbuffer.h> +#include <spa/param/audio/format.h> + +#include <pipewire/pipewire.h> + +#define BUFFER_SIZE (1u<<16) +#define BUFFER_MASK (BUFFER_SIZE-1) + +struct stream { + struct spa_list link; + + struct server *server; + + uint16_t direction; + uint16_t index; + const struct descriptor *desc; + uint64_t id; + uint64_t peer_id; + + struct pw_stream *stream; + struct spa_hook stream_listener; + + uint8_t addr6; + struct spa_source *source; + int prio; + int vlan_id; + int mtt; + int t_uncertainty; + uint32_t frames_per_pdu; + int ptime_tolerance; + + uint8_t pdu2048; + size_t hdr_size; + size_t payload_size; + size_t pdu_size; + int64_t pdu_period; + uint8_t pdu_seq; + uint8_t prev_seq; + uint8_t dbc; + + struct iovec iov3; + struct sockaddr_ll sock_addr; + struct msghdr msg; + char controlCMSG_SPACE(sizeof(uint64_t)); + struct cmsghdr *cmsg; + + struct spa_ringbuffer ring; + void *buffer_data; + size_t buffer_size; + + uint64_t format; + uint32_t stride; + struct spa_audio_info info; + + struct avb_msrp_attribute *talker_attr; + struct avb_msrp_attribute *listener_attr; + struct avb_mvrp_attribute *vlan_attr; +}; + +#include "msrp.h" +#include "mvrp.h" +#include "maap.h" + +struct stream *server_create_stream(struct server *server, + enum spa_direction direction, uint16_t index); + +void stream_destroy(struct stream *stream); + +int stream_activate(struct stream *stream, uint64_t now); +int stream_deactivate(struct stream *stream, uint64_t now); + +#endif /* AVB_STREAM_H */
View file
pipewire-0.3.56.tar.gz/src/modules/module-avb/utils.h
Added
@@ -0,0 +1,86 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_UTILS_H +#define AVB_UTILS_H + +#include <spa/utils/json.h> + +#include "internal.h" + +static inline char *avb_utils_format_id(char *str, size_t size, const uint64_t id) +{ + snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x:%04x", + (uint8_t)(id >> 56), + (uint8_t)(id >> 48), + (uint8_t)(id >> 40), + (uint8_t)(id >> 32), + (uint8_t)(id >> 24), + (uint8_t)(id >> 16), + (uint16_t)(id)); + return str; +} + +static inline int avb_utils_parse_id(const char *str, int len, uint64_t *id) +{ + char s64; + uint8_t v6; + uint16_t unique_id; + if (spa_json_parse_stringn(str, len, s, sizeof(s)) <= 0) + return -EINVAL; + if (sscanf(s, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx:%hx", + &v0, &v1, &v2, &v3, + &v4, &v5, &unique_id) == 7) { + *id = (uint64_t) v0 << 56 | + (uint64_t) v1 << 48 | + (uint64_t) v2 << 40 | + (uint64_t) v3 << 32 | + (uint64_t) v4 << 24 | + (uint64_t) v5 << 16 | + unique_id; + } else if (!spa_atou64(str, id, 0)) + return -EINVAL; + return 0; +} + +static inline char *avb_utils_format_addr(char *str, size_t size, const uint8_t addr6) +{ + snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x", + addr0, addr1, addr2, addr3, addr4, addr5); + return str; +} +static inline int avb_utils_parse_addr(const char *str, int len, uint8_t addr6) +{ + char s64; + uint8_t v6; + if (spa_json_parse_stringn(str, len, s, sizeof(s)) <= 0) + return -EINVAL; + if (sscanf(s, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &v0, &v1, &v2, &v3, &v4, &v5) != 6) + return -EINVAL; + memcpy(addr, v, 6); + return 0; +} + +#endif /* AVB_UTILS_H */
View file
pipewire-0.3.54.tar.gz/src/modules/module-filter-chain.c -> pipewire-0.3.56.tar.gz/src/modules/module-filter-chain.c
Changed
@@ -591,6 +591,8 @@ struct graph *graph = &impl->graph; uint32_t i, outsize = 0, n_hndl = graph->n_hndl; int32_t stride = 0; + struct graph_port *port; + struct spa_data *bd; if ((in = pw_stream_dequeue_buffer(impl->capture)) == NULL) pw_log_debug("out of capture buffers: %m"); @@ -602,30 +604,37 @@ goto done; for (i = 0; i < in->buffer->n_datas; i++) { - struct spa_data *ds = &in->buffer->datasi; - struct graph_port *port = &graph->inputi; uint32_t offs, size; - offs = SPA_MIN(ds->chunk->offset, ds->maxsize); - size = SPA_MIN(ds->chunk->size, ds->maxsize - offs); + bd = &in->buffer->datasi; - if (port->desc) + offs = SPA_MIN(bd->chunk->offset, bd->maxsize); + size = SPA_MIN(bd->chunk->size, bd->maxsize - offs); + + port = i < graph->n_input ? &graph->inputi : NULL; + + if (port && port->desc) port->desc->connect_port(port->hndl, port->port, - SPA_PTROFF(ds->data, offs, void)); + SPA_PTROFF(bd->data, offs, void)); - outsize = SPA_MAX(outsize, size); - stride = SPA_MAX(stride, ds->chunk->stride); + outsize = i == 0 ? size : SPA_MIN(outsize, size); + stride = SPA_MAX(stride, bd->chunk->stride); } for (i = 0; i < out->buffer->n_datas; i++) { - struct spa_data *dd = &out->buffer->datasi; - struct graph_port *port = &graph->outputi; - if (port->desc) - port->desc->connect_port(port->hndl, port->port, dd->data); + bd = &out->buffer->datasi; + + outsize = SPA_MIN(outsize, bd->maxsize); + + port = i < graph->n_output ? &graph->outputi : NULL; + + if (port && port->desc) + port->desc->connect_port(port->hndl, port->port, bd->data); else - memset(dd->data, 0, outsize); - dd->chunk->offset = 0; - dd->chunk->size = outsize; - dd->chunk->stride = stride; + memset(bd->data, 0, outsize); + + bd->chunk->offset = 0; + bd->chunk->size = outsize; + bd->chunk->stride = stride; } for (i = 0; i < n_hndl; i++) { struct graph_hndl *hndl = &graph->hndli;
View file
pipewire-0.3.54.tar.gz/src/modules/module-loopback.c -> pipewire-0.3.56.tar.gz/src/modules/module-loopback.c
Changed
@@ -99,7 +99,8 @@ * playback.props = { * node.name = "playback.CM106_stereo_pair_2" * audio.position = RL RR - * node.target = "alsa_output.usb-0d8c_USB_Sound_Device-00.analog-surround-71" + * target.object = "alsa_output.usb-0d8c_USB_Sound_Device-00.analog-surround-71" + * node.dont-reconnect = true * stream.dont-remix = true * node.passive = true * } @@ -188,33 +189,35 @@ pw_log_debug("out of playback buffers: %m"); if (in != NULL && out != NULL) { + uint32_t outsize = UINT32_MAX; + int32_t stride = 0; + struct spa_data *d; + const void *srcin->buffer->n_datas; - for (i = 0; i < out->buffer->n_datas; i++) { - struct spa_data *ds, *dd; - uint32_t outsize = 0; - int32_t stride = 0; - - dd = &out->buffer->datasi; + for (i = 0; i < in->buffer->n_datas; i++) { + uint32_t offs, size; - if (i < in->buffer->n_datas) { - uint32_t offs, size; + d = &in->buffer->datasi; + offs = SPA_MIN(d->chunk->offset, d->maxsize); + size = SPA_MIN(d->chunk->size, d->maxsize - offs); - ds = &in->buffer->datasi; + srci = SPA_PTROFF(d->data, offs, void); + outsize = SPA_MIN(outsize, size); + stride = SPA_MAX(stride, d->chunk->stride); + } + for (i = 0; i < out->buffer->n_datas; i++) { + d = &out->buffer->datasi; - offs = SPA_MIN(ds->chunk->offset, ds->maxsize); - size = SPA_MIN(ds->chunk->size, ds->maxsize - offs); - stride = SPA_MAX(stride, stride); + outsize = SPA_MIN(outsize, d->maxsize); - memcpy(dd->data, - SPA_PTROFF(ds->data, offs, void), size); + if (i < in->buffer->n_datas) + memcpy(d->data, srci, outsize); + else + memset(d->data, 0, outsize); - outsize = SPA_MAX(outsize, size); - } else { - memset(dd->data, 0, outsize); - } - dd->chunk->offset = 0; - dd->chunk->size = outsize; - dd->chunk->stride = stride; + d->chunk->offset = 0; + d->chunk->size = outsize; + d->chunk->stride = stride; } }
View file
pipewire-0.3.54.tar.gz/src/modules/module-pipe-tunnel.c -> pipewire-0.3.56.tar.gz/src/modules/module-pipe-tunnel.c
Changed
@@ -94,7 +94,7 @@ * - \ref PW_KEY_NODE_GROUP * - \ref PW_KEY_NODE_VIRTUAL * - \ref PW_KEY_MEDIA_CLASS - * - \ref PW_KEY_NODE_TARGET to specify the remote name or id to link to + * - \ref PW_KEY_TARGET_OBJECT to specify the remote name or serial id to link to * * When not otherwise specified, the pipe will accept or produce a * 16 bits, stereo, 48KHz sample stream. @@ -112,7 +112,7 @@ * #audio.rate=<sample rate> * #audio.channels=<number of channels> * #audio.position=<channel map> - * #node.target=<remote target node> + * #target.object=<remote target node> * stream.props = { * # extra sink properties * } @@ -139,7 +139,7 @@ " node.latency=<latency as fraction> " \ " node.name=<name of the nodes> " \ " node.description=<description of the nodes> " \ - " node.target=<remote node target name> " \ + " target.object=<remote node target name> " \ " audio.format=<sample format> " \ " audio.rate=<sample rate> " \ " audio.channels=<number of channels> " \
View file
pipewire-0.3.54.tar.gz/src/modules/module-protocol-pulse/pulse-server.c -> pipewire-0.3.56.tar.gz/src/modules/module-protocol-pulse/pulse-server.c
Changed
@@ -1277,7 +1277,7 @@ stream->timestamp = pd->pwt.now; stream->delay = pd->pwt.buffered * SPA_USEC_PER_SEC / stream->ss.rate; if (pd->pwt.rate.denom > 0) - stream->delay = pd->pwt.delay * SPA_USEC_PER_SEC / pd->pwt.rate.denom; + stream->delay += pd->pwt.delay * SPA_USEC_PER_SEC * pd->pwt.rate.num / pd->pwt.rate.denom; if (stream->direction == PW_DIRECTION_OUTPUT) { if (pd->quantum != stream->last_quantum) @@ -1332,6 +1332,7 @@ stream->read_index += skip; avail = stream->attr.fragsize; } + pw_log_trace("avail:%d index:%u", avail, index); while ((uint32_t)avail >= stream->attr.fragsize) { towrite = SPA_MIN((uint32_t)avail, stream->attr.fragsize); @@ -2124,7 +2125,7 @@ if (stream == NULL || stream->type != STREAM_TYPE_PLAYBACK) return -ENOENT; - pw_log_debug("read:%"PRIx64" write:%"PRIx64" queued:%"PRIi64" delay:%"PRIi64 + pw_log_debug("read:0x%"PRIx64" write:0x%"PRIx64" queued:%"PRIi64" delay:%"PRIi64 " playing:%"PRIu64, stream->read_index, stream->write_index, stream->write_index - stream->read_index, stream->delay, @@ -2173,6 +2174,11 @@ if (stream == NULL || stream->type != STREAM_TYPE_RECORD) return -ENOENT; + pw_log_debug("read:0x%"PRIx64" write:0x%"PRIx64" queued:%"PRIi64" delay:%"PRIi64, + stream->read_index, stream->write_index, + stream->write_index - stream->read_index, stream->delay); + + gettimeofday(&now, NULL); reply = reply_new(client, tag); message_put(reply,
View file
pipewire-0.3.54.tar.gz/src/modules/module-pulse-tunnel.c -> pipewire-0.3.56.tar.gz/src/modules/module-pulse-tunnel.c
Changed
@@ -90,7 +90,7 @@ * - \ref PW_KEY_NODE_GROUP * - \ref PW_KEY_NODE_VIRTUAL * - \ref PW_KEY_MEDIA_CLASS - * - \ref PW_KEY_NODE_TARGET to specify the remote name or id to link to + * - \ref PW_KEY_TARGET_OBJECT to specify the remote node.name or serial.id to link to * * ## Example configuration of a virtual sink * @@ -104,7 +104,7 @@ * #audio.rate=<sample rate> * #audio.channels=<number of channels> * #audio.position=<channel map> - * #node.target=<remote target node> + * #target.object=<remote target name> * stream.props = { * # extra sink properties * } @@ -660,7 +660,9 @@ pa_stream_set_overflow_callback(impl->pa_stream, stream_overflow_cb, impl); pa_stream_set_latency_update_callback(impl->pa_stream, stream_latency_update_cb, impl); - remote_node_target = pw_properties_get(impl->props, PW_KEY_NODE_TARGET); + remote_node_target = pw_properties_get(impl->props, PW_KEY_TARGET_OBJECT); + if (remote_node_target == NULL) + remote_node_target = pw_properties_get(impl->props, PW_KEY_NODE_TARGET); bufferattr.fragsize = (uint32_t) -1; bufferattr.minreq = (uint32_t) -1;
View file
pipewire-0.3.54.tar.gz/src/modules/module-raop-discover.c -> pipewire-0.3.56.tar.gz/src/modules/module-raop-discover.c
Changed
@@ -223,6 +223,8 @@ * 4 = FairPlay SAPv2.5. */ if (str_in_list(value, ",", "1")) value = "RSA"; + else if (str_in_list(value, ",", "4")) + value = "auth_setup"; else value = "none"; pw_properties_set(props, "raop.encryption.type", value);
View file
pipewire-0.3.54.tar.gz/src/modules/module-raop-sink.c -> pipewire-0.3.56.tar.gz/src/modules/module-raop-sink.c
Changed
@@ -130,6 +130,7 @@ enum { CRYPTO_NONE, CRYPTO_RSA, + CRYPTO_AUTH_SETUP, }; enum { CODEC_PCM, @@ -257,7 +258,8 @@ return timespec_to_ntp(&now); } -static int send_udp_sync_packet(struct impl *impl) +static int send_udp_sync_packet(struct impl *impl, + struct sockaddr *dest_addr, socklen_t addrlen) { uint32_t pkt5; uint32_t rtptime = impl->rtptime; @@ -278,10 +280,11 @@ pw_log_debug("sync: delayed:%u now:%"PRIu64" rtptime:%u", rtptime - delay, transmitted, rtptime); - return write(impl->control_fd, pkt, sizeof(pkt)); + return sendto(impl->control_fd, pkt, sizeof(pkt), 0, dest_addr, addrlen); } -static int send_udp_timing_packet(struct impl *impl, uint64_t remote, uint64_t received) +static int send_udp_timing_packet(struct impl *impl, uint64_t remote, uint64_t received, + struct sockaddr *dest_addr, socklen_t addrlen) { uint32_t pkt8; uint64_t transmitted; @@ -299,7 +302,7 @@ pw_log_debug("sync: remote:%"PRIu64" received:%"PRIu64" transmitted:%"PRIu64, remote, received, transmitted); - return write(impl->timing_fd, pkt, sizeof(pkt)); + return sendto(impl->timing_fd, pkt, sizeof(pkt), 0, dest_addr, addrlen); } static int write_codec_pcm(void *dst, void *frames, uint32_t n_frames) @@ -345,7 +348,7 @@ impl->sync++; if (impl->first || impl->sync == impl->sync_period) { impl->sync = 0; - send_udp_sync_packet(impl); + send_udp_sync_packet(impl, NULL, 0); } pkt0 = htonl(0x80600000); if (impl->first) @@ -373,7 +376,7 @@ impl->seq = (impl->seq + 1) & 0xffff; pw_log_debug("send %u", len + 12); - res = write(impl->server_fd, pkt, len + 12); + res = send(impl->server_fd, pkt, len + 12, 0); impl->first = false; @@ -417,7 +420,7 @@ impl->seq = (impl->seq + 1) & 0xffff; pw_log_debug("send %u", len + 16); - res = write(impl->server_fd, pkt, len + 16); + res = send(impl->server_fd, pkt, len + 16, 0); impl->first = false; @@ -593,9 +596,12 @@ if (mask & SPA_IO_IN) { uint64_t remote, received; + struct sockaddr_storage sender; + socklen_t sender_size = sizeof(sender); received = ntp_now(CLOCK_MONOTONIC); - bytes = read(impl->timing_fd, packet, sizeof(packet)); + bytes = recvfrom(impl->timing_fd, packet, sizeof(packet), 0, + (struct sockaddr*)&sender, &sender_size); if (bytes < 0) { pw_log_debug("error reading timing packet: %m"); return; @@ -609,7 +615,11 @@ return; remote = ((uint64_t)ntohl(packet6)) << 32 | ntohl(packet7); - send_udp_timing_packet(impl, remote, received); + if (send_udp_timing_packet(impl, remote, received, + (struct sockaddr *)&sender, sender_size) < 0) { + pw_log_warn("error sending timing packet"); + return; + } } } @@ -833,10 +843,8 @@ return; ntp = ntp_now(CLOCK_MONOTONIC); - send_udp_timing_packet(impl, ntp, ntp); + send_udp_timing_packet(impl, ntp, ntp, NULL, 0); - impl->timing_source = pw_loop_add_io(impl->loop, impl->timing_fd, - SPA_IO_IN, false, on_timing_source_io, impl); impl->control_source = pw_loop_add_io(impl->loop, impl->control_fd, SPA_IO_IN, false, on_control_source_io, impl); @@ -868,6 +876,9 @@ if (impl->control_fd < 0 || impl->timing_fd < 0) goto error; + impl->timing_source = pw_loop_add_io(impl->loop, impl->timing_fd, + SPA_IO_IN, false, on_timing_source_io, impl); + pw_properties_setf(impl->headers, "Transport", "RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;" "control_port=%u;timing_port=%u", @@ -988,7 +999,7 @@ char iv16*2; int res, frames, i, ip_version; char *sdp; - char local_ip256; + char local_ip256; host = pw_properties_get(impl->props, "raop.hostname"); @@ -1048,6 +1059,32 @@ return res; } +static void rtsp_auth_setup_reply(void *data, int status, const struct spa_dict *headers) +{ + struct impl *impl = data; + + pw_log_info("reply %d", status); + + impl->encryption = CRYPTO_NONE; + + rtsp_do_announce(impl); +} + +static int rtsp_do_auth_setup(struct impl *impl) +{ + int res; + + char output = + "\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"; + + res = pw_rtsp_client_url_send(impl->rtsp, "/auth-setup", "POST", &impl->headers->dict, + "application/octet-stream", output, rtsp_auth_setup_reply, impl); + + return res; +} + static const char *find_attr(char **tokens, const char *key) { int i; @@ -1168,7 +1205,10 @@ rtsp_do_auth(impl, headers); break; case 200: - rtsp_do_announce(impl); + if (impl->encryption == CRYPTO_AUTH_SETUP) + rtsp_do_auth_setup(impl); + else + rtsp_do_announce(impl); break; } } @@ -1648,6 +1688,8 @@ impl->encryption = CRYPTO_NONE; else if (spa_streq(str, "RSA")) impl->encryption = CRYPTO_RSA; + else if (spa_streq(str, "auth_setup")) + impl->encryption = CRYPTO_AUTH_SETUP; else { pw_log_error( "can't handle encryption type %s", str); res = -EINVAL;
View file
pipewire-0.3.54.tar.gz/src/modules/module-raop/rtsp-client.c -> pipewire-0.3.56.tar.gz/src/modules/module-raop/rtsp-client.c
Changed
@@ -273,12 +273,30 @@ int cseq; struct message *msg; const struct spa_dict_item *it; + const char *content_type; + unsigned int content_length; spa_dict_for_each(it, &client->headers->dict) pw_log_info(" %s: %s", it->key, it->value); cseq = pw_properties_get_int32(client->headers, "CSeq", 0); - + content_type = pw_properties_get(client->headers, "Content-Type"); + if (content_type != NULL && strcmp(content_type, "application/octet-stream") == 0) { + pw_log_info("binary response received"); + content_length = pw_properties_get_uint64(client->headers, "Content-Length", 0); + char content_bufcontent_length; + res = read(client->source->fd, content_buf, content_length); + pw_log_debug("read %d bytes", res); + if (res == 0) + return -EPIPE; + if (res < 0) { + res = -errno; + if (res != -EAGAIN && res != -EWOULDBLOCK) + return res; + return 0; + } + pw_properties_set(client->headers, "body", content_buf); + } if ((msg = find_pending(client, cseq)) != NULL) { msg->reply(msg->user_data, client->status, &client->headers->dict); spa_list_remove(&msg->link); @@ -466,7 +484,7 @@ return 0; } -int pw_rtsp_client_send(struct pw_rtsp_client *client, +int pw_rtsp_client_url_send(struct pw_rtsp_client *client, const char *url, const char *cmd, const struct spa_dict *headers, const char *content_type, const char *content, void (*reply) (void *user_data, int status, const struct spa_dict *headers), @@ -485,7 +503,7 @@ cseq = ++client->cseq; - fprintf(f, "%s %s RTSP/1.0\r\n", cmd, client->url); + fprintf(f, "%s %s RTSP/1.0\r\n", cmd, url); fprintf(f, "CSeq: %d\r\n", cseq); if (headers != NULL) { @@ -519,3 +537,12 @@ } return 0; } + +int pw_rtsp_client_send(struct pw_rtsp_client *client, + const char *cmd, const struct spa_dict *headers, + const char *content_type, const char *content, + void (*reply) (void *user_data, int status, const struct spa_dict *headers), + void *user_data) +{ + return pw_rtsp_client_url_send(client, client->url, cmd, headers, content_type, content, reply, user_data); +}
View file
pipewire-0.3.54.tar.gz/src/modules/module-raop/rtsp-client.h -> pipewire-0.3.56.tar.gz/src/modules/module-raop/rtsp-client.h
Changed
@@ -71,6 +71,12 @@ int pw_rtsp_client_get_local_ip(struct pw_rtsp_client *client, int *version, char *ip, size_t len); +int pw_rtsp_client_url_send(struct pw_rtsp_client *client, const char *url, + const char *cmd, const struct spa_dict *headers, + const char *content_type, const char *content, + void (*reply) (void *user_data, int status, const struct spa_dict *headers), + void *user_data); + int pw_rtsp_client_send(struct pw_rtsp_client *client, const char *cmd, const struct spa_dict *headers, const char *content_type, const char *content,
View file
pipewire-0.3.54.tar.gz/src/modules/module-rt.c -> pipewire-0.3.56.tar.gz/src/modules/module-rt.c
Changed
@@ -520,9 +520,8 @@ */ static bool check_realtime_privileges(rlim_t priority) { - int old_policy; + int err, old_policy, new_policy = REALTIME_POLICY; struct sched_param old_sched_params; - int new_policy = REALTIME_POLICY; struct sched_param new_sched_params; /* We could check `RLIMIT_RTPRIO`, but the BSDs generally don't have @@ -530,8 +529,8 @@ * scheduling without that rlimit being set such as `CAP_SYS_NICE` or * running as root. Instead of checking a bunch of preconditions, we * just try if setting realtime scheduling works or not. */ - if (pthread_getschedparam(pthread_self(),&old_policy,&old_sched_params) < 0) { - pw_log_warn("Failed to check RLIMIT_RTPRIO %m"); + if ((err = pthread_getschedparam(pthread_self(),&old_policy,&old_sched_params)) != 0) { + pw_log_warn("Failed to check RLIMIT_RTPRIO: %s", strerror(err)); return false; } @@ -600,8 +599,8 @@ rttime = pw_rtkit_get_rttime_usec_max(impl->system_bus); if (rttime >= 0) { if ((rlim_t)rttime < rl.rlim_cur) { - pw_log_debug("clamping rt.time.soft from %ld to %lld because of RTKit", - rl.rlim_cur, rttime); + pw_log_debug("clamping rt.time.soft from %llu to %lld because of RTKit", + (long long)rl.rlim_cur, rttime); } rl.rlim_cur = SPA_MIN(rl.rlim_cur, (rlim_t)rttime);
View file
pipewire-0.3.54.tar.gz/src/pipewire/buffers.c -> pipewire-0.3.56.tar.gz/src/pipewire/buffers.c
Changed
@@ -166,8 +166,8 @@ uint8_t ibuf4096; struct spa_pod_builder ib = { 0 }; struct spa_pod *oparam, *iparam; - uint32_t iidx, oidx, num = 0; - int in_res = -EIO, out_res = -EIO; + uint32_t iidx, oidx; + int in_res = -EIO, out_res = -EIO, num = 0; for (iidx = 0;;) { spa_pod_builder_init(&ib, ibuf, sizeof(ibuf));
View file
pipewire-0.3.54.tar.gz/src/pipewire/context.c -> pipewire-0.3.56.tar.gz/src/pipewire/context.c
Changed
@@ -543,6 +543,12 @@ } SPA_EXPORT +struct pw_data_loop *pw_context_get_data_loop(struct pw_context *context) +{ + return context->data_loop_impl; +} + +SPA_EXPORT struct pw_work_queue *pw_context_get_work_queue(struct pw_context *context) { return context->work_queue; @@ -1113,7 +1119,7 @@ * the desired final value and activate the followers and then the driver. * * A complete graph evaluation is performed for each change that is made to the - * graph, such as making/destroting links, adding/removing nodes, property changes such + * graph, such as making/destroying links, adding/removing nodes, property changes such * as quantum/rate changes or metadata changes. */ int pw_context_recalc_graph(struct pw_context *context, const char *reason)
View file
pipewire-0.3.54.tar.gz/src/pipewire/context.h -> pipewire-0.3.56.tar.gz/src/pipewire/context.h
Changed
@@ -136,6 +136,9 @@ /** get the context main loop */ struct pw_loop *pw_context_get_main_loop(struct pw_context *context); +/** get the context data loop. Since 0.3.56 */ +struct pw_data_loop *pw_context_get_data_loop(struct pw_context *context); + /** Get the work queue from the context: Since 0.3.26 */ struct pw_work_queue *pw_context_get_work_queue(struct pw_context *context);
View file
pipewire-0.3.54.tar.gz/src/pipewire/pipewire.c -> pipewire-0.3.56.tar.gz/src/pipewire/pipewire.c
Changed
@@ -826,8 +826,7 @@ return global_support.no_color == spa_atob(value); else if (spa_streq(option, "no-config")) return global_support.no_config == spa_atob(value); - else - return false; + return false; } /** Get the client name @@ -841,15 +840,11 @@ const char *cc; static char cname256; - if ((cc = pw_get_application_name())) + if ((cc = pw_get_application_name()) || (cc = pw_get_prgname())) return cc; - else if ((cc = pw_get_prgname())) - return cc; - else { - if (snprintf(cname, sizeof(cname), "pipewire-pid-%zd", (size_t) getpid()) < 0) - return NULL; - return cname; - } + else if (snprintf(cname, sizeof(cname), "pipewire-pid-%zd", (size_t) getpid()) < 0) + return NULL; + return cname; } /** Reverse the direction */
View file
pipewire-0.3.54.tar.gz/src/pipewire/stream.c -> pipewire-0.3.56.tar.gz/src/pipewire/stream.c
Changed
@@ -313,7 +313,7 @@ } -static inline int push_queue(struct stream *stream, struct queue *queue, struct buffer *buffer) +static inline int queue_push(struct stream *stream, struct queue *queue, struct buffer *buffer) { uint32_t index; @@ -330,7 +330,13 @@ return 0; } -static inline struct buffer *pop_queue(struct stream *stream, struct queue *queue) +static inline bool queue_is_empty(struct stream *stream, struct queue *queue) +{ + uint32_t index; + return spa_ringbuffer_get_read_index(&queue->ring, &index) < 1; +} + +static inline struct buffer *queue_pop(struct stream *stream, struct queue *queue) { uint32_t index, id; struct buffer *buffer; @@ -568,7 +574,7 @@ buffer->this.requested = impl->quantum; res = 1; } - pw_log_trace_fp("%p: update buffer:%u size:%u", impl, id, r->size); + pw_log_trace_fp("%p: update buffer:%u size:%"PRIu64, impl, id, buffer->this.requested); return res; } @@ -788,7 +794,7 @@ if (impl->direction == SPA_DIRECTION_INPUT) { struct buffer *b; - while ((b = pop_queue(impl, &impl->dequeued))) { + while ((b = queue_pop(impl, &impl->dequeued))) { if (b->busy) ATOMIC_DEC(b->busy->count); } @@ -927,7 +933,7 @@ if (impl->direction == SPA_DIRECTION_OUTPUT) { pw_log_trace("%p: recycle buffer %d", stream, b->id); - push_queue(impl, &impl->dequeued, b); + queue_push(impl, &impl->dequeued, b); } SPA_FLAG_SET(b->flags, BUFFER_FLAG_ADDED); @@ -945,7 +951,7 @@ struct stream *d = object; pw_log_trace("%p: recycle buffer %d", d, buffer_id); if (buffer_id < d->n_buffers) - push_queue(d, &d->queued, &d->buffersbuffer_id); + queue_push(d, &d->queued, &d->buffersbuffer_id); return 0; } @@ -984,7 +990,7 @@ if (io->status == SPA_STATUS_HAVE_DATA && (b = get_buffer(stream, io->buffer_id)) != NULL) { /* push new buffer */ - if (push_queue(impl, &impl->dequeued, b) == 0) { + if (queue_push(impl, &impl->dequeued, b) == 0) { copy_position(impl, impl->dequeued.incount); if (b->busy) ATOMIC_INC(b->busy->count); @@ -993,7 +999,7 @@ } if (io->status != SPA_STATUS_NEED_DATA) { /* pop buffer to recycle */ - if ((b = pop_queue(impl, &impl->queued))) { + if ((b = queue_pop(impl, &impl->queued))) { pw_log_trace_fp("%p: recycle buffer %d", stream, b->id); } else if (io->status == -EPIPE) return io->status; @@ -1013,28 +1019,36 @@ struct spa_io_buffers *io = impl->io; struct buffer *b; int res; - uint32_t index; - bool recycled; + bool ask_more; again: pw_log_trace_fp("%p: process out status:%d id:%d", stream, io->status, io->buffer_id); - recycled = false; + ask_more = false; if ((res = io->status) != SPA_STATUS_HAVE_DATA) { /* recycle old buffer */ if ((b = get_buffer(stream, io->buffer_id)) != NULL) { pw_log_trace_fp("%p: recycle buffer %d", stream, b->id); - push_queue(impl, &impl->dequeued, b); - recycled = true; + queue_push(impl, &impl->dequeued, b); } /* pop new buffer */ - if ((b = pop_queue(impl, &impl->queued)) != NULL) { + if ((b = queue_pop(impl, &impl->queued)) != NULL) { impl->drained = false; io->buffer_id = b->id; res = io->status = SPA_STATUS_HAVE_DATA; pw_log_trace_fp("%p: pop %d %p", stream, b->id, io); + /* we have a buffer, if we are not rt and don't follow + * any rate matching and there are no more + * buffers queued and there is a buffer to dequeue, ask for + * more buffers so that we have one in the next round. + * If we are using rate matching we need to wait until the + * rate matching node (audioconvert) has been scheduled to + * update the values. */ + ask_more = !impl->process_rt && impl->rate_match == NULL && + queue_is_empty(impl, &impl->queued) && + !queue_is_empty(impl, &impl->dequeued); } else if (impl->draining || impl->drained) { impl->draining = true; impl->drained = true; @@ -1045,7 +1059,12 @@ io->buffer_id = SPA_ID_INVALID; res = io->status = SPA_STATUS_NEED_DATA; pw_log_trace_fp("%p: no more buffers %p", stream, io); + ask_more = true; } + } else { + ask_more = !impl->process_rt && + queue_is_empty(impl, &impl->queued) && + !queue_is_empty(impl, &impl->dequeued); } copy_position(impl, impl->queued.outcount); @@ -1053,18 +1072,13 @@ if (!impl->draining && !impl->driving) { /* we're not draining, not a driver check if we need to get * more buffers */ - if (!impl->process_rt && (recycled || res == SPA_STATUS_NEED_DATA)) { - /* not realtime and we have a free buffer, trigger process so that we have - * data in the next round. */ - if (update_requested(impl) > 0) - call_process(impl); - } else if (res == SPA_STATUS_NEED_DATA) { - /* realtime and we don't have a buffer, trigger process and try - * again when there is something in the queue now */ + if (ask_more) { if (update_requested(impl) > 0) call_process(impl); - if (impl->draining || - spa_ringbuffer_get_read_index(&impl->queued.ring, &index) > 0) + /* realtime, we can try again now if there is something. + * non-realtime, we will have to try in the next round */ + if (impl->process_rt && + (impl->draining || !queue_is_empty(impl, &impl->queued))) goto again; } } @@ -1155,7 +1169,7 @@ struct control *c; const struct spa_pod *type, *pod; uint32_t iid, choice, n_vals, container = SPA_ID_INVALID; - float *vals, bool_range3 = { 1.0, 0.0, 1.0 }, dbl3; + float *vals, bool_range3 = { 1.0f, 0.0f, 1.0f }, dbl3; if (spa_pod_parse_object(param, SPA_TYPE_OBJECT_PropInfo, NULL, @@ -1279,7 +1293,7 @@ case SPA_TYPE_Bool: if (spa_pod_get_bool(&prop->value, &value.b) < 0) continue; - value.f = value.b ? 1.0 : 0.0; + value.f = value.b ? 1.0f : 0.0f; n_values = 1; values = &value.f; break; @@ -2229,7 +2243,7 @@ struct buffer *b; int res; - if ((b = pop_queue(impl, &impl->dequeued)) == NULL) { + if ((b = queue_pop(impl, &impl->dequeued)) == NULL) { res = -errno; pw_log_trace_fp("%p: no more buffers: %m", stream); errno = -res; @@ -2240,7 +2254,7 @@ if (b->busy && impl->direction == SPA_DIRECTION_OUTPUT) { if (ATOMIC_INC(b->busy->count) > 1) { ATOMIC_DEC(b->busy->count); - push_queue(impl, &impl->dequeued, b); + queue_push(impl, &impl->dequeued, b); pw_log_trace_fp("%p: buffer busy", stream); errno = EBUSY; return NULL; @@ -2260,7 +2274,7 @@ ATOMIC_DEC(b->busy->count); pw_log_trace_fp("%p: queue buffer %d", stream, b->id); - if ((res = push_queue(impl, &impl->queued, b)) < 0) + if ((res = queue_push(impl, &impl->queued, b)) < 0) return res; if (impl->direction == SPA_DIRECTION_OUTPUT && @@ -2281,9 +2295,9 @@ pw_log_trace_fp("%p: flush", impl); do { - b = pop_queue(impl, &impl->queued); + b = queue_pop(impl, &impl->queued); if (b != NULL) - push_queue(impl, &impl->dequeued, b); + queue_push(impl, &impl->dequeued, b); } while (b);
View file
pipewire-0.3.54.tar.gz/src/pipewire/stream.h -> pipewire-0.3.56.tar.gz/src/pipewire/stream.h
Changed
@@ -436,9 +436,16 @@ int pw_stream_connect(struct pw_stream *stream, /**< a \ref pw_stream */ enum pw_direction direction, /**< the stream direction */ - uint32_t target_id, /**< the target object id to connect to or - * PW_ID_ANY to let the manager - * select a target. */ + uint32_t target_id, /**< should have the value PW_ID_ANY. + * To select a specific target + * node, specify the + * PW_KEY_OBJECT_SERIAL or the + * PW_KEY_NODE_NAME value of the target + * node in the PW_KEY_TARGET_OBJECT + * property of the stream. + * Specifying target nodes by + * their id is deprecated. + */ enum pw_stream_flags flags, /**< stream flags */ const struct spa_pod **params, /**< an array with params. The params * should ideally contain supported
View file
pipewire-0.3.54.tar.gz/src/tests/meson.build -> pipewire-0.3.56.tar.gz/src/tests/meson.build
Changed
@@ -3,6 +3,7 @@ 'test-interfaces', # 'test-remote', 'test-stream', + 'test-filter', foreach a : test_apps
View file
pipewire-0.3.56.tar.gz/src/tests/test-filter.c
Added
@@ -0,0 +1,375 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <pipewire/pipewire.h> +#include <pipewire/main-loop.h> +#include <pipewire/filter.h> + +#include <spa/utils/string.h> + +#define TEST_FUNC(a,b,func) \ +do { \ + a.func = b.func; \ + spa_assert_se(SPA_PTRDIFF(&a.func, &a) == SPA_PTRDIFF(&b.func, &b)); \ +} while(0) + +static void test_abi(void) +{ + static const struct { + uint32_t version; + void (*destroy) (void *data); + void (*state_changed) (void *data, enum pw_filter_state old, + enum pw_filter_state state, const char *error); + void (*io_changed) (void *data, void *port_data, uint32_t id, void *area, uint32_t size); + void (*param_changed) (void *data, void *port_data, uint32_t id, const struct spa_pod *param); + void (*add_buffer) (void *data, void *port_data, struct pw_buffer *buffer); + void (*remove_buffer) (void *data, void *port_data, struct pw_buffer *buffer); + void (*process) (void *data, struct spa_io_position *position); + void (*drained) (void *data); + void (*command) (void *data, const struct spa_command *command); + } test = { PW_VERSION_FILTER_EVENTS, NULL }; + + struct pw_filter_events ev; + + TEST_FUNC(ev, test, destroy); + TEST_FUNC(ev, test, state_changed); + TEST_FUNC(ev, test, io_changed); + TEST_FUNC(ev, test, param_changed); + TEST_FUNC(ev, test, add_buffer); + TEST_FUNC(ev, test, remove_buffer); + TEST_FUNC(ev, test, process); + TEST_FUNC(ev, test, drained); + TEST_FUNC(ev, test, command); + + spa_assert_se(PW_VERSION_FILTER_EVENTS == 1); + spa_assert_se(sizeof(ev) == sizeof(test)); + + spa_assert_se(PW_FILTER_STATE_ERROR == -1); + spa_assert_se(PW_FILTER_STATE_UNCONNECTED == 0); + spa_assert_se(PW_FILTER_STATE_CONNECTING == 1); + spa_assert_se(PW_FILTER_STATE_PAUSED == 2); + spa_assert_se(PW_FILTER_STATE_STREAMING == 3); + + spa_assert_se(pw_filter_state_as_string(PW_FILTER_STATE_ERROR) != NULL); + spa_assert_se(pw_filter_state_as_string(PW_FILTER_STATE_UNCONNECTED) != NULL); + spa_assert_se(pw_filter_state_as_string(PW_FILTER_STATE_CONNECTING) != NULL); + spa_assert_se(pw_filter_state_as_string(PW_FILTER_STATE_PAUSED) != NULL); + spa_assert_se(pw_filter_state_as_string(PW_FILTER_STATE_STREAMING) != NULL); +} + +static void filter_destroy_error(void *data) +{ + spa_assert_not_reached(); +} +static void filter_state_changed_error(void *data, enum pw_filter_state old, + enum pw_filter_state state, const char *error) +{ + spa_assert_not_reached(); +} +static void filter_io_changed_error(void *data, void *port_data, uint32_t id, void *area, uint32_t size) +{ + spa_assert_not_reached(); +} +static void filter_param_changed_error(void *data, void *port_data, uint32_t id, const struct spa_pod *format) +{ + spa_assert_not_reached(); +} +static void filter_add_buffer_error(void *data, void *port_data, struct pw_buffer *buffer) +{ + spa_assert_not_reached(); +} +static void filter_remove_buffer_error(void *data, void *port_data, struct pw_buffer *buffer) +{ + spa_assert_not_reached(); +} +static void filter_process_error(void *data, struct spa_io_position *position) +{ + spa_assert_not_reached(); +} +static void filter_drained_error(void *data) +{ + spa_assert_not_reached(); +} + +static const struct pw_filter_events filter_events_error = +{ + PW_VERSION_FILTER_EVENTS, + .destroy = filter_destroy_error, + .state_changed = filter_state_changed_error, + .io_changed = filter_io_changed_error, + .param_changed = filter_param_changed_error, + .add_buffer = filter_add_buffer_error, + .remove_buffer = filter_remove_buffer_error, + .process = filter_process_error, + .drained = filter_drained_error +}; + +static int destroy_count = 0; +static void filter_destroy_count(void *data) +{ + destroy_count++; +} +static void test_create(void) +{ + struct pw_main_loop *loop; + struct pw_context *context; + struct pw_core *core; + struct pw_filter *filter; + struct pw_filter_events filter_events = filter_events_error; + struct spa_hook listener = { 0, }; + const char *error = NULL; + + loop = pw_main_loop_new(NULL); + context = pw_context_new(pw_main_loop_get_loop(loop), NULL, 12); + spa_assert_se(context != NULL); + core = pw_context_connect_self(context, NULL, 0); + spa_assert_se(core != NULL); + filter = pw_filter_new(core, "test", NULL); + spa_assert_se(filter != NULL); + pw_filter_add_listener(filter, &listener, &filter_events, filter); + + /* check state */ + spa_assert_se(pw_filter_get_state(filter, &error) == PW_FILTER_STATE_UNCONNECTED); + spa_assert_se(error == NULL); + /* check name */ + spa_assert_se(spa_streq(pw_filter_get_name(filter), "test")); + + /* check id, only when connected */ + spa_assert_se(pw_filter_get_node_id(filter) == SPA_ID_INVALID); + + /* check destroy */ + destroy_count = 0; + filter_events.destroy = filter_destroy_count; + pw_filter_destroy(filter); + spa_assert_se(destroy_count == 1); + + pw_context_destroy(context); + pw_main_loop_destroy(loop); +} + +static void test_properties(void) +{ + struct pw_main_loop *loop; + struct pw_context *context; + struct pw_core *core; + const struct pw_properties *props; + struct pw_filter *filter; + struct pw_filter_events filter_events = filter_events_error; + struct spa_hook listener = { { NULL }, }; + struct spa_dict_item items3; + + loop = pw_main_loop_new(NULL); + context = pw_context_new(pw_main_loop_get_loop(loop), NULL, 12); + spa_assert_se(context != NULL); + core = pw_context_connect_self(context, NULL, 0); + spa_assert_se(core != NULL); + filter = pw_filter_new(core, "test", + pw_properties_new("foo", "bar", + "biz", "fuzz", + NULL)); + spa_assert_se(filter != NULL); + pw_filter_add_listener(filter, &listener, &filter_events, filter); + + props = pw_filter_get_properties(filter, NULL); + spa_assert_se(props != NULL); + spa_assert_se(spa_streq(pw_properties_get(props, "foo"), "bar")); + spa_assert_se(spa_streq(pw_properties_get(props, "biz"), "fuzz")); + spa_assert_se(pw_properties_get(props, "buzz") == NULL); + + /* remove foo */ + items0 = SPA_DICT_ITEM_INIT("foo", NULL); + /* change biz */ + items1 = SPA_DICT_ITEM_INIT("biz", "buzz"); + /* add buzz */ + items2 = SPA_DICT_ITEM_INIT("buzz", "frizz"); + pw_filter_update_properties(filter, NULL, &SPA_DICT_INIT(items, 3)); + + spa_assert_se(props == pw_filter_get_properties(filter, NULL)); + spa_assert_se(pw_properties_get(props, "foo") == NULL); + spa_assert_se(spa_streq(pw_properties_get(props, "biz"), "buzz")); + spa_assert_se(spa_streq(pw_properties_get(props, "buzz"), "frizz")); + + /* check destroy */ + destroy_count = 0; + filter_events.destroy = filter_destroy_count; + pw_context_destroy(context); + spa_assert_se(destroy_count == 1); + + pw_main_loop_destroy(loop); +} + +struct roundtrip_data +{ + struct pw_main_loop *loop; + int pending; + int done; +}; + +static void core_event_done(void *object, uint32_t id, int seq) +{ + struct roundtrip_data *data = object; + if (id == PW_ID_CORE && seq == data->pending) { + data->done = 1; + printf("done %d\n", seq); + pw_main_loop_quit(data->loop); + } +} + +static int roundtrip(struct pw_core *core, struct pw_main_loop *loop) +{ + struct spa_hook core_listener; + struct roundtrip_data data = { .loop = loop }; + const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .done = core_event_done, + }; + spa_zero(core_listener); + pw_core_add_listener(core, &core_listener, + &core_events, &data); + + data.pending = pw_core_sync(core, PW_ID_CORE, 0); + printf("sync %d\n", data.pending); + + while (!data.done) { + pw_main_loop_run(loop); + } + spa_hook_remove(&core_listener); + return 0; +} + +static int node_count = 0; +static int port_count = 0; +static void registry_event_global(void *data, uint32_t id, + uint32_t permissions, const char *type, uint32_t version, + const struct spa_dict *props) +{ + printf("object: id:%u type:%s/%d\n", id, type, version); + if (spa_streq(type, PW_TYPE_INTERFACE_Port)) + port_count++; + else if (spa_streq(type, PW_TYPE_INTERFACE_Node)) + node_count++; + +} +static void registry_event_global_remove(void *data, uint32_t id) +{ + printf("object: id:%u\n", id); +} + +struct port { + struct pw_filter *filter; +}; + +static void test_create_port(void) +{ + struct pw_main_loop *loop; + struct pw_context *context; + struct pw_core *core; + struct pw_registry *registry; + struct pw_filter *filter; + struct spa_hook registry_listener = { 0, }; + static const struct pw_registry_events registry_events = { + PW_VERSION_REGISTRY_EVENTS, + .global = registry_event_global, + .global_remove = registry_event_global_remove, + }; + int res; + struct port *port; + enum pw_filter_state state; + + loop = pw_main_loop_new(NULL); + context = pw_context_new(pw_main_loop_get_loop(loop), NULL, 12); + spa_assert_se(context != NULL); + core = pw_context_connect_self(context, NULL, 0); + spa_assert_se(core != NULL); + filter = pw_filter_new(core, "test", NULL); + spa_assert_se(filter != NULL); + + registry = pw_core_get_registry(core, PW_VERSION_REGISTRY, 0); + spa_assert_se(registry != NULL); + pw_registry_add_listener(registry, ®istry_listener, + ®istry_events, NULL); + + state = pw_filter_get_state(filter, NULL); + printf("state %s\n", pw_filter_state_as_string(state)); + res = pw_filter_connect(filter, PW_FILTER_FLAG_RT_PROCESS, NULL, 0); + spa_assert_se(res >= 0); + + printf("wait connect\n"); + while (true) { + state = pw_filter_get_state(filter, NULL); + printf("state %s\n", pw_filter_state_as_string(state)); + spa_assert_se(state != PW_FILTER_STATE_ERROR); + + if (state == PW_FILTER_STATE_PAUSED) + break; + + roundtrip(core, loop); + } + spa_assert_se(node_count == 1); + + printf("add port\n"); + /* make an audio DSP output port */ + port = pw_filter_add_port(filter, + PW_DIRECTION_OUTPUT, + PW_FILTER_PORT_FLAG_MAP_BUFFERS, + sizeof(struct port), + pw_properties_new( + PW_KEY_FORMAT_DSP, "32 bit float mono audio", + PW_KEY_PORT_NAME, "output", + NULL), + NULL, 0); + + printf("wait port\n"); + roundtrip(core, loop); + + spa_assert_se(port_count == 1); + printf("port added\n"); + + printf("remove port\n"); + pw_filter_remove_port(port); + roundtrip(core, loop); + + printf("destroy\n"); + /* check destroy */ + pw_filter_destroy(filter); + + pw_proxy_destroy((struct pw_proxy*)registry); + + pw_context_destroy(context); + pw_main_loop_destroy(loop); +} + +int main(int argc, char *argv) +{ + pw_init(&argc, &argv); + + test_abi(); + test_create(); + test_properties(); + test_create_port(); + + pw_deinit(); + + return 0; +}
View file
pipewire-0.3.54.tar.gz/src/tests/test-stream.c -> pipewire-0.3.56.tar.gz/src/tests/test-stream.c
Changed
@@ -251,5 +251,7 @@ test_create(); test_properties(); + pw_deinit(); + return 0; }
View file
pipewire-0.3.54.tar.gz/src/tools/pw-cat.c -> pipewire-0.3.56.tar.gz/src/tools/pw-cat.c
Changed
@@ -1439,9 +1439,7 @@ case OPT_VOLUME: data.volume = atof(optarg); break; - default: - fprintf(stderr, "error: unknown option '%c'\n", c); goto error_usage; } }
View file
pipewire-0.3.54.tar.gz/test/test-spa-json.c -> pipewire-0.3.56.tar.gz/test/test-spa-json.c
Changed
@@ -86,7 +86,7 @@ { const char *value; int len; - float f; + float f = 0.0f; pwtest_int_gt((len = spa_json_next(it, &value)), 0); check_type(TYPE_FLOAT, value, len); pwtest_int_gt(spa_json_parse_float(value, len, &f), 0); @@ -281,6 +281,36 @@ return PWTEST_PASS; } +PWTEST(json_float_check) +{ + struct { + const char *str; + int res; + } val = { + { "0.0", 1 }, + { ".0", 1 }, + { "+.0E0", 1 }, + { "-.0e0", 1 }, + + { "0,0", 0 }, + { "0.0.5", 0 }, + { "0x0", 0 }, + { "0x0.0", 0 }, + { "E10", 0 }, + { "e20", 0 }, + { " 0.0", 0 }, + { "0.0 ", 0 }, + { " 0.0 ", 0 }, + }; + unsigned i; + float v; + + for (i = 0; i < SPA_N_ELEMENTS(val); i++) { + pwtest_int_eq(spa_json_parse_float(vali.str, strlen(vali.str), &v), vali.res); + } + return PWTEST_PASS; +} + PWTEST(json_int) { int v; @@ -296,6 +326,7 @@ pwtest_add(json_array, PWTEST_NOARG); pwtest_add(json_overflow, PWTEST_NOARG); pwtest_add(json_float, PWTEST_NOARG); + pwtest_add(json_float_check, PWTEST_NOARG); pwtest_add(json_int, PWTEST_NOARG); return PWTEST_PASS;
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.