Projects
Essentials
pipewire-aptx
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 25
View file
pipewire-aptx.changes
Changed
@@ -1,4 +1,9 @@ ------------------------------------------------------------------- +Sat Apr 8 17:49:24 UTC 2023 - Bjørn Lie <zaitor@opensuse.org> + +- Update to version 0.3.68 + +------------------------------------------------------------------- Sat Mar 18 12:35:35 UTC 2023 - Bjørn Lie <zaitor@opensuse.org> - Update to version 0.3.67
View file
pipewire-aptx.spec
Changed
@@ -7,7 +7,7 @@ %define soversion 0_2 Name: pipewire-aptx -Version: 0.3.67 +Version: 0.3.68 Release: 0 Summary: PipeWire Bluetooth aptX codec plugin License: MIT
View file
pipewire-0.3.67.tar.gz/.cirrus.yml
Deleted
@@ -1,24 +0,0 @@ -task: - freebsd_instance: - matrix: - - image_family: freebsd-13-1-snap - env: - # /usr/ports/Mk/Uses/localbase.mk localbase:ldflags - LOCALBASE: /usr/local - CFLAGS: -isystem $LOCALBASE/include - CPPFLAGS: $CFLAGS - CXXFLAGS: $CFLAGS - LDFLAGS: -L$LOCALBASE/lib - deps_script: - - sed -i.bak -e 's/quarterly/latest/' /etc/pkg/FreeBSD.conf - - pkg install -y meson pkgconf git-lite dbus glib libepoll-shim libudev-devd vulkan-loader vulkan-headers v4l_compat gstreamer1 gstreamer1-plugins libinotify gettext libsndfile sdl2 alsa-lib - - sysrc dbus_enable=YES - - service dbus restart - build_script: - - mkdir build - - cd build - - meson setup -Dalsa=enabled -Draop=enabled -Dv4l2=enabled -Dpipewire-alsa=enabled -Dbluez5=disabled -Djack=disabled -Dpipewire-jack=enabled -Dpw-cat=enabled -Dpipewire-v4l2=disabled -Dsdl2=enabled -Dsystemd=disabled -Dsession-managers=media-session .. - - ninja - test_script: - - cd build - - ninja test
View file
pipewire-0.3.67.tar.gz/.gitlab-ci.yml -> pipewire-0.3.68.tar.gz/.gitlab-ci.yml
Changed
@@ -25,7 +25,7 @@ .fedora: variables: # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2023-01-18.0' + FDO_DISTRIBUTION_TAG: '2023-03-09.0' FDO_DISTRIBUTION_VERSION: '35' FDO_DISTRIBUTION_PACKAGES: >- alsa-lib-devel @@ -44,10 +44,12 @@ gstreamer1-devel gstreamer1-plugins-base-devel jack-audio-connection-kit-devel + libasan libcanberra-devel libldac-devel libmysofa-devel libsndfile-devel + libubsan libusb-devel lilv-devel libv4l-devel @@ -172,15 +174,15 @@ - export XDG_RUNTIME_DIR="$(mktemp -p $PWD -d xdg-runtime-XXXXXX)" - | if -n "$FDO_CI_CONCURRENT" ; then - NINJA_ARGS="-j$FDO_CI_CONCURRENT $NINJA_ARGS" - export NINJA_ARGS + COMPILE_ARGS="-j$FDO_CI_CONCURRENT" + export COMPILE_ARGS fi script: - echo "Building with meson options $MESON_OPTIONS" - - meson "$BUILD_DIR" . --prefix="$PREFIX" $MESON_OPTIONS - - ninja $NINJA_ARGS -C "$BUILD_DIR" - - ninja $NINJA_ARGS -C "$BUILD_DIR" test - - ninja $NINJA_ARGS -C "$BUILD_DIR" install + - meson setup "$BUILD_DIR" --prefix="$PREFIX" $MESON_OPTIONS + - meson compile -C "$BUILD_DIR" $COMPILE_ARGS + - meson test -C "$BUILD_DIR" --no-rebuild + - meson install -C "$BUILD_DIR" --no-rebuild artifacts: name: pipewire-$CI_COMMIT_SHA when: always @@ -312,9 +314,18 @@ MESON_OPTION_VALUE: enabled, disabled script: - echo "Building with -D$MESON_OPTION=$MESON_OPTION_VALUE" - - meson "$BUILD_DIR" . --prefix="$PREFIX" "-D$MESON_OPTION=$MESON_OPTION_VALUE" -Dsession-managers= - - ninja $NINJA_ARGS -C "$BUILD_DIR" - - ninja $NINJA_ARGS -C "$BUILD_DIR" test + - meson setup "$BUILD_DIR" --prefix="$PREFIX" "-D$MESON_OPTION=$MESON_OPTION_VALUE" -Dsession-managers= + - meson compile -C "$BUILD_DIR" $COMPILE_ARGS + - meson test -C "$BUILD_DIR" --no-rebuild + +build_with_asan_ubsan: + extends: + - .build_on_fedora + script: + - echo "Building with ASan and UBSan" + - meson setup "$BUILD_DIR" --prefix="$PREFIX" -D debug=true -D optimization=g -D b_sanitize=address,undefined -D session-managers= + - meson compile -C "$BUILD_DIR" $COMPILE_ARGS + - meson test -C "$BUILD_DIR" --no-rebuild # A release build with NDEBUG, all options on auto() but tests explicitly # enabled. This should show issues with tests failing due to different @@ -333,9 +344,9 @@ - .build_on_fedora script: - echo "Building with meson options $MESON_OPTIONS" - - meson "$BUILD_DIR" . --prefix="$PREFIX" $MESON_OPTIONS - - ninja $NINJA_ARGS -C "$BUILD_DIR" - - ninja $NINJA_ARGS -C "$BUILD_DIR" install + - meson setup "$BUILD_DIR" --prefix="$PREFIX" $MESON_OPTIONS + - meson compile -C "$BUILD_DIR" $COMPILE_ARGS + - meson install -C "$BUILD_DIR" --no-rebuild variables: MESON_OPTIONS: "-Dsession-managers=$SESSION_MANAGERS" parallel: @@ -349,9 +360,9 @@ script: - pip3 install --upgrade --pre meson - echo "Building with meson options $MESON_OPTIONS" - - meson "$BUILD_DIR" . --prefix="$PREFIX" $MESON_OPTIONS - - ninja $NINJA_ARGS -C "$BUILD_DIR" - - ninja $NINJA_ARGS -C "$BUILD_DIR" install + - meson setup "$BUILD_DIR" --prefix="$PREFIX" $MESON_OPTIONS + - meson compile -C "$BUILD_DIR" $COMPILE_ARGS + - meson install -C "$BUILD_DIR" --no-rebuild variables: MESON_OPTIONS: "-Dsession-managers=wireplumber,media-session" allow_failure: true @@ -366,9 +377,9 @@ - pip3 uninstall --yes meson - pip3 install "meson==$meson_version" - echo "Building with meson options $MESON_OPTIONS" - - meson "$BUILD_DIR" . --prefix="$PREFIX" $MESON_OPTIONS - - ninja $NINJA_ARGS -C "$BUILD_DIR" - - ninja $NINJA_ARGS -C "$BUILD_DIR" install + - meson setup "$BUILD_DIR" --prefix="$PREFIX" $MESON_OPTIONS + - meson compile -C "$BUILD_DIR" $COMPILE_ARGS + - meson install -C "$BUILD_DIR" --no-rebuild variables: MESON_OPTIONS: "-Dsession-managers=" @@ -377,7 +388,7 @@ - .build_on_fedora script: - echo "Building with meson options $MESON_OPTIONS" - - meson "$BUILD_DIR" . --prefix="$PREFIX" $MESON_OPTIONS + - meson setup "$BUILD_DIR" --prefix="$PREFIX" $MESON_OPTIONS - meson test -C "$BUILD_DIR" --setup=valgrind variables: MESON_OPTIONS: "-Dsession-managers=" @@ -391,7 +402,7 @@ stage: analysis script: - export PATH=/opt/coverity/bin:$PATH - - meson "$BUILD_DIR" . --prefix="$PREFIX" + - meson setup "$BUILD_DIR" --prefix="$PREFIX" -Ddocs=disabled -Dbluez5-backend-hsphfpd=enabled -Daudiotestsrc=enabled @@ -410,7 +421,7 @@ --xml-option=append_arg@C:"replace/GLIB_(DEPRECATED|AVAILABLE)_ENUMERATOR_IN_\d_\d\d(_FOR\(\w+\)|)\s+=/ =" --xml-option=append_arg@C:--ppp_translator --xml-option=append_arg@C:"replace/(__has_builtin|_GLIBCXX_HAS_BUILTIN)\(\w+\)/1" - - cov-build --dir cov-int --config coverity_conf.xml ninja $NINJA_ARGS -C "$BUILD_DIR" + - cov-build --dir cov-int --config coverity_conf.xml meson compile -C "$BUILD_DIR" $COMPILE_ARGS - tar czf cov-int.tar.gz cov-int - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL
View file
pipewire-0.3.67.tar.gz/NEWS -> pipewire-0.3.68.tar.gz/NEWS
Changed
@@ -1,3 +1,143 @@ +# PipeWire 0.3.68 (2023-04-06) + +This is a bugfix release that is API and ABI compatible with previous +0.3.x releases. + +This release contains a huge number of changes, some of which might cause +regressions. Please report anything that seems to fail after the upgrade. +UCM devices in particular might have changed names, profiles and ports that +might require changes in custom scripts. + +## Highlights + - Symbolic links to the pipewire binary are now used instead of recompiling + the same binary multiple times. + - Changes to the graph scheduler related to quantum/rate updates and + calculation of the node states. Things should start and switch between + quantums and rates more smoothly now and especially virtual devices should + now only run when required. + - A new RTP session module was added. This uses the Apple MIDI protocol + to configure low-latency bidirectional MIDI (and with a PipeWire specific + extension, also audio) between machines. OPUS encoding was added to the + RTP formats. The SAP module was separated from the rtp-sink/source module + to make it more usable. + - A new runtime debug property was added to all streams and nodes to trigger + a save of the raw samples to a wav file. Support for this has also been + added to the echo-canceler to debug potential issues. + - Module pulse-tunnel has improved rate matching and synchronization + support. It should also not drift anymore for capture devices. + - The link-factory now ignores by default the link.passive property. This means + that tools like pw-link or jack clients and wireplumber can't make passive + links anymore. The reason is that there is now much more advanced logic in + PipeWire itself to handle passive links based on node and port properties. + - The RAOP sink was ported to new OpenSSL functions. Digest passwords are + handled correctly now and support for more devices was added. + - The ACP code was updated with new PulseAudio UCM code: "Create multiple + profiles per verb for conflicting devices". This might change the names + of devices, profiles and ports so scripts might need to be updated. + - Upmixing is disabled again by default. We now ship config files that + distros can install to enable upmixing again. The reason being that PipeWire + should not apply fancy DSP processing to audio by default. + - Many cleanups and bugfixes, including some crashes and memory corruption + bugs. + +## PipeWire + - Various FreeBSD compilation fixes. + - Don't crash when calling _connect twice in stream/filter. (#3091) + - Links are now installed instead of compiling the pipewire binary + multiple times. + - There is now a new core event bound_props that augments the bound_id event + with the global properties. This can be used to get the global.serial among + other global properties. It also makes it possible in the future to let the + server allocate unique names or uuids. + - Fix a bug where the server could go into an infinite reconfigure loop when + the samplerate of a driver would change. + - When a samplerate was forced, restore the previous best samplerate when the + samplerate is no longer forced. (#2133) + - Rework how the states of the nodes in the graph are calculated. A more + refined algorithm is now used that only runs nodes that need to run. + - Rework how the quantum change is applied to the graph. Drivers are now + responsible for using the new updated rate/quantum before starting a new + cycle. This avoids starting a cycle with an old quantum first. + - pw-stream and pw-filter will now ensure that the Trigger event is called + from the main thread. + - node.force-rate=0 will now force the node.rate on the graph, forcefully + switching the hardware into the new rate if possible. (#3026) + - Additional checks were added to the thread-loop to check locking order. + - Additional checks were added to pw-stream and pw-filter to check if methods + are called from the right thread context. + +## modules + - A new RTP session module was added. This uses the Apple MIDI protocol + to configure bidirectional MIDI (or audio) between machines. + - SAP support was removed from module-rtp-source and module-rtp-sink and + moved to a separate module. This makes it possible to use the RTP modules + without SAP support as well. + - The echo-cancel module now has support to save the signals to a wav + file for debugging purposes. + - The RTP modules now have support for the OPUS codec. + - The RAOP module was ported to new openssl encryption functions and handles + digest passwords correctly now. + - module-raop-discover now has match rules to be able to select the streams + and set properties. + - Module pulse-tunnel has improved rate matching and synchronization + support. (#3093) + - Fix potential memory corruption and infinite loops because + module-pulse-tunnel was unloaded from the wrong thread. + - The link-factory now ignores by default the link.passive property. This means + that tools like pw-link or jack clients and wireplumber can't make passive + links anymore. The reason is that there is now much more advanced logic in + PipeWire itself to handle passive links based on node and port properties. + - module-echo-cancel will now clear its buffers after a suspend to avoid + playing stray samples. + - module-raop-sink will now handle 0 timing_port replies. (#3133) + +## SPA + - The adapter module now has support for saving the raw audio to a wav + file for debugging purposes. + - The ACP code was updated with new PulseAudio UCM code: "Create multiple + profiles per verb for conflicting devices". This might change the names + of devices, profiles and ports so scripts might need to be updated. + - Upmixing was disabled again by default. We now ship config files that + distros can install to enable upmixing again. (#3081) + - audioadapter and audioconvert have seen improvements in the experimental + non-DSP/passthrough mode. + - Fix a potential race where the dummy drivers could fail to stop a timer + and cause endless warnings in the logs. + - The ALSA plugin has experimental support for IRQ based scheduling. This + should decrease latency for some (mostly USB) drivers. This should bring + latency within JACK latency. More work on this will be done before the + 1.0 release later this year. + - Audioconvert now has support for volume ramping. (#3046) + - A new loop method was added the check if a thread is currently running the + loop. + - channelmix.disable and resample.disable now generate an error when true + and channelmixing or resampling is required in the converter. + +## Bluetooth + - Fix a crash in some cases when a device was disconnected. + - Support async transport state changes. This avoids some lockups when the + bluetooth backend is having issues. (#3023) + - Align BAP sinks. This improves synchronization between earpieces. + +## ALSA + - Improve properties in pw-top and pavucontrol. + +## pulse-server + - Improve error handling from pulse-tunnel. + - Generate silence correctly for unsigned formats as well. + - Review buffer params. The streams should now just work with 1 or 2 + buffers. + - module-rtp-send and module-rtp-recv now have support for the OPUS codec. + +# JACK + - Make sure we don't call any callbacks anymore when deactivating. (#2781) + +## GStreamer + - Sort the device by priority in deviceprovider. (#3072) + + +Older versions: + # PipeWire 0.3.67 (2023-03-09) This is a bugfix release that is API and ABI compatible with previous @@ -92,9 +232,6 @@ - The metadata plane count is now handled correctly in more cases. - Stream errors are now handled correctly to stop the GStreamer elements. -Older versions: - - # PipeWire 0.3.66 (2023-02-16) This is a bugfix release that is API and ABI compatible with previous
View file
pipewire-0.3.67.tar.gz/doc/index.dox -> pipewire-0.3.68.tar.gz/doc/index.dox
Changed
@@ -41,5 +41,6 @@ - PipeWire Wikipedia(https://en.wikipedia.org/wiki/PipeWire) - Bluetooth, PipeWire and Whatsapp calls(https://gjhenrique.com/pipewire.html) - Intoduction to PipeWire(https://bootlin.com/blog/an-introduction-to-pipewire/) +- A custom PipeWire node(https://bootlin.com/blog/a-custom-pipewire-node/) */
View file
pipewire-0.3.67.tar.gz/doc/pipewire-modules.dox -> pipewire-0.3.68.tar.gz/doc/pipewire-modules.dox
Changed
@@ -74,8 +74,10 @@ - \subpage page_module_raop_discover - \subpage page_module_roc_sink - \subpage page_module_roc_source +- \subpage page_module_rtp_sap - \subpage page_module_rtp_sink - \subpage page_module_rtp_source +- \subpage page_module_rtp_session - \subpage page_module_rt - \subpage page_module_session_manager - \subpage page_module_x11_bell
View file
pipewire-0.3.67.tar.gz/meson.build -> pipewire-0.3.68.tar.gz/meson.build
Changed
@@ -1,7 +1,7 @@ project('pipewire', 'c' , - version : '0.3.67', + version : '0.3.68', license : 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' , - meson_version : '>= 0.59.0', + meson_version : '>= 0.61.1', default_options : 'warning_level=3', 'c_std=gnu11', 'cpp_std=c++17', @@ -282,6 +282,10 @@ endif cdata.set('HAVE_PW_CAT_FFMPEG_INTEGRATION', pw_cat_ffmpeg.allowed()) +opus_dep = dependency('opus', required : get_option('opus')) +summary({'opus (Bluetooth, RTP)': opus_dep.found()}, bool_yn: true, section: 'Misc dependencies') +cdata.set('HAVE_OPUS', opus_dep.found()) + summary({'readline (for pw-cli)': readline_dep.found()}, bool_yn: true, section: 'Misc dependencies') cdata.set('HAVE_READLINE', readline_dep.found()) ncurses_dep = dependency('ncursesw', required : false)
View file
pipewire-0.3.67.tar.gz/meson_options.txt -> pipewire-0.3.68.tar.gz/meson_options.txt
Changed
@@ -318,3 +318,7 @@ min: -20, max: -1, value: -19) +option('opus', + description: 'Enable code that depends on opus', + type: 'feature', + value: 'auto')
View file
pipewire-0.3.67.tar.gz/pipewire-alsa/alsa-plugins/pcm_pipewire.c -> pipewire-0.3.68.tar.gz/pipewire-alsa/alsa-plugins/pcm_pipewire.c
Changed
@@ -1121,7 +1121,15 @@ pw_properties_setf(pw->props, PW_KEY_APP_NAME, "PipeWire ALSA %s", pw_get_prgname()); if (pw_properties_get(pw->props, PW_KEY_NODE_NAME) == NULL) - pw_properties_setf(pw->props, PW_KEY_NODE_NAME, "ALSA %s", + pw_properties_setf(pw->props, PW_KEY_NODE_NAME, "alsa_%s.%s", + stream == SND_PCM_STREAM_PLAYBACK ? "playback" : "capture", + pw_get_prgname()); + if (pw_properties_get(pw->props, PW_KEY_NODE_DESCRIPTION) == NULL) + pw_properties_setf(pw->props, PW_KEY_NODE_DESCRIPTION, "ALSA %s %s", + stream == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture", + pw_get_prgname()); + if (pw_properties_get(pw->props, PW_KEY_MEDIA_NAME) == NULL) + pw_properties_setf(pw->props, PW_KEY_MEDIA_NAME, "ALSA %s", stream == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture"); if (pw_properties_get(pw->props, PW_KEY_MEDIA_TYPE) == NULL) pw_properties_set(pw->props, PW_KEY_MEDIA_TYPE, "Audio");
View file
pipewire-0.3.67.tar.gz/pipewire-jack/src/pipewire-jack.c -> pipewire-0.3.68.tar.gz/pipewire-jack/src/pipewire-jack.c
Changed
@@ -898,17 +898,19 @@ spa_hook_remove(&client->node_listener); } -static void on_node_bound(void *data, uint32_t global_id) +static void on_node_bound_props(void *data, uint32_t global_id, const struct spa_dict *props) { struct client *client = data; client->node_id = global_id; + if (props) + pw_properties_update(client->props, props); } static const struct pw_proxy_events node_proxy_events = { PW_VERSION_PROXY_EVENTS, .removed = on_node_removed, .destroy = on_node_destroy, - .bound = on_node_bound, + .bound_props = on_node_bound_props, }; static struct link *find_activation(struct spa_list *links, uint32_t node_id) @@ -3798,6 +3800,8 @@ return 0; pw_thread_loop_lock(c->context.loop); + c->active = false; + pw_data_loop_stop(c->loop); pw_client_node_set_active(c->node, false); @@ -3816,12 +3820,7 @@ pw_thread_loop_unlock(c->context.loop); - if (res < 0) - return res; - - c->active = false; - - return 0; + return res; } SPA_EXPORT @@ -5414,6 +5413,8 @@ latency = SPA_LATENCY_INFO(direction); nframes = jack_get_buffer_size((jack_client_t*)c); + if (nframes == 0) + nframes = 1; latency.min_rate = range->min; if (latency.min_rate >= nframes) {
View file
pipewire-0.3.67.tar.gz/pipewire-v4l2/src/pipewire-v4l2.c -> pipewire-0.3.68.tar.gz/pipewire-v4l2/src/pipewire-v4l2.c
Changed
@@ -800,8 +800,10 @@ if ((file = find_file_by_dev(dev_id)) != NULL) { res = do_dup(file->fd, 0); unref_file(file); - if (res >= 0) - fcntl(res, F_SETFL, oflag); + if (res < 0) + return res; + if (fcntl(res, F_SETFL, oflag) < 0) + pw_log_warn("fd:%d failed to set flags: %m", res); return res; }
View file
pipewire-0.3.67.tar.gz/po/be.po -> pipewire-0.3.68.tar.gz/po/be.po
Changed
@@ -2,7 +2,8 @@ # Copyright (C) 2016 PipeWire's COPYRIGHT HOLDER # This file is distributed under the same license as the PipeWire package. # -# Viktar Vaŭčkievič <victorenator@gmail.com>, 2016. +# +# Viktar Vaŭčkievič <victorenator@gmail.com>, 2016, 2023. # msgid "" msgstr "" @@ -10,16 +11,16 @@ "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" -"PO-Revision-Date: 2016-07-19 11:06+0300\n" +"PO-Revision-Date: 2023-03-11 01:14+0300\n" "Last-Translator: Viktar Vaŭčkievič <victorenator@gmail.com>\n" "Language-Team: Belarusian <>\n" "Language: be\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -"X-Generator: Lokalize 2.0\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"X-Generator: Gtranslator 42.0\n" #: src/daemon/pipewire.c:43 #, c-format @@ -29,14 +30,19 @@ " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" +"%s параметры\n" +" -h, --help Паказаць гэту даведку\n" +" --version Паказаць версію\n" +" -c, --config Загрузіць канфігурацыю (Агаданая " +"%s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" -msgstr "" +msgstr "Медыйная сістэма PipeWire" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" -msgstr "" +msgstr "Старт медыйнай сістэмы PipeWire" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 @@ -50,7 +56,7 @@ #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" -msgstr "" +msgstr "Невядомая прылада" #: src/tools/pw-cat.c:991 #, c-format @@ -61,6 +67,11 @@ " -v, --verbose Enable verbose operations\n" "\n" msgstr "" +"%s параметры <file>\n" +" -h, --help Паказаць гэту даведку\n" +" --version Паказаць версію\n" +" -v, --verbose Уключыць падрабязнасці\n" +"\n" #: src/tools/pw-cat.c:998 #, c-format @@ -79,6 +90,21 @@ " --list-targets List available targets for --target\n" "\n" msgstr "" +" -R, --remote Назва адаленага сэрвіса\n" +" --media-type Задаць тып медыя (агаданы %s)\n" +" --media-category Задаць катэгорыю медыя (агаданая " +"%s)\n" +" --media-role Задаць ролю медыя (агаданая %s)\n" +" --target Задаць мэтавы вузел (агаданы %s)\n" +" 0 азначае не спасылацца\n" +" --latency Задаць затрымку вузла (агаданая %s)\n" +" Xадзінка (адзінка = s, ms, us, " +"ns)\n" +" ці наўпрост сэмплы (256)\n" +" з частатой аднаго з уваходных " +"файлаў\n" +" --list-targets Ліставаць наяўныя мэты для --target\n" +"\n" #: src/tools/pw-cat.c:1016 #, c-format @@ -99,6 +125,22 @@ "%d)\n" "\n" msgstr "" +" --rate Частата сэмплаў (для запісу) " +"(агаданая %u)\n" +" --channels Колькасць каналаў (для запісу) " +"(агаданая %u)\n" +" --channel-map Мапа каналаў\n" +" адзін з: «stereo», " +"«surround-51»,... ці\n" +" назвы каналаў праз коску: пр. " +"«FL,FR»\n" +" --format Фармат сэмплаў %s (req. for rec) " +"(агаданы %s)\n" +" --volume Гучнасць патоку 0-1.0 (агаданая " +"%.3f)\n" +" -q --quality Якасць перадыскрэтызацыі (0 - 15) " +"(агаданая %d)\n" +"\n" #: src/tools/pw-cat.c:1033 msgid "" @@ -107,6 +149,10 @@ " -m, --midi Midi mode\n" "\n" msgstr "" +" -p, --playback Рэжым прайгравальніка\n" +" -r, --record Рэжым запісу\n" +" -m, --midi Midi-рэжым\n" +"\n" #: src/tools/pw-cli.c:2932 #, c-format @@ -118,10 +164,17 @@ " -r, --remote Remote daemon name\n" "\n" msgstr "" +"%s параметры каманда\n" +" -h, --help Паказаць гэту даведку\n" +" --version Паказаць версію\n" +" -d, --daemon Запусціць як фонавы сэрвіс (Default " +"false)\n" +" -r, --remote Назва адаленага сэрвіса\n" +"\n" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" -msgstr "" +msgstr "Pro Audio" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 @@ -247,14 +300,12 @@ msgstr "Аналагавы выхад" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 -#, fuzzy msgid "Headphones 2" -msgstr "Навушнікі" +msgstr "Навушнікі 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 -#, fuzzy msgid "Headphones Mono Output" -msgstr "Аналагавы монавыхад" +msgstr "Навушнікавы монавыхад" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" @@ -289,39 +340,33 @@ msgstr "Шматканальны выхад" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 -#, fuzzy msgid "Game Output" -msgstr "%s выхад" +msgstr "Гульнявы выхад" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 -#, fuzzy msgid "Chat Output" -msgstr "%s выхад" +msgstr "Чатавы выхад" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 -#, fuzzy msgid "Chat Input" -msgstr "%s уваход" +msgstr "Чатавы уваход" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 -#, fuzzy msgid "Virtual Surround 7.1" -msgstr "Віртуальны абʼёмны прыёмнік" +msgstr "Віртуальны абʼёмны 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Аналагавы мона" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 -#, fuzzy msgid "Analog Mono (Left)" -msgstr "Аналагавы мона" +msgstr "Аналагавы мона (левы)" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 -#, fuzzy msgid "Analog Mono (Right)" -msgstr "Аналагавы мона" +msgstr "Аналагавы мона (правы)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" @@ -350,7 +395,6 @@ #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 -#, fuzzy msgid "Speakerphone" msgstr "Дынамік" @@ -429,11 +473,11 @@ #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" -msgstr "" +msgstr "Чат" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" -msgstr "" +msgstr "Гульня" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" @@ -452,13 +496,12 @@ msgstr "Шматканальны дуплекс" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 -#, fuzzy msgid "Stereo Duplex" -msgstr "Аналагавы стэрэа дуплекс" +msgstr "Стэрэа дуплекс" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" -msgstr "" +msgstr "Чатавы мона + Аб'ёмны 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format @@ -471,7 +514,7 @@ msgstr "%s уваход" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 -#, fuzzy, c-format +#, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" @@ -483,12 +526,11 @@ "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr0 "" -"Выклік snd_pcm_avail() вярнуў выключна вялікае значэнне: %lu байтаў (%lu " -"мс).\n" +"Выклік snd_pcm_avail() вярнуў выключна вялікае значэнне: %lu байт (%lu мс).\n" "Хутчэй за ўсё, гэта памылка ў ALSA-драйверы «%s». Калі ласка, паведаміце аб " "гэтым распрацоўнікам ALSA." msgstr1 "" -"Выклік snd_pcm_avail() вярнуў выключна вялікае значэнне: %lu байтаў (%lu " +"Выклік snd_pcm_avail() вярнуў выключна вялікае значэнне: %lu байта (%lu " "мс).\n" "Хутчэй за ўсё, гэта памылка ў ALSA-драйверы «%s». Калі ласка, паведаміце аб " "гэтым распрацоўнікам ALSA." @@ -499,24 +541,24 @@ "гэтым распрацоўнікам ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1241 -#, fuzzy, c-format +#, c-format msgid "" -"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" -"%lu ms).\n" +"snd_pcm_delay() returned a value that is exceptionally large: %li byte " +"(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" -"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" -"%lu ms).\n" +"snd_pcm_delay() returned a value that is exceptionally large: %li bytes " +"(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr0 "" -"Выклік snd_pcm_delay() вярнуў выключна вялікае значэнне: %li байтаў (%s%lu " +"Выклік snd_pcm_delay() вярнуў выключна вялікае значэнне: %li байт (%s%lu " "мс).\n" "Хутчэй за ўсё, гэта памылка ў ALSA-драйверы «%s». Калі ласка, паведаміце аб " "гэтым распрацоўнікам ALSA." msgstr1 "" -"Выклік snd_pcm_delay() вярнуў выключна вялікае значэнне: %li байтаў (%s%lu " +"Выклік snd_pcm_delay() вярнуў выключна вялікае значэнне: %li байта (%s%lu " "мс).\n" "Хутчэй за ўсё, гэта памылка ў ALSA-драйверы «%s». Калі ласка, паведаміце аб " "гэтым распрацоўнікам ALSA." @@ -540,7 +582,7 @@ "гэтым распрацоўнікам ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1331 -#, fuzzy, c-format +#, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" @@ -552,13 +594,13 @@ "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr0 "" -"Выклік snd_pcm_mmap_begin() вярнуў выключна вялікае значэнне: %lu байтаў " -"(%lu мс).\n" +"Выклік snd_pcm_mmap_begin() вярнуў выключна вялікае значэнне: %lu байт (%lu " +"мс).\n" "Хутчэй за ўсё, гэта памылка ў ALSA-драйверы «%s». Калі ласка, паведаміце аб " "гэтым распрацоўнікам ALSA." msgstr1 "" -"Выклік snd_pcm_mmap_begin() вярнуў выключна вялікае значэнне: %lu байтаў " -"(%lu мс).\n" +"Выклік snd_pcm_mmap_begin() вярнуў выключна вялікае значэнне: %lu байта (%lu " +"мс).\n" "Хутчэй за ўсё, гэта памылка ў ALSA-драйверы «%s». Калі ласка, паведаміце аб " "гэтым распрацоўнікам ALSA." msgstr2 "" @@ -569,34 +611,34 @@ #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" -msgstr "" +msgstr "Аўдыяшлюз (A2DP-крыніца і HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" -msgstr "" +msgstr "Высокадакладнае прайграванне (A2DP-прыёмнік, кодэк %s)" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" -msgstr "" +msgstr "Высокадакладны дуплекс (A2DP-крыніца/прыёмнік, кодэк %s)" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" -msgstr "" +msgstr "Высокадакладнае прайграванне (A2DP-прыёмнік)" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" -msgstr "" +msgstr "Высокадакладны дуплекс (A2DP-крыніца/прыёмнік)" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" -msgstr "" +msgstr "Навушнікі гарнітуры (HSP/HFP, кодэк %s)" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" -msgstr "" +msgstr "Навушнікі гарнітуры (HSP/HFP)" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" @@ -623,6 +665,5 @@ msgstr "Тэлефон" #: spa/plugins/bluez5/bluez5-device.c:1181 -#, fuzzy msgid "Bluetooth" -msgstr "Bluetooth-уваход" +msgstr "Bluetooth"
View file
pipewire-0.3.67.tar.gz/po/ru.po -> pipewire-0.3.68.tar.gz/po/ru.po
Changed
@@ -10,19 +10,19 @@ "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" -"POT-Creation-Date: 2021-05-16 13:13+0000\n" -"PO-Revision-Date: 2021-06-30 13:26+0300\n" -"Last-Translator: Alexey Rubtsov <rushills@gmail.com>\n" +"POT-Creation-Date: 2023-04-06 03:27+0000\n" +"PO-Revision-Date: 2023-04-06 10:51+0300\n" +"Last-Translator: Aleksandr Melman <Alexmelman88@gmail.com>\n" "Language-Team: ru\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -"X-Generator: Poedit 3.0\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"X-Generator: Poedit 3.2.2\n" -#: src/daemon/pipewire.c:45 +#: src/daemon/pipewire.c:26 #, c-format msgid "" "%s options\n" @@ -33,8 +33,8 @@ "%s опции\n" " -h, --help Показать справку\n" " --version Информация о версии\n" -" -c, --config Указать файл конфигурации (По " -"умолчанию %s)\n" +" -c, --config Загрузить конфигурацию (По умолчанию " +"%s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" @@ -44,72 +44,66 @@ msgid "Start the PipeWire Media System" msgstr "Запустить PipeWire" -#: src/examples/media-session/alsa-monitor.c:585 -#: spa/plugins/alsa/acp/compat.c:187 -msgid "Built-in Audio" -msgstr "Встроенное аудио" - -#: src/examples/media-session/alsa-monitor.c:589 -#: spa/plugins/alsa/acp/compat.c:192 -msgid "Modem" -msgstr "Модем" - -#: src/examples/media-session/alsa-monitor.c:598 -#: src/modules/module-zeroconf-discover.c:290 -msgid "Unknown device" -msgstr "Неизвестное устройство" - -#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:182 -#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:182 +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 #, c-format msgid "Tunnel to %s/%s" msgstr "Туннель на %s/%s" -#: src/modules/module-pulse-tunnel.c:511 +#: src/modules/module-fallback-sink.c:31 +msgid "Dummy Output" +msgstr "Холостой выход" + +#: src/modules/module-pulse-tunnel.c:688 #, c-format msgid "Tunnel for %s@%s" msgstr "Туннель для %s@%s" -#: src/modules/module-zeroconf-discover.c:302 +#: src/modules/module-zeroconf-discover.c:315 +msgid "Unknown device" +msgstr "Неизвестное устройство" + +#: src/modules/module-zeroconf-discover.c:327 #, c-format msgid "%s on %s@%s" msgstr "%s на %s@%s" -#: src/modules/module-zeroconf-discover.c:306 +#: src/modules/module-zeroconf-discover.c:331 #, c-format msgid "%s on %s" msgstr "%s по %s" -#: src/tools/pw-cat.c:991 +#: src/tools/pw-cat.c:974 #, c-format msgid "" -"%s options <file>\n" +"%s options <file>|-\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" -"%s опции <файл>\n" +"%s опции <file>|-\n" " -h, --help Показать справку\n" " --version Информация о версии\n" " -v, --verbose Включить показ подробной информации\n" "\n" -#: src/tools/pw-cat.c:998 +#: src/tools/pw-cat.c:981 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" -" --target Set node target (default %s)\n" +" --target Set node target serial or name " +"(default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" -" --list-targets List available targets for --target\n" +" -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Имя удаленного фоновой службы\n" @@ -119,7 +113,8 @@ "умолчанию %s)\n" " --media-role Задать роль мультимедиа (по " "умолчанию %s)\n" -" --target Задать цель узла (по умолчанию %s)\n" +" --target Задать серийный номер или имя " +"целевого узла (по умолчанию %s)\n" " 0 значит не связывать\n" " --latency Задать задежку узла (по умолчанию " "%s)\n" @@ -128,11 +123,10 @@ " or direct samples (256)\n" " частота такая же как у источника " "file\n" -" --list-targets Перечислить доступные цели для --" -"target\n" +" -P --properties Задать свойства узла\n" "\n" -#: src/tools/pw-cat.c:1016 +#: src/tools/pw-cat.c:999 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -167,19 +161,23 @@ "умолчанию %d)\n" "\n" -#: src/tools/pw-cat.c:1033 +#: src/tools/pw-cat.c:1016 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" +" -d, --dsd DSD mode\n" +" -o, --encoded Encoded mode\n" "\n" msgstr "" " -p, --playback Режим проигрывания\n" " -r, --record Режим записи\n" " -m, --midi Режим MIDI\n" +" -d, --dsd Режим DSD\n" +" -o, --encoded Режим кодирования\n" "\n" -#: src/tools/pw-cli.c:2959 +#: src/tools/pw-cli.c:2220 #, c-format msgid "" "%s options command\n" @@ -187,6 +185,7 @@ " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" +" -m, --monitor Monitor activity\n" "\n" msgstr "" "%s опции команда\n" @@ -195,197 +194,198 @@ " -d, --daemon Запустить в режиме фоновой службы " "(По умолчанию false)\n" " -r, --remote Имя удаленного фоновой службы\n" +" -m, --monitor Контроль активности\n" "\n" -#: spa/plugins/alsa/acp/acp.c:291 +#: spa/plugins/alsa/acp/acp.c:303 msgid "Pro Audio" msgstr "Pro Audio" -#: spa/plugins/alsa/acp/acp.c:412 spa/plugins/alsa/acp/alsa-mixer.c:4704 -#: spa/plugins/bluez5/bluez5-device.c:1020 +#: spa/plugins/alsa/acp/acp.c:427 spa/plugins/alsa/acp/alsa-mixer.c:4648 +#: spa/plugins/bluez5/bluez5-device.c:1286 msgid "Off" msgstr "Выключено" -#: spa/plugins/alsa/acp/alsa-mixer.c:2709 +#: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Вход" -#: spa/plugins/alsa/acp/alsa-mixer.c:2710 +#: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Вход док-станции" -#: spa/plugins/alsa/acp/alsa-mixer.c:2711 +#: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Микрофон док-станции" -#: spa/plugins/alsa/acp/alsa-mixer.c:2712 +#: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Линейный вход док-станции" -#: spa/plugins/alsa/acp/alsa-mixer.c:2713 -#: spa/plugins/alsa/acp/alsa-mixer.c:2804 +#: spa/plugins/alsa/acp/alsa-mixer.c:2656 +#: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Линейный вход" -#: spa/plugins/alsa/acp/alsa-mixer.c:2714 -#: spa/plugins/alsa/acp/alsa-mixer.c:2798 -#: spa/plugins/bluez5/bluez5-device.c:1175 +#: spa/plugins/alsa/acp/alsa-mixer.c:2657 +#: spa/plugins/alsa/acp/alsa-mixer.c:2741 +#: spa/plugins/bluez5/bluez5-device.c:1522 msgid "Microphone" msgstr "Микрофон" -#: spa/plugins/alsa/acp/alsa-mixer.c:2715 -#: spa/plugins/alsa/acp/alsa-mixer.c:2799 +#: spa/plugins/alsa/acp/alsa-mixer.c:2658 +#: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Фронтальный микрофон" -#: spa/plugins/alsa/acp/alsa-mixer.c:2716 -#: spa/plugins/alsa/acp/alsa-mixer.c:2800 +#: spa/plugins/alsa/acp/alsa-mixer.c:2659 +#: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Тыловой микрофон" -#: spa/plugins/alsa/acp/alsa-mixer.c:2717 +#: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Внешний микрофон" -#: spa/plugins/alsa/acp/alsa-mixer.c:2718 -#: spa/plugins/alsa/acp/alsa-mixer.c:2802 +#: spa/plugins/alsa/acp/alsa-mixer.c:2661 +#: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Встроенный микрофон" -#: spa/plugins/alsa/acp/alsa-mixer.c:2719 -#: spa/plugins/alsa/acp/alsa-mixer.c:2805 +#: spa/plugins/alsa/acp/alsa-mixer.c:2662 +#: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Радио" -#: spa/plugins/alsa/acp/alsa-mixer.c:2720 -#: spa/plugins/alsa/acp/alsa-mixer.c:2806 +#: spa/plugins/alsa/acp/alsa-mixer.c:2663 +#: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Видео" -#: spa/plugins/alsa/acp/alsa-mixer.c:2721 +#: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Автоматическая регулировка усиления" -#: spa/plugins/alsa/acp/alsa-mixer.c:2722 +#: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Нет автоматической регулировки усиления" -#: spa/plugins/alsa/acp/alsa-mixer.c:2723 +#: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Усиление" -#: spa/plugins/alsa/acp/alsa-mixer.c:2724 +#: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Нет усиления" -#: spa/plugins/alsa/acp/alsa-mixer.c:2725 +#: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Усилитель" -#: spa/plugins/alsa/acp/alsa-mixer.c:2726 +#: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Нет усилителя" -#: spa/plugins/alsa/acp/alsa-mixer.c:2727 +#: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Усиление басов" -#: spa/plugins/alsa/acp/alsa-mixer.c:2728 +#: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Нет усиления басов" -#: spa/plugins/alsa/acp/alsa-mixer.c:2729 -#: spa/plugins/bluez5/bluez5-device.c:1180 +#: spa/plugins/alsa/acp/alsa-mixer.c:2672 +#: spa/plugins/bluez5/bluez5-device.c:1528 msgid "Speaker" msgstr "Динамик" -#: spa/plugins/alsa/acp/alsa-mixer.c:2730 -#: spa/plugins/alsa/acp/alsa-mixer.c:2808 +#: spa/plugins/alsa/acp/alsa-mixer.c:2673 +#: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Наушники" -#: spa/plugins/alsa/acp/alsa-mixer.c:2797 +#: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Аналоговый вход" -#: spa/plugins/alsa/acp/alsa-mixer.c:2801 +#: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Микрофон док-станции" -#: spa/plugins/alsa/acp/alsa-mixer.c:2803 +#: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Микрофон гарнитуры" -#: spa/plugins/alsa/acp/alsa-mixer.c:2807 +#: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Аналоговый выход" -#: spa/plugins/alsa/acp/alsa-mixer.c:2809 +#: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Наушники 2" -#: spa/plugins/alsa/acp/alsa-mixer.c:2810 +#: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Моно-выход наушников" -#: spa/plugins/alsa/acp/alsa-mixer.c:2811 +#: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Линейный выход" -#: spa/plugins/alsa/acp/alsa-mixer.c:2812 +#: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Аналоговый моно-выход" -#: spa/plugins/alsa/acp/alsa-mixer.c:2813 +#: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Динамики" -#: spa/plugins/alsa/acp/alsa-mixer.c:2814 +#: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" -#: spa/plugins/alsa/acp/alsa-mixer.c:2815 +#: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Цифровой выход (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2816 +#: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Цифровой вход (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2817 +#: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Многоканальный вход" -#: spa/plugins/alsa/acp/alsa-mixer.c:2818 +#: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Многоканальный выход" -#: spa/plugins/alsa/acp/alsa-mixer.c:2819 +#: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Игровой выход" -#: spa/plugins/alsa/acp/alsa-mixer.c:2820 -#: spa/plugins/alsa/acp/alsa-mixer.c:2821 +#: spa/plugins/alsa/acp/alsa-mixer.c:2763 +#: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Разговорный выход" -#: spa/plugins/alsa/acp/alsa-mixer.c:2822 +#: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" -msgstr "Разговорный выход" +msgstr "Разговорный вход" -#: spa/plugins/alsa/acp/alsa-mixer.c:2823 +#: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Виртуальный объёмный звук 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4527 +#: spa/plugins/alsa/acp/alsa-mixer.c:4471 msgid "Analog Mono" msgstr "Аналоговый моно" -#: spa/plugins/alsa/acp/alsa-mixer.c:4528 +#: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Analog Mono (Left)" msgstr "Аналоговый моно (левый)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4529 +#: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Mono (Right)" msgstr "Аналоговый моно (правый)" @@ -394,147 +394,147 @@ #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. -#: spa/plugins/alsa/acp/alsa-mixer.c:4530 -#: spa/plugins/alsa/acp/alsa-mixer.c:4538 -#: spa/plugins/alsa/acp/alsa-mixer.c:4539 +#: spa/plugins/alsa/acp/alsa-mixer.c:4474 +#: spa/plugins/alsa/acp/alsa-mixer.c:4482 +#: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Stereo" msgstr "Аналоговый стерео" -#: spa/plugins/alsa/acp/alsa-mixer.c:4531 +#: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Mono" msgstr "Моно" -#: spa/plugins/alsa/acp/alsa-mixer.c:4532 +#: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Stereo" msgstr "Стерео" -#: spa/plugins/alsa/acp/alsa-mixer.c:4540 -#: spa/plugins/alsa/acp/alsa-mixer.c:4698 -#: spa/plugins/bluez5/bluez5-device.c:1165 +#: spa/plugins/alsa/acp/alsa-mixer.c:4484 +#: spa/plugins/alsa/acp/alsa-mixer.c:4642 +#: spa/plugins/bluez5/bluez5-device.c:1510 msgid "Headset" msgstr "Гарнитура" -#: spa/plugins/alsa/acp/alsa-mixer.c:4541 -#: spa/plugins/alsa/acp/alsa-mixer.c:4699 +#: spa/plugins/alsa/acp/alsa-mixer.c:4485 +#: spa/plugins/alsa/acp/alsa-mixer.c:4643 msgid "Speakerphone" msgstr "Спикерфон" -#: spa/plugins/alsa/acp/alsa-mixer.c:4542 -#: spa/plugins/alsa/acp/alsa-mixer.c:4543 +#: spa/plugins/alsa/acp/alsa-mixer.c:4486 +#: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Multichannel" msgstr "Многоканальный" -#: spa/plugins/alsa/acp/alsa-mixer.c:4544 +#: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Analog Surround 2.1" msgstr "Аналоговый объёмный 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4545 +#: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Analog Surround 3.0" msgstr "Аналоговый объёмный 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4546 +#: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Analog Surround 3.1" msgstr "Аналоговый объёмный 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4547 +#: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Analog Surround 4.0" msgstr "Аналоговый объёмный 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4548 +#: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Analog Surround 4.1" msgstr "Аналоговый объёмный 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4549 +#: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Analog Surround 5.0" msgstr "Аналоговый объёмный 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4550 +#: spa/plugins/alsa/acp/alsa-mixer.c:4494 msgid "Analog Surround 5.1" msgstr "Аналоговый объёмный 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4551 +#: spa/plugins/alsa/acp/alsa-mixer.c:4495 msgid "Analog Surround 6.0" msgstr "Аналоговый объёмный 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4552 +#: spa/plugins/alsa/acp/alsa-mixer.c:4496 msgid "Analog Surround 6.1" msgstr "Аналоговый объёмный 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4553 +#: spa/plugins/alsa/acp/alsa-mixer.c:4497 msgid "Analog Surround 7.0" msgstr "Аналоговый объёмный 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4554 +#: spa/plugins/alsa/acp/alsa-mixer.c:4498 msgid "Analog Surround 7.1" msgstr "Аналоговый объёмный 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4555 +#: spa/plugins/alsa/acp/alsa-mixer.c:4499 msgid "Digital Stereo (IEC958)" msgstr "Цифровой стерео (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4556 +#: spa/plugins/alsa/acp/alsa-mixer.c:4500 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Цифровой объёмный 4.0 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4557 +#: spa/plugins/alsa/acp/alsa-mixer.c:4501 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Цифровой объёмный 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4558 +#: spa/plugins/alsa/acp/alsa-mixer.c:4502 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Цифровой объёмный 5.1 (IEC958/DTS)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4559 +#: spa/plugins/alsa/acp/alsa-mixer.c:4503 msgid "Digital Stereo (HDMI)" msgstr "Цифровой стерео (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4560 +#: spa/plugins/alsa/acp/alsa-mixer.c:4504 msgid "Digital Surround 5.1 (HDMI)" msgstr "Цифровой объёмный 5.1 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4561 +#: spa/plugins/alsa/acp/alsa-mixer.c:4505 msgid "Chat" msgstr "Чат" -#: spa/plugins/alsa/acp/alsa-mixer.c:4562 +#: spa/plugins/alsa/acp/alsa-mixer.c:4506 msgid "Game" msgstr "Игра" -#: spa/plugins/alsa/acp/alsa-mixer.c:4696 +#: spa/plugins/alsa/acp/alsa-mixer.c:4640 msgid "Analog Mono Duplex" msgstr "Аналоговый моно дуплекс" -#: spa/plugins/alsa/acp/alsa-mixer.c:4697 +#: spa/plugins/alsa/acp/alsa-mixer.c:4641 msgid "Analog Stereo Duplex" msgstr "Аналоговый стерео дуплекс" -#: spa/plugins/alsa/acp/alsa-mixer.c:4700 +#: spa/plugins/alsa/acp/alsa-mixer.c:4644 msgid "Digital Stereo Duplex (IEC958)" msgstr "Цифровой стерео дуплекс (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4701 +#: spa/plugins/alsa/acp/alsa-mixer.c:4645 msgid "Multichannel Duplex" msgstr "Многоканальный дуплекс" -#: spa/plugins/alsa/acp/alsa-mixer.c:4702 +#: spa/plugins/alsa/acp/alsa-mixer.c:4646 msgid "Stereo Duplex" msgstr "Стерео дуплекс" -#: spa/plugins/alsa/acp/alsa-mixer.c:4703 +#: spa/plugins/alsa/acp/alsa-mixer.c:4647 msgid "Mono Chat + 7.1 Surround" msgstr "Моно чат + 7.1 Surround" -#: spa/plugins/alsa/acp/alsa-mixer.c:4806 +#: spa/plugins/alsa/acp/alsa-mixer.c:4748 #, c-format msgid "%s Output" msgstr "%s выход" -#: spa/plugins/alsa/acp/alsa-mixer.c:4813 +#: spa/plugins/alsa/acp/alsa-mixer.c:4756 #, c-format msgid "%s Input" msgstr "%s вход" -#: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 +#: spa/plugins/alsa/acp/alsa-util.c:1211 spa/plugins/alsa/acp/alsa-util.c:1305 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -562,16 +562,16 @@ "Вероятно, это ошибка в драйвере ALSA «%s». Пожалуйста, сообщите об этой " "проблеме разработчикам ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1241 +#: spa/plugins/alsa/acp/alsa-util.c:1277 #, c-format msgid "" -"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" -"%lu ms).\n" +"snd_pcm_delay() returned a value that is exceptionally large: %li byte " +"(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" -"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" -"%lu ms).\n" +"snd_pcm_delay() returned a value that is exceptionally large: %li bytes " +"(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr0 "" @@ -590,7 +590,7 @@ "Вероятно, это ошибка в драйвере ALSA «%s». Пожалуйста, сообщите об этой " "проблеме разработчикам ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1288 +#: spa/plugins/alsa/acp/alsa-util.c:1324 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -603,7 +603,7 @@ "Вероятно, это ошибка в драйвере ALSA «%s». Пожалуйста, сообщите об этой " "проблеме разработчикам ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1331 +#: spa/plugins/alsa/acp/alsa-util.c:1367 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -631,65 +631,116 @@ "Вероятно, это ошибка в драйвере ALSA «%s». Пожалуйста, сообщите об этой " "проблеме разработчикам ALSA." -#: spa/plugins/alsa/acp/channelmap.h:466 +#: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(недействительно)" -#: spa/plugins/bluez5/bluez5-device.c:1030 +#: spa/plugins/alsa/acp/compat.c:189 +msgid "Built-in Audio" +msgstr "Встроенное аудио" + +#: spa/plugins/alsa/acp/compat.c:194 +msgid "Modem" +msgstr "Модем" + +#: spa/plugins/bluez5/bluez5-device.c:1297 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Адаптер аудиогарнитуры (приёмник A2DP и HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1053 +#: spa/plugins/bluez5/bluez5-device.c:1322 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Воспроизведение высокого качества (приёмник A2DP, кодек %s)" -#: spa/plugins/bluez5/bluez5-device.c:1055 +#: spa/plugins/bluez5/bluez5-device.c:1325 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Дуплекс высокого качества (источник / приёмник A2DP, кодек %s)" -#: spa/plugins/bluez5/bluez5-device.c:1061 +#: spa/plugins/bluez5/bluez5-device.c:1333 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Воспроизведение высокого качества (приёмник A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1063 +#: spa/plugins/bluez5/bluez5-device.c:1335 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Дуплекс высокого качества (источник / приёмник A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1090 +#: spa/plugins/bluez5/bluez5-device.c:1377 +#, c-format +msgid "High Fidelity Playback (BAP Sink, codec %s)" +msgstr "Воспроизведение высокого качества (приёмник BAP, кодек %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1381 +#, c-format +msgid "High Fidelity Input (BAP Source, codec %s)" +msgstr "Вход высокого качества (источник BAP, кодек %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1385 +#, c-format +msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" +msgstr "Дуплекс высокого качества (источник / приёмник BAP, кодек %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1393 +msgid "High Fidelity Playback (BAP Sink)" +msgstr "Воспроизведение высокого качества (приёмник BAP)" + +#: spa/plugins/bluez5/bluez5-device.c:1396 +msgid "High Fidelity Input (BAP Source)" +msgstr "Вход высокого качества (источник BAP)" + +#: spa/plugins/bluez5/bluez5-device.c:1399 +msgid "High Fidelity Duplex (BAP Source/Sink)" +msgstr "Дуплекс высокого качества (источник / приёмник BAP)" + +#: spa/plugins/bluez5/bluez5-device.c:1427 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Гарнитура (HSP/HFP, кодек %s)" -#: spa/plugins/bluez5/bluez5-device.c:1094 +#: spa/plugins/bluez5/bluez5-device.c:1432 msgid "Headset Head Unit (HSP/HFP)" msgstr "Гарнитура (HSP/HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1170 +#: spa/plugins/bluez5/bluez5-device.c:1511 +#: spa/plugins/bluez5/bluez5-device.c:1516 +#: spa/plugins/bluez5/bluez5-device.c:1523 +#: spa/plugins/bluez5/bluez5-device.c:1529 +#: spa/plugins/bluez5/bluez5-device.c:1535 +#: spa/plugins/bluez5/bluez5-device.c:1541 +#: spa/plugins/bluez5/bluez5-device.c:1547 +#: spa/plugins/bluez5/bluez5-device.c:1553 +#: spa/plugins/bluez5/bluez5-device.c:1559 msgid "Handsfree" -msgstr "Hands-Free устройство" +msgstr "Handsfree" -#: spa/plugins/bluez5/bluez5-device.c:1185 +#: spa/plugins/bluez5/bluez5-device.c:1517 +msgid "Handsfree (HFP)" +msgstr "Handsfree (HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:1534 msgid "Headphone" msgstr "Наушники" -#: spa/plugins/bluez5/bluez5-device.c:1190 +#: spa/plugins/bluez5/bluez5-device.c:1540 msgid "Portable" msgstr "Портативное устройство" -#: spa/plugins/bluez5/bluez5-device.c:1195 +#: spa/plugins/bluez5/bluez5-device.c:1546 msgid "Car" msgstr "Автомобильное устройство" -#: spa/plugins/bluez5/bluez5-device.c:1200 +#: spa/plugins/bluez5/bluez5-device.c:1552 msgid "HiFi" msgstr "Hi-Fi" -#: spa/plugins/bluez5/bluez5-device.c:1205 +#: spa/plugins/bluez5/bluez5-device.c:1558 msgid "Phone" msgstr "Телефон" -#: spa/plugins/bluez5/bluez5-device.c:1211 +#: spa/plugins/bluez5/bluez5-device.c:1565 msgid "Bluetooth" msgstr "Bluetooth" + +#: spa/plugins/bluez5/bluez5-device.c:1566 +msgid "Bluetooth (HFP)" +msgstr "Bluetooth (HFP)"
View file
pipewire-0.3.67.tar.gz/spa/examples/adapter-control.c -> pipewire-0.3.68.tar.gz/spa/examples/adapter-control.c
Changed
@@ -24,6 +24,7 @@ #include <errno.h> #include <pthread.h> #include <poll.h> +#include <getopt.h> #include <spa/control/control.h> #include <spa/graph/graph.h> @@ -45,6 +46,22 @@ #define MIN_LATENCY 1024 #define CONTROL_BUFFER_SIZE 32768 +#define DEFAULT_RAMP_SAMPLES (64*1*1024) +#define DEFAULT_RAMP_STEP_SAMPLES 200 + +#define DEFAULT_RAMP_TIME 2000 // 2 seconds +#define DEFAULT_RAMP_STEP_TIME 5000 // 5 milli seconds + +#define DEFAULT_DEVICE "hw:0,0" + +#define LINEAR "linear" +#define CUBIC "cubic" +#define DEFAULT_SCALE SPA_AUDIO_VOLUME_RAMP_LINEAR + +#define NON_NATIVE "non-native" +#define NATIVE "native" +#define DEFAULT_MODE NON_NATIVE + struct buffer { struct spa_buffer buffer; @@ -91,11 +108,22 @@ double volume_accum; uint32_t volume_offs; + const char *alsa_device; + + const char *mode; + enum spa_audio_volume_ramp_scale scale; + + uint32_t volume_ramp_samples; + uint32_t volume_ramp_step_samples; + uint32_t volume_ramp_time; + uint32_t volume_ramp_step_time; + bool running; pthread_t thread; }; -static int load_handle(struct data *data, struct spa_handle **handle, const char *lib, const char *name) +static int load_handle (struct data *data, struct spa_handle **handle, const + char *lib, const char *name, struct spa_dict *info) { int res; void *hnd; @@ -134,7 +162,7 @@ *handle = calloc(1, spa_handle_factory_get_size(factory, NULL)); if ((res = spa_handle_factory_init(factory, *handle, - NULL, data->support, + info, data->support, data->n_support)) < 0) { printf("can't make factory instance: %d\n", res); goto exit_cleanup; @@ -153,6 +181,8 @@ int res; const char *str; struct spa_handle *handle = NULL; + struct spa_dict_item items 2; + struct spa_dict info; void *iface; if ((str = getenv("SPA_PLUGIN_DIR")) == NULL) @@ -167,14 +197,23 @@ /* init the graph */ spa_graph_init(&data->graph, &data->graph_state); - /* set the default log */ - data->log = &default_log.log; + /* enable the debug messages in SPA */ + items 0 = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_TIMESTAMP, "true"); + info = SPA_DICT_INIT(items, 1); + if ((res = load_handle (data, &handle, "support/libspa-support.so", + SPA_NAME_SUPPORT_LOG, &info)) < 0) + return res; + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Log, &iface)) < 0) { + printf("can't get System interface %d\n", res); + return res; + } + data->log = iface; data->supportdata->n_support++ = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, data->log); /* load and set support system */ if ((res = load_handle(data, &handle, "support/libspa-support.so", - SPA_NAME_SUPPORT_SYSTEM)) < 0) + SPA_NAME_SUPPORT_SYSTEM, NULL)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_System, &iface)) < 0) { printf("can't get System interface %d\n", res); @@ -187,7 +226,7 @@ /* load and set support loop and loop control */ if ((res = load_handle(data, &handle, "support/libspa-support.so", - SPA_NAME_SUPPORT_LOOP)) < 0) + SPA_NAME_SUPPORT_LOOP, NULL)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Loop, &iface)) < 0) { @@ -270,68 +309,159 @@ return res; } -static int fade_in(struct data *data) +static int get_ramp_samples(struct data *data) { - struct spa_pod_builder b; - struct spa_pod_frame f1; - void *buffer = data->control_buffer->datas0.data; - uint32_t buffer_size = data->control_buffer->datas0.maxsize; - data->control_buffer->datas0.chunk0.size = buffer_size; + int samples = -1; + if (data->volume_ramp_samples) + samples = data->volume_ramp_samples; + else if (data->volume_ramp_time) { + samples = (data->volume_ramp_time * 48000) / 1000; + } + if (!samples) + samples = -1; - printf ("fading in\n"); + return samples; +} - spa_pod_builder_init(&b, buffer, buffer_size); - spa_pod_builder_push_sequence(&b, &f0, 0); - data->volume_offs = 0; - do { - spa_pod_builder_control(&b, data->volume_offs, SPA_CONTROL_Properties); +static int get_ramp_step_samples(struct data *data) +{ + int samples = -1; + if (data->volume_ramp_step_samples) + samples = data->volume_ramp_step_samples; + else if (data->volume_ramp_step_time) { + /* convert the step time which is in nano seconds to seconds */ + samples = (data->volume_ramp_step_time / 1000) * (48000 / 1000); + } + if (!samples) + samples = -1; + + return samples; +} + +static double get_volume_at_scale(struct data *data) +{ + if (data->scale == SPA_AUDIO_VOLUME_RAMP_LINEAR) + return data->volume_accum; + else if (data->scale == SPA_AUDIO_VOLUME_RAMP_CUBIC) + return (data->volume_accum * data->volume_accum * data->volume_accum); + + return 0.0; +} + +static int fade_in(struct data *data) +{ + printf("fading in\n"); + if (spa_streq (data->mode, NON_NATIVE)) { + struct spa_pod_builder b; + struct spa_pod_frame f1; + void *buffer = data->control_buffer->datas0.data; + int ramp_samples = get_ramp_samples(data); + int ramp_step_samples = get_ramp_step_samples(data); + double step_size = ((double) ramp_step_samples / (double) ramp_samples); + uint32_t buffer_size = data->control_buffer->datas0.maxsize; + data->control_buffer->datas0.chunk0.size = buffer_size; + + spa_pod_builder_init(&b, buffer, buffer_size); + spa_pod_builder_push_sequence(&b, &f0, 0); + data->volume_offs = 0; + do { + // printf("volume level %f offset %d\n", get_volume_at_scale(data), data->volume_offs); + spa_pod_builder_control(&b, data->volume_offs, SPA_CONTROL_Properties); spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, 0, + SPA_PROP_volume, SPA_POD_Float(get_volume_at_scale(data))); + data->volume_accum += step_size; + data->volume_offs += ramp_step_samples; + } while (data->volume_accum < 1.0); + spa_pod_builder_pop(&b, &f0); + } + else { + struct spa_pod_builder b; + struct spa_pod *props; + int res = 0; + uint8_t buffer1024; + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + props = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, 0, - SPA_PROP_volume, SPA_POD_Float(data->volume_accum)); - data->volume_accum += 0.003; - data->volume_offs += 200; - } while (data->volume_accum < 1.0); - spa_pod_builder_pop(&b, &f0); + SPA_PROP_volume, SPA_POD_Float(1.0), + SPA_PROP_volumeRampSamples, SPA_POD_Int(data->volume_ramp_samples), + SPA_PROP_volumeRampStepSamples, SPA_POD_Int(data->volume_ramp_step_samples), + SPA_PROP_volumeRampTime, SPA_POD_Int(data->volume_ramp_time), + SPA_PROP_volumeRampStepTime, SPA_POD_Int(data->volume_ramp_step_time), + SPA_PROP_volumeRampScale, SPA_POD_Id(data->scale)); + if ((res = spa_node_set_param(data->sink_node, SPA_PARAM_Props, 0, props)) < 0) { + printf("can't call volramp set params %d\n", res); + return res; + } + } return 0; } static int fade_out(struct data *data) { - struct spa_pod_builder b; - struct spa_pod_frame f1; - void *buffer = data->control_buffer->datas0.data; - uint32_t buffer_size = data->control_buffer->datas0.maxsize; - data->control_buffer->datas0.chunk0.size = buffer_size; - - printf ("fading out\n"); - - spa_pod_builder_init(&b, buffer, buffer_size); - spa_pod_builder_push_sequence(&b, &f0, 0); - data->volume_offs = 200; - do { - spa_pod_builder_control(&b, data->volume_offs, SPA_CONTROL_Properties); + printf("fading out\n"); + if (spa_streq (data->mode, NON_NATIVE)) { + struct spa_pod_builder b; + struct spa_pod_frame f1; + int ramp_samples = get_ramp_samples(data); + int ramp_step_samples = get_ramp_step_samples(data); + double step_size = ((double) ramp_step_samples / (double) ramp_samples); + + + void *buffer = data->control_buffer->datas0.data; + uint32_t buffer_size = data->control_buffer->datas0.maxsize; + data->control_buffer->datas0.chunk0.size = buffer_size; + + spa_pod_builder_init(&b, buffer, buffer_size); + spa_pod_builder_push_sequence(&b, &f0, 0); + data->volume_offs = ramp_step_samples; + do { + // printf("volume level %f offset %d\n", get_volume_at_scale(data), data->volume_offs); + spa_pod_builder_control(&b, data->volume_offs, SPA_CONTROL_Properties); spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, 0, + SPA_PROP_volume, SPA_POD_Float(get_volume_at_scale(data))); + data->volume_accum -= step_size; + data->volume_offs += ramp_step_samples; + } while (data->volume_accum > 0.0); + spa_pod_builder_pop(&b, &f0); + } else { + struct spa_pod_builder b; + uint8_t buffer1024; + struct spa_pod *props; + int res = 0; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + props = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, 0, - SPA_PROP_volume, SPA_POD_Float(data->volume_accum)); - data->volume_accum -= 0.003; - data->volume_offs += 200; - } while (data->volume_accum > 0.0); - spa_pod_builder_pop(&b, &f0); + SPA_PROP_volume, SPA_POD_Float(0.0), + SPA_PROP_volumeRampSamples, SPA_POD_Int(data->volume_ramp_samples), + SPA_PROP_volumeRampStepSamples, SPA_POD_Int(data->volume_ramp_step_samples), + SPA_PROP_volumeRampTime, SPA_POD_Int(data->volume_ramp_time), + SPA_PROP_volumeRampStepTime, SPA_POD_Int(data->volume_ramp_step_time), + SPA_PROP_volumeRampScale, SPA_POD_Id(data->scale)); + if ((res = spa_node_set_param(data->sink_node, SPA_PARAM_Props, 0, props)) < 0) { + printf("can't call volramp set params %d\n", res); + return res; + } + } return 0; } static void do_fade(struct data *data) { - switch (data->control_io.status) { - case SPA_STATUS_OK: - case SPA_STATUS_NEED_DATA: - break; - case SPA_STATUS_HAVE_DATA: - case SPA_STATUS_STOPPED: - default: - return; + if (spa_streq (data->mode, NON_NATIVE)) { + switch (data->control_io.status) { + case SPA_STATUS_OK: + case SPA_STATUS_NEED_DATA: + break; + case SPA_STATUS_HAVE_DATA: + case SPA_STATUS_STOPPED: + default: + return; + } } /* fade */ @@ -340,8 +470,10 @@ else fade_out(data); - data->control_io.status = SPA_STATUS_HAVE_DATA; - data->control_io.buffer_id = 0; + if (spa_streq (data->mode, NON_NATIVE)) { + data->control_io.status = SPA_STATUS_HAVE_DATA; + data->control_io.buffer_id = 0; + } /* alternate */ data->start_fade_in = !data->start_fade_in; @@ -350,14 +482,15 @@ static int on_sink_node_ready(void *_data, int status) { struct data *data = _data; + int runway = (get_ramp_samples(data) / 1024); /* only do fade in/out when buffer count is 0 */ if (data->buffer_count == 0) - do_fade(data); + do_fade(data); /* update buffer count */ data->buffer_count++; - if (data->buffer_count > 64) + if (data->buffer_count > (runway * 2)) data->buffer_count = 0; spa_graph_node_process(&data->graph_source_node); @@ -370,7 +503,7 @@ .ready = on_sink_node_ready, }; -static int make_nodes(struct data *data, const char *device) +static int make_nodes(struct data *data) { int res = 0; struct spa_pod *props; @@ -380,6 +513,7 @@ struct spa_dict_item items2; struct spa_audio_info_raw info; struct spa_pod *param; + float initial_volume = 0.0; items0 = SPA_DICT_ITEM_INIT("clock.quantum-limit", "8192"); @@ -391,6 +525,7 @@ printf("can't create source follower node (audiotestsrc): %d\n", res); return res; } + printf("created source follower node %p\n", data->source_follower_node); /* set the format on the source */ spa_pod_builder_init(&b, buffer, sizeof(buffer)); @@ -406,7 +541,7 @@ return res; } - /* make the sink adapter node */ + /* make the source adapter node */ snprintf(value, sizeof(value), "pointer:%p", data->source_follower_node); items1 = SPA_DICT_ITEM_INIT("audio.adapt.follower", value); if ((res = make_node(data, &data->source_node, @@ -416,6 +551,7 @@ printf("can't create source adapter node: %d\n", res); return res; } + printf("created source adapter node %p\n", data->source_node); /* setup the source node props */ spa_pod_builder_init(&b, buffer, sizeof(buffer)); @@ -455,6 +591,7 @@ printf("can't create sink follower node (alsa-pcm-sink): %d\n", res); return res; } + printf("created sink follower node %p\n", data->sink_follower_node); /* make the sink adapter node */ snprintf(value, sizeof(value), "pointer:%p", data->sink_follower_node); @@ -466,6 +603,7 @@ printf("can't create sink adapter node: %d\n", res); return res; } + printf("created sink adapter node %p\n", data->sink_node); /* add sink follower node callbacks */ spa_node_set_callbacks(data->sink_node, &sink_node_callbacks, data); @@ -474,12 +612,16 @@ spa_pod_builder_init(&b, buffer, sizeof(buffer)); props = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, 0, - SPA_PROP_device, SPA_POD_String(device ? device : "hw:0"), + SPA_PROP_device, SPA_POD_String(data->alsa_device), SPA_PROP_minLatency, SPA_POD_Int(MIN_LATENCY)); if ((res = spa_node_set_param(data->sink_follower_node, SPA_PARAM_Props, 0, props)) < 0) { printf("can't setup sink follower node %d\n", res); return res; } + printf("Selected (%s) alsa device\n", data->alsa_device); + + if (!data->start_fade_in) + initial_volume = 1.0; /* setup the sink node port config */ spa_zero(info); @@ -489,17 +631,36 @@ info.position0 = SPA_AUDIO_CHANNEL_MONO; spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_INPUT), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), - SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(true), - SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); + + if (spa_streq (data->mode, NON_NATIVE)) + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_INPUT), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), + SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(true), + SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); + else + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_INPUT), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), + SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); + + if ((res = spa_node_set_param(data->sink_node, SPA_PARAM_PortConfig, 0, param) < 0)) { printf("can't setup sink node %d\n", res); return res; } + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + props = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, 0, + SPA_PROP_volume, SPA_POD_Float(initial_volume)); + if ((res = spa_node_set_param(data->sink_node, SPA_PARAM_Props, 0, props)) < 0) { + printf("can't configure initial volume %d\n", res); + return res; + } + /* set io buffers on source and sink nodes */ data->source_sink_io0 = SPA_IO_BUFFERS_INIT; if ((res = spa_node_port_set_io(data->source_node, @@ -509,6 +670,9 @@ printf("can't set io buffers on port 0 of source node: %d\n", res); return res; } + printf("set io buffers on port 0 of source node %p\n", data->source_node); + + if ((res = spa_node_port_set_io(data->sink_node, SPA_DIRECTION_INPUT, 0, SPA_IO_Buffers, @@ -516,9 +680,13 @@ printf("can't set io buffers on port 0 of sink node: %d\n", res); return res; } + printf("set io buffers on port 0 of sink node %p\n", data->sink_node); + /* set io position and clock on source and sink nodes */ - data->position.clock.rate = SPA_FRACTION(1, 48000); - data->position.clock.duration = 1024; + data->position.clock.target_rate = SPA_FRACTION(1, 48000); + data->position.clock.target_duration = 1024; + data->position.clock.rate = data->position.clock.target_rate; + data->position.clock.duration = data->position.clock.target_duration; if ((res = spa_node_set_io(data->source_node, SPA_IO_Position, &data->position, sizeof(data->position))) < 0) { @@ -544,15 +712,16 @@ return res; } - /* set io buffers on control port of sink node */ - if ((res = spa_node_port_set_io(data->sink_node, + if (spa_streq (data->mode, NON_NATIVE)) { + /* set io buffers on control port of sink node */ + if ((res = spa_node_port_set_io(data->sink_node, SPA_DIRECTION_INPUT, 1, SPA_IO_Buffers, &data->control_io, sizeof(data->control_io))) < 0) { - printf("can't set io buffers on control port 1 of sink node\n"); - return res; + printf("can't set io buffers on control port 1 of sink node\n"); + return res; + } } - /* add source node to the graph */ spa_graph_node_init(&data->graph_source_node, &data->graph_source_state); spa_graph_node_set_callbacks(&data->graph_source_node, &spa_graph_node_impl_default, data->source_node); @@ -624,20 +793,28 @@ &SPA_AUDIO_INFO_DSP_INIT( .format = SPA_AUDIO_FORMAT_F32P)); if ((res = spa_node_port_set_param(data->source_node, - SPA_DIRECTION_OUTPUT, 0, SPA_PARAM_Format, 0, param)) < 0) + SPA_DIRECTION_OUTPUT, 0, SPA_PARAM_Format, 0, param)) < 0) { + printf("can't set format on source node: %d\n", res); return res; + } if ((res = spa_node_port_set_param(data->sink_node, - SPA_DIRECTION_INPUT, 0, SPA_PARAM_Format, 0, param)) < 0) + SPA_DIRECTION_INPUT, 0, SPA_PARAM_Format, 0, param)) < 0) { + printf("can't set format on source node: %d\n", res); return res; + } - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - param = spa_pod_builder_add_object(&b, + if (spa_streq (data->mode, NON_NATIVE)) { + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_Format, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); - if ((res = spa_node_port_set_param(data->sink_node, - SPA_DIRECTION_INPUT, 1, SPA_PARAM_Format, 0, param)) < 0) - return res; + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); + if ((res = spa_node_port_set_param(data->sink_node, + SPA_DIRECTION_INPUT, 1, SPA_PARAM_Format, 0, param)) < 0) { + printf("can't set format on control port of source node: %d\n", res); + return res; + } + } /* get the source node buffer size */ spa_pod_builder_init(&b, buffer, sizeof(buffer)); @@ -655,15 +832,20 @@ if ((res = spa_node_port_use_buffers(data->source_node, SPA_DIRECTION_OUTPUT, 0, 0, data->source_buffers, 1)) < 0) return res; + printf("allocated and assigned buffer(%ld) to source node %p\n", buffer_size, data->source_node); if ((res = spa_node_port_use_buffers(data->sink_node, SPA_DIRECTION_INPUT, 0, 0, data->source_buffers, 1)) < 0) return res; - - /* Set the control buffers */ - init_buffer(data, data->control_buffers, data->control_buffer, 1, CONTROL_BUFFER_SIZE); - if ((res = spa_node_port_use_buffers(data->sink_node, - SPA_DIRECTION_INPUT, 1, 0, data->control_buffers, 1)) < 0) - return res; + printf("allocated and assigned buffers to sink node %p\n", data->sink_node); + + if (spa_streq (data->mode, NON_NATIVE)) { + /* Set the control buffers */ + init_buffer(data, data->control_buffers, data->control_buffer, 1, CONTROL_BUFFER_SIZE); + if ((res = spa_node_port_use_buffers(data->sink_node, + SPA_DIRECTION_INPUT, 1, 0, data->control_buffers, 1)) < 0) + return res; + printf("allocated and assigned control buffers(%d) to sink node %p\n", CONTROL_BUFFER_SIZE, data->sink_node); + } return 0; } @@ -694,8 +876,10 @@ cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start); if ((res = spa_node_send_command(data->source_node, &cmd)) < 0) printf("got error %d\n", res); + printf("Source node started\n"); if ((res = spa_node_send_command(data->sink_node, &cmd)) < 0) printf("got error %d\n", res); + printf("sink node started\n"); spa_loop_control_leave(data->control); @@ -722,10 +906,134 @@ printf("got error %d\n", res); } +static char *getscale(uint32_t scale) +{ + char *scale_s = NULL; + + if (scale == SPA_AUDIO_VOLUME_RAMP_LINEAR) + scale_s = LINEAR; + else if (scale == SPA_AUDIO_VOLUME_RAMP_CUBIC) + scale_s = CUBIC; + + return scale_s; +} +static void show_help(struct data *data, const char *name, bool error) +{ + fprintf(error ? stderr : stdout, "%s options command\n" + " -h, --help Show this help\n" + " -d, --alsa-device ALSA device(\"aplay -l\" for more info) to play the samples on(default %s)\n" + " -m, --mode Volume Ramp Mode(\"NonNative\"(via Control Port) \"Native\" (via Volume Ramp Params of AudioAdapter plugin)) (default %s)\n" + " -s, --ramp-samples SPA_PROP_volumeRampSamples(Samples to ramp the volume over)(default %d)\n" + " -a, --ramp-step-samples SPA_PROP_volumeRampStepSamples(Step or incremental Samples to ramp the volume over)(default %d)\n" + " -t, --ramp-time SPA_PROP_volumeRampTime(Time to ramp the volume over in msec)(default %d)\n" + " -i, --ramp-step-time SPA_PROP_volumeRampStepTime(Step or incremental Time to ramp the volume over in nano sec)(default %d)\n" + " -c, --scale SPA_PROP_volumeRampScale(the scale or graph to used to ramp the volume)(\"linear\" or \"cubic\")(default %s)\n" + "examples:\n" + "adapter-control\n" + "-->when invoked with out any params, ramps volume with default values\n" + "adapter-control --ramp-samples=70000, rest of the parameters are defaults\n" + "-->ramps volume over 70000 samples(it is 1.45 seconds)\n" + "adapter-control --alsa-device=hw:0,0 --ramp-samples=70000\n" + "-->ramps volume on \"hw:0,0\" alsa device over 70000 samples\n" + "adapter-control --alsa-device=hw:0,0 --ramp-samples=70000 --mode=native\n" + "-->ramps volume on \"hw:0,0\" alsa device over 70000 samples in native mode\n" + "adapter-control --alsa-device=hw:0,0 --ramp-time=1000 --mode=native\n" + "-->ramps volume on \"hw:0,0\" alsa device over 1000 msec in native mode\n" + "adapter-control --alsa-device=hw:0,0 --ramp-time=1000 --ramp-step-time=5000 --mode=native\n" + "-->ramps volume on \"hw:0,0\" alsa device over 1000 msec in steps of 5000 nano seconds(5 msec)in native mode\n" + "adapter-control --alsa-device=hw:0,0 --ramp-samples=70000 --ramp-step-samples=200 --mode=native\n" + "-->ramps volume on \"hw:0,0\" alsa device over 70000 samples with a step size of 200 samples in native mode\n" + "adapter-control --alsa-device=hw:1,0 --scale=linear\n" + "-->ramps volume on \"hw:1,0\" in linear volume scale, one can leave choose to not use the linear scale here as it is the default\n" + "adapter-control --alsa-device=hw:1,0 --ramp-samples=70000 --scale=cubic\n" + "-->ramps volume on \"hw:1,0\" alsa device over 70000 samples deploying cubic volume scale\n" + "adapter-control --alsa-device=hw:1,0 --ramp-samples=70000 --mode=native --scale=cubic\n" + "-->ramps volume on \"hw:1,0\" alsa device over 70000 samples deploying cubic volume scale in native mode\n" + "adapter-control --alsa-device=hw:1,0 --ramp-time=3000 --scale=cubic --mode=native\n" + "-->ramps volume on \"hw:1,0\" alsa device over 3 seconds samples with a step size of 200 samples in native mode\n", + name, + DEFAULT_DEVICE, + DEFAULT_MODE, + DEFAULT_RAMP_SAMPLES, + DEFAULT_RAMP_STEP_SAMPLES, + DEFAULT_RAMP_TIME, + DEFAULT_RAMP_STEP_TIME, + getscale(DEFAULT_SCALE)); +} + int main(int argc, char *argv) { struct data data = { 0 }; int res = 0; + char c; + + /* default values*/ + data.volume_ramp_samples = DEFAULT_RAMP_SAMPLES; + data.volume_ramp_step_samples = DEFAULT_RAMP_STEP_SAMPLES; + data.alsa_device = DEFAULT_DEVICE; + data.mode = DEFAULT_MODE; + data.scale = DEFAULT_SCALE; + + static const struct option long_options = { + { "help", no_argument, NULL, 'h' }, + { "alsa-device", required_argument, NULL, 'd' }, + { "mode", required_argument, NULL, 'm' }, + { "ramp-samples", required_argument, NULL, 's' }, + { "ramp-time", required_argument, NULL, 't' }, + { "ramp-step-samples", required_argument, NULL, 'a' }, + { "ramp-step-time", required_argument, NULL, 'i' }, + { "scale", required_argument, NULL, 'c' }, + { NULL, 0, NULL, 0} + }; + + setlocale(LC_ALL, ""); + + while ((c = getopt_long(argc, argv, "hdmstiac:", long_options, NULL)) != -1) { + switch (c) { + case 'h': + show_help(&data, argv0, false); + return 0; + case 'm': + if (!spa_streq (optarg, NATIVE) && !spa_streq (optarg, NON_NATIVE)) + printf("Invalid Mode(\"%s\"), using default(\"%s\")\n", optarg, DEFAULT_MODE); + else + data.mode = optarg; + break; + case 'c': + if (!spa_streq (optarg, LINEAR) && !spa_streq (optarg, CUBIC)) + printf("Invalid Scale(\"%s\"), using default(\"%s\")\n", optarg, + getscale(DEFAULT_SCALE)); + else + if (spa_streq (optarg, LINEAR)) + data.scale = SPA_AUDIO_VOLUME_RAMP_LINEAR; + else if (spa_streq (optarg, CUBIC)) + data.scale = SPA_AUDIO_VOLUME_RAMP_CUBIC; + break; + case 'd': + data.alsa_device = optarg; + break; + case 's': + data.volume_ramp_samples = atoi(optarg); + break; + case 't': + data.volume_ramp_time = atoi(optarg); + if (!data.volume_ramp_step_time) + data.volume_ramp_step_time = DEFAULT_RAMP_STEP_TIME; + data.volume_ramp_samples = 0; + data.volume_ramp_step_samples = 0; + break; + case 'a': + data.volume_ramp_step_samples = atoi(optarg); + break; + case 'i': + data.volume_ramp_step_time = atoi(optarg); + break; + default: + show_help(&data, argv0, true); + return -1; + } + } + /* init data */ if ((res = init_data(&data)) < 0) { @@ -734,7 +1042,7 @@ } /* make the nodes (audiotestsrc and adapter with alsa-pcm-sink as follower) */ - if ((res = make_nodes(&data, argc > 1 ? argv1 : NULL)) < 0) { + if ((res = make_nodes(&data)) < 0) { printf("can't make nodes: %d (%s)\n", res, spa_strerror(res)); return -1; } @@ -745,6 +1053,14 @@ return -1; } + printf("using %s mode\n", data.mode); + if (data.volume_ramp_samples && data.volume_ramp_step_samples) + printf("using %d samples with a step size of %d samples to ramp volume at %s scale\n", + data.volume_ramp_samples, data.volume_ramp_step_samples, getscale(data.scale)); + else if (data.volume_ramp_time && data.volume_ramp_step_time) + printf("using %d msec with a step size of %d msec to ramp volume at %s scale\n", + data.volume_ramp_time, (data.volume_ramp_step_time/1000), getscale(data.scale)); + spa_loop_control_enter(data.control); run_async_sink(&data); spa_loop_control_leave(data.control);
View file
pipewire-0.3.67.tar.gz/spa/include/spa/node/io.h -> pipewire-0.3.68.tar.gz/spa/include/spa/node/io.h
Changed
@@ -124,7 +124,13 @@ * positive for capture, negative for playback */ double rate_diff; /**< rate difference between clock and monotonic time */ uint64_t next_nsec; /**< estimated next wakeup time in nanoseconds */ - uint32_t padding8; + + struct spa_fraction target_rate; /**< target rate of next cycle */ + uint64_t target_duration; /**< target duration of next cycle */ + uint32_t target_seq; /**< seq counter. must be equal at start and + * end of read and lower bit must be 0 */ + + uint32_t padding3; }; /* the size of the video in this cycle */
View file
pipewire-0.3.67.tar.gz/spa/include/spa/param/audio/format.h -> pipewire-0.3.68.tar.gz/spa/include/spa/param/audio/format.h
Changed
@@ -28,6 +28,7 @@ #include <spa/param/audio/alac.h> #include <spa/param/audio/flac.h> #include <spa/param/audio/ape.h> +#include <spa/param/audio/opus.h> struct spa_audio_info { uint32_t media_type; @@ -46,6 +47,7 @@ struct spa_audio_info_alac alac; struct spa_audio_info_flac flac; struct spa_audio_info_ape ape; + struct spa_audio_info_ape opus; } info; };
View file
pipewire-0.3.68.tar.gz/spa/include/spa/param/audio/opus.h
Added
@@ -0,0 +1,29 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_AUDIO_OPUS_H +#define SPA_AUDIO_OPUS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/param/audio/raw.h> + +struct spa_audio_info_opus { + uint32_t rate; /*< sample rate */ + uint32_t channels; /*< number of channels */ +}; + +#define SPA_AUDIO_INFO_OPUS_INIT(...) ((struct spa_audio_info_opus) { __VA_ARGS__ }) + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_AUDIO_OPUS_H */
View file
pipewire-0.3.67.tar.gz/spa/include/spa/param/audio/raw.h -> pipewire-0.3.68.tar.gz/spa/include/spa/param/audio/raw.h
Changed
@@ -261,6 +261,12 @@ SPA_AUDIO_CHANNEL_START_Custom = 0x10000, }; +enum spa_audio_volume_ramp_scale { + SPA_AUDIO_VOLUME_RAMP_INVALID, + SPA_AUDIO_VOLUME_RAMP_LINEAR, + SPA_AUDIO_VOLUME_RAMP_CUBIC, +}; + /** Extra audio flags */ #define SPA_AUDIO_FLAG_NONE (0) /*< no valid flag */ #define SPA_AUDIO_FLAG_UNPOSITIONED (1 << 0) /*< the position array explicitly
View file
pipewire-0.3.67.tar.gz/spa/include/spa/param/format-types.h -> pipewire-0.3.68.tar.gz/spa/include/spa/param/format-types.h
Changed
@@ -63,6 +63,7 @@ { SPA_MEDIA_SUBTYPE_alac, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "alac", NULL }, { SPA_MEDIA_SUBTYPE_flac, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "flac", NULL }, { SPA_MEDIA_SUBTYPE_ape, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "ape", NULL }, + { SPA_MEDIA_SUBTYPE_opus, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "opus", NULL }, /* video subtypes */ { SPA_MEDIA_SUBTYPE_h264, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "h264", NULL }, { SPA_MEDIA_SUBTYPE_mjpg, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mjpg", NULL },
View file
pipewire-0.3.67.tar.gz/spa/include/spa/param/format.h -> pipewire-0.3.68.tar.gz/spa/include/spa/param/format.h
Changed
@@ -51,6 +51,7 @@ SPA_MEDIA_SUBTYPE_alac, /** since 0.3.65 */ SPA_MEDIA_SUBTYPE_flac, /** since 0.3.65 */ SPA_MEDIA_SUBTYPE_ape, /** since 0.3.65 */ + SPA_MEDIA_SUBTYPE_opus, /** since 0.3.68 */ SPA_MEDIA_SUBTYPE_START_Video = 0x20000, SPA_MEDIA_SUBTYPE_h264,
View file
pipewire-0.3.67.tar.gz/spa/include/spa/param/props-types.h -> pipewire-0.3.68.tar.gz/spa/include/spa/param/props-types.h
Changed
@@ -58,6 +58,11 @@ { SPA_PROP_softMute, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "softMute", NULL }, { SPA_PROP_softVolumes, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "softVolumes", spa_type_prop_float_array }, { SPA_PROP_iec958Codecs, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "iec958Codecs", spa_type_prop_iec958_codec }, + { SPA_PROP_volumeRampSamples, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "volumeRampSamples", NULL }, + { SPA_PROP_volumeRampStepSamples, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "volumeRampStepSamples", NULL }, + { SPA_PROP_volumeRampTime, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "volumeRampTime", NULL }, + { SPA_PROP_volumeRampStepTime, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "volumeRampStepTime", NULL }, + { SPA_PROP_volumeRampScale, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "volumeRampScale", NULL }, { SPA_PROP_brightness, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "brightness", NULL }, { SPA_PROP_contrast, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "contrast", NULL },
View file
pipewire-0.3.67.tar.gz/spa/include/spa/param/props.h -> pipewire-0.3.68.tar.gz/spa/include/spa/param/props.h
Changed
@@ -59,7 +59,7 @@ SPA_PROP_START_Audio = 0x10000, /**< audio related properties */ SPA_PROP_waveType, SPA_PROP_frequency, - SPA_PROP_volume, /**< a volume (Float), 0.0 silence, 1.0 normal */ + SPA_PROP_volume, /**< a volume (Float), 0.0 silence, 1.0 normal */ SPA_PROP_mute, /**< mute (Bool) */ SPA_PROP_patternType, SPA_PROP_ditherType, @@ -80,6 +80,14 @@ SPA_PROP_iec958Codecs, /**< enabled IEC958 (S/PDIF) codecs, * (Array (Id enum spa_audio_iec958_codec) */ + SPA_PROP_volumeRampSamples, /**< Samples to ramp the volume over */ + SPA_PROP_volumeRampStepSamples, /**< Step or incremental Samples to ramp + * the volume over */ + SPA_PROP_volumeRampTime, /**< Time in millisec to ramp the volume over */ + SPA_PROP_volumeRampStepTime, /**< Step or incremental Time in nano seconds + * to ramp the */ + SPA_PROP_volumeRampScale, /**< the scale or graph to used to ramp the + * volume */ SPA_PROP_START_Video = 0x20000, /**< video related properties */ SPA_PROP_brightness,
View file
pipewire-0.3.67.tar.gz/spa/include/spa/support/loop.h -> pipewire-0.3.68.tar.gz/spa/include/spa/support/loop.h
Changed
@@ -28,7 +28,7 @@ struct spa_loop { struct spa_interface iface; }; #define SPA_TYPE_INTERFACE_LoopControl SPA_TYPE_INFO_INTERFACE_BASE "LoopControl" -#define SPA_VERSION_LOOP_CONTROL 0 +#define SPA_VERSION_LOOP_CONTROL 1 struct spa_loop_control { struct spa_interface iface; }; #define SPA_TYPE_INTERFACE_LoopUtils SPA_TYPE_INFO_INTERFACE_BASE "LoopUtils" @@ -140,7 +140,7 @@ struct spa_loop_control_methods { /* the version of this structure. This can be used to expand this * structure in the future */ -#define SPA_VERSION_LOOP_CONTROL_METHODS 0 +#define SPA_VERSION_LOOP_CONTROL_METHODS 1 uint32_t version; int (*get_fd) (void *object); @@ -182,6 +182,16 @@ * The number of dispatched fds is returned. */ int (*iterate) (void *object, int timeout); + + /** Check context of the loop + * \param ctrl the control + * + * This function will check if the current thread is currently the + * one that did the enter call. Since version 1:1. + * + * returns 1 on success, 0 or negative errno value on error. + */ + int (*check) (void *object); }; #define spa_loop_control_method_v(o,method,version,...) \ @@ -207,6 +217,7 @@ #define spa_loop_control_enter(l) spa_loop_control_method_v(l,enter,0) #define spa_loop_control_leave(l) spa_loop_control_method_v(l,leave,0) #define spa_loop_control_iterate(l,...) spa_loop_control_method_r(l,iterate,0,__VA_ARGS__) +#define spa_loop_control_check(l) spa_loop_control_method_r(l,check,1) typedef void (*spa_source_io_func_t) (void *data, int fd, uint32_t mask); typedef void (*spa_source_idle_func_t) (void *data);
View file
pipewire-0.3.67.tar.gz/spa/meson.build -> pipewire-0.3.68.tar.gz/spa/meson.build
Changed
@@ -74,10 +74,13 @@ endif endif summary({'LC3plus': lc3plus_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') - opus_dep = dependency('opus', required : get_option('bluez5-codec-opus')) + if get_option('bluez5-codec-opus').enabled() and not opus_dep.found() + error('bluez5-codec-opus enabled, but opus dependency not found') + endif summary({'Opus': opus_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') lc3_dep = dependency('lc3', required : get_option('bluez5-codec-lc3')) summary({'LC3': lc3_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') + cdata.set('HAVE_BLUETOOTH_BAP', get_option('bluez5-codec-lc3').allowed() and lc3_dep.found()) if get_option('bluez5-backend-hsp-native').allowed() or get_option('bluez5-backend-hfp-native').allowed() mm_dep = dependency('ModemManager', version : '>= 1.10.0', required : get_option('bluez5-backend-native-mm')) summary({'ModemManager': mm_dep.found()}, bool_yn: true, section: 'Bluetooth backends')
View file
pipewire-0.3.67.tar.gz/spa/plugins/alsa/acp/acp.c -> pipewire-0.3.68.tar.gz/spa/plugins/alsa/acp/acp.c
Changed
@@ -362,7 +362,7 @@ devstr, NULL, &m->sample_spec, &m->channel_map, SND_PCM_STREAM_PLAYBACK, &try_period_size, &try_buffer_size, - 0, NULL, NULL, false))) { + 0, NULL, NULL, NULL, NULL, false))) { pa_alsa_init_proplist_pcm(NULL, m->output_proplist, m->output_pcm); pa_proplist_setf(m->output_proplist, "clock.name", "api.alsa.%u", index); pa_alsa_close(&m->output_pcm); @@ -392,7 +392,7 @@ devstr, NULL, &m->sample_spec, &m->channel_map, SND_PCM_STREAM_CAPTURE, &try_period_size, &try_buffer_size, - 0, NULL, NULL, false))) { + 0, NULL, NULL, NULL, NULL, false))) { pa_alsa_init_proplist_pcm(NULL, m->input_proplist, m->input_pcm); pa_proplist_setf(m->input_proplist, "clock.name", "api.alsa.%u", index); pa_alsa_close(&m->input_pcm); @@ -449,8 +449,8 @@ pa_dynarray_append(&impl->out.devices, dev); } if (impl->use_ucm) { - if (m->ucm_context.ucm_devices) { - pa_alsa_ucm_add_ports_combination(NULL, &m->ucm_context, + if (m->ucm_context.ucm_device) { + pa_alsa_ucm_add_port(NULL, &m->ucm_context, true, impl->ports, ap, NULL); pa_alsa_ucm_add_ports(&dev->ports, m->proplist, &m->ucm_context, true, impl, dev->pcm_handle, impl->profile_set->ignore_dB); @@ -473,8 +473,8 @@ } if (impl->use_ucm) { - if (m->ucm_context.ucm_devices) { - pa_alsa_ucm_add_ports_combination(NULL, &m->ucm_context, + if (m->ucm_context.ucm_device) { + pa_alsa_ucm_add_port(NULL, &m->ucm_context, false, impl->ports, ap, NULL); pa_alsa_ucm_add_ports(&dev->ports, m->proplist, &m->ucm_context, false, impl, dev->pcm_handle, impl->profile_set->ignore_dB); @@ -608,7 +608,7 @@ static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) { pa_card *impl = snd_mixer_elem_get_callback_private(melem); - snd_hctl_elem_t *elem = snd_mixer_elem_get_private(melem); + snd_hctl_elem_t **_elem = snd_mixer_elem_get_private(melem), *elem; snd_ctl_elem_value_t *elem_value; bool plugged_in, any_input_port_available; void *state; @@ -618,6 +618,8 @@ enum acp_available active_available = ACP_AVAILABLE_UNKNOWN; size_t size; + pa_assert(_elem); + elem = *_elem; #if 0 /* Changing the jack state may cause a port change, and a port change will * make the sink or source change the mixer settings. If there are multiple @@ -886,13 +888,17 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) { pa_card *impl = snd_mixer_elem_get_callback_private(melem); - snd_hctl_elem_t *elem = snd_mixer_elem_get_private(melem); - int device = snd_hctl_elem_get_device(elem); + snd_hctl_elem_t **_elem = snd_mixer_elem_get_private(melem), *elem; + int device; const char *old_monitor_name; pa_device_port *p; pa_hdmi_eld eld; bool changed = false; + pa_assert(_elem); + elem = *_elem; + device = snd_hctl_elem_get_device(elem); + if (mask == SND_CTL_EVENT_MASK_REMOVE) return 0; @@ -1253,8 +1259,7 @@ * will be NULL, but the UCM device enable sequence will still need to be * executed. */ if (dev->active_port && dev->ucm_context) { - if ((res = pa_alsa_ucm_set_port(dev->ucm_context, dev->active_port, - dev->direction == PA_ALSA_DIRECTION_OUTPUT)) < 0) + if ((res = pa_alsa_ucm_set_port(dev->ucm_context, dev->active_port)) < 0) return res; } @@ -1429,8 +1434,7 @@ /* if UCM is available for this card then update the verb */ if (impl->use_ucm && !(np->profile.flags & ACP_PROFILE_PRO)) { if ((res = pa_alsa_ucm_set_profile(&impl->ucm, impl, - np->profile.flags & ACP_PROFILE_OFF ? NULL : np->profile.name, - op ? op->profile.name : NULL)) < 0) { + np->profile.flags & ACP_PROFILE_OFF ? NULL : np, op)) < 0) { return res; } } @@ -1439,8 +1443,8 @@ PA_IDXSET_FOREACH(am, np->output_mappings, idx) { if (impl->use_ucm) { /* Update ports priorities */ - if (am->ucm_context.ucm_devices) { - pa_alsa_ucm_add_ports_combination(am->output.ports, &am->ucm_context, + if (am->ucm_context.ucm_device) { + pa_alsa_ucm_add_port(am->output.ports, &am->ucm_context, true, impl->ports, np, NULL); } } @@ -1452,8 +1456,8 @@ PA_IDXSET_FOREACH(am, np->input_mappings, idx) { if (impl->use_ucm) { /* Update ports priorities */ - if (am->ucm_context.ucm_devices) { - pa_alsa_ucm_add_ports_combination(am->input.ports, &am->ucm_context, + if (am->ucm_context.ucm_device) { + pa_alsa_ucm_add_port(am->input.ports, &am->ucm_context, false, impl->ports, np, NULL); } } @@ -1589,7 +1593,7 @@ res = impl->use_ucm ? pa_alsa_ucm_query_profiles(&impl->ucm, card->index) : -1; if (res == -PA_ALSA_ERR_UCM_LINKED) { - res = -ENOENT; + res = -EEXIST; goto error; } if (res == 0) { @@ -1844,8 +1848,7 @@ mixer_volume_init(impl, d); sync_mixer(d, p); - res = pa_alsa_ucm_set_port(d->ucm_context, p, - dev->direction == ACP_DIRECTION_PLAYBACK); + res = pa_alsa_ucm_set_port(d->ucm_context, p); } else { pa_alsa_port_data *data;
View file
pipewire-0.3.67.tar.gz/spa/plugins/alsa/acp/alsa-mixer.c -> pipewire-0.3.68.tar.gz/spa/plugins/alsa/acp/alsa-mixer.c
Changed
@@ -4734,35 +4734,29 @@ PA_ELEMENTSOF(well_known_descriptions))); if (!p->description) { + pa_strbuf *sb; uint32_t idx; pa_alsa_mapping *m; - char *ptr; - size_t size; - FILE *f; - int count = 0; - - f = open_memstream(&ptr, &size); - if (f == NULL) { - pa_log("failed to open memstream: %m"); - return -1; - } + + sb = pa_strbuf_new(); if (p->output_mappings) PA_IDXSET_FOREACH(m, p->output_mappings, idx) { - if (count++ > 0) - fprintf(f, " + "); - fprintf(f, _("%s Output"), m->description); + if (!pa_strbuf_isempty(sb)) + pa_strbuf_puts(sb, " + "); + + pa_strbuf_printf(sb, _("%s Output"), m->description); } if (p->input_mappings) PA_IDXSET_FOREACH(m, p->input_mappings, idx) { - if (count++ > 0) - fprintf(f, " + "); - fprintf(f, _("%s Input"), m->description); + if (!pa_strbuf_isempty(sb)) + pa_strbuf_puts(sb, " + "); + + pa_strbuf_printf(sb, _("%s Input"), m->description); } - fclose(f); - p->description = ptr; + p->description = pa_strbuf_to_string_free(sb); } return 0; @@ -4814,23 +4808,17 @@ pa_assert(db_fix); if (db_fix->db_values) { + pa_strbuf *buf; unsigned long i, nsteps; - FILE *f; - char *ptr; - size_t size; - - f = open_memstream(&ptr, &size); - if (f == NULL) - return; pa_assert(db_fix->min_step <= db_fix->max_step); nsteps = db_fix->max_step - db_fix->min_step + 1; + buf = pa_strbuf_new(); for (i = 0; i < nsteps; ++i) - fprintf(f, "%li:%0.2f ", i + db_fix->min_step, db_fix->db_valuesi / 100.0); + pa_strbuf_printf(buf, "%li:%0.2f ", i + db_fix->min_step, db_fix->db_valuesi / 100.0); - fclose(f); - db_values = ptr; + db_values = pa_strbuf_to_string_free(buf); } pa_log_debug("Decibel fix %s, min_step=%li, max_step=%li, db_values=%s", @@ -5012,7 +5000,7 @@ handle = pa_alsa_open_by_template( m->device_strings, dev_id, NULL, &try_ss, &try_map, mode, &try_period_size, - &try_buffer_size, 0, NULL, NULL, exact_channels); + &try_buffer_size, 0, NULL, NULL, NULL, NULL, exact_channels); if (handle && !exact_channels && m->channel_map.channels != try_map.channels) { char bufPA_CHANNEL_MAP_SNPRINT_MAX; pa_log_debug("Channel map for mapping '%s' permanently changed to '%s'", m->name,
View file
pipewire-0.3.67.tar.gz/spa/plugins/alsa/acp/alsa-mixer.h -> pipewire-0.3.68.tar.gz/spa/plugins/alsa/acp/alsa-mixer.h
Changed
@@ -354,7 +354,7 @@ pa_alsa_device output; pa_alsa_device input; - /* ucm device context*/ + /* ucm device context */ pa_alsa_ucm_mapping_context ucm_context; }; @@ -381,6 +381,9 @@ pa_idxset *input_mappings; pa_idxset *output_mappings; + /* ucm device context */ + pa_alsa_ucm_profile_context ucm_context; + struct { pa_dynarray devices; } out;
View file
pipewire-0.3.67.tar.gz/spa/plugins/alsa/acp/alsa-ucm.c -> pipewire-0.3.68.tar.gz/spa/plugins/alsa/acp/alsa-ucm.c
Changed
@@ -72,9 +72,8 @@ static void ucm_port_data_init(pa_alsa_ucm_port_data *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port, - pa_alsa_ucm_device **devices, unsigned n_devices); + pa_alsa_ucm_device *device); static void ucm_port_data_free(pa_device_port *port); -static void ucm_port_update_available(pa_alsa_ucm_port_data *port); static struct ucm_type types = { {"None", PA_DEVICE_PORT_TYPE_UNKNOWN}, @@ -170,17 +169,6 @@ return (char *)value; } -static int ucm_device_exists(pa_idxset *idxset, pa_alsa_ucm_device *dev) { - pa_alsa_ucm_device *d; - uint32_t idx; - - PA_IDXSET_FOREACH(d, idxset, idx) - if (d == dev) - return 1; - - return 0; -} - static void ucm_add_devices_to_idxset( pa_idxset *idxset, pa_alsa_ucm_device *me, @@ -506,10 +494,10 @@ n_confdev = snd_use_case_get_list(uc_mgr, id, &devices); pa_xfree(id); + device->conflicting_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); if (n_confdev <= 0) pa_log_debug("No %s for device %s", "_conflictingdevs", device_name); else { - device->conflicting_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); ucm_add_devices_to_idxset(device->conflicting_devices, device, verb->devices, devices, n_confdev); snd_use_case_free_list(devices, n_confdev); } @@ -518,10 +506,10 @@ n_suppdev = snd_use_case_get_list(uc_mgr, id, &devices); pa_xfree(id); + device->supported_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); if (n_suppdev <= 0) pa_log_debug("No %s for device %s", "_supporteddevs", device_name); else { - device->supported_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); ucm_add_devices_to_idxset(device->supported_devices, device, verb->devices, devices, n_suppdev); snd_use_case_free_list(devices, n_suppdev); } @@ -530,10 +518,16 @@ }; /* Create a property list for this ucm modifier */ -static int ucm_get_modifier_property(pa_alsa_ucm_modifier *modifier, snd_use_case_mgr_t *uc_mgr, const char *modifier_name) { +static int ucm_get_modifier_property( + pa_alsa_ucm_modifier *modifier, + snd_use_case_mgr_t *uc_mgr, + pa_alsa_ucm_verb *verb, + const char *modifier_name) { const char *value; char *id; int i; + const char **devices; + int n_confdev, n_suppdev; for (i = 0; itemi.id; i++) { int err; @@ -550,16 +544,28 @@ } id = pa_sprintf_malloc("%s/%s", "_conflictingdevs", modifier_name); - modifier->n_confdev = snd_use_case_get_list(uc_mgr, id, &modifier->conflicting_devices); + n_confdev = snd_use_case_get_list(uc_mgr, id, &devices); pa_xfree(id); - if (modifier->n_confdev < 0) + + modifier->conflicting_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + if (n_confdev <= 0) pa_log_debug("No %s for modifier %s", "_conflictingdevs", modifier_name); + else { + ucm_add_devices_to_idxset(modifier->conflicting_devices, NULL, verb->devices, devices, n_confdev); + snd_use_case_free_list(devices, n_confdev); + } id = pa_sprintf_malloc("%s/%s", "_supporteddevs", modifier_name); - modifier->n_suppdev = snd_use_case_get_list(uc_mgr, id, &modifier->supported_devices); + n_suppdev = snd_use_case_get_list(uc_mgr, id, &devices); pa_xfree(id); - if (modifier->n_suppdev < 0) + + modifier->supported_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + if (n_suppdev <= 0) pa_log_debug("No %s for modifier %s", "_supporteddevs", modifier_name); + else { + ucm_add_devices_to_idxset(modifier->supported_devices, NULL, verb->devices, devices, n_suppdev); + snd_use_case_free_list(devices, n_suppdev); + } return 0; }; @@ -596,6 +602,59 @@ return 0; }; +static long ucm_device_status(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *dev) { + const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); + char *devstatus; + long status = 0; + + devstatus = pa_sprintf_malloc("_devstatus/%s", dev_name); + if (snd_use_case_geti(ucm->ucm_mgr, devstatus, &status) < 0) { + pa_log_debug("Failed to get status for UCM device %s", dev_name); + status = -1; + } + pa_xfree(devstatus); + + return status; +} + +static int ucm_device_disable(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *dev) { + const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); + + /* If any of dev's conflicting devices is enabled, trying to disable + * dev gives an error despite the fact that it's already disabled. + * Check that dev is enabled to avoid this error. */ + if (ucm_device_status(ucm, dev) == 0) { + pa_log_debug("UCM device %s is already disabled", dev_name); + return 0; + } + + pa_log_debug("Disabling UCM device %s", dev_name); + if (snd_use_case_set(ucm->ucm_mgr, "_disdev", dev_name) < 0) { + pa_log("Failed to disable UCM device %s", dev_name); + return -1; + } + + return 0; +} + +static int ucm_device_enable(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *dev) { + const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); + + /* We don't need to enable devices that are already enabled */ + if (ucm_device_status(ucm, dev) > 0) { + pa_log_debug("UCM device %s is already enabled", dev_name); + return 0; + } + + pa_log_debug("Enabling UCM device %s", dev_name); + if (snd_use_case_set(ucm->ucm_mgr, "_enadev", dev_name) < 0) { + pa_log("Failed to enable UCM device %s", dev_name); + return -1; + } + + return 0; +} + static int ucm_get_modifiers(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) { const char **mod_list; int num_mod, i; @@ -626,6 +685,57 @@ return 0; }; +static long ucm_modifier_status(pa_alsa_ucm_config *ucm, pa_alsa_ucm_modifier *mod) { + const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME); + char *modstatus; + long status = 0; + + modstatus = pa_sprintf_malloc("_modstatus/%s", mod_name); + if (snd_use_case_geti(ucm->ucm_mgr, modstatus, &status) < 0) { + pa_log_debug("Failed to get status for UCM modifier %s", mod_name); + status = -1; + } + pa_xfree(modstatus); + + return status; +} + +static int ucm_modifier_disable(pa_alsa_ucm_config *ucm, pa_alsa_ucm_modifier *mod) { + const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME); + + /* We don't need to disable modifiers that are already disabled */ + if (ucm_modifier_status(ucm, mod) == 0) { + pa_log_debug("UCM modifier %s is already disabled", mod_name); + return 0; + } + + pa_log_debug("Disabling UCM modifier %s", mod_name); + if (snd_use_case_set(ucm->ucm_mgr, "_dismod", mod_name) < 0) { + pa_log("Failed to disable UCM modifier %s", mod_name); + return -1; + } + + return 0; +} + +static int ucm_modifier_enable(pa_alsa_ucm_config *ucm, pa_alsa_ucm_modifier *mod) { + const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME); + + /* We don't need to enable modifiers that are already enabled */ + if (ucm_modifier_status(ucm, mod) > 0) { + pa_log_debug("UCM modifier %s is already enabled", mod_name); + return 0; + } + + pa_log_debug("Enabling UCM modifier %s", mod_name); + if (snd_use_case_set(ucm->ucm_mgr, "_enamod", mod_name) < 0) { + pa_log("Failed to enable UCM modifier %s", mod_name); + return -1; + } + + return 0; +} + static void add_role_to_device(pa_alsa_ucm_device *dev, const char *dev_name, const char *role_name, const char *role) { const char *cur = pa_proplist_gets(dev->proplist, role_name); @@ -642,27 +752,19 @@ role_name)); } -static void add_media_role(const char *name, pa_alsa_ucm_device *list, const char *role_name, const char *role, bool is_sink) { - pa_alsa_ucm_device *d; - - PA_LLIST_FOREACH(d, list) { - const char *dev_name = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME); - - if (pa_streq(dev_name, name)) { - const char *sink = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SINK); - const char *source = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SOURCE); +static void add_media_role(pa_alsa_ucm_device *dev, const char *role_name, const char *role, bool is_sink) { + const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); + const char *sink = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SINK); + const char *source = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SOURCE); - if (is_sink && sink) - add_role_to_device(d, dev_name, role_name, role); - else if (!is_sink && source) - add_role_to_device(d, dev_name, role_name, role); - break; - } - } + if (is_sink && sink) + add_role_to_device(dev, dev_name, role_name, role); + else if (!is_sink && source) + add_role_to_device(dev, dev_name, role_name, role); } static char *modifier_name_to_role(const char *mod_name, bool *is_sink) { - char *sub = NULL, *tmp; + char *sub = NULL, *tmp, *pos; *is_sink = false; @@ -672,26 +774,32 @@ } else if (pa_startswith(mod_name, "Capture")) sub = pa_xstrdup(mod_name + 7); - if (!sub || !*sub) { + pos = sub; + while (pos && *pos == ' ') pos++; + + if (!pos || !*pos) { pa_xfree(sub); pa_log_warn("Can't match media roles for modifier %s", mod_name); return NULL; } - tmp = sub; + tmp = pos; do { *tmp = tolower(*tmp); } while (*(++tmp)); - return sub; + tmp = pa_xstrdup(pos); + pa_xfree(sub); + return tmp; } -static void ucm_set_media_roles(pa_alsa_ucm_modifier *modifier, pa_alsa_ucm_device *list, const char *mod_name) { - int i; +static void ucm_set_media_roles(pa_alsa_ucm_modifier *modifier, const char *mod_name) { + pa_alsa_ucm_device *dev; bool is_sink = false; char *sub = NULL; const char *role_name; + uint32_t idx; sub = modifier_name_to_role(mod_name, &is_sink); if (!sub) @@ -701,11 +809,11 @@ modifier->media_role = sub; role_name = is_sink ? PA_ALSA_PROP_UCM_PLAYBACK_ROLES : PA_ALSA_PROP_UCM_CAPTURE_ROLES; - for (i = 0; i < modifier->n_suppdev; i++) { + PA_IDXSET_FOREACH(dev, modifier->supported_devices, idx) { /* if modifier has no specific pcm, we add role intent to its supported devices */ if (!pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_SINK) && !pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_SOURCE)) - add_media_role(modifier->supported_devicesi, list, role_name, sub, is_sink); + add_media_role(dev, role_name, sub, is_sink); } } @@ -713,29 +821,17 @@ uint32_t idx; pa_alsa_ucm_device *d; - if (dev->conflicting_devices) { - PA_IDXSET_FOREACH(d, dev->conflicting_devices, idx) { - if (!d->conflicting_devices) - d->conflicting_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); - - if (pa_idxset_put(d->conflicting_devices, dev, NULL) == 0) - pa_log_warn("Add lost conflicting device %s to %s", - pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME), - pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME)); - } - } - - if (dev->supported_devices) { - PA_IDXSET_FOREACH(d, dev->supported_devices, idx) { - if (!d->supported_devices) - d->supported_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + PA_IDXSET_FOREACH(d, dev->conflicting_devices, idx) + if (pa_idxset_put(d->conflicting_devices, dev, NULL) == 0) + pa_log_warn("Add lost conflicting device %s to %s", + pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME), + pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME)); - if (pa_idxset_put(d->supported_devices, dev, NULL) == 0) - pa_log_warn("Add lost supported device %s to %s", - pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME), - pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME)); - } - } + PA_IDXSET_FOREACH(d, dev->supported_devices, idx) + if (pa_idxset_put(d->supported_devices, dev, NULL) == 0) + pa_log_warn("Add lost supported device %s to %s", + pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME), + pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME)); } int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) { @@ -787,7 +883,7 @@ free((void *)value); } - /* get a list of all UCM verbs (profiles) for this card */ + /* get a list of all UCM verbs for this card */ num_verbs = snd_use_case_verb_list(ucm->ucm_mgr, &verb_list); if (num_verbs < 0) { pa_log("UCM verb list not found for %s", card_name); @@ -876,11 +972,11 @@ const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME); /* Modifier properties */ - ucm_get_modifier_property(mod, uc_mgr, mod_name); + ucm_get_modifier_property(mod, uc_mgr, verb, mod_name); /* Set PA_PROP_DEVICE_INTENDED_ROLES property to devices */ pa_log_debug("Set media roles for verb %s, modifier %s", verb_name, mod_name); - ucm_set_media_roles(mod, verb->devices, mod_name); + ucm_set_media_roles(mod, mod_name); } *p_verb = verb; @@ -899,43 +995,27 @@ pa_device_port *port; pa_alsa_ucm_port_data *data; pa_alsa_ucm_device *dev; - const char *eld_mixer_device_name; void *state; - int idx, eld_device; PA_HASHMAP_FOREACH(port, hash, state) { data = PA_DEVICE_PORT_DATA(port); - eld_mixer_device_name = NULL; - eld_device = -1; - PA_DYNARRAY_FOREACH(dev, data->devices, idx) { - if (dev->eld_device >= 0 && dev->eld_mixer_device_name) { - if (eld_device >= 0 && eld_device != dev->eld_device) { - pa_log_error("The ELD device is already set!"); - } else if (eld_mixer_device_name && pa_streq(dev->eld_mixer_device_name, eld_mixer_device_name)) { - pa_log_error("The ELD mixer device is already set (%s, %s)!", dev->eld_mixer_device_name, dev->eld_mixer_device_name); - } else { - eld_mixer_device_name = dev->eld_mixer_device_name; - eld_device = dev->eld_device; - } - } - } - data->eld_device = eld_device; - if (data->eld_mixer_device_name) - pa_xfree(data->eld_mixer_device_name); - data->eld_mixer_device_name = pa_xstrdup(eld_mixer_device_name); + dev = data->device; + data->eld_device = dev->eld_device; + pa_xfree(data->eld_mixer_device_name); + data->eld_mixer_device_name = pa_xstrdup(dev->eld_mixer_device_name); } } -static void update_mixer_paths(pa_hashmap *ports, const char *profile) { +static void update_mixer_paths(pa_hashmap *ports, const char *verb_name) { pa_device_port *port; pa_alsa_ucm_port_data *data; void *state; /* select volume controls on ports */ PA_HASHMAP_FOREACH(port, ports, state) { - pa_log_info("Updating mixer path for %s: %s", profile, port->name); + pa_log_info("Updating mixer path for %s: %s", verb_name, port->name); data = PA_DEVICE_PORT_DATA(port); - data->path = pa_hashmap_get(data->paths, profile); + data->path = pa_hashmap_get(data->paths, verb_name); } } @@ -945,39 +1025,29 @@ pa_alsa_ucm_port_data *data; pa_alsa_ucm_device *dev; snd_mixer_t *mixer_handle; - const char *profile, *mdev, *mdev2; + const char *verb_name, *mdev; void *state, *state2; - int idx; PA_HASHMAP_FOREACH(port, hash, state) { data = PA_DEVICE_PORT_DATA(port); - mdev = NULL; - PA_DYNARRAY_FOREACH(dev, data->devices, idx) { - mdev2 = get_mixer_device(dev, is_sink); - if (mdev && mdev2 && !pa_streq(mdev, mdev2)) { - pa_log_error("Two mixer device names found ('%s', '%s'), using s/w volume", mdev, mdev2); - goto fail; - } - if (mdev2) - mdev = mdev2; - } - + dev = data->device; + mdev = get_mixer_device(dev, is_sink); if (mdev == NULL || !(mixer_handle = pa_alsa_open_mixer_by_name(mixers, mdev, true))) { pa_log_error("Failed to find a working mixer device (%s).", mdev); goto fail; } - PA_HASHMAP_FOREACH_KV(profile, path, data->paths, state2) { + PA_HASHMAP_FOREACH_KV(verb_name, path, data->paths, state2) { if (pa_alsa_path_probe(path, NULL, mixer_handle, ignore_dB) < 0) { pa_log_warn("Could not probe path: %s, using s/w volume", path->name); - pa_hashmap_remove(data->paths, profile); + pa_hashmap_remove(data->paths, verb_name); } else if (!path->has_volume && !path->has_mute) { pa_log_warn("Path %s is not a volume or mute control", path->name); - pa_hashmap_remove(data->paths, profile); + pa_hashmap_remove(data->paths, verb_name); } else pa_log_debug("Set up h/w %s using '%s' for %s:%s", path->has_volume ? "volume" : "mute", - path->name, profile, port->name); + path->name, verb_name, port->name); } } @@ -991,91 +1061,141 @@ } } -static void ucm_add_port_combination( +static char *devset_name(pa_idxset *devices, const char *sep) { + int i = 0; + int num = pa_idxset_size(devices); + pa_alsa_ucm_device *sortednum, *dev; + char *dev_names = NULL; + char *tmp = NULL; + uint32_t idx; + + PA_IDXSET_FOREACH(dev, devices, idx) { + sortedi = dev; + i++; + } + + /* Sort by alphabetical order so as to have a deterministic naming scheme */ + qsort(&sorted0, num, sizeof(pa_alsa_ucm_device *), pa_alsa_ucm_device_cmp); + + for (i = 0; i < num; i++) { + dev = sortedi; + const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); + + if (!dev_names) { + dev_names = pa_xstrdup(dev_name); + } else { + tmp = pa_sprintf_malloc("%s%s%s", dev_names, sep, dev_name); + pa_xfree(dev_names); + dev_names = tmp; + } + } + + return dev_names; +} + +PA_UNUSED static char *devset_description(pa_idxset *devices, const char *sep) { + int i = 0; + int num = pa_idxset_size(devices); + pa_alsa_ucm_device *sortednum, *dev; + char *dev_descs = NULL; + char *tmp = NULL; + uint32_t idx; + + PA_IDXSET_FOREACH(dev, devices, idx) { + sortedi = dev; + i++; + } + + /* Sort by alphabetical order to match devset_name() */ + qsort(&sorted0, num, sizeof(pa_alsa_ucm_device *), pa_alsa_ucm_device_cmp); + + for (i = 0; i < num; i++) { + dev = sortedi; + const char *dev_desc = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_DESCRIPTION); + + if (!dev_descs) { + dev_descs = pa_xstrdup(dev_desc); + } else { + tmp = pa_sprintf_malloc("%s%s%s", dev_descs, sep, dev_desc); + pa_xfree(dev_descs); + dev_descs = tmp; + } + } + + return dev_descs; +} + +/* If invert is true, uses the formula 1/p = 1/p1 + 1/p2 + ... 1/pn. + * This way, the result will always be less than the individual components, + * yet higher components will lead to higher result. */ +static unsigned devset_playback_priority(pa_idxset *devices, bool invert) { + pa_alsa_ucm_device *dev; + uint32_t idx; + double priority = 0; + + PA_IDXSET_FOREACH(dev, devices, idx) { + if (dev->playback_priority > 0 && invert) + priority += 1.0 / dev->playback_priority; + else + priority += dev->playback_priority; + } + + if (priority > 0 && invert) + return 1.0 / priority; + + return (unsigned) priority; +} + +static unsigned devset_capture_priority(pa_idxset *devices, bool invert) { + pa_alsa_ucm_device *dev; + uint32_t idx; + double priority = 0; + + PA_IDXSET_FOREACH(dev, devices, idx) { + if (dev->capture_priority > 0 && invert) + priority += 1.0 / dev->capture_priority; + else + priority += dev->capture_priority; + } + + if (priority > 0 && invert) + return 1.0 / priority; + + return (unsigned) priority; +} + +void pa_alsa_ucm_add_port( pa_hashmap *hash, pa_alsa_ucm_mapping_context *context, bool is_sink, - pa_alsa_ucm_device **pdevices, - int num, pa_hashmap *ports, pa_card_profile *cp, pa_core *core) { pa_device_port *port; - int i; unsigned priority; - double prio2; char *name, *desc; const char *dev_name; const char *direction; - const char *profile; - pa_alsa_ucm_device *sortednum, *dev; + const char *verb_name; + pa_alsa_ucm_device *dev; pa_alsa_ucm_port_data *data; pa_alsa_ucm_volume *vol; - pa_alsa_jack *jack, *jack2; - pa_device_port_type_t type, type2; + pa_alsa_jack *jack; + pa_device_port_type_t type; void *state; - for (i = 0; i < num; i++) - sortedi = pdevicesi; - - /* Sort by alphabetical order so as to have a deterministic naming scheme - * for combination ports */ - qsort(&sorted0, num, sizeof(pa_alsa_ucm_device *), pa_alsa_ucm_device_cmp); + dev = context->ucm_device; + if (!dev) + return; - dev = sorted0; dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); - name = pa_sprintf_malloc("%s%s", is_sink ? PA_UCM_PRE_TAG_OUTPUT : PA_UCM_PRE_TAG_INPUT, dev_name); - desc = num == 1 ? pa_xstrdup(pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_DESCRIPTION)) - : pa_sprintf_malloc("Combination port for %s", dev_name); - + desc = pa_xstrdup(pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_DESCRIPTION)); priority = is_sink ? dev->playback_priority : dev->capture_priority; - prio2 = (priority == 0 ? 0 : 1.0/priority); jack = ucm_get_jack(context->ucm, dev); type = dev->type; - for (i = 1; i < num; i++) { - char *tmp; - - dev = sortedi; - dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); - - tmp = pa_sprintf_malloc("%s+%s", name, dev_name); - pa_xfree(name); - name = tmp; - - tmp = pa_sprintf_malloc("%s,%s", desc, dev_name); - pa_xfree(desc); - desc = tmp; - - priority = is_sink ? dev->playback_priority : dev->capture_priority; - if (priority != 0 && prio2 > 0) - prio2 += 1.0/priority; - - jack2 = ucm_get_jack(context->ucm, dev); - if (jack2) { - if (jack && jack != jack2) - pa_log_warn("Multiple jacks per combined device '%s': '%s' '%s'", name, jack->name, jack2->name); - jack = jack2; - } - - type2 = dev->type; - if (type2 != PA_DEVICE_PORT_TYPE_UNKNOWN) { - if (type != PA_DEVICE_PORT_TYPE_UNKNOWN && type != type2) - pa_log_warn("Multiple device types per combined device '%s': %d %d", name, type, type2); - type = type2; - } - } - - /* Make combination ports always have lower priority, and use the formula - 1/p = 1/p1 + 1/p2 + ... 1/pn. - This way, the result will always be less than the individual components, - yet higher components will lead to higher result. */ - - if (num > 1) - priority = prio2 > 0 ? 1.0/prio2 : 0; - port = pa_hashmap_get(ports, name); if (!port) { pa_device_port_new_data port_data; @@ -1092,37 +1212,32 @@ pa_device_port_new_data_done(&port_data); data = PA_DEVICE_PORT_DATA(port); - ucm_port_data_init(data, context->ucm, port, pdevices, num); + ucm_port_data_init(data, context->ucm, port, dev); port->impl_free = ucm_port_data_free; pa_hashmap_put(ports, port->name, port); pa_log_debug("Add port %s: %s", port->name, port->description); - if (num == 1) { - /* To keep things simple and not worry about stacking controls, we only support hardware volumes on non-combination - * ports. */ - PA_HASHMAP_FOREACH_KV(profile, vol, is_sink ? dev->playback_volumes : dev->capture_volumes, state) { - pa_alsa_path *path = pa_alsa_path_synthesize(vol->mixer_elem, - is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT); - - if (!path) - pa_log_warn("Failed to set up volume control: %s", vol->mixer_elem); - else { - if (vol->master_elem) { - pa_alsa_element *e = pa_alsa_element_get(path, vol->master_elem, false); - e->switch_use = PA_ALSA_SWITCH_MUTE; - e->volume_use = PA_ALSA_VOLUME_MERGE; - } + PA_HASHMAP_FOREACH_KV(verb_name, vol, is_sink ? dev->playback_volumes : dev->capture_volumes, state) { + pa_alsa_path *path = pa_alsa_path_synthesize(vol->mixer_elem, + is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT); + + if (!path) + pa_log_warn("Failed to set up volume control: %s", vol->mixer_elem); + else { + if (vol->master_elem) { + pa_alsa_element *e = pa_alsa_element_get(path, vol->master_elem, false); + e->switch_use = PA_ALSA_SWITCH_MUTE; + e->volume_use = PA_ALSA_VOLUME_MERGE; + } - pa_hashmap_put(data->paths, pa_xstrdup(profile), path); + pa_hashmap_put(data->paths, pa_xstrdup(verb_name), path); - /* Add path also to already created empty path set */ - dev = sorted0; - if (is_sink) - pa_hashmap_put(dev->playback_mapping->output_path_set->paths, pa_xstrdup(vol->mixer_elem), path); - else - pa_hashmap_put(dev->capture_mapping->input_path_set->paths, pa_xstrdup(vol->mixer_elem), path); - } + /* Add path also to already created empty path set */ + if (is_sink) + pa_hashmap_put(dev->playback_mapping->output_path_set->paths, pa_xstrdup(vol->mixer_elem), path); + else + pa_hashmap_put(dev->capture_mapping->input_path_set->paths, pa_xstrdup(vol->mixer_elem), path); } } } @@ -1143,113 +1258,127 @@ if (hash) { pa_hashmap_put(hash, port->name, port); } + + /* ELD devices */ + set_eld_devices(ports); } -static int ucm_port_contains(const char *port_name, const char *dev_name, bool is_sink) { - int ret = 0; - const char *r; - const char *state = NULL; - size_t len; +static bool devset_supports_device(pa_idxset *devices, pa_alsa_ucm_device *dev) { + const char *sink, *sink2, *source, *source2; + pa_alsa_ucm_device *d; + uint32_t idx; - if (!port_name || !dev_name) - return false; + pa_assert(devices); + pa_assert(dev); - port_name += is_sink ? strlen(PA_UCM_PRE_TAG_OUTPUT) : strlen(PA_UCM_PRE_TAG_INPUT); + /* Can add anything to empty group */ + if (pa_idxset_isempty(devices)) + return true; - while ((r = pa_split_in_place(port_name, "+", &len, &state))) { - if (strlen(dev_name) == len && !strncmp(r, dev_name, len)) { - ret = 1; - break; - } - } + /* Device already selected */ + if (pa_idxset_contains(devices, dev)) + return true; - return ret; -} + /* No conflicting device must already be selected */ + if (!pa_idxset_isdisjoint(devices, dev->conflicting_devices)) + return false; -static int ucm_check_conformance( - pa_alsa_ucm_mapping_context *context, - pa_alsa_ucm_device **pdevices, - int dev_num, - pa_alsa_ucm_device *dev) { + /* No already selected device must be unsupported */ + if (!pa_idxset_isempty(dev->supported_devices)) + if (!pa_idxset_issubset(devices, dev->supported_devices)) + return false; - uint32_t idx; - pa_alsa_ucm_device *d; - int i; + sink = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SINK); + source = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SOURCE); - pa_assert(dev); + PA_IDXSET_FOREACH(d, devices, idx) { + /* Must not be unsupported by any selected device */ + if (!pa_idxset_isempty(d->supported_devices)) + if (!pa_idxset_contains(d->supported_devices, dev)) + return false; - pa_log_debug("Check device %s conformance with %d other devices", - pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME), dev_num); - if (dev_num == 0) { - pa_log_debug("First device in combination, number 1"); - return 1; - } + /* PlaybackPCM must not be the same as any selected device */ + sink2 = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SINK); + if (sink && sink2 && pa_streq(sink, sink2)) + return false; - if (dev->conflicting_devices) { /* the device defines conflicting devices */ - PA_IDXSET_FOREACH(d, dev->conflicting_devices, idx) { - for (i = 0; i < dev_num; i++) { - if (pdevicesi == d) { - pa_log_debug("Conflicting device found"); - return 0; - } - } - } - } else if (dev->supported_devices) { /* the device defines supported devices */ - for (i = 0; i < dev_num; i++) { - if (!ucm_device_exists(dev->supported_devices, pdevicesi)) { - pa_log_debug("Supported device not found"); - return 0; - } - } - } else { /* not support any other devices */ - pa_log_debug("Not support any other devices"); - return 0; + /* CapturePCM must not be the same as any selected device */ + source2 = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SOURCE); + if (source && source2 && pa_streq(source, source2)) + return false; } - pa_log_debug("Device added to combination, number %d", dev_num + 1); - return 1; + return true; } -static inline pa_alsa_ucm_device *get_next_device(pa_idxset *idxset, uint32_t *idx) { +/* Iterates nonempty subsets of UCM devices that can be simultaneously + * used, including subsets of previously returned subsets. At start, + * *state should be NULL. It's not safe to modify the devices argument + * until iteration ends. The returned idxsets must be freed by the + * caller. */ +static pa_idxset *iterate_device_subsets(pa_idxset *devices, void **state) { + uint32_t idx; pa_alsa_ucm_device *dev; - if (*idx == PA_IDXSET_INVALID) - dev = pa_idxset_first(idxset, idx); - else - dev = pa_idxset_next(idxset, idx); + pa_assert(devices); + pa_assert(state); - return dev; -} + if (*state == NULL) { + /* First iteration, start adding from first device */ + *state = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + dev = pa_idxset_first(devices, &idx); -static void ucm_add_ports_combination( - pa_hashmap *hash, - pa_alsa_ucm_mapping_context *context, - bool is_sink, - pa_alsa_ucm_device **pdevices, - int dev_num, - uint32_t map_index, - pa_hashmap *ports, - pa_card_profile *cp, - pa_core *core) { + } else { + /* Backtrack the most recent device we added and skip it */ + dev = pa_idxset_steal_last(*state, NULL); + pa_idxset_get_by_data(devices, dev, &idx); + if (dev) + dev = pa_idxset_next(devices, &idx); + } - pa_alsa_ucm_device *dev; - uint32_t idx = map_index; + /* Try adding devices we haven't decided on yet */ + for (; dev; dev = pa_idxset_next(devices, &idx)) { + if (devset_supports_device(*state, dev)) + pa_idxset_put(*state, dev, NULL); + } - if ((dev = get_next_device(context->ucm_devices, &idx)) == NULL) - return; + if (pa_idxset_isempty(*state)) { + /* No more choices to backtrack on, therefore no more subsets to + * return after this. Don't return the empty set, instead clean + * up and end iteration. */ + pa_idxset_free(*state, NULL); + *state = NULL; + return NULL; + } + + return pa_idxset_copy(*state, NULL); +} + +/* This a wrapper around iterate_device_subsets() that only returns the + * biggest possible groups and not any of their subsets. */ +static pa_idxset *iterate_maximal_device_subsets(pa_idxset *devices, void **state) { + uint32_t idx; + pa_alsa_ucm_device *dev; + pa_idxset *subset; - /* check if device at map_index can combine with existing devices combination */ - if (ucm_check_conformance(context, pdevices, dev_num, dev)) { - /* add device at map_index to devices combination */ - pdevicesdev_num = dev; - /* add current devices combination as a new port */ - ucm_add_port_combination(hash, context, is_sink, pdevices, dev_num + 1, ports, cp, core); - /* try more elements combination */ - ucm_add_ports_combination(hash, context, is_sink, pdevices, dev_num + 1, idx, ports, cp, core); + pa_assert(devices); + pa_assert(state); + + subset = iterate_device_subsets(devices, state); + if (!subset) + return subset; + + /* Skip this group if it's incomplete, by checking if we can add any + * other device. If we can, this iteration is a subset of another + * group that we already returned or eventually return. */ + PA_IDXSET_FOREACH(dev, devices, idx) { + if (!pa_idxset_contains(subset, dev) && devset_supports_device(subset, dev)) { + pa_idxset_free(subset, NULL); + return iterate_maximal_device_subsets(devices, state); + } } - /* try other device with current elements number */ - ucm_add_ports_combination(hash, context, is_sink, pdevices, dev_num, idx, ports, cp, core); + return subset; } static char* merge_roles(const char *cur, const char *add) { @@ -1281,28 +1410,6 @@ return ret; } -void pa_alsa_ucm_add_ports_combination( - pa_hashmap *p, - pa_alsa_ucm_mapping_context *context, - bool is_sink, - pa_hashmap *ports, - pa_card_profile *cp, - pa_core *core) { - - pa_alsa_ucm_device **pdevices; - - pa_assert(context->ucm_devices); - - if (pa_idxset_size(context->ucm_devices) > 0) { - pdevices = pa_xnew(pa_alsa_ucm_device *, pa_idxset_size(context->ucm_devices)); - ucm_add_ports_combination(p, context, is_sink, pdevices, 0, PA_IDXSET_INVALID, ports, cp, core); - pa_xfree(pdevices); - } - - /* ELD devices */ - set_eld_devices(ports); -} - void pa_alsa_ucm_add_ports( pa_hashmap **p, pa_proplist *proplist, @@ -1312,7 +1419,6 @@ snd_pcm_t *pcm_handle, bool ignore_dB) { - uint32_t idx; char *merged_roles; const char *role_name = is_sink ? PA_ALSA_PROP_UCM_PLAYBACK_ROLES : PA_ALSA_PROP_UCM_CAPTURE_ROLES; pa_alsa_ucm_device *dev; @@ -1323,34 +1429,39 @@ pa_assert(*p); /* add ports first */ - pa_alsa_ucm_add_ports_combination(*p, context, is_sink, card->ports, NULL, card->core); + pa_alsa_ucm_add_port(*p, context, is_sink, card->ports, NULL, card->core); /* now set up volume paths if any */ probe_volumes(*p, is_sink, pcm_handle, context->ucm->mixers, ignore_dB); - /* probe_volumes() removes per-profile paths from ports if probing them - * fails. The path for the current profile is cached in + /* probe_volumes() removes per-verb paths from ports if probing them + * fails. The path for the current verb is cached in * pa_alsa_ucm_port_data.path, which is not cleared by probe_volumes() if * the path gets removed, so we have to call update_mixer_paths() here to * unset the cached path if needed. */ - if (card->card.active_profile_index < card->card.n_profiles) - update_mixer_paths(*p, card->card.profilescard->card.active_profile_index->name); + if (context->ucm->active_verb) { + const char *verb_name; + verb_name = pa_proplist_gets(context->ucm->active_verb->proplist, PA_ALSA_PROP_UCM_NAME); + update_mixer_paths(*p, verb_name); + } /* then set property PA_PROP_DEVICE_INTENDED_ROLES */ merged_roles = pa_xstrdup(pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES)); - PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { + + dev = context->ucm_device; + if (dev) { const char *roles = pa_proplist_gets(dev->proplist, role_name); tmp = merge_roles(merged_roles, roles); pa_xfree(merged_roles); merged_roles = tmp; } - if (context->ucm_modifiers) - PA_IDXSET_FOREACH(mod, context->ucm_modifiers, idx) { - tmp = merge_roles(merged_roles, mod->media_role); - pa_xfree(merged_roles); - merged_roles = tmp; - } + mod = context->ucm_modifier; + if (mod) { + tmp = merge_roles(merged_roles, mod->media_role); + pa_xfree(merged_roles); + merged_roles = tmp; + } if (merged_roles) pa_proplist_sets(proplist, PA_PROP_DEVICE_INTENDED_ROLES, merged_roles); @@ -1360,85 +1471,81 @@ } /* Change UCM verb and device to match selected card profile */ -int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile) { +int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, pa_alsa_profile *new_profile, pa_alsa_profile *old_profile) { int ret = 0; - const char *profile; + const char *verb_name, *profile_name; pa_alsa_ucm_verb *verb; + pa_alsa_mapping *map; + uint32_t idx; if (new_profile == old_profile) - return ret; - else if (new_profile == NULL || old_profile == NULL) - profile = new_profile ? new_profile : SND_USE_CASE_VERB_INACTIVE; - else if (!pa_streq(new_profile, old_profile)) - profile = new_profile; - else - return ret; + return 0; - /* change verb */ - pa_log_info("Set UCM verb to %s", profile); - if ((ret = snd_use_case_set(ucm->ucm_mgr, "_verb", profile)) < 0) { - pa_log("Failed to set verb %s: %s", profile, snd_strerror(ret)); + if (new_profile == NULL) { + verb = NULL; + profile_name = SND_USE_CASE_VERB_INACTIVE; + verb_name = SND_USE_CASE_VERB_INACTIVE; + } else { + verb = new_profile->ucm_context.verb; + profile_name = new_profile->name; + verb_name = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME); } - /* find active verb */ - ucm->active_verb = NULL; - PA_LLIST_FOREACH(verb, ucm->verbs) { - const char *verb_name; - verb_name = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME); - if (pa_streq(verb_name, profile)) { - ucm->active_verb = verb; - break; + pa_log_info("Set profile to %s", profile_name); + if (ucm->active_verb != verb) { + /* change verb */ + pa_log_info("Set UCM verb to %s", verb_name); + if ((snd_use_case_set(ucm->ucm_mgr, "_verb", verb_name)) < 0) { + pa_log("Failed to set verb %s", verb_name); + ret = -1; } + + } else if (ucm->active_verb) { + /* Disable modifiers not in new profile. Has to be done before + * devices, because _dismod fails if a modifier's supported + * devices are disabled. */ + PA_IDXSET_FOREACH(map, old_profile->input_mappings, idx) + if (new_profile && !pa_idxset_contains(new_profile->input_mappings, map)) + if (map->ucm_context.ucm_modifier && ucm_modifier_disable(ucm, map->ucm_context.ucm_modifier) < 0) + ret = -1; + + PA_IDXSET_FOREACH(map, old_profile->output_mappings, idx) + if (new_profile && !pa_idxset_contains(new_profile->output_mappings, map)) + if (map->ucm_context.ucm_modifier && ucm_modifier_disable(ucm, map->ucm_context.ucm_modifier) < 0) + ret = -1; + + /* Disable devices not in new profile */ + PA_IDXSET_FOREACH(map, old_profile->input_mappings, idx) + if (new_profile && !pa_idxset_contains(new_profile->input_mappings, map)) + if (map->ucm_context.ucm_device && ucm_device_disable(ucm, map->ucm_context.ucm_device) < 0) + ret = -1; + + PA_IDXSET_FOREACH(map, old_profile->output_mappings, idx) + if (new_profile && !pa_idxset_contains(new_profile->output_mappings, map)) + if (map->ucm_context.ucm_device && ucm_device_disable(ucm, map->ucm_context.ucm_device) < 0) + ret = -1; } + ucm->active_verb = verb; + + update_mixer_paths(card->ports, verb_name); - update_mixer_paths(card->ports, profile); return ret; } -int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port, bool is_sink) { - int i; - int ret = 0; +int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port) { pa_alsa_ucm_config *ucm; - const char **enable_devs; - int enable_num = 0; - uint32_t idx; pa_alsa_ucm_device *dev; + pa_alsa_ucm_port_data *data; pa_assert(context && context->ucm); ucm = context->ucm; pa_assert(ucm->ucm_mgr); - enable_devs = pa_xnew(const char *, pa_idxset_size(context->ucm_devices)); - - /* first disable then enable */ - PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { - const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); - - if (ucm_port_contains(port->name, dev_name, is_sink)) - enable_devsenable_num++ = dev_name; - else { - pa_log_debug("Disable ucm device %s", dev_name); - if (snd_use_case_set(ucm->ucm_mgr, "_disdev", dev_name) > 0) { - pa_log("Failed to disable ucm device %s", dev_name); - ret = -1; - break; - } - } - } + data = PA_DEVICE_PORT_DATA(port); + dev = data->device; - for (i = 0; i < enable_num; i++) { - pa_log_debug("Enable ucm device %s", enable_devsi); - if (snd_use_case_set(ucm->ucm_mgr, "_enadev", enable_devsi) < 0) { - pa_log("Failed to enable ucm device %s", enable_devsi); - ret = -1; - break; - } - } - - pa_xfree(enable_devs); - - return ret; + return ucm_device_enable(ucm, dev); } static void ucm_add_mapping(pa_alsa_profile *p, pa_alsa_mapping *m) { @@ -1474,7 +1581,7 @@ const char *new_desc, *mdev; bool is_sink = m->direction == PA_ALSA_DIRECTION_OUTPUT; - pa_idxset_put(m->ucm_context.ucm_devices, device, NULL); + m->ucm_context.ucm_device = device; new_desc = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_DESCRIPTION); cur_desc = m->description; @@ -1503,7 +1610,7 @@ const char *new_desc, *mod_name, *channel_str; uint32_t channels = 0; - pa_idxset_put(m->ucm_context.ucm_modifiers, modifier, NULL); + m->ucm_context.ucm_modifier = modifier; new_desc = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_DESCRIPTION); cur_desc = m->description; @@ -1544,17 +1651,11 @@ pa_channel_map_init(&m->channel_map); } -static pa_alsa_mapping* ucm_alsa_mapping_get(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, const char *verb_name, const char *device_str, bool is_sink) { +static pa_alsa_mapping* ucm_alsa_mapping_get(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, const char *verb_name, const char *ucm_name, bool is_sink) { pa_alsa_mapping *m; char *mapping_name; - size_t ucm_alibpref_len = 0; - /* find private alsa-lib's configuration device prefix */ - - if (ucm->alib_prefix && pa_startswith(device_str, ucm->alib_prefix)) - ucm_alibpref_len = strlen(ucm->alib_prefix); - - mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name, device_str + ucm_alibpref_len, is_sink ? "sink" : "source"); + mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name, ucm_name, is_sink ? "sink" : "source"); m = pa_alsa_mapping_get(ps, mapping_name); @@ -1569,7 +1670,6 @@ static int ucm_create_mapping_direction( pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, - pa_alsa_profile *p, pa_alsa_ucm_device *device, const char *verb_name, const char *device_name, @@ -1579,7 +1679,7 @@ pa_alsa_mapping *m; unsigned priority, rate, channels; - m = ucm_alsa_mapping_get(ucm, ps, verb_name, device_str, is_sink); + m = ucm_alsa_mapping_get(ucm, ps, verb_name, device_name, is_sink); if (!m) return -1; @@ -1590,8 +1690,7 @@ rate = is_sink ? device->playback_rate : device->capture_rate; channels = is_sink ? device->playback_channels : device->capture_channels; - if (!m->ucm_context.ucm_devices) { /* new mapping */ - m->ucm_context.ucm_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + if (!m->ucm_context.ucm_device) { /* new mapping */ m->ucm_context.ucm = ucm; m->ucm_context.direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT; @@ -1599,7 +1698,6 @@ m->device_strings0 = pa_xstrdup(device_str); m->direction = is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT; - ucm_add_mapping(p, m); if (rate) m->sample_spec.rate = rate; pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA); @@ -1621,7 +1719,6 @@ static int ucm_create_mapping_for_modifier( pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, - pa_alsa_profile *p, pa_alsa_ucm_modifier *modifier, const char *verb_name, const char *mod_name, @@ -1630,16 +1727,14 @@ pa_alsa_mapping *m; - m = ucm_alsa_mapping_get(ucm, ps, verb_name, device_str, is_sink); + m = ucm_alsa_mapping_get(ucm, ps, verb_name, mod_name, is_sink); if (!m) return -1; pa_log_info("UCM mapping: %s modifier %s", m->name, mod_name); - if (!m->ucm_context.ucm_devices && !m->ucm_context.ucm_modifiers) { /* new mapping */ - m->ucm_context.ucm_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); - m->ucm_context.ucm_modifiers = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + if (!m->ucm_context.ucm_device && !m->ucm_context.ucm_modifier) { /* new mapping */ m->ucm_context.ucm = ucm; m->ucm_context.direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT; @@ -1648,10 +1743,7 @@ m->direction = is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT; /* Modifier sinks should not be routed to by default */ m->priority = 0; - - ucm_add_mapping(p, m); - } else if (!m->ucm_context.ucm_modifiers) /* share pcm with device */ - m->ucm_context.ucm_modifiers = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + } alsa_mapping_add_ucm_modifier(m, modifier); @@ -1661,7 +1753,6 @@ static int ucm_create_mapping( pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, - pa_alsa_profile *p, pa_alsa_ucm_device *device, const char *verb_name, const char *device_name, @@ -1676,9 +1767,9 @@ } if (sink) - ret = ucm_create_mapping_direction(ucm, ps, p, device, verb_name, device_name, sink, true); + ret = ucm_create_mapping_direction(ucm, ps, device, verb_name, device_name, sink, true); if (ret == 0 && source) - ret = ucm_create_mapping_direction(ucm, ps, p, device, verb_name, device_name, source, false); + ret = ucm_create_mapping_direction(ucm, ps, device, verb_name, device_name, source, false); return ret; } @@ -1751,27 +1842,28 @@ pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, pa_alsa_ucm_verb *verb, - const char *verb_name, - const char *verb_desc) { + pa_idxset *mappings, + const char *profile_name, + const char *profile_desc, + unsigned int profile_priority) { pa_alsa_profile *p; - pa_alsa_ucm_device *dev; - pa_alsa_ucm_modifier *mod; - int i = 0; - const char *name, *sink, *source; - unsigned int priority; + pa_alsa_mapping *map; + uint32_t idx; pa_assert(ps); - if (pa_hashmap_get(ps->profiles, verb_name)) { - pa_log("Verb %s already exists", verb_name); + if (pa_hashmap_get(ps->profiles, profile_name)) { + pa_log("Profile %s already exists", profile_name); return -1; } p = pa_xnew0(pa_alsa_profile, 1); p->profile_set = ps; - p->name = pa_xstrdup(verb_name); - p->description = pa_xstrdup(verb_desc); + p->name = pa_xstrdup(profile_name); + p->description = pa_xstrdup(profile_desc); + p->priority = profile_priority; + p->ucm_context.verb = verb; p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); @@ -1779,10 +1871,36 @@ p->supported = true; pa_hashmap_put(ps->profiles, p->name, p); + PA_IDXSET_FOREACH(map, mappings, idx) + ucm_add_mapping(p, map); + + pa_alsa_profile_dump(p); + + return 0; +} + +static int ucm_create_verb_profiles( + pa_alsa_ucm_config *ucm, + pa_alsa_profile_set *ps, + pa_alsa_ucm_verb *verb, + const char *verb_name, + const char *verb_desc) { + + pa_idxset *verb_devices, *p_devices, *p_mappings; + pa_alsa_ucm_device *dev; + pa_alsa_ucm_modifier *mod; + int i = 0; + int n_profiles = 0; + const char *name, *sink, *source; + char *p_name, *p_desc, *tmp; + unsigned int verb_priority, p_priority; + uint32_t idx; + void *state = NULL; + /* TODO: get profile priority from policy management */ - priority = verb->priority; + verb_priority = verb->priority; - if (priority == 0) { + if (verb_priority == 0) { char *verb_cmp, *c; c = verb_cmp = pa_xstrdup(verb_name); while (*c) { @@ -1791,15 +1909,13 @@ } for (i = 0; verb_infoi.id; i++) { if (strcasecmp(verb_infoi.id, verb_cmp) == 0) { - priority = verb_infoi.priority; + verb_priority = verb_infoi.priority; break; } } pa_xfree(verb_cmp); } - p->priority = priority; - PA_LLIST_FOREACH(dev, verb->devices) { pa_alsa_jack *jack; const char *jack_hw_mute; @@ -1809,7 +1925,7 @@ sink = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SINK); source = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SOURCE); - ucm_create_mapping(ucm, ps, p, dev, verb_name, name, sink, source); + ucm_create_mapping(ucm, ps, dev, verb_name, name, sink, source); jack = ucm_get_jack(ucm, dev); if (jack) @@ -1860,12 +1976,74 @@ source = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_SOURCE); if (sink) - ucm_create_mapping_for_modifier(ucm, ps, p, mod, verb_name, name, sink, true); + ucm_create_mapping_for_modifier(ucm, ps, mod, verb_name, name, sink, true); else if (source) - ucm_create_mapping_for_modifier(ucm, ps, p, mod, verb_name, name, source, false); + ucm_create_mapping_for_modifier(ucm, ps, mod, verb_name, name, source, false); } - pa_alsa_profile_dump(p); + verb_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + PA_LLIST_FOREACH(dev, verb->devices) + pa_idxset_put(verb_devices, dev, NULL); + + while ((p_devices = iterate_maximal_device_subsets(verb_devices, &state))) { + p_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + /* Add the mappings that include our selected devices */ + PA_IDXSET_FOREACH(dev, p_devices, idx) { + if (dev->playback_mapping) + pa_idxset_put(p_mappings, dev->playback_mapping, NULL); + if (dev->capture_mapping) + pa_idxset_put(p_mappings, dev->capture_mapping, NULL); + } + + /* Add mappings only for the modifiers that can work with our + * device selection */ + PA_LLIST_FOREACH(mod, verb->modifiers) + if (pa_idxset_isempty(mod->supported_devices) || pa_idxset_issubset(mod->supported_devices, p_devices)) + if (pa_idxset_isdisjoint(mod->conflicting_devices, p_devices)) { + if (mod->playback_mapping) + pa_idxset_put(p_mappings, mod->playback_mapping, NULL); + if (mod->capture_mapping) + pa_idxset_put(p_mappings, mod->capture_mapping, NULL); + } + + /* If we'll have multiple profiles for this verb, their names + * must be unique. Use a list of chosen devices to disambiguate + * them. If the profile contains all devices of a verb, we'll + * generate only onle profile whose name should be the verb + * name. GUIs usually show the profile description instead of + * the name, add the device names to those as well. */ + tmp = devset_name(p_devices, ", "); + if (pa_idxset_equals(p_devices, verb_devices)) { + p_name = pa_xstrdup(verb_name); + p_desc = pa_xstrdup(verb_desc); + } else { + p_name = pa_sprintf_malloc("%s (%s)", verb_name, tmp); + p_desc = pa_sprintf_malloc("%s (%s)", verb_desc, tmp); + } + + /* Make sure profiles with higher-priority devices are + * prioritized. */ + p_priority = verb_priority + devset_playback_priority(p_devices, false) + devset_capture_priority(p_devices, false); + + if (ucm_create_profile(ucm, ps, verb, p_mappings, p_name, p_desc, p_priority) == 0) { + pa_log_debug("Created profile %s for UCM verb %s", p_name, verb_name); + n_profiles++; + } + + pa_xfree(tmp); + pa_xfree(p_name); + pa_xfree(p_desc); + pa_idxset_free(p_mappings, NULL); + pa_idxset_free(p_devices, NULL); + } + + pa_idxset_free(verb_devices, NULL); + + if (n_profiles == 0) { + pa_log("UCM verb %s created no profiles", verb_name); + return -1; + } return 0; } @@ -1874,7 +2052,6 @@ { pa_alsa_ucm_mapping_context *context = &m->ucm_context; pa_alsa_ucm_device *dev; - uint32_t idx; char *mdev, *alib_prefix; snd_pcm_info_t *info; int pcm_card, pcm_device; @@ -1890,13 +2067,12 @@ alib_prefix = context->ucm->alib_prefix; - PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { - mdev = pa_sprintf_malloc("%shw:%i", alib_prefix ? alib_prefix : "", pcm_card); - if (mdev == NULL) - continue; - dev->eld_mixer_device_name = mdev; - dev->eld_device = pcm_device; - } + dev = context->ucm_device; + mdev = pa_sprintf_malloc("%shw:%i", alib_prefix ? alib_prefix : "", pcm_card); + if (mdev == NULL) + return; + dev->eld_mixer_device_name = mdev; + dev->eld_device = pcm_device; } static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode) { @@ -1918,7 +2094,7 @@ try_buffer_size = ucm->default_n_fragments * try_period_size; pcm = pa_alsa_open_by_device_string(m->device_strings0, NULL, &try_ss, - &try_map, mode, &try_period_size, &try_buffer_size, 0, NULL, NULL, exact_channels); + &try_map, mode, &try_period_size, &try_buffer_size, 0, NULL, NULL, NULL, NULL, exact_channels); if (pcm) { if (!exact_channels) @@ -1960,38 +2136,39 @@ snd_mixer_t *mixer_handle; pa_alsa_ucm_mapping_context *context = &m->ucm_context; pa_alsa_ucm_device *dev; - uint32_t idx; - - PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { - bool has_control; + bool has_control; - if (!dev->jack || !dev->jack->mixer_device_name) - continue; - - mixer_handle = pa_alsa_open_mixer_by_name(mixers, dev->jack->mixer_device_name, true); - if (!mixer_handle) { - pa_log_error("Unable to determine open mixer device '%s' for jack %s", dev->jack->mixer_device_name, dev->jack->name); - continue; - } + dev = context->ucm_device; + if (!dev->jack || !dev->jack->mixer_device_name) + return; - has_control = pa_alsa_mixer_find_card(mixer_handle, &dev->jack->alsa_id, 0) != NULL; - pa_alsa_jack_set_has_control(dev->jack, has_control); - pa_log_info("UCM jack %s has_control=%d", dev->jack->name, dev->jack->has_control); + mixer_handle = pa_alsa_open_mixer_by_name(mixers, dev->jack->mixer_device_name, true); + if (!mixer_handle) { + pa_log_error("Unable to determine open mixer device '%s' for jack %s", dev->jack->mixer_device_name, dev->jack->name); + return; } + + has_control = pa_alsa_mixer_find_card(mixer_handle, &dev->jack->alsa_id, 0) != NULL; + pa_alsa_jack_set_has_control(dev->jack, has_control); + pa_log_info("UCM jack %s has_control=%d", dev->jack->name, dev->jack->has_control); } static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps) { void *state; pa_alsa_profile *p; pa_alsa_mapping *m; + const char *verb_name; uint32_t idx; PA_HASHMAP_FOREACH(p, ps->profiles, state) { + pa_log_info("Probing profile %s", p->name); + /* change verb */ - pa_log_info("Set ucm verb to %s", p->name); + verb_name = pa_proplist_gets(p->ucm_context.verb->proplist, PA_ALSA_PROP_UCM_NAME); + pa_log_info("Set ucm verb to %s", verb_name); - if ((snd_use_case_set(ucm->ucm_mgr, "_verb", p->name)) < 0) { - pa_log("Failed to set verb %s", p->name); + if ((snd_use_case_set(ucm->ucm_mgr, "_verb", verb_name)) < 0) { + pa_log("Failed to set verb %s", verb_name); p->supported = false; continue; } @@ -2061,7 +2238,7 @@ (pa_free_cb_t) pa_alsa_profile_free); ps->decibel_fixes = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); - /* create a profile for each verb */ + /* create profiles for each verb */ PA_LLIST_FOREACH(verb, ucm->verbs) { const char *verb_name; const char *verb_desc; @@ -2073,7 +2250,7 @@ continue; } - ucm_create_profile(ucm, ps, verb, verb_name, verb_desc); + ucm_create_verb_profiles(ucm, ps, verb, verb_name, verb_desc); } ucm_probe_profile_set(ucm, ps); @@ -2102,10 +2279,8 @@ pa_proplist_free(di->proplist); - if (di->conflicting_devices) - pa_idxset_free(di->conflicting_devices, NULL); - if (di->supported_devices) - pa_idxset_free(di->supported_devices, NULL); + pa_idxset_free(di->conflicting_devices, NULL); + pa_idxset_free(di->supported_devices, NULL); pa_xfree(di->eld_mixer_device_name); @@ -2115,10 +2290,8 @@ PA_LLIST_FOREACH_SAFE(mi, mn, verb->modifiers) { PA_LLIST_REMOVE(pa_alsa_ucm_modifier, verb->modifiers, mi); pa_proplist_free(mi->proplist); - if (mi->n_suppdev > 0) - snd_use_case_free_list(mi->supported_devices, mi->n_suppdev); - if (mi->n_confdev > 0) - snd_use_case_free_list(mi->conflicting_devices, mi->n_confdev); + pa_idxset_free(mi->conflicting_devices, NULL); + pa_idxset_free(mi->supported_devices, NULL); pa_xfree(mi->media_role); pa_xfree(mi); } @@ -2166,29 +2339,22 @@ void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) { pa_alsa_ucm_device *dev; pa_alsa_ucm_modifier *mod; - uint32_t idx; - if (context->ucm_devices) { + dev = context->ucm_device; + if (dev) { /* clear ucm device pointer to mapping */ - PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { - if (context->direction == PA_DIRECTION_OUTPUT) - dev->playback_mapping = NULL; - else - dev->capture_mapping = NULL; - } - - pa_idxset_free(context->ucm_devices, NULL); + if (context->direction == PA_DIRECTION_OUTPUT) + dev->playback_mapping = NULL; + else + dev->capture_mapping = NULL; } - if (context->ucm_modifiers) { - PA_IDXSET_FOREACH(mod, context->ucm_modifiers, idx) { - if (context->direction == PA_DIRECTION_OUTPUT) - mod->playback_mapping = NULL; - else - mod->capture_mapping = NULL; - } - - pa_idxset_free(context->ucm_modifiers, NULL); + mod = context->ucm_modifier; + if (mod) { + if (context->direction == PA_DIRECTION_OUTPUT) + mod->playback_mapping = NULL; + else + mod->capture_mapping = NULL; } } @@ -2202,12 +2368,7 @@ PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) { if ((mod->action_direction == dir) && (pa_streq(mod->media_role, role))) { if (mod->enabled_counter == 0) { - const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME); - - pa_log_info("Enable ucm modifier %s", mod_name); - if (snd_use_case_set(ucm->ucm_mgr, "_enamod", mod_name) < 0) { - pa_log("Failed to enable ucm modifier %s", mod_name); - } + ucm_modifier_enable(ucm, mod); } mod->enabled_counter++; @@ -2227,27 +2388,14 @@ if ((mod->action_direction == dir) && (pa_streq(mod->media_role, role))) { mod->enabled_counter--; - if (mod->enabled_counter == 0) { - const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME); - - pa_log_info("Disable ucm modifier %s", mod_name); - if (snd_use_case_set(ucm->ucm_mgr, "_dismod", mod_name) < 0) { - pa_log("Failed to disable ucm modifier %s", mod_name); - } - } + if (mod->enabled_counter == 0) + ucm_modifier_disable(ucm, mod); break; } } } -static void device_add_ucm_port(pa_alsa_ucm_device *device, pa_alsa_ucm_port_data *port) { - pa_assert(device); - pa_assert(port); - - pa_dynarray_append(device->ucm_ports, port); -} - static void device_set_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack) { pa_assert(device); pa_assert(jack); @@ -2280,7 +2428,7 @@ device->available = available; PA_DYNARRAY_FOREACH(port, device->ucm_ports, idx) - ucm_port_update_available(port); + pa_device_port_set_available(port->core_port, port->device->available); } void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device) { @@ -2304,26 +2452,21 @@ } static void ucm_port_data_init(pa_alsa_ucm_port_data *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port, - pa_alsa_ucm_device **devices, unsigned n_devices) { - unsigned i; - + pa_alsa_ucm_device *device) { pa_assert(ucm); pa_assert(core_port); - pa_assert(devices); + pa_assert(device); port->ucm = ucm; port->core_port = core_port; - port->devices = pa_dynarray_new(NULL); port->eld_device = -1; - for (i = 0; i < n_devices; i++) { - pa_dynarray_append(port->devices, devicesi); - device_add_ucm_port(devicesi, port); - } + port->device = device; + pa_dynarray_append(device->ucm_ports, port); port->paths = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree, NULL); - ucm_port_update_available(port); + pa_device_port_set_available(port->core_port, port->device->available); } static void ucm_port_data_free(pa_device_port *port) { @@ -2333,34 +2476,12 @@ ucm_port = PA_DEVICE_PORT_DATA(port); - if (ucm_port->devices) - pa_dynarray_free(ucm_port->devices); - if (ucm_port->paths) pa_hashmap_free(ucm_port->paths); pa_xfree(ucm_port->eld_mixer_device_name); } -static void ucm_port_update_available(pa_alsa_ucm_port_data *port) { - pa_alsa_ucm_device *device; - unsigned idx; - pa_available_t available = PA_AVAILABLE_YES; - - pa_assert(port); - - PA_DYNARRAY_FOREACH(device, port->devices, idx) { - if (device->available == PA_AVAILABLE_UNKNOWN) - available = PA_AVAILABLE_UNKNOWN; - else if (device->available == PA_AVAILABLE_NO) { - available = PA_AVAILABLE_NO; - break; - } - } - - pa_device_port_set_available(port->core_port, available); -} - #else /* HAVE_ALSA_UCM */ /* Dummy functions for systems without UCM support */ @@ -2374,7 +2495,7 @@ return NULL; } -int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile) { +int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, pa_alsa_profile *new_profile, pa_alsa_profile *old_profile) { return -1; } @@ -2392,7 +2513,7 @@ bool ignore_dB) { } -void pa_alsa_ucm_add_ports_combination( +void pa_alsa_ucm_add_port( pa_hashmap *hash, pa_alsa_ucm_mapping_context *context, bool is_sink, @@ -2401,7 +2522,7 @@ pa_core *core) { } -int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port, bool is_sink) { +int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port) { return -1; }
View file
pipewire-0.3.67.tar.gz/spa/plugins/alsa/acp/alsa-ucm.h -> pipewire-0.3.68.tar.gz/spa/plugins/alsa/acp/alsa-ucm.h
Changed
@@ -142,12 +142,13 @@ typedef struct pa_alsa_ucm_device pa_alsa_ucm_device; typedef struct pa_alsa_ucm_config pa_alsa_ucm_config; typedef struct pa_alsa_ucm_mapping_context pa_alsa_ucm_mapping_context; +typedef struct pa_alsa_ucm_profile_context pa_alsa_ucm_profile_context; typedef struct pa_alsa_ucm_port_data pa_alsa_ucm_port_data; typedef struct pa_alsa_ucm_volume pa_alsa_ucm_volume; int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index); pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map); -int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile); +int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, pa_alsa_profile *new_profile, pa_alsa_profile *old_profile); int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, const char *verb_desc, pa_alsa_ucm_verb **p_verb); @@ -159,14 +160,14 @@ pa_card *card, snd_pcm_t *pcm_handle, bool ignore_dB); -void pa_alsa_ucm_add_ports_combination( +void pa_alsa_ucm_add_port( pa_hashmap *hash, pa_alsa_ucm_mapping_context *context, bool is_sink, pa_hashmap *ports, pa_card_profile *cp, pa_core *core); -int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port, bool is_sink); +int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port); void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm); void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context); @@ -223,11 +224,8 @@ pa_proplist *proplist; - int n_confdev; - int n_suppdev; - - const char **conflicting_devices; - const char **supported_devices; + pa_idxset *conflicting_devices; + pa_idxset *supported_devices; pa_direction_t action_direction; @@ -270,21 +268,23 @@ pa_alsa_ucm_config *ucm; pa_direction_t direction; - pa_idxset *ucm_devices; - pa_idxset *ucm_modifiers; + pa_alsa_ucm_device *ucm_device; + pa_alsa_ucm_modifier *ucm_modifier; +}; + +struct pa_alsa_ucm_profile_context { + pa_alsa_ucm_verb *verb; }; struct pa_alsa_ucm_port_data { pa_alsa_ucm_config *ucm; pa_device_port *core_port; - /* A single port will be associated with multiple devices if it represents - * a combination of devices. */ - pa_dynarray *devices; /* pa_alsa_ucm_device */ + pa_alsa_ucm_device *device; - /* profile name -> pa_alsa_path for volume control */ + /* verb name -> pa_alsa_path for volume control */ pa_hashmap *paths; - /* Current path, set when activating profile */ + /* Current path, set when activating verb */ pa_alsa_path *path; /* ELD info */
View file
pipewire-0.3.67.tar.gz/spa/plugins/alsa/acp/alsa-util.c -> pipewire-0.3.68.tar.gz/spa/plugins/alsa/acp/alsa-util.c
Changed
@@ -505,6 +505,8 @@ snd_pcm_uframes_t tsched_size, bool *use_mmap, bool *use_tsched, + pa_sample_format_t **query_supported_formats, + unsigned int **query_supported_rates, pa_alsa_profile_set *ps, pa_alsa_mapping **mapping) { @@ -543,6 +545,8 @@ tsched_size, use_mmap, use_tsched, + query_supported_formats, + query_supported_rates, m); if (pcm_handle) { @@ -570,6 +574,8 @@ tsched_size, use_mmap, use_tsched, + query_supported_formats, + query_supported_rates, m); if (pcm_handle) { @@ -594,6 +600,8 @@ tsched_size, use_mmap, use_tsched, + query_supported_formats, + query_supported_rates, false); pa_xfree(d); @@ -615,6 +623,8 @@ snd_pcm_uframes_t tsched_size, bool *use_mmap, bool *use_tsched, + pa_sample_format_t **query_supported_formats, + unsigned int **query_supported_rates, pa_alsa_mapping *m) { snd_pcm_t *pcm_handle; @@ -644,6 +654,8 @@ tsched_size, use_mmap, use_tsched, + query_supported_formats, + query_supported_rates, pa_channel_map_valid(&m->channel_map) /* Query the channel count if we don't know what we want */); if (!pcm_handle) @@ -681,6 +693,8 @@ snd_pcm_uframes_t tsched_size, bool *use_mmap, bool *use_tsched, + pa_sample_format_t **query_supported_formats, + unsigned int **query_supported_rates, bool require_exact_channel_number) { int err; @@ -708,6 +722,12 @@ pa_log_info("ALSA device open '%s' %s: %p", d, mode == SND_PCM_STREAM_CAPTURE ? "capture" : "playback", pcm_handle); + if (query_supported_formats) + *query_supported_formats = pa_alsa_get_supported_formats(pcm_handle, ss->format); + + if (query_supported_rates) + *query_supported_rates = pa_alsa_get_supported_rates(pcm_handle, ss->rate); + if ((err = pa_alsa_set_hw_params( pcm_handle, ss, @@ -781,6 +801,8 @@ snd_pcm_uframes_t tsched_size, bool *use_mmap, bool *use_tsched, + pa_sample_format_t **query_supported_formats, + unsigned int **query_supported_rates, bool require_exact_channel_number) { snd_pcm_t *pcm_handle; @@ -802,6 +824,8 @@ tsched_size, use_mmap, use_tsched, + query_supported_formats, + query_supported_rates, require_exact_channel_number); pa_xfree(d); @@ -1411,6 +1435,24 @@ return pa_sprintf_malloc("Audio%i", i); } +#endif + +static void dump_supported_rates(unsigned int* values) +{ + pa_strbuf *buf; + char *str; + int i; + + buf = pa_strbuf_new(); + + for (i = 0; valuesi; i++) { + pa_strbuf_printf(buf, " %u", valuesi); + } + + str = pa_strbuf_to_string_free(buf); + pa_log_debug("Supported rates:%s", str); + pa_xfree(str); +} unsigned int *pa_alsa_get_supported_rates(snd_pcm_t *pcm, unsigned int fallback_rate) { static unsigned int all_rates = { 8000, 11025, 12000, @@ -1418,7 +1460,8 @@ 32000, 44100, 48000, 64000, 88200, 96000, 128000, 176400, 192000, - 384000 }; + 352800, 384000, + 705600, 768000 }; bool supportedPA_ELEMENTSOF(all_rates) = { false, }; snd_pcm_hw_params_t *hwparams; unsigned int i, j, n, *rates = NULL; @@ -1460,39 +1503,40 @@ rates1 = 0; } + dump_supported_rates(rates); return rates; } pa_sample_format_t *pa_alsa_get_supported_formats(snd_pcm_t *pcm, pa_sample_format_t fallback_format) { - static const snd_pcm_format_t format_trans_to_pa = { - SND_PCM_FORMAT_U8 = PA_SAMPLE_U8, - SND_PCM_FORMAT_A_LAW = PA_SAMPLE_ALAW, - SND_PCM_FORMAT_MU_LAW = PA_SAMPLE_ULAW, - SND_PCM_FORMAT_S16_LE = PA_SAMPLE_S16LE, - SND_PCM_FORMAT_S16_BE = PA_SAMPLE_S16BE, - SND_PCM_FORMAT_FLOAT_LE = PA_SAMPLE_FLOAT32LE, - SND_PCM_FORMAT_FLOAT_BE = PA_SAMPLE_FLOAT32BE, - SND_PCM_FORMAT_S32_LE = PA_SAMPLE_S32LE, - SND_PCM_FORMAT_S32_BE = PA_SAMPLE_S32BE, - SND_PCM_FORMAT_S24_3LE = PA_SAMPLE_S24LE, - SND_PCM_FORMAT_S24_3BE = PA_SAMPLE_S24BE, - SND_PCM_FORMAT_S24_LE = PA_SAMPLE_S24_32LE, - SND_PCM_FORMAT_S24_BE = PA_SAMPLE_S24_32BE, + static const snd_pcm_format_t format_trans_to_pcm = { + PA_SAMPLE_U8 = SND_PCM_FORMAT_U8, + PA_SAMPLE_ALAW = SND_PCM_FORMAT_A_LAW, + PA_SAMPLE_ULAW = SND_PCM_FORMAT_MU_LAW, + PA_SAMPLE_S16LE = SND_PCM_FORMAT_S16_LE, + PA_SAMPLE_S16BE = SND_PCM_FORMAT_S16_BE, + PA_SAMPLE_FLOAT32LE = SND_PCM_FORMAT_FLOAT_LE, + PA_SAMPLE_FLOAT32BE = SND_PCM_FORMAT_FLOAT_BE, + PA_SAMPLE_S32LE = SND_PCM_FORMAT_S32_LE, + PA_SAMPLE_S32BE = SND_PCM_FORMAT_S32_BE, + PA_SAMPLE_S24LE = SND_PCM_FORMAT_S24_3LE, + PA_SAMPLE_S24BE = SND_PCM_FORMAT_S24_3BE, + PA_SAMPLE_S24_32LE = SND_PCM_FORMAT_S24_LE, + PA_SAMPLE_S24_32BE = SND_PCM_FORMAT_S24_BE, }; - static const snd_pcm_format_t all_formats = { - SND_PCM_FORMAT_U8, - SND_PCM_FORMAT_A_LAW, - SND_PCM_FORMAT_MU_LAW, - SND_PCM_FORMAT_S16_LE, - SND_PCM_FORMAT_S16_BE, - SND_PCM_FORMAT_FLOAT_LE, - SND_PCM_FORMAT_FLOAT_BE, - SND_PCM_FORMAT_S32_LE, - SND_PCM_FORMAT_S32_BE, - SND_PCM_FORMAT_S24_3LE, - SND_PCM_FORMAT_S24_3BE, - SND_PCM_FORMAT_S24_LE, - SND_PCM_FORMAT_S24_BE, + static const pa_sample_format_t all_formats = { + PA_SAMPLE_U8, + PA_SAMPLE_ALAW, + PA_SAMPLE_ULAW, + PA_SAMPLE_S16LE, + PA_SAMPLE_S16BE, + PA_SAMPLE_FLOAT32LE, + PA_SAMPLE_FLOAT32BE, + PA_SAMPLE_S32LE, + PA_SAMPLE_S32BE, + PA_SAMPLE_S24LE, + PA_SAMPLE_S24BE, + PA_SAMPLE_S24_32LE, + PA_SAMPLE_S24_32BE, }; bool supportedPA_ELEMENTSOF(all_formats) = { false, @@ -1510,7 +1554,7 @@ } for (i = 0, n = 0; i < PA_ELEMENTSOF(all_formats); i++) { - if (snd_pcm_hw_params_test_format(pcm, hwparams, all_formatsi) == 0) { + if (snd_pcm_hw_params_test_format(pcm, hwparams, format_trans_to_pcmall_formatsi) == 0) { supportedi = true; n++; } @@ -1521,7 +1565,7 @@ for (i = 0, j = 0; i < PA_ELEMENTSOF(all_formats); i++) { if (supportedi) - formatsj++ = format_trans_to_paall_formatsi; + formatsj++ = all_formatsi; } formatsj = PA_SAMPLE_MAX; @@ -1529,7 +1573,7 @@ formats = pa_xnew(pa_sample_format_t, 2); formats0 = fallback_format; - if ((ret = snd_pcm_hw_params_set_format(pcm, hwparams, format_trans_to_paformats0)) < 0) { + if ((ret = snd_pcm_hw_params_set_format(pcm, hwparams, format_trans_to_pcmformats0)) < 0) { pa_log_debug("snd_pcm_hw_params_set_format() failed: %s", pa_alsa_strerror(ret)); pa_xfree(formats); return NULL; @@ -1540,7 +1584,6 @@ return formats; } -#endif bool pa_alsa_pcm_is_hw(snd_pcm_t *pcm) { snd_pcm_info_t* info; @@ -1600,14 +1643,16 @@ snd_ctl_elem_iface_t iface, const char *name, unsigned int index, - unsigned int device) { + unsigned int device, + unsigned int subdevice) { snd_mixer_elem_t *elem; for (elem = snd_mixer_first_elem(mixer); elem; elem = snd_mixer_elem_next(elem)) { - snd_hctl_elem_t *helem; + snd_hctl_elem_t **_helem, *helem; if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_PULSEAUDIO) continue; - helem = snd_mixer_elem_get_private(elem); + _helem = snd_mixer_elem_get_private(elem); + helem = *_helem; if (snd_hctl_elem_get_interface(helem) != iface) continue; if (!pa_streq(snd_hctl_elem_get_name(helem), name)) @@ -1616,17 +1661,19 @@ continue; if (snd_hctl_elem_get_device(helem) != device) continue; + if (snd_hctl_elem_get_subdevice(helem) != subdevice) + continue; return elem; } return NULL; } snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, struct pa_alsa_mixer_id *alsa_id, unsigned int device) { - return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_CARD, alsa_id->name, alsa_id->index, device); + return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_CARD, alsa_id->name, alsa_id->index, device, 0); } snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device) { - return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_PCM, name, 0, device); + return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_PCM, name, 0, device, 0); } static int mixer_class_compare(const snd_mixer_elem_t *c1, const snd_mixer_elem_t *c2) @@ -1635,36 +1682,79 @@ return c1 == c2 ? 0 : (c1 > c2 ? 1 : -1); } +static void mixer_melem_free(snd_mixer_elem_t *elem) +{ + snd_hctl_elem_t **_helem; + _helem = snd_mixer_elem_get_private(elem); + pa_xfree(_helem); +} + static int mixer_class_event(snd_mixer_class_t *class, unsigned int mask, snd_hctl_elem_t *helem, snd_mixer_elem_t *melem) { int err; const char *name = snd_hctl_elem_get_name(helem); - // NOTE: The remove event defined as '~0U`. + snd_hctl_elem_t **_helem; + /* NOTE: The remove event is defined as '~0U`. */ if (mask == SND_CTL_EVENT_MASK_REMOVE) { - // NOTE: unless remove pointer to melem from link-list at private_data of helem, hits - // assersion in alsa-lib since the list is not empty. + /* NOTE: Unless we remove the pointer to melem from the linked-list at + * private_data of helem, an assertion will be hit in alsa-lib since + * the list is not empty. */ + _helem = snd_mixer_elem_get_private(melem); + *_helem = NULL; snd_mixer_elem_detach(melem, helem); } else if (mask & SND_CTL_EVENT_MASK_ADD) { snd_ctl_elem_iface_t iface = snd_hctl_elem_get_interface(helem); if (iface == SND_CTL_ELEM_IFACE_CARD || iface == SND_CTL_ELEM_IFACE_PCM) { + snd_mixer_t *mixer = snd_mixer_class_get_mixer(class); + snd_ctl_elem_iface_t iface = snd_hctl_elem_get_interface(helem); + const char *name = snd_hctl_elem_get_name(helem); + const int index = snd_hctl_elem_get_index(helem); + const int device = snd_hctl_elem_get_device(helem); + const int subdevice = snd_hctl_elem_get_subdevice(helem); snd_mixer_elem_t *new_melem; - - /* Put the hctl pointer as our private data - it will be useful for callbacks */ - if ((err = snd_mixer_elem_new(&new_melem, SND_MIXER_ELEM_PULSEAUDIO, 0, helem, NULL)) < 0) { - pa_log_warn("snd_mixer_elem_new failed: %s", pa_alsa_strerror(err)); - return 0; + bool found = true; + + new_melem = pa_alsa_mixer_find(mixer, iface, name, index, device, subdevice); + if (!new_melem) { + _helem = pa_xmalloc(sizeof(snd_hctl_elem_t *)); + *_helem = helem; + /* Put the hctl pointer as our private data - it will be useful for callbacks */ + if ((err = snd_mixer_elem_new(&new_melem, SND_MIXER_ELEM_PULSEAUDIO, 0, _helem, mixer_melem_free)) < 0) { + pa_log_warn("snd_mixer_elem_new failed: %s", pa_alsa_strerror(err)); + return 0; + } + found = false; + } else { + _helem = snd_mixer_elem_get_private(new_melem); + if (_helem) { + char *s1, *s2; + snd_ctl_elem_id_t *id1, *id2; + snd_ctl_elem_id_alloca(&id1); + snd_ctl_elem_id_alloca(&id2); + snd_hctl_elem_get_id(helem, id1); + snd_hctl_elem_get_id(*_helem, id2); + s1 = snd_ctl_ascii_elem_id_get(id1); + s2 = snd_ctl_ascii_elem_id_get(id2); + pa_log_warn("mixer_class_event - duplicate mixer controls: %s | %s", s1, s2); + free(s2); + free(s1); + return 0; + } + *_helem = helem; } if ((err = snd_mixer_elem_attach(new_melem, helem)) < 0) { pa_log_warn("snd_mixer_elem_attach failed: %s", pa_alsa_strerror(err)); - snd_mixer_elem_free(melem); + snd_mixer_elem_free(melem); return 0; } - if ((err = snd_mixer_elem_add(new_melem, class)) < 0) { - pa_log_warn("snd_mixer_elem_add failed: %s", pa_alsa_strerror(err)); - return 0; + if (!found) { + if ((err = snd_mixer_elem_add(new_melem, class)) < 0) { + pa_log_warn("snd_mixer_elem_add failed: %s", pa_alsa_strerror(err)); + return 0; + } } } }
View file
pipewire-0.3.67.tar.gz/spa/plugins/alsa/acp/alsa-util.h -> pipewire-0.3.68.tar.gz/spa/plugins/alsa/acp/alsa-util.h
Changed
@@ -64,6 +64,8 @@ snd_pcm_uframes_t tsched_size, bool *use_mmap, /* modified at return */ bool *use_tsched, /* modified at return */ + pa_sample_format_t **query_supported_formats, /* modified at return */ + unsigned int **query_supported_rates, /* modified at return */ pa_alsa_profile_set *ps, pa_alsa_mapping **mapping); /* modified at return */ #endif @@ -80,6 +82,8 @@ snd_pcm_uframes_t tsched_size, bool *use_mmap, /* modified at return */ bool *use_tsched, /* modified at return */ + pa_sample_format_t **query_supported_formats, /* modified at return */ + unsigned int **query_supported_rates, /* modified at return */ pa_alsa_mapping *mapping); /* Opens the explicit ALSA device */ @@ -94,6 +98,8 @@ snd_pcm_uframes_t tsched_size, bool *use_mmap, /* modified at return */ bool *use_tsched, /* modified at return */ + pa_sample_format_t **query_supported_formats, /* modified at return */ + unsigned int **query_supported_rates, /* modified at return */ bool require_exact_channel_number); /* Opens the explicit ALSA device with a fallback list */ @@ -109,6 +115,8 @@ snd_pcm_uframes_t tsched_size, bool *use_mmap, /* modified at return */ bool *use_tsched, /* modified at return */ + pa_sample_format_t **query_supported_formats, /* modified at return */ + unsigned int **query_supported_rates, /* modified at return */ bool require_exact_channel_number); #if 0
View file
pipewire-0.3.67.tar.gz/spa/plugins/alsa/acp/compat.h -> pipewire-0.3.68.tar.gz/spa/plugins/alsa/acp/compat.h
Changed
@@ -47,10 +47,12 @@ #define PA_LIKELY(x) (__builtin_expect(!!(x),1)) #define PA_UNLIKELY(x) (__builtin_expect(!!(x),0)) #define PA_PRINTF_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1))) +#define PA_UNUSED __attribute__ ((unused)) #else #define PA_LIKELY(x) (x) #define PA_UNLIKELY(x) (x) #define PA_PRINTF_FUNC(fmt, arg1) +#define PA_UNUSED #endif #define PA_MIN(a,b) \ @@ -96,7 +98,7 @@ PA_AVAILABLE_YES = 2, } pa_available_t; -#define PA_RATE_MAX (48000U*8U) +#define PA_RATE_MAX (48000U*16U) typedef enum pa_sample_format { PA_SAMPLE_U8, /**< Unsigned 8 Bit PCM */ @@ -348,6 +350,48 @@ pa_xfreev((void**)a); } +typedef struct { + size_t size; + char *ptr; + FILE *f; +} pa_strbuf; + +static inline pa_strbuf *pa_strbuf_new(void) +{ + pa_strbuf *s = pa_xnew0(pa_strbuf,1); + s->f = open_memstream(&s->ptr, &s->size); + return s; +} + +static PA_PRINTF_FUNC(2,3) inline size_t pa_strbuf_printf(pa_strbuf *sb, const char *format, ...) +{ + int ret; + va_list args; + va_start(args, format); + ret = vfprintf(sb->f, format, args); + va_end(args); + return ret > 0 ? ret : 0; +} + +static inline void pa_strbuf_puts(pa_strbuf *sb, const char *t) +{ + fputs(t, sb->f); +} + +static inline bool pa_strbuf_isempty(pa_strbuf *sb) +{ + fflush(sb->f); + return sb->size == 0; +} + +static inline char *pa_strbuf_to_string_free(pa_strbuf *sb) +{ + char *ptr; + fclose(sb->f); + ptr = sb->ptr; + free(sb); + return ptr; +} #define pa_cstrerror strerror
View file
pipewire-0.3.67.tar.gz/spa/plugins/alsa/acp/idxset.h -> pipewire-0.3.68.tar.gz/spa/plugins/alsa/acp/idxset.h
Changed
@@ -74,7 +74,13 @@ { pa_idxset_item *item; pa_array_for_each(item, &s->array) { - if (item->ptr == ptr) + if (item->ptr == NULL) { + if (ptr == NULL) + return item; + else + continue; + } + if (s->compare_func(item->ptr, ptr) == 0) return item; } return NULL; @@ -124,13 +130,25 @@ return count; } -static inline void *pa_idxset_search(pa_idxset *s, uint32_t *idx) +static inline pa_idxset_item *pa_idxset_search(pa_idxset *s, uint32_t *idx) { pa_idxset_item *item; for (item = pa_array_get_unchecked(&s->array, *idx, pa_idxset_item); pa_array_check(&s->array, item); item++, (*idx)++) { if (item->ptr != NULL) - return item->ptr; + return item; + } + *idx = PA_IDXSET_INVALID; + return NULL; +} + +static inline pa_idxset_item *pa_idxset_reverse_search(pa_idxset *s, uint32_t *idx) +{ + pa_idxset_item *item; + for (item = pa_array_get_unchecked(&s->array, *idx, pa_idxset_item); + pa_array_check(&s->array, item); item--, (*idx)--) { + if (item->ptr != NULL) + return item; } *idx = PA_IDXSET_INVALID; return NULL; @@ -138,29 +156,93 @@ static inline void *pa_idxset_next(pa_idxset *s, uint32_t *idx) { + pa_idxset_item *item; (*idx)++;; - return pa_idxset_search(s, idx); + item = pa_idxset_search(s, idx); + return item ? item->ptr : NULL; } static inline void* pa_idxset_first(pa_idxset *s, uint32_t *idx) { uint32_t i = 0; - void *ptr = pa_idxset_search(s, &i); + pa_idxset_item *item = pa_idxset_search(s, &i); if (idx) *idx = i; + return item ? item->ptr : NULL; +} + +static inline void* pa_idxset_last(pa_idxset *s, uint32_t *idx) +{ + uint32_t i = pa_array_get_len(&s->array, pa_idxset_item) - 1; + pa_idxset_item *item = pa_idxset_reverse_search(s, &i); + if (idx) + *idx = i; + return item ? item->ptr : NULL; +} + +static inline void* pa_idxset_steal_last(pa_idxset *s, uint32_t *idx) +{ + uint32_t i = pa_array_get_len(&s->array, pa_idxset_item) - 1; + void *ptr = NULL; + pa_idxset_item *item = pa_idxset_reverse_search(s, &i); + if (idx) + *idx = i; + if (item) { + ptr = item->ptr; + item->ptr = NULL; + pa_array_remove(&s->array, item); + } return ptr; } static inline void* pa_idxset_get_by_data(pa_idxset*s, const void *p, uint32_t *idx) { pa_idxset_item *item = pa_idxset_find(s, p); - if (item == NULL) + if (item == NULL) { + if (idx) + *idx = PA_IDXSET_INVALID; return NULL; + } if (idx) *idx = item - (pa_idxset_item*)s->array.data; return item->ptr; } +static inline bool pa_idxset_contains(pa_idxset *s, const void *p) +{ + return pa_idxset_get_by_data(s, p, NULL) == p; +} + +static inline bool pa_idxset_isdisjoint(pa_idxset *s, pa_idxset *t) +{ + pa_idxset_item *item; + pa_array_for_each(item, &s->array) { + if (item->ptr && pa_idxset_contains(t, item->ptr)) + return false; + } + return true; +} + +static inline bool pa_idxset_issubset(pa_idxset *s, pa_idxset *t) +{ + pa_idxset_item *item; + pa_array_for_each(item, &s->array) { + if (item->ptr && !pa_idxset_contains(t, item->ptr)) + return false; + } + return true; +} + +static inline bool pa_idxset_issuperset(pa_idxset *s, pa_idxset *t) +{ + return pa_idxset_issubset(t, s); +} + +static inline bool pa_idxset_equals(pa_idxset *s, pa_idxset *t) +{ + return pa_idxset_issubset(s, t) && pa_idxset_issuperset(s, t); +} + static inline void* pa_idxset_get_by_index(pa_idxset*s, uint32_t idx) { pa_idxset_item *item;
View file
pipewire-0.3.67.tar.gz/spa/plugins/alsa/alsa-compress-offload-sink.c -> pipewire-0.3.68.tar.gz/spa/plugins/alsa/alsa-compress-offload-sink.c
Changed
@@ -703,14 +703,23 @@ } } - check_position_and_clock_config(this); + if (SPA_LIKELY(this->node_position_io != NULL)) { + this->cycle_duration = this->node_position_io->clock.target_duration; + this->cycle_rate = this->node_position_io->clock.target_rate.denom; + } else { + /* This can happen at the very beginning if node_position_io + * isn't passed to this node in time. */ + this->cycle_duration = 1024; + this->cycle_rate = 48000; + } current_time = this->next_driver_time; this->next_driver_time += ((uint64_t)(this->cycle_duration)) * 1000000000ULL / this->cycle_rate; if (this->node_clock_io != NULL) { this->node_clock_io->nsec = current_time; - this->node_clock_io->position += this->cycle_duration; + this->node_clock_io->rate = this->node_clock_io->target_rate; + this->node_clock_io->position += this->node_clock_io->duration; this->node_clock_io->duration = this->cycle_duration; this->node_clock_io->delay = 0; this->node_clock_io->rate_diff = 1.0;
View file
pipewire-0.3.67.tar.gz/spa/plugins/alsa/alsa-pcm-source.c -> pipewire-0.3.68.tar.gz/spa/plugins/alsa/alsa-pcm-source.c
Changed
@@ -630,6 +630,7 @@ this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; this->port_paramsPORT_Latency.user++; emit_port_info(this, false); + res = 0; break; } default:
View file
pipewire-0.3.67.tar.gz/spa/plugins/alsa/alsa-pcm.c -> pipewire-0.3.68.tar.gz/spa/plugins/alsa/alsa-pcm.c
Changed
@@ -122,6 +122,8 @@ state->disable_mmap = spa_atob(s); } else if (spa_streq(k, "api.alsa.disable-batch")) { state->disable_batch = spa_atob(s); + } else if (spa_streq(k, "api.alsa.disable-tsched")) { + state->disable_tsched = spa_atob(s); } else if (spa_streq(k, "api.alsa.use-chmap")) { state->props.use_chmap = spa_atob(s); } else if (spa_streq(k, "api.alsa.multi-rate")) { @@ -284,12 +286,20 @@ case 11: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("api.alsa.disable-tsched"), + SPA_PROP_INFO_description, SPA_POD_String("Disable timer based scheduling"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(state->disable_tsched), + 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("api.alsa.use-chmap"), SPA_PROP_INFO_description, SPA_POD_String("Use the driver channelmap"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(state->props.use_chmap), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 12: + case 13: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("api.alsa.multi-rate"), @@ -297,7 +307,7 @@ SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(state->multi_rate), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 13: + 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.rate"), @@ -306,7 +316,7 @@ 0, 65536), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 14: + case 15: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("latency.internal.ns"), @@ -315,7 +325,7 @@ 0LL, 2 * SPA_NSEC_PER_SEC), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; - case 15: + case 16: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("clock.name"), @@ -375,6 +385,9 @@ spa_pod_builder_string(b, "api.alsa.disable-batch"); spa_pod_builder_bool(b, state->disable_batch); + spa_pod_builder_string(b, "api.alsa.disable-tsched"); + spa_pod_builder_bool(b, state->disable_tsched); + spa_pod_builder_string(b, "api.alsa.use-chmap"); spa_pod_builder_bool(b, state->props.use_chmap); @@ -557,11 +570,16 @@ device_name, state->stream == SND_PCM_STREAM_CAPTURE ? "capture" : "playback"); - if ((err = spa_system_timerfd_create(state->data_system, - CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK)) < 0) - goto error_exit_close; + if (!state->disable_tsched) { + if ((err = spa_system_timerfd_create(state->data_system, + CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK)) < 0) + goto error_exit_close; - state->timerfd = err; + state->timerfd = err; + } else { + /* ALSA pollfds may only be ready after setting swparams, so + * these are initialised in spa_alsa_start() */ + } if (state->clock) spa_scnprintf(state->clock->name, sizeof(state->clock->name), @@ -593,7 +611,10 @@ spa_log_warn(state->log, "%s: close failed: %s", state->props.device, snd_strerror(err)); - spa_system_close(state->data_system, state->timerfd); + if (!state->disable_tsched) + spa_system_close(state->data_system, state->timerfd); + else + state->n_fds = 0; if (state->have_format) state->card->format_ref--; @@ -1555,14 +1576,15 @@ * the period smaller and add one period of headroom. Limit the * period size to our default so that we don't create too much * headroom. */ - period_size = SPA_MIN(period_size, DEFAULT_PERIOD) / 2; + if (!state->disable_tsched) + period_size = SPA_MIN(period_size, DEFAULT_PERIOD) / 2; spa_log_info(state->log, "%s: batch mode, period_size:%ld", state->props.device, period_size); } else { if (period_size == 0) period_size = DEFAULT_PERIOD; - /* disable ALSA wakeups, we use a timer */ - if (snd_pcm_hw_params_can_disable_period_wakeup(params)) + /* disable ALSA wakeups, if we use a timer */ + if (!state->disable_tsched && snd_pcm_hw_params_can_disable_period_wakeup(params)) CHECK(snd_pcm_hw_params_set_period_wakeup(hndl, params, 0), "set_period_wakeup"); } @@ -1594,7 +1616,9 @@ } state->headroom = state->default_headroom; - if (is_batch) + /* If tsched is disabled, we know the pointers are updated when we wake + * up, so we don't need the additional headroom */ + if (is_batch && !state->disable_tsched) state->headroom += period_size; state->max_delay = state->buffer_frames / 2; @@ -1613,14 +1637,15 @@ spa_log_info(state->log, "%s (%s): format:%s access:%s-%s rate:%d channels:%d " "buffer frames %lu, period frames %lu, periods %u, frame_size %zd " - "headroom %u start-delay:%u", + "headroom %u start-delay:%u tsched:%u", state->props.device, state->stream == SND_PCM_STREAM_CAPTURE ? "capture" : "playback", snd_pcm_format_name(state->format), state->use_mmap ? "mmap" : "rw", planar ? "planar" : "interleaved", state->rate, state->channels, state->buffer_frames, state->period_frames, - periods, state->frame_size, state->headroom, state->start_delay); + periods, state->frame_size, state->headroom, state->start_delay, + !state->disable_tsched); /* write the parameters to device */ CHECK(snd_pcm_hw_params(hndl, params), "set_hw_params"); @@ -1653,7 +1678,21 @@ /* start the transfer */ CHECK(snd_pcm_sw_params_set_start_threshold(hndl, params, LONG_MAX), "set_start_threshold"); - CHECK(snd_pcm_sw_params_set_period_event(hndl, params, 0), "set_period_event"); + CHECK(snd_pcm_sw_params_set_period_event(hndl, params, state->disable_tsched), "set_period_event"); + + if (state->disable_tsched) { + snd_pcm_uframes_t avail_min; + + if (state->stream == SND_PCM_STREAM_PLAYBACK) { + /* wake up when buffer has target frames or less data (will underrun soon) */ + avail_min = state->buffer_frames - state->threshold; + } else { + /* wake up when there's target frames or more (enough for us to read and push a buffer) */ + avail_min = state->threshold; + } + + CHECK(snd_pcm_sw_params_set_avail_min(hndl, params, avail_min), "set_avail_min"); + } /* write the parameters to the playback device */ CHECK(snd_pcm_sw_params(hndl, params), "sw_params"); @@ -1671,6 +1710,9 @@ { struct itimerspec ts; + if (state->disable_tsched) + return 0; + ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; ts.it_interval.tv_sec = 0; @@ -1952,7 +1994,8 @@ if (SPA_LIKELY(!follower && state->clock)) { state->clock->nsec = current_time; - state->clock->position += state->duration; + state->clock->rate = state->clock->target_rate; + state->clock->position += state->clock->duration; state->clock->duration = state->duration; state->clock->delay = delay + state->delay; state->clock->rate_diff = corr; @@ -1993,20 +2036,39 @@ return 0; } -static inline void check_position_config(struct state *state) +static void update_sources(struct state *state, bool active) { + if (state->disable_tsched && state->source0.data != NULL) { + for (int i = 0; i < state->n_fds; i++) { + state->sourcei.mask = active ? state->pfdsi.events : 0; + spa_loop_update_source(state->data_loop, &state->sourcei); + } + } +} + +static inline int check_position_config(struct state *state) +{ + uint64_t target_duration; + struct spa_fraction target_rate; + if (SPA_UNLIKELY(state->position == NULL)) - return; + return 0; - if (SPA_UNLIKELY((state->duration != state->position->clock.duration) || - (state->rate_denom != state->position->clock.rate.denom))) { - state->duration = state->position->clock.duration; - state->rate_denom = state->position->clock.rate.denom; + target_duration = state->position->clock.target_duration; + target_rate = state->position->clock.target_rate; + + if (SPA_UNLIKELY((state->duration != target_duration) || + (state->rate_denom != target_rate.denom))) { + state->duration = target_duration; + state->rate_denom = target_rate.denom; + if (state->rate_denom == 0 || state->duration == 0) + return -EIO; state->threshold = SPA_SCALE32_UP(state->duration, state->rate, state->rate_denom); state->max_error = SPA_MAX(256.0f, state->threshold / 2.0f); state->resample = ((uint32_t)state->rate != state->rate_denom) || state->matching; state->alsa_sync = true; } + return 0; } int spa_alsa_write(struct state *state) @@ -2015,10 +2077,11 @@ const snd_pcm_channel_area_t *my_areas; snd_pcm_uframes_t written, frames, offset, off, to_write, total_written, max_write; snd_pcm_sframes_t commitres; - int res = 0, missed; + int res, missed; size_t frame_size = state->frame_size; - check_position_config(state); + if ((res = check_position_config(state)) < 0) + return res; max_write = state->buffer_frames; @@ -2068,7 +2131,7 @@ state->props.device, snd_strerror(res)); return res; } - spa_log_trace_fp(state->log, "%p: begin %ld %ld %d", + spa_log_trace_fp(state->log, "%p: begin offset:%ld avail:%ld threshold:%d", state, offset, frames, state->threshold); off = offset; } else { @@ -2130,7 +2193,7 @@ to_write -= n_frames; } - spa_log_trace_fp(state->log, "%p: commit %ld %ld %"PRIi64, + spa_log_trace_fp(state->log, "%p: commit offset:%ld written:%ld sample_count:%"PRIi64, state, offset, written, state->sample_count); total_written += written; @@ -2155,6 +2218,8 @@ if (SPA_UNLIKELY(!state->alsa_started && (total_written > 0 || frames == 0))) do_start(state); + update_sources(state, true); + return 0; } @@ -2248,9 +2313,10 @@ const snd_pcm_channel_area_t *my_areas; snd_pcm_uframes_t read, frames, offset; snd_pcm_sframes_t commitres; - int res = 0, missed; + int res, missed; - check_position_config(state); + if ((res = check_position_config(state)) < 0) + return res; max_read = state->buffer_frames; @@ -2395,8 +2461,9 @@ spa_log_trace_fp(state->log, "%p: %d", state, io->status); - io->status = SPA_STATUS_NEED_DATA; + update_sources(state, false); + io->status = SPA_STATUS_NEED_DATA; res = spa_node_call_ready(&state->callbacks, SPA_STATUS_NEED_DATA); } else { @@ -2447,14 +2514,36 @@ return 0; } -static void alsa_on_timeout_event(struct spa_source *source) +static void alsa_wakeup_event(struct spa_source *source) { struct state *state = source->data; snd_pcm_uframes_t delay, target; uint64_t expire, current_time; int res; - if (SPA_LIKELY(state->started)) { + if (SPA_UNLIKELY(state->disable_tsched)) { + /* ALSA poll fds need to be "demangled" to know whether it's a real wakeup */ + int err; + unsigned short revents; + + for (int i = 0; i < state->n_fds; i++) { + state->pfdsi.revents = state->sourcei.rmask; + /* Reset so that we only handle all our sources' events once */ + state->sourcei.rmask = 0; + } + + if (SPA_UNLIKELY(err = snd_pcm_poll_descriptors_revents(state->hndl, + state->pfds, state->n_fds, &revents))) { + spa_log_error(state->log, "Could not look up revents: %s", + snd_strerror(err)); + return; + } + + if (!revents) { + spa_log_trace_fp(state->log, "Woken up with no work to do"); + return; + } + } else if (SPA_LIKELY(state->started)) { if (SPA_UNLIKELY((res = spa_system_timerfd_read(state->data_system, state->timerfd, &expire)) < 0)) { /* we can get here when the timer is changed since the last @@ -2467,7 +2556,11 @@ } } - check_position_config(state); + if (SPA_UNLIKELY((res = check_position_config(state)) < 0)) { + spa_log_warn(state->log, "%p: error invalid position: %s", + state, spa_strerror(res)); + return; + } current_time = state->next_time; @@ -2484,7 +2577,7 @@ if (spa_system_clock_gettime(state->data_system, CLOCK_MONOTONIC, &now) < 0) return; nsec = SPA_TIMESPEC_TO_NSEC(&now); - spa_log_trace_fp(state->log, "%p: timeout %lu %lu %"PRIu64" %"PRIu64" %"PRIi64 + spa_log_trace_fp(state->log, "%p: wakeup %lu %lu %"PRIu64" %"PRIu64" %"PRIi64 " %d %"PRIi64, state, delay, target, nsec, nsec, nsec - current_time, state->threshold, state->sample_count); } @@ -2496,8 +2589,9 @@ handle_capture(state, current_time, delay, target); done: - if (state->next_time > current_time + SPA_NSEC_PER_SEC || - current_time > state->next_time + SPA_NSEC_PER_SEC) { + if (!state->disable_tsched && + (state->next_time > current_time + SPA_NSEC_PER_SEC || + current_time > state->next_time + SPA_NSEC_PER_SEC)) { spa_log_error(state->log, "%s: impossible timeout %lu %lu %"PRIu64" %"PRIu64" %"PRIi64 " %d %"PRIi64, state->props.device, delay, target, current_time, state->next_time, state->next_time - current_time, state->threshold, state->sample_count); @@ -2525,23 +2619,68 @@ } } -static int set_timers(struct state *state) +static void clear_period_sources(struct state *state) { + /* This check is to make sure we've actually added the sources + * previously */ + if (state->source0.data) { + for (int i = 0; i < state->n_fds; i++) { + spa_loop_remove_source(state->data_loop, &state->sourcei); + state->sourcei.func = NULL; + state->sourcei.data = NULL; + state->sourcei.fd = -1; + state->sourcei.mask = 0; + state->sourcei.rmask = 0; + } + } +} + +static int setup_sources(struct state *state) { struct timespec now; int res; if ((res = spa_system_clock_gettime(state->data_system, CLOCK_MONOTONIC, &now)) < 0) - return res; + return res; state->next_time = SPA_TIMESPEC_TO_NSEC(&now); if (state->following) { - set_timeout(state, 0); + /* Disable wakeups from this node */ + if (!state->disable_tsched) { + set_timeout(state, 0); + } else { + clear_period_sources(state); + } } else { - set_timeout(state, state->next_time); + /* We're driving, so let's wake up when data is ready/needed */ + if (!state->disable_tsched) { + set_timeout(state, state->next_time); + } else { + for (int i = 0; i < state->n_fds; i++) { + state->sourcei.func = alsa_wakeup_event; + state->sourcei.data = state; + state->sourcei.fd = state->pfdsi.fd; + state->sourcei.mask = state->pfdsi.events; + state->sourcei.rmask = 0; + spa_loop_add_source(state->data_loop, &state->sourcei); + } + } } return 0; } +static int do_setup_sources(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); + setup_sources(state); + return 0; +} + int spa_alsa_start(struct state *state) { int err; @@ -2549,32 +2688,17 @@ if (state->started) return 0; - if (state->position) { - state->duration = state->position->clock.duration; - state->rate_denom = state->position->clock.rate.denom; - } - else { - spa_log_warn(state->log, "%s: no position set, using defaults", - state->props.device); - state->duration = 1024; - state->rate_denom = state->rate; - } - if (state->rate_denom == 0) { - spa_log_error(state->log, "%s: unset rate_denom", state->props.device); - return -EIO; - } - if (state->duration == 0) { - spa_log_error(state->log, "%s: unset duration", state->props.device); + state->following = is_following(state); + + if (check_position_config(state) < 0) { + spa_log_error(state->log, "%s: invalid position config", state->props.device); return -EIO; } - state->following = is_following(state); setup_matching(state); spa_dll_init(&state->dll); - state->threshold = SPA_SCALE32_UP(state->duration, state->rate, state->rate_denom); state->last_threshold = state->threshold; - state->max_error = SPA_MAX(256.0f, state->threshold / 2.0f); spa_log_debug(state->log, "%p: start %d duration:%d rate:%d follower:%d match:%d resample:%d", state, state->threshold, state->duration, state->rate_denom, @@ -2588,12 +2712,44 @@ return err; } - state->source.func = alsa_on_timeout_event; - state->source.data = state; - state->source.fd = state->timerfd; - state->source.mask = SPA_IO_IN; - state->source.rmask = 0; - spa_loop_add_source(state->data_loop, &state->source); + if (!state->disable_tsched) { + /* Timer-based scheduling */ + state->source0.func = alsa_wakeup_event; + state->source0.data = state; + state->source0.fd = state->timerfd; + state->source0.mask = SPA_IO_IN; + state->source0.rmask = 0; + spa_loop_add_source(state->data_loop, &state->source0); + } else { + /* ALSA period-based scheduling */ + err = snd_pcm_poll_descriptors_count(state->hndl); + if (err < 0) { + spa_log_error(state->log, "Could not get poll descriptor count: %s", + snd_strerror(err)); + return err; + } + if (err > MAX_POLL) { + spa_log_error(state->log, "Unsupported poll descriptor count: %d", err); + return -EIO; + } + state->n_fds = err; + + if ((err = snd_pcm_poll_descriptors(state->hndl, state->pfds, state->n_fds)) < 0) { + spa_log_error(state->log, "Could not get poll descriptors: %s", + snd_strerror(err)); + return err; + } + + /* We only add the source to the data loop if we're driving. + * This is done in setup_sources() */ + for (int i = 0; i < state->n_fds; i++) { + state->sourcei.func = NULL; + state->sourcei.data = NULL; + state->sourcei.fd = -1; + state->sourcei.mask = 0; + state->sourcei.rmask = 0; + } + } reset_buffers(state); state->alsa_sync = true; @@ -2601,32 +2757,23 @@ state->alsa_recovering = false; state->alsa_started = false; - /* start capture now, playback will start after first write */ - if (state->stream == SND_PCM_STREAM_PLAYBACK) + /* start capture now, playback will start after first write. Without tsched, we start + * right away so that the fds become active in poll right away. */ + if (state->stream == SND_PCM_STREAM_PLAYBACK) { spa_alsa_silence(state, state->start_delay + state->threshold + state->headroom); + if (state->disable_tsched) + do_start(state); + } else if ((err = do_start(state)) < 0) return err; - set_timers(state); + spa_loop_invoke(state->data_loop, do_setup_sources, 0, NULL, 0, true, state); state->started = true; return 0; } -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; - set_timers(state); - spa_dll_init(&state->dll); - return 0; -} - int spa_alsa_reassign_follower(struct state *state) { bool following, freewheel; @@ -2638,7 +2785,7 @@ 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); + spa_loop_invoke(state->data_loop, do_setup_sources, 0, NULL, 0, true, state); } setup_matching(state); @@ -2666,15 +2813,13 @@ void *user_data) { struct state *state = user_data; - struct itimerspec ts; - - spa_loop_remove_source(state->data_loop, &state->source); - ts.it_value.tv_sec = 0; - ts.it_value.tv_nsec = 0; - ts.it_interval.tv_sec = 0; - ts.it_interval.tv_nsec = 0; - spa_system_timerfd_settime(state->data_system, state->timerfd, 0, &ts, NULL); + if (!state->disable_tsched) { + spa_loop_remove_source(state->data_loop, &state->source0); + set_timeout(state, 0); + } else { + clear_period_sources(state); + } return 0; }
View file
pipewire-0.3.67.tar.gz/spa/plugins/alsa/alsa-pcm.h -> pipewire-0.3.68.tar.gz/spa/plugins/alsa/alsa-pcm.h
Changed
@@ -47,6 +47,7 @@ }; #define MAX_BUFFERS 32 +#define MAX_POLL 16 struct buffer { uint32_t id; @@ -131,6 +132,7 @@ struct channel_map default_pos; unsigned int disable_mmap; unsigned int disable_batch; + unsigned int disable_tsched; char clock_name64; uint32_t quantum_limit; @@ -171,8 +173,11 @@ size_t ready_offset; bool started; - struct spa_source source; + /* Either a single source for tsched, or a set of pollfds from ALSA */ + struct spa_source sourceMAX_POLL; int timerfd; + struct pollfd pfdsMAX_POLL; + int n_fds; uint32_t threshold; uint32_t last_threshold; uint32_t headroom;
View file
pipewire-0.3.67.tar.gz/spa/plugins/alsa/alsa-seq.c -> pipewire-0.3.68.tar.gz/spa/plugins/alsa/alsa-seq.c
Changed
@@ -684,7 +684,7 @@ static void update_position(struct seq_state *state) { - if (state->position) { + if (SPA_LIKELY(state->position)) { struct spa_io_clock *clock = &state->position->clock; state->rate = clock->rate; if (state->rate.num == 0 || state->rate.denom == 0) @@ -744,7 +744,8 @@ if (!follower && state->clock) { state->clock->nsec = nsec; - state->clock->position += state->duration; + state->clock->rate = state->rate; + state->clock->position += state->clock->duration; state->clock->duration = state->duration; state->clock->delay = state->duration * corr; state->clock->rate_diff = corr; @@ -793,7 +794,17 @@ spa_log_trace(state->log, "timeout %"PRIu64, state->current_time); - update_position(state); + if (SPA_LIKELY(state->position)) { + struct spa_io_clock *clock = &state->position->clock; + state->rate = clock->target_rate; + if (state->rate.num == 0 || state->rate.denom == 0) + state->rate = SPA_FRACTION(1, 48000); + state->duration = clock->target_duration; + } else { + state->rate = SPA_FRACTION(1, 48000); + state->duration = 1024; + } + state->threshold = state->duration; update_time(state, state->current_time, false);
View file
pipewire-0.3.67.tar.gz/spa/plugins/alsa/mixer/profile-sets/asus-xonar-se.conf -> pipewire-0.3.68.tar.gz/spa/plugins/alsa/mixer/profile-sets/asus-xonar-se.conf
Changed
@@ -21,16 +21,14 @@ General auto-profiles = yes -Mapping analog-stereo-front -description = Analog Stereo Front +Mapping analog-stereo-headset device-strings = hw:%f,1 channel-map = left,right paths-output = analog-output analog-output-headphones paths-input = analog-input-mic analog-input-headphone-mic analog-input-headset-mic priority = 15 -Mapping analog-stereo-rear -description = Analog Stereo Rear +Mapping analog-stereo device-strings = hw:%f,0 channel-map = left,right paths-output = analog-output analog-output-speaker
View file
pipewire-0.3.67.tar.gz/spa/plugins/audioconvert/audioadapter.c -> pipewire-0.3.68.tar.gz/spa/plugins/audioconvert/audioadapter.c
Changed
@@ -177,6 +177,7 @@ SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id( SPA_PARAM_PORT_CONFIG_MODE_passthrough)); result.next++; + res = 1; break; default: return 0; @@ -226,30 +227,40 @@ static int link_io(struct impl *this) { int res; - - if (this->convert == NULL) - return 0; + struct spa_io_rate_match *rate_match; + size_t rate_match_size; spa_log_debug(this->log, "%p: controls", this); spa_zero(this->io_rate_match); this->io_rate_match.rate = 1.0; + if (this->follower == this->target) { + rate_match = NULL; + rate_match_size = 0; + } else { + rate_match = &this->io_rate_match; + rate_match_size = sizeof(this->io_rate_match); + } + if ((res = spa_node_port_set_io(this->follower, this->direction, 0, SPA_IO_RateMatch, - &this->io_rate_match, sizeof(this->io_rate_match))) < 0) { + rate_match, rate_match_size)) < 0) { spa_log_debug(this->log, "%p: set RateMatch on follower disabled %d %s", this, res, spa_strerror(res)); } else if ((res = spa_node_port_set_io(this->convert, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_IO_RateMatch, - &this->io_rate_match, sizeof(this->io_rate_match))) < 0) { + rate_match, rate_match_size)) < 0) { spa_log_warn(this->log, "%p: set RateMatch on convert failed %d %s", this, res, spa_strerror(res)); } + if (this->follower == this->target) + return 0; + this->io_buffers = SPA_IO_BUFFERS_INIT; if ((res = spa_node_port_set_io(this->follower, @@ -355,11 +366,11 @@ struct spa_data *datas; uint64_t follower_flags, conv_flags; - spa_log_debug(this->log, "%p: n_buffers:%d", this, this->n_buffers); - if (this->target == this->follower) return 0; + spa_log_debug(this->log, "%p: n_buffers:%d", this, this->n_buffers); + if (this->n_buffers > 0) return 0; @@ -487,7 +498,7 @@ format = fmt; } - if (this->target != this->follower && this->convert) { + if (this->target != this->follower) { if ((res = spa_node_port_set_param(this->convert, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_Format, flags, @@ -565,12 +576,13 @@ } else { /* add converter ports */ configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_dsp); - link_io(this); } + link_io(this); } - this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; - this->info.flags &= ~SPA_NODE_FLAG_NEED_CONFIGURE; + SPA_FLAG_CLEAR(this->info.flags, SPA_NODE_FLAG_NEED_CONFIGURE); + SPA_FLAG_UPDATE(this->info.flags, SPA_NODE_FLAG_ASYNC, + this->async && this->follower == this->target); this->paramsIDX_Props.user++; emit_node_info(this, false); @@ -746,17 +758,16 @@ struct spa_pod_builder b = { 0 }; int res; + if (this->target == this->follower) + return 0; + spa_log_debug(this->log, "%p: have_format:%d", this, this->have_format); if (this->have_format) return 0; - if (this->target == this->follower) - return 0; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - spa_node_send_command(this->follower, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamBegin)); @@ -774,18 +785,16 @@ goto done; } } - if (this->convert) { - state = 0; - if ((res = spa_node_port_enum_params_sync(this->convert, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_EnumFormat, &state, - format, &format, &b)) != 1) { - debug_params(this, this->convert, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_EnumFormat, format, "convert format", res); - res = -ENOTSUP; - goto done; - } + state = 0; + if ((res = spa_node_port_enum_params_sync(this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_EnumFormat, &state, + format, &format, &b)) != 1) { + debug_params(this, this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_EnumFormat, format, "convert format", res); + res = -ENOTSUP; + goto done; } if (format == NULL) { res = -ENOTSUP; @@ -998,6 +1007,8 @@ this->info.flags |= SPA_NODE_FLAG_OUT_PORT_CONFIG; this->info.max_output_ports = MAX_PORTS; } + SPA_FLAG_UPDATE(this->info.flags, SPA_NODE_FLAG_ASYNC, + this->async && this->follower == this->target); spa_log_debug(this->log, "%p: follower info %s", this, this->direction == SPA_DIRECTION_INPUT ? @@ -1229,7 +1240,7 @@ int res; struct impl *this = data; - if (this->target != this->follower && this->convert) + if (this->target != this->follower) res = spa_node_port_reuse_buffer(this->convert, port_id, buffer_id); else res = spa_node_call_reuse_buffer(&this->callbacks, port_id, buffer_id); @@ -1272,11 +1283,10 @@ spa_node_add_listener(this->follower, &l, &follower_node_events, this); spa_hook_remove(&l); - if (this->convert) { - spa_zero(l); - spa_node_add_listener(this->convert, &l, &convert_node_events, this); - spa_hook_remove(&l); - } + spa_zero(l); + spa_node_add_listener(this->convert, &l, &convert_node_events, this); + spa_hook_remove(&l); + this->add_listener = false; emit_node_info(this, true); @@ -1466,7 +1476,7 @@ * First we run the converter to process the input for the follower * then if it produced data, we run the follower. */ while (retry--) { - status = this->convert ? spa_node_process(this->convert) : 0; + status = spa_node_process(this->convert); /* schedule the follower when the converter needed * a recycled buffer */ if (status == -EPIPE || status == 0) @@ -1498,7 +1508,7 @@ /* output node (source). First run the converter to make * sure we push out any queued data. Then when it needs * more data, schedule the follower. */ - status = this->convert ? spa_node_process(this->convert) : 0; + status = spa_node_process(this->convert); if (status == 0) status = SPA_STATUS_NEED_DATA; else if (status < 0) @@ -1660,6 +1670,9 @@ info, support, n_support); spa_handle_get_interface(this->hnd_convert, SPA_TYPE_INTERFACE_Node, &iface); + if (iface == NULL) + return -EINVAL; + this->convert = iface; this->target = this->convert;
View file
pipewire-0.3.67.tar.gz/spa/plugins/audioconvert/audioconvert.c -> pipewire-0.3.68.tar.gz/spa/plugins/audioconvert/audioconvert.c
Changed
@@ -23,12 +23,14 @@ #include <spa/param/param.h> #include <spa/param/latency-utils.h> #include <spa/pod/filter.h> +#include <spa/pod/dynamic.h> #include <spa/debug/types.h> #include "volume-ops.h" #include "fmt-ops.h" #include "channelmix-ops.h" #include "resample.h" +#include "wavfile.h" #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT log_topic @@ -60,18 +62,29 @@ vol->volumesi = DEFAULT_VOLUME; } +struct volume_ramp_params { + unsigned int volume_ramp_samples; + unsigned int volume_ramp_step_samples; + unsigned int volume_ramp_time; + unsigned int volume_ramp_step_time; + enum spa_audio_volume_ramp_scale scale; +}; + struct props { float volume; + float prev_volume; uint32_t n_channels; uint32_t channel_mapSPA_AUDIO_MAX_CHANNELS; struct volumes channel; struct volumes soft; struct volumes monitor; + struct volume_ramp_params vrp; unsigned int have_soft_volume:1; unsigned int mix_disabled:1; unsigned int resample_disabled:1; unsigned int resample_quality; double rate; + char wav_path512; }; static void props_reset(struct props *props) @@ -89,6 +102,7 @@ props->resample_disabled = false; props->resample_quality = RESAMPLE_DEFAULT_QUALITY; props->rate = 1.0; + spa_zero(props->wav_path); } struct buffer { @@ -192,6 +206,8 @@ struct resample resample; struct volume volume; double rate_scale; + struct spa_pod_sequence *vol_ramp_sequence; + uint32_t vol_ramp_offset; uint32_t in_offset; uint32_t out_offset; @@ -199,6 +215,7 @@ unsigned int setup:1; unsigned int resample_peaks:1; unsigned int is_passthrough:1; + unsigned int ramp_volume:1; unsigned int drained:1; unsigned int rate_adjust:1; @@ -207,6 +224,8 @@ float *scratch; float *tmp2; float *tmp_datas2MAX_PORTS; + + struct wav_file *wav_file; }; #define CHECK_PORT(this,d,p) ((p) < this->dird.n_ports) @@ -637,6 +656,14 @@ spa_pod_builder_pop(&b, &f1); param = spa_pod_builder_pop(&b, &f0); break; + case 24: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("debug.wav-path"), + SPA_PROP_INFO_description, SPA_POD_String("Path to WAV file"), + SPA_PROP_INFO_type, SPA_POD_String(p->wav_path), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; default: return 0; } @@ -709,6 +736,8 @@ 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_string(&b, "debug.wav-path"); + spa_pod_builder_string(&b, p->wav_path); spa_pod_builder_pop(&b, &f1); param = spa_pod_builder_pop(&b, &f0); break; @@ -782,6 +811,10 @@ 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 if (spa_streq(k, "debug.wav-path")) { + spa_scnprintf(this->props.wav_path, + sizeof(this->props.wav_path), "%s", s ? s : ""); + } else return 0; return 1; @@ -837,6 +870,127 @@ return changed; } +static int get_ramp_samples(struct impl *this) +{ + struct volume_ramp_params *vrp = &this->props.vrp; + int samples = -1; + + if (vrp->volume_ramp_samples) + samples = vrp->volume_ramp_samples; + else if (vrp->volume_ramp_time) { + struct dir *d = &this->dirSPA_DIRECTION_OUTPUT; + unsigned int sample_rate = d->format.info.raw.rate; + samples = (vrp->volume_ramp_time * sample_rate) / 1000; + spa_log_info(this->log, "volume ramp samples calculated from time is %d", samples); + } + if (!samples) + samples = -1; + + return samples; + +} + +static int get_ramp_step_samples(struct impl *this) +{ + struct volume_ramp_params *vrp = &this->props.vrp; + int samples = -1; + + if (vrp->volume_ramp_step_samples) + samples = vrp->volume_ramp_step_samples; + else if (vrp->volume_ramp_step_time) { + struct dir *d = &this->dirSPA_DIRECTION_OUTPUT; + int sample_rate = d->format.info.raw.rate; + /* convert the step time which is in nano seconds to seconds */ + samples = (vrp->volume_ramp_step_time/1000) * (sample_rate/1000); + spa_log_debug(this->log, "volume ramp step samples calculated from time is %d", samples); + } + if (!samples) + samples = -1; + + return samples; + +} + +static double get_volume_at_scale(struct impl *this, double value) +{ + struct volume_ramp_params *vrp = &this->props.vrp; + if (vrp->scale == SPA_AUDIO_VOLUME_RAMP_LINEAR || vrp->scale == SPA_AUDIO_VOLUME_RAMP_INVALID) + return value; + else if (vrp->scale == SPA_AUDIO_VOLUME_RAMP_CUBIC) + return (value * value * value); + + return 0.0; +} + +static struct spa_pod *generate_ramp_up_seq(struct impl *this) +{ + struct spa_pod_dynamic_builder b; + struct spa_pod_frame f1; + struct props *p = &this->props; + double volume_accum = p->prev_volume; + int ramp_samples = get_ramp_samples(this); + int ramp_step_samples = get_ramp_step_samples(this); + double volume_step = ((p->volume - p->prev_volume) / (ramp_samples / ramp_step_samples)); + uint32_t volume_offs = 0; + + spa_pod_dynamic_builder_init(&b, NULL, 0, 4096); + + spa_pod_builder_push_sequence(&b.b, &f0, 0); + spa_log_info(this->log, "generating ramp up sequence from %f to %f with a" + " step value %f at scale %d", p->prev_volume, p->volume, volume_step, p->vrp.scale); + do { + // spa_log_debug(this->log, "volume accum %f", get_volume_at_scale(this, volume_accum)); + spa_pod_builder_control(&b.b, volume_offs, SPA_CONTROL_Properties); + spa_pod_builder_add_object(&b.b, + SPA_TYPE_OBJECT_Props, 0, + SPA_PROP_volume, + SPA_POD_Float(get_volume_at_scale(this, volume_accum))); + volume_accum += volume_step; + volume_offs += ramp_step_samples; + } while (volume_accum < p->volume); + return spa_pod_builder_pop(&b.b, &f0); +} + +static struct spa_pod *generate_ramp_down_seq(struct impl *this) +{ + struct spa_pod_dynamic_builder b; + struct spa_pod_frame f1; + int ramp_samples = get_ramp_samples(this); + int ramp_step_samples = get_ramp_step_samples(this); + struct props *p = &this->props; + double volume_accum = p->prev_volume; + double volume_step = ((p->prev_volume - p->volume) / (ramp_samples / ramp_step_samples)); + uint32_t volume_offs = 0; + + spa_pod_dynamic_builder_init(&b, NULL, 0, 4096); + + spa_pod_builder_push_sequence(&b.b, &f0, 0); + spa_log_info(this->log, "generating ramp down sequence from %f to %f with a" + " step value %f at scale %d", p->prev_volume, p->volume, volume_step, p->vrp.scale); + do { + // spa_log_debug(this->log, "volume accum %f", get_volume_at_scale(this, volume_accum)); + spa_pod_builder_control(&b.b, volume_offs, SPA_CONTROL_Properties); + spa_pod_builder_add_object(&b.b, + SPA_TYPE_OBJECT_Props, 0, + SPA_PROP_volume, + SPA_POD_Float(get_volume_at_scale(this, volume_accum))); + + volume_accum -= volume_step; + volume_offs += ramp_step_samples; + } while (volume_accum > p->volume); + return spa_pod_builder_pop(&b.b, &f0); +} + +static struct volume_ramp_params *reset_volume_ramp_params(struct impl *this) +{ + if (!this->vol_ramp_sequence) { + struct volume_ramp_params *vrp = &this->props.vrp; + spa_zero(this->props.vrp); + return vrp; + } + return 0; +} + static int apply_props(struct impl *this, const struct spa_pod *param) { struct spa_pod_prop *prop; @@ -845,13 +999,21 @@ bool have_channel_volume = false; bool have_soft_volume = false; int changed = 0; + int vol_ramp_params_changed = 0; + struct volume_ramp_params *vrp = reset_volume_ramp_params(this); uint32_t n; + int32_t value; + uint32_t id; SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { case SPA_PROP_volume: - if (spa_pod_get_float(&prop->value, &p->volume) == 0) + p->prev_volume = p->volume; + + if (spa_pod_get_float(&prop->value, &p->volume) == 0) { + spa_log_debug(this->log, "%p new volume %f", this, p->volume); changed++; + } break; case SPA_PROP_mute: if (spa_pod_get_bool(&prop->value, &p->channel.mute) == 0) { @@ -859,6 +1021,69 @@ changed++; } break; + case SPA_PROP_volumeRampSamples: + if (this->vol_ramp_sequence) { + spa_log_error(this->log, "%p volume ramp sequence is being " + "applied try again", this); + break; + } + + if (spa_pod_get_int(&prop->value, &value) == 0 && value) { + vrp->volume_ramp_samples = value; + spa_log_info(this->log, "%p volume ramp samples %d", this, value); + vol_ramp_params_changed++; + } + break; + case SPA_PROP_volumeRampStepSamples: + if (this->vol_ramp_sequence) { + spa_log_error(this->log, "%p volume ramp sequence is being " + "applied try again", this); + break; + } + + if (spa_pod_get_int(&prop->value, &value) == 0 && value) { + vrp->volume_ramp_step_samples = value; + spa_log_info(this->log, "%p volume ramp step samples is %d", + this, value); + } + break; + case SPA_PROP_volumeRampTime: + if (this->vol_ramp_sequence) { + spa_log_error(this->log, "%p volume ramp sequence is being " + "applied try again", this); + break; + } + + if (spa_pod_get_int(&prop->value, &value) == 0 && value) { + vrp->volume_ramp_time = value; + spa_log_info(this->log, "%p volume ramp time %d", this, value); + vol_ramp_params_changed++; + } + break; + case SPA_PROP_volumeRampStepTime: + if (this->vol_ramp_sequence) { + spa_log_error(this->log, "%p volume ramp sequence is being " + "applied try again", this); + break; + } + + if (spa_pod_get_int(&prop->value, &value) == 0 && value) { + vrp->volume_ramp_step_time = value; + spa_log_info(this->log, "%p volume ramp time %d", this, value); + } + break; + case SPA_PROP_volumeRampScale: + if (this->vol_ramp_sequence) { + spa_log_error(this->log, "%p volume ramp sequence is being " + "applied try again", this); + break; + } + + if (spa_pod_get_id(&prop->value, &id) == 0 && id) { + vrp->scale = id; + spa_log_info(this->log, "%p volume ramp scale %d", this, id); + } + break; case SPA_PROP_channelVolumes: if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, p->channel.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { @@ -900,8 +1125,8 @@ } break; case SPA_PROP_rate: - spa_pod_get_double(&prop->value, &p->rate); - if (!this->rate_adjust && p->rate != 1.0) { + if (spa_pod_get_double(&prop->value, &p->rate) == 0 && + !this->rate_adjust && p->rate != 1.0) { this->rate_adjust = true; spa_log_info(this->log, "%p: activating adaptive resampler", this); @@ -922,6 +1147,22 @@ set_volume(this); } + + if (vol_ramp_params_changed) { + void *sequence = NULL; + if (p->volume == p->prev_volume) + spa_log_error(this->log, "no change in volume, cannot ramp volume"); + else if (p->volume > p->prev_volume) + sequence = generate_ramp_up_seq(this); + else + sequence = generate_ramp_down_seq(this); + + if (!sequence) + spa_log_error(this->log, "unable to generate sequence"); + + this->vol_ramp_sequence = (struct spa_pod_sequence *) sequence; + this->vol_ramp_offset = 0; + } return changed; } @@ -1218,7 +1459,7 @@ float volumesSPA_AUDIO_MAX_CHANNELS; struct dir *dir = &this->dirthis->direction; - spa_log_debug(this->log, "%p have_format:%d", this, dir->have_format); + spa_log_debug(this->log, "%p set volume %f have_format:%d", this, this->props.volume, dir->have_format); if (dir->have_format) remap_volumes(this, &dir->format); @@ -1271,7 +1512,6 @@ p = out->format.info.raw.positioni; dst_mask |= 1ULL << (p < 64 ? p : 0); } - spa_log_info(this->log, "in %s (%016"PRIx64")", format_position(str, sizeof(str), src_chan, in->format.info.raw.position), src_mask); spa_log_info(this->log, "out %s (%016"PRIx64")", format_position(str, sizeof(str), @@ -1286,6 +1526,10 @@ in->format.info.raw.rate, src_mask, dst_mask); + if (this->props.mix_disabled && + (src_chan != dst_chan || src_mask != dst_mask)) + return -EPERM; + this->mix.src_chan = src_chan; this->mix.src_mask = src_mask; this->mix.dst_chan = dst_chan; @@ -1319,6 +1563,10 @@ out->format.info.raw.channels, out->format.info.raw.rate); + if (this->props.resample_disabled && + in->format.info.raw.rate != out->format.info.raw.rate) + return -EPERM; + if (this->resample.free) resample_free(&this->resample); @@ -2140,17 +2388,42 @@ return 0; } -static int channelmix_process_control(struct impl *this, struct port *ctrlport, - void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, - uint32_t n_samples) +static void handle_wav(struct impl *this, const void **src, uint32_t n_samples) +{ + if (SPA_UNLIKELY(this->props.wav_path0)) { + if (this->wav_file == NULL) { + struct wav_file_info info; + + info.info = this->dirthis->direction.format; + + this->wav_file = wav_file_open(this->props.wav_path, + "w", &info); + if (this->wav_file == NULL) + spa_log_warn(this->log, "can't open wav path: %m"); + } + if (this->wav_file) { + wav_file_write(this->wav_file, src, n_samples); + } else { + spa_zero(this->props.wav_path); + } + } else if (this->wav_file != NULL) { + wav_file_close(this->wav_file); + this->wav_file = NULL; + } +} + +static int channelmix_process_apply_sequence(struct impl *this, + const struct spa_pod_sequence *sequence, uint32_t *processed_offset, + void *SPA_RESTRICT dst, const void *SPA_RESTRICT src, + uint32_t n_samples) { struct spa_pod_control *c, *prev = NULL; uint32_t avail_samples = n_samples; uint32_t i; const float *sMAX_PORTS, **ss = (const float**) src; float *dMAX_PORTS, **sd = (float **) dst; - const struct spa_pod_sequence_body *body = &(ctrlport->ctrl)->body; - uint32_t size = SPA_POD_BODY_SIZE(ctrlport->ctrl); + const struct spa_pod_sequence_body *body = &(sequence)->body; + uint32_t size = SPA_POD_BODY_SIZE(sequence); bool end = false; c = spa_pod_control_first(body); @@ -2166,15 +2439,15 @@ /* ignore old control offsets */ if (c != NULL) { - if (c->offset <= ctrlport->ctrl_offset) { + if (c->offset <= *processed_offset) { prev = c; if (c != NULL) c = spa_pod_control_next(c); continue; } - chunk = SPA_MIN(avail_samples, c->offset - ctrlport->ctrl_offset); + chunk = SPA_MIN(avail_samples, c->offset - *processed_offset); spa_log_trace_fp(this->log, "%p: process %d-%d %d/%d", this, - ctrlport->ctrl_offset, c->offset, chunk, avail_samples); + *processed_offset, c->offset, chunk, avail_samples); } else { chunk = avail_samples; spa_log_trace_fp(this->log, "%p: process remain %d", this, chunk); @@ -2210,7 +2483,7 @@ sdi += chunk; } avail_samples -= chunk; - ctrlport->ctrl_offset += chunk; + *processed_offset += chunk; } return end ? 1 : 0; } @@ -2251,9 +2524,18 @@ static inline bool resample_is_passthrough(struct impl *this) { - return this->resample.i_rate == this->resample.o_rate && this->rate_scale == 1.0 && - !this->rate_adjust && (this->io_rate_match == NULL || - !SPA_FLAG_IS_SET(this->io_rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE)); + if (this->props.resample_disabled) + return true; + if (this->resample.i_rate != this->resample.o_rate) + return false; + if (this->rate_scale != 1.0) + return false; + if (this->rate_adjust) + return false; + if (this->io_rate_match != NULL && + SPA_FLAG_IS_SET(this->io_rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE)) + return false; + return true; } static int impl_node_process(void *object) @@ -2516,7 +2798,7 @@ } mix_passthrough = SPA_FLAG_IS_SET(this->mix.flags, CHANNELMIX_FLAG_IDENTITY) && - (ctrlport == NULL || ctrlport->ctrl == NULL); + (ctrlport == NULL || ctrlport->ctrl == NULL) && (this->vol_ramp_sequence == NULL); out_passthrough = dir->conv.is_passthrough; if (in_passthrough && mix_passthrough && resample_passthrough) @@ -2532,6 +2814,9 @@ dst_remap = (void **)dst_datas; } + if (this->direction == SPA_DIRECTION_INPUT) + handle_wav(this, src_datas, n_samples); + dir = &this->dirSPA_DIRECTION_INPUT; if (!in_passthrough) { if (mix_passthrough && resample_passthrough && out_passthrough) @@ -2574,12 +2859,19 @@ spa_log_trace_fp(this->log, "%p: channelmix %d %d %d", this, n_samples, resample_passthrough, out_passthrough); if (ctrlport != NULL && ctrlport->ctrl != NULL) { - if (channelmix_process_control(this, ctrlport, out_datas, - in_datas, n_samples) == 1) { + if (channelmix_process_apply_sequence(this, ctrlport->ctrl, + &ctrlport->ctrl_offset, out_datas, in_datas, n_samples) == 1) { ctrlio->status = SPA_STATUS_OK; ctrlport->ctrl = NULL; } - } else { + } else if (this->vol_ramp_sequence) { + if (channelmix_process_apply_sequence(this, this->vol_ramp_sequence, + &this->vol_ramp_offset, out_datas, in_datas, n_samples) == 1) { + free(this->vol_ramp_sequence); + this->vol_ramp_sequence = NULL; + } + } + else { channelmix_process(&this->mix, out_datas, in_datas, n_samples); } } @@ -2619,6 +2911,8 @@ spa_log_trace_fp(this->log, "%p: output convert %d", this, n_samples); convert_process(&dir->conv, dst_datas, in_datas, n_samples); } + if (this->direction == SPA_DIRECTION_OUTPUT) + handle_wav(this, (const void**)dst_datas, n_samples); spa_log_trace_fp(this->log, "%d/%d %d/%d %d->%d", this->in_offset, max_in, this->out_offset, max_out, n_samples, n_out); @@ -2752,7 +3046,9 @@ convert_free(&this->dir0.conv); if (this->dir1.conv.free) convert_free(&this->dir1.conv); - + if (this->wav_file != NULL) + wav_file_close(this->wav_file); + free (this->vol_ramp_sequence); return 0; } @@ -2821,11 +3117,11 @@ props_reset(&this->props); this->mix.options = CHANNELMIX_OPTION_UPMIX | CHANNELMIX_OPTION_MIX_LFE; - this->mix.upmix = CHANNELMIX_UPMIX_PSD; + this->mix.upmix = CHANNELMIX_UPMIX_NONE; this->mix.log = this->log; - this->mix.lfe_cutoff = 150.0f; - this->mix.fc_cutoff = 12000.0f; - this->mix.rear_delay = 12.0f; + this->mix.lfe_cutoff = 0.0f; + this->mix.fc_cutoff = 0.0f; + this->mix.rear_delay = 0.0f; this->mix.widen = 0.0f; for (i = 0; info && i < info->n_items; i++) {
View file
pipewire-0.3.67.tar.gz/spa/plugins/audioconvert/channelmix-ops.c -> pipewire-0.3.68.tar.gz/spa/plugins/audioconvert/channelmix-ops.c
Changed
@@ -629,8 +629,12 @@ for (jc = 0, ic = 0, i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) { float sum = 0.0f; - char str1024, str21024; - int idx = 0, idx2 = 0; + char str11024, str21024; + struct spa_strbuf sb1, sb2; + + spa_strbuf_init(&sb1, str1, sizeof(str1)); + spa_strbuf_init(&sb2, str2, sizeof(str2)); + if ((dst_paired & (1UL << i)) == 0) continue; for (jc = 0, j = 0; j < SPA_AUDIO_MAX_CHANNELS; j++) { @@ -640,7 +644,7 @@ continue; if (ic == 0) - idx2 += snprintf(str2 + idx2, sizeof(str2) - idx2, "%-4.4s ", + spa_strbuf_append(&sb2, "%-4.4s ", src_mask == 0 ? "UNK" : spa_debug_type_find_short_name(spa_type_audio_channel, j + _SH)); @@ -648,17 +652,17 @@ sum += fabs(matrixij); if (matrixij == 0.0f) - idx += snprintf(str + idx, sizeof(str) - idx, " "); + spa_strbuf_append(&sb1, " "); else - idx += snprintf(str + idx, sizeof(str) - idx, "%1.3f ", matrixij); + spa_strbuf_append(&sb1, "%1.3f ", matrixij); } - if (idx2 > 0) + if (sb2.pos > 0) spa_log_info(mix->log, " %s", str2); - if (idx > 0) { + if (sb1.pos > 0) { spa_log_info(mix->log, "%-4.4s %s %f", dst_mask == 0 ? "UNK" : spa_debug_type_find_short_name(spa_type_audio_channel, i + _SH), - str, sum); + str1, sum); } maxsum = SPA_MAX(maxsum, sum);
View file
pipewire-0.3.67.tar.gz/spa/plugins/audioconvert/meson.build -> pipewire-0.3.68.tar.gz/spa/plugins/audioconvert/meson.build
Changed
@@ -104,6 +104,7 @@ 'peaks-ops.c', 'resample-native.c', 'resample-peaks.c', + 'wavfile.c', 'volume-ops.c' , c_args : simd_cargs, '-O3', link_with : simd_dependencies,
View file
pipewire-0.3.67.tar.gz/spa/plugins/audioconvert/test-audioconvert.c -> pipewire-0.3.68.tar.gz/spa/plugins/audioconvert/test-audioconvert.c
Changed
@@ -53,7 +53,7 @@ size_t size; int res; struct spa_support support1; - struct spa_dict_item items2; + struct spa_dict_item items6; const struct spa_handle_factory *factory; void *iface; @@ -70,10 +70,15 @@ spa_assert_se(ctx->convert_handle != NULL); items0 = SPA_DICT_ITEM_INIT("clock.quantum-limit", "8192"); + items1 = SPA_DICT_ITEM_INIT("channelmix.upmix", "true"); + items2 = SPA_DICT_ITEM_INIT("channelmix.upmix-method", "psd"); + items3 = SPA_DICT_ITEM_INIT("channelmix.lfe-cutoff", "150"); + items4 = SPA_DICT_ITEM_INIT("channelmix.fc-cutoff", "12000"); + items5 = SPA_DICT_ITEM_INIT("channelmix.rear-delay", "12.0"); res = spa_handle_factory_init(factory, ctx->convert_handle, - &SPA_DICT_INIT(items, 1), + &SPA_DICT_INIT(items, 6), support, 1); spa_assert_se(res >= 0);
View file
pipewire-0.3.67.tar.gz/spa/plugins/audioconvert/test-fmt-ops.c -> pipewire-0.3.68.tar.gz/spa/plugins/audioconvert/test-fmt-ops.c
Changed
@@ -546,13 +546,17 @@ } static void test_lossless_s16(void) { - int16_t i; + int32_t i; fprintf(stderr, "test %s:\n", __func__); - for (i = S16_MIN; i < S16_MAX; i+=3) { - float v = S16_TO_F32(i); + for (i = S16_MIN; i <= S16_MAX; i+=3) { + float v = S16_TO_F32((int16_t)i); int16_t t = F32_TO_S16(v); spa_assert_se(i == t); + + int32_t t2 = F32_TO_S32(v); + spa_assert_se(i<<16 == t2); + spa_assert_se(i == t2>>16); } } @@ -561,10 +565,14 @@ uint32_t i; fprintf(stderr, "test %s:\n", __func__); - for (i = U16_MIN; i < U16_MAX; i+=3) { - float v = U16_TO_F32(i); + for (i = U16_MIN; i <= U16_MAX; i+=3) { + float v = U16_TO_F32((uint16_t)i); uint16_t t = F32_TO_U16(v); spa_assert_se(i == t); + + uint32_t t2 = F32_TO_U32(v); + spa_assert_se(i<<16 == t2); + spa_assert_se(i == t2>>16); } }
View file
pipewire-0.3.68.tar.gz/spa/plugins/audioconvert/wavfile.c
Added
@@ -0,0 +1,250 @@ +/* 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 <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#include <spa/utils/string.h> + +#include "wavfile.h" + +#define BLOCK_SIZE 4096 + +struct wav_file { + struct spa_audio_info info; + int fd; + const struct format_info *fi; + + uint32_t length; + + uint32_t stride; + uint32_t blocks; +}; + +static inline ssize_t write_data(struct wav_file *wf, const void *data, size_t size) +{ + ssize_t len; + len = write(wf->fd, data, size); + if (len > 0) + wf->length += len; + return len; +} + +static ssize_t writei(struct wav_file *wf, const void **data, size_t samples) +{ + return write_data(wf, data0, samples * wf->stride); +} + +typedef struct { + uint8_t v3; +} __attribute__ ((packed)) uint24_t; + +#define MAKE_WRITEN_FUNC(name, type) \ +static ssize_t name (struct wav_file *wf, const void **data, size_t samples) \ +{ \ + uint32_t b, n, k, blocks = wf->blocks, chunk; \ + uint8_t bufBLOCK_SIZE; \ + ssize_t res = 0; \ + type **d = (type**)data; \ + uint32_t chunk_size = sizeof(buf) / (blocks * sizeof(type)); \ + for (n = 0; n < samples; ) { \ + type *p = (type*)buf; \ + chunk = SPA_MIN(samples - n, chunk_size); \ + for (k = 0; k < chunk; k++, n++) { \ + for (b = 0; b < blocks; b++) \ + *p++ = dbn; \ + } \ + res += write_data(wf, buf, \ + chunk * blocks * sizeof(type)); \ + } \ + return res; \ +} + +MAKE_WRITEN_FUNC(writen_8, uint8_t); +MAKE_WRITEN_FUNC(writen_16, uint16_t); +MAKE_WRITEN_FUNC(writen_24, uint24_t); +MAKE_WRITEN_FUNC(writen_32, uint32_t); +MAKE_WRITEN_FUNC(writen_64, uint64_t); + +static inline int write_n(int fd, const void *buf, int count) +{ + return write(fd, buf, count) == (ssize_t)count ? count : -errno; +} + +static inline int write_le16(int fd, uint16_t val) +{ + uint8_t buf2 = { val, val >> 8 }; + return write_n(fd, buf, 2); +} + +static inline int write_le32(int fd, uint32_t val) +{ + uint8_t buf4 = { val, val >> 8, val >> 16, val >> 24 }; + return write_n(fd, buf, 4); +} + +#define MAKE_AUDIO_RAW(format,bits,planar,fmt,...) \ + { SPA_MEDIA_TYPE_audio, SPA_MEDIA_SUBTYPE_raw, format, bits, planar, fmt, __VA_ARGS__ } + +static struct format_info { + uint32_t media_type; + uint32_t media_subtype; + uint32_t format; + uint32_t bits; + bool planar; + uint32_t fmt; + ssize_t (*write) (struct wav_file *wf, const void **data, size_t samples); +} format_info = { + MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_U8P, 8, true, 1, writen_8), + MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_U8, 8, false, 1, writei), + MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_S16P, 16, true, 1, writen_16), + MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_S16_LE, 16, false, 1, writei), + MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_S24P, 24, true, 1, writen_24), + MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_S24_LE, 24, false, 1, writei), + MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_S24_32P, 32, true, 1, writen_32), + MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_S32P, 32, true, 1, writen_32), + MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_S24_32_LE, 32, false, 1, writei), + MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_S32_LE, 32, false, 1, writei), + MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_F32P, 32, true, 3, writen_32), + MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_F32_LE, 32, false, 3, writei), + MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_F64P, 64, true, 3, writen_64), + MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_F64_LE, 32, false, 3, writei), +}; + +#define CHECK_RES(expr) if ((res = (expr)) < 0) return res + +static int write_headers(struct wav_file *wf) +{ + int res; + uint32_t channels, rate, bps, bits; + const struct format_info *fi = wf->fi; + + lseek(wf->fd, 0, SEEK_SET); + + rate = wf->info.info.raw.rate; + channels = wf->info.info.raw.channels; + bits = fi->bits; + bps = channels * bits / 8; + + CHECK_RES(write_n(wf->fd, "RIFF", 4)); + CHECK_RES(write_le32(wf->fd, wf->length == 0 ? (uint32_t)-1 : wf->length + 12 + 8 + 16)); + CHECK_RES(write_n(wf->fd, "WAVE", 4)); + CHECK_RES(write_n(wf->fd, "fmt ", 4)); + CHECK_RES(write_le32(wf->fd, 16)); + CHECK_RES(write_le16(wf->fd, fi->fmt)); /* format */ + CHECK_RES(write_le16(wf->fd, channels)); /* channels */ + CHECK_RES(write_le32(wf->fd, rate)); /* rate */ + CHECK_RES(write_le32(wf->fd, bps * rate)); /* bytes per sec */ + CHECK_RES(write_le16(wf->fd, bps)); /* bytes per samples */ + CHECK_RES(write_le16(wf->fd, bits)); /* bits per sample */ + CHECK_RES(write_n(wf->fd, "data", 4)); + CHECK_RES(write_le32(wf->fd, wf->length == 0 ? (uint32_t)-1 : wf->length)); + + return 0; +} + +static const struct format_info *find_info(struct wav_file_info *info) +{ + uint32_t i; + + if (info->info.media_type != SPA_MEDIA_TYPE_audio || + info->info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return NULL; + + for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) { + if (format_infoi.format == info->info.info.raw.format) + return &format_infoi; + } + return NULL; +} + +static int open_write(struct wav_file *wf, const char *filename, struct wav_file_info *info) +{ + int res; + const struct format_info *fi; + + fi = find_info(info); + if (fi == NULL) + return -ENOTSUP; + + if ((wf->fd = open(filename, O_WRONLY | O_CREAT | O_CLOEXEC | O_TRUNC, 0660)) < 0) { + res = -errno; + goto exit; + } + wf->info = info->info; + wf->fi = fi; + if (fi->planar) { + wf->stride = fi->bits / 8; + wf->blocks = info->info.info.raw.channels; + } else { + wf->stride = info->info.info.raw.channels * (fi->bits / 8); + wf->blocks = 1; + } + + res = write_headers(wf); +exit: + return res; +} + +struct wav_file * +wav_file_open(const char *filename, const char *mode, struct wav_file_info *info) +{ + int res; + struct wav_file *wf; + + wf = calloc(1, sizeof(struct wav_file)); + if (wf == NULL) + return NULL; + + if (spa_streq(mode, "w")) { + if ((res = open_write(wf, filename, info)) < 0) + goto exit_free; + } else { + res = -EINVAL; + goto exit_free; + } + return wf; + +exit_free: + free(wf); + errno = -res; + return NULL; +} + +int wav_file_close(struct wav_file *wf) +{ + int res; + + CHECK_RES(write_headers(wf)); + + close(wf->fd); + free(wf); + return 0; +} + +ssize_t wav_file_write(struct wav_file *wf, const void **data, size_t samples) +{ + return wf->fi->write(wf, data, samples); +}
View file
pipewire-0.3.68.tar.gz/spa/plugins/audioconvert/wavfile.h
Added
@@ -0,0 +1,39 @@ +/* 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 <spa/utils/defs.h> +#include <spa/param/audio/format.h> + +struct wav_file; + +struct wav_file_info { + struct spa_audio_info info; +}; + +struct wav_file * +wav_file_open(const char *filename, const char *mode, struct wav_file_info *info); + +int wav_file_close(struct wav_file *wf); + +ssize_t wav_file_write(struct wav_file *wf, const void **data, size_t size);
View file
pipewire-0.3.67.tar.gz/spa/plugins/audiomixer/audiomixer.c -> pipewire-0.3.68.tar.gz/spa/plugins/audiomixer/audiomixer.c
Changed
@@ -770,9 +770,9 @@ size = SPA_MIN(bd->maxsize - offs, bd->chunk->size); maxsize = SPA_MIN(size, maxsize); - spa_log_trace_fp(this->log, "%p: mix input %d %p->%p %d %d %d:%d", this, + spa_log_trace_fp(this->log, "%p: mix input %d %p->%p %d %d %d:%d/%d", this, i, inio, outio, inio->status, inio->buffer_id, - offs, size); + offs, size, this->stride); if (!SPA_FLAG_IS_SET(bd->chunk->flags, SPA_CHUNK_FLAG_EMPTY)) { datasn_buffers = SPA_PTROFF(bd->data, offs, void); @@ -783,7 +783,8 @@ outb = dequeue_buffer(this, outport); if (SPA_UNLIKELY(outb == NULL)) { - spa_log_trace(this->log, "%p: out of buffers", this); + spa_log_trace(this->log, "%p: out of buffers (%d)", this, + outport->n_buffers); return -EPIPE; }
View file
pipewire-0.3.67.tar.gz/spa/plugins/audiomixer/mixer-dsp.c -> pipewire-0.3.68.tar.gz/spa/plugins/audiomixer/mixer-dsp.c
Changed
@@ -706,9 +706,9 @@ size = SPA_MIN(bd->maxsize - offs, bd->chunk->size); maxsize = SPA_MIN(maxsize, size); - spa_log_trace_fp(this->log, "%p: mix input %d %p->%p %d %d %d:%d", this, + spa_log_trace_fp(this->log, "%p: mix input %d %p->%p %d %d %d:%d/%d", this, i, inio, outio, inio->status, inio->buffer_id, - offs, size); + offs, size, (int)sizeof(float)); if (!SPA_FLAG_IS_SET(bd->chunk->flags, SPA_CHUNK_FLAG_EMPTY)) { datasn_buffers = SPA_PTROFF(bd->data, offs, void);
View file
pipewire-0.3.67.tar.gz/spa/plugins/audiotestsrc/audiotestsrc.c -> pipewire-0.3.68.tar.gz/spa/plugins/audiotestsrc/audiotestsrc.c
Changed
@@ -345,6 +345,14 @@ return 0; } +static void update_target(struct impl *this) +{ + if (this->position) { + this->position->clock.duration = this->position->clock.target_duration; + this->position->clock.rate = this->position->clock.target_rate; + } +} + static int make_buffer(struct impl *this) { struct buffer *b; @@ -424,6 +432,8 @@ struct impl *this = source->data; int res; + update_target(this); + res = make_buffer(this); if (res == SPA_STATUS_HAVE_DATA)
View file
pipewire-0.3.67.tar.gz/spa/plugins/avb/avb-pcm.c -> pipewire-0.3.68.tar.gz/spa/plugins/avb/avb-pcm.c
Changed
@@ -832,6 +832,17 @@ state->timer_source.fd, SPA_FD_TIMER_ABSTIME, &ts, NULL); } +static void update_position(struct state *state) +{ + 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; + } +} + static int flush_write(struct state *state, uint64_t current_time) { int32_t avail, wanted; @@ -884,6 +895,8 @@ uint32_t index, to_write; struct port *port = &state->ports0; + update_position(state); + filled = spa_ringbuffer_get_write_index(&state->ring, &index); if (filled < 0) { spa_log_warn(state->log, "underrun %d", filled); @@ -933,14 +946,16 @@ } spa_ringbuffer_write_update(&state->ring, index); - if (state->following) { + if (state->following) flush_write(state, state->position->clock.nsec); - } + return 0; } static int handle_play(struct state *state, uint64_t current_time) { + update_position(state); + flush_write(state, current_time); spa_node_call_ready(&state->callbacks, SPA_STATUS_NEED_DATA); return 0; @@ -955,8 +970,7 @@ struct spa_data *d; uint32_t n_bytes; - if (state->position) - state->duration = state->position->clock.duration; + update_position(state); avail = spa_ringbuffer_get_read_index(&state->ring, &index); wanted = state->duration * state->stride; @@ -1028,7 +1042,7 @@ { struct state *state = source->data; uint64_t expirations, current_time, duration; - uint32_t rate; + struct spa_fraction rate; int res; spa_log_trace(state->log, "timeout"); @@ -1042,24 +1056,24 @@ current_time = state->next_time; if (SPA_LIKELY(state->position)) { - duration = state->position->clock.duration; - rate = state->position->clock.rate.denom; + duration = state->position->clock.target_duration; + rate = state->position->clock.target_rate; } else { duration = 1024; - rate = 48000; + rate = SPA_FRACTION(1, 48000); } - state->duration = duration; + + state->next_time = current_time + duration * SPA_NSEC_PER_SEC / rate.denom; 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->rate = rate; + state->clock->position += state->clock->duration; state->clock->duration = duration; state->clock->delay = 0; state->clock->rate_diff = 1.0; @@ -1134,13 +1148,7 @@ 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; - } + update_position(state); spa_dll_init(&state->dll); state->max_error = (256.0 * state->rate) / state->rate_denom; @@ -1184,10 +1192,11 @@ struct state *state = user_data; spa_loop_remove_source(state->data_loop, &state->timer_source); + set_timeout(state, 0); - if (state->ports0.direction == SPA_DIRECTION_OUTPUT) { + if (state->ports0.direction == SPA_DIRECTION_OUTPUT) spa_loop_remove_source(state->data_loop, &state->sock_source); - } + return 0; } @@ -1201,7 +1210,6 @@ 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.67.tar.gz/spa/plugins/bluez5/backend-hsphfpd.c -> pipewire-0.3.68.tar.gz/spa/plugins/bluez5/backend-hsphfpd.c
Changed
@@ -835,12 +835,14 @@ static void hsphfpd_audio_acquire_reply(DBusPendingCall *pending, void *user_data) { - struct impl *backend = user_data; + struct spa_bt_transport *transport = user_data; + struct impl *backend = SPA_CONTAINER_OF(transport->backend, struct impl, this); DBusMessage *r; const char *transport_path; const char *service_id; const char *agent_path; DBusError error; + int ret = 0; dbus_error_init(&error); @@ -853,16 +855,19 @@ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(backend->log, "RegisterApplication() failed: %s", dbus_message_get_error_name(r)); + ret = -EIO; goto finish; } if (!spa_streq(dbus_message_get_sender(r), backend->hsphfpd_service_id)) { spa_log_error(backend->log, "Reply for " HSPHFPD_ENDPOINT_INTERFACE ".ConnectAudio() from invalid sender"); + ret = -EIO; goto finish; } if (!check_signature(r, "oso")) { spa_log_error(backend->log, "Invalid reply signature for " HSPHFPD_ENDPOINT_INTERFACE ".ConnectAudio()"); + ret = -EIO; goto finish; } @@ -872,11 +877,13 @@ DBUS_TYPE_OBJECT_PATH, &agent_path, DBUS_TYPE_INVALID) == FALSE) { spa_log_error(backend->log, "Failed to parse " HSPHFPD_ENDPOINT_INTERFACE ".ConnectAudio() reply: %s", error.message); + ret = -EIO; goto finish; } if (!spa_streq(service_id, dbus_bus_get_unique_name(backend->conn))) { spa_log_warn(backend->log, HSPHFPD_ENDPOINT_INTERFACE ".ConnectAudio() failed: Other audio application took audio socket"); + ret = -EIO; goto finish; } @@ -885,6 +892,11 @@ finish: dbus_message_unref(r); dbus_pending_call_unref(pending); + + if (ret < 0) + spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ERROR); + else + spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ACTIVE); } static int hsphfpd_audio_acquire(void *data, bool optional) @@ -919,15 +931,10 @@ dbus_error_init(&err); dbus_connection_send_with_reply(backend->conn, m, &call, -1); - dbus_pending_call_set_notify(call, hsphfpd_audio_acquire_reply, backend, NULL); + dbus_pending_call_set_notify(call, hsphfpd_audio_acquire_reply, transport, NULL); dbus_message_unref(m); - /* The ConnectAudio method triggers Introspect and NewConnection calls, - which will set the fd to use for the SCO data. - We need to run the DBus loop to be able to reply to those method calls */ backend->acquire_in_progress = true; - while (backend->acquire_in_progress && dbus_connection_read_write_dispatch(backend->conn, -1)) - ; // empty loop body return 0; } @@ -941,6 +948,8 @@ spa_log_debug(backend->log, "transport %p: Release %s", transport, transport->path); + spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_IDLE); + if (transport->sco_io) { spa_bt_sco_io_destroy(transport->sco_io); transport->sco_io = NULL;
View file
pipewire-0.3.67.tar.gz/spa/plugins/bluez5/backend-native.c -> pipewire-0.3.68.tar.gz/spa/plugins/bluez5/backend-native.c
Changed
@@ -40,6 +40,7 @@ #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic +#define PROP_KEY_ROLES "bluez5.roles" #define PROP_KEY_HEADSET_ROLES "bluez5.headset-roles" #define HFP_CODEC_SWITCH_INITIAL_TIMEOUT_MSEC 5000 @@ -111,6 +112,8 @@ struct transport_data { struct rfcomm *rfcomm; struct spa_source sco; + int err; + bool requesting; }; enum hfp_hf_state { @@ -1361,7 +1364,7 @@ bdaddr_t src; int sock = -1; - sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); + sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET | SOCK_NONBLOCK, BTPROTO_SCO); if (sock < 0) { spa_log_error(backend->log, "socket(SEQPACKET, SCO) %s", strerror(errno)); goto fail; @@ -1414,6 +1417,8 @@ spa_log_debug(backend->log, "transport %p: enter sco_do_connect, codec=%u", t, t->codec); + td->err = -EIO; + if (d->adapter == NULL) return -EIO; @@ -1456,6 +1461,8 @@ goto fail_close; } + td->err = -EINPROGRESS; + return sock; fail_close: @@ -1465,37 +1472,76 @@ 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) +static void sco_ready(struct spa_bt_transport *t) { - struct spa_bt_transport *t = data; - struct transport_data *td = t->user_data; struct impl *backend = SPA_CONTAINER_OF(t->backend, struct impl, this); - int sock; + struct transport_data *td = t->user_data; + struct sco_options sco_opt; socklen_t len; + int err; - spa_log_debug(backend->log, "transport %p: enter sco_acquire_cb", t); + spa_log_debug(backend->log, "transport %p: ready", t); - if (optional || t->fd > 0) - sock = t->fd; - else - sock = sco_do_connect(t); + /* Read socket error status */ + if (t->fd >= 0) { + if (td->err == -EINPROGRESS) { + len = sizeof(err); + memset(&err, 0, len); + if (getsockopt(t->fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0) + td->err = -errno; + else + td->err = -err; + } + } else { + td->err = -EIO; + } - if (sock < 0) - goto fail; + if (!td->requesting) + return; -#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE - rfcomm_hfp_ag_set_cind(td->rfcomm, true); -#endif + td->requesting = false; + + if (td->err) + goto done; + + /* XXX: The MTU as currently reported by kernel (6.2) here is not a valid packet size, + * XXX: for USB adapters, see sco-io. + */ + len = sizeof(sco_opt); + memset(&sco_opt, 0, len); + if (getsockopt(t->fd, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0) { + spa_log_warn(backend->log, "getsockopt(SCO_OPTIONS) failed, using defaults"); + t->read_mtu = 48; + t->write_mtu = 48; + } else { + spa_log_debug(backend->log, "autodetected mtu = %u", sco_opt.mtu); + t->read_mtu = sco_opt.mtu; + t->write_mtu = sco_opt.mtu; + } + + /* Clear nonblocking flag we set for connect() */ + err = fcntl(t->fd, F_GETFL, O_NONBLOCK); + if (err < 0) { + td->err = -errno; + goto done; + } + err &= ~O_NONBLOCK; + err = fcntl(t->fd, F_SETFL, O_NONBLOCK, err); + if (err < 0) { + td->err = -errno; + goto done; + } + +done: + if (td->err) { + spa_log_debug(backend->log, "transport %p: acquire failed: %s (%d)", + t, strerror(-td->err), td->err); + spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_ERROR); + return; + } + + spa_log_debug(backend->log, "transport %p: acquire complete, read_mtu=%u, write_mtu=%u", + t, t->read_mtu, t->write_mtu); /* * Send RFCOMM volume after connection is ready, and also after @@ -1512,55 +1558,65 @@ * 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; + spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_ACTIVE); +} + +static void sco_start_source(struct spa_bt_transport *t); + +static int sco_acquire_cb(void *data, bool optional) +{ + struct spa_bt_transport *t = data; + struct transport_data *td = t->user_data; + struct impl *backend = SPA_CONTAINER_OF(t->backend, struct impl, this); + int sock; - /* Fallback value */ - t->read_mtu = 48; - t->write_mtu = 48; + spa_log_debug(backend->log, "transport %p: enter sco_acquire_cb", t); - if (true) { - struct sco_options sco_opt; + if (optional || t->fd > 0) + sock = t->fd; + else + sock = sco_do_connect(t); - len = sizeof(sco_opt); - memset(&sco_opt, 0, len); + if (sock < 0) + goto fail; - if (getsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0) - spa_log_warn(backend->log, "getsockopt(SCO_OPTIONS) failed, loading defaults"); - else { - spa_log_debug(backend->log, "autodetected mtu = %u", sco_opt.mtu); - t->read_mtu = sco_opt.mtu; - t->write_mtu = sco_opt.mtu; - } - } - spa_log_debug(backend->log, "transport %p: read_mtu=%u, write_mtu=%u", t, t->read_mtu, t->write_mtu); +#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE + rfcomm_hfp_ag_set_cind(td->rfcomm, true); +#endif + + t->fd = sock; + + td->requesting = true; + + sco_start_source(t); + + if (td->err != -EINPROGRESS) + sco_ready(t); return 0; fail: + spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_ERROR); return -1; } -static int sco_release_cb(void *data) +static int sco_destroy_cb(void *data) { struct spa_bt_transport *t = data; struct transport_data *td = t->user_data; struct impl *backend = SPA_CONTAINER_OF(t->backend, struct impl, this); - spa_log_info(backend->log, "Transport %s released", t->path); - -#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE - rfcomm_hfp_ag_set_cind(td->rfcomm, false); -#endif - if (t->sco_io) { spa_bt_sco_io_destroy(t->sco_io); t->sco_io = NULL; } + if (td->sco.loop) + spa_loop_remove_source(backend->main_loop, &td->sco); + if (t->fd > 0) { /* Shutdown and close the socket */ shutdown(t->fd, SHUT_RDWR); @@ -1571,6 +1627,25 @@ return 0; } +static int sco_release_cb(void *data) +{ + struct spa_bt_transport *t = data; + struct transport_data *td = t->user_data; + struct impl *backend = SPA_CONTAINER_OF(t->backend, struct impl, this); + + spa_log_info(backend->log, "Transport %s released", t->path); + + spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_IDLE); + +#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE + rfcomm_hfp_ag_set_cind(td->rfcomm, false); +#endif + + sco_destroy_cb(t); + + return 0; +} + static void sco_event(struct spa_source *source) { struct spa_bt_transport *t = source->data; @@ -1578,15 +1653,44 @@ if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) { spa_log_debug(backend->log, "transport %p: error on SCO socket: %s", t, strerror(errno)); + sco_ready(t); + if (source->loop) + spa_loop_remove_source(source->loop, source); if (t->fd >= 0) { - if (source->loop) - spa_loop_remove_source(source->loop, source); + spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_IDLE); shutdown(t->fd, SHUT_RDWR); - close (t->fd); + close(t->fd); t->fd = -1; - spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_IDLE); } } + + if (source->rmask & SPA_IO_IN) { + SPA_FLAG_UPDATE(source->mask, SPA_IO_IN, false); + spa_loop_update_source(backend->main_loop, source); + sco_ready(t); + } +} + +static void sco_start_source(struct spa_bt_transport *t) +{ + struct impl *backend = SPA_CONTAINER_OF(t->backend, struct impl, this); + struct transport_data *td = t->user_data; + + if (td->sco.loop) + return; + + td->err = -EINPROGRESS; + + /* + * We on purpose wait for POLLIN when connecting (not POLLOUT as usual), to + * indicate ready only after we are sure the device is sending data. + */ + td->sco.func = sco_event; + td->sco.data = t; + td->sco.fd = t->fd; + td->sco.mask = SPA_IO_HUP | SPA_IO_ERR | SPA_IO_IN; + td->sco.rmask = 0; + spa_loop_add_source(backend->main_loop, &td->sco); } static void sco_listen_event(struct spa_source *source) @@ -1598,7 +1702,6 @@ char local_address18, remote_address18; struct rfcomm *rfcomm; struct spa_bt_transport *t = NULL; - struct transport_data *td; if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) { spa_log_error(backend->log, "error listening SCO connection: %s", strerror(errno)); @@ -1681,13 +1784,7 @@ t->fd = sock; - td = t->user_data; - td->sco.func = sco_event; - td->sco.data = t; - td->sco.fd = sock; - td->sco.mask = SPA_IO_HUP | SPA_IO_ERR; - td->sco.rmask = 0; - spa_loop_add_source(backend->main_loop, &td->sco); + sco_start_source(t); spa_log_debug(backend->log, "transport %p: audio connected", t); @@ -1823,6 +1920,7 @@ .acquire = sco_acquire_cb, .release = sco_release_cb, .set_volume = sco_set_volume_cb, + .destroy = sco_destroy_cb, }; static struct rfcomm *device_find_rfcomm(struct impl *backend, struct spa_bt_device *device) @@ -2706,8 +2804,11 @@ const char *str; int profiles = SPA_BT_PROFILE_NULL; - if (info == NULL || - (str = spa_dict_lookup(info, PROP_KEY_HEADSET_ROLES)) == NULL) + if (!info) + goto fallback; + + if ((str = spa_dict_lookup(info, PROP_KEY_ROLES)) == NULL && + (str = spa_dict_lookup(info, PROP_KEY_HEADSET_ROLES)) == NULL) goto fallback; profiles = spa_bt_profiles_from_json_array(str);
View file
pipewire-0.3.67.tar.gz/spa/plugins/bluez5/backend-ofono.c -> pipewire-0.3.68.tar.gz/spa/plugins/bluez5/backend-ofono.c
Changed
@@ -231,7 +231,9 @@ ts.tv_nsec = 1; spa_loop_utils_update_timer(backend->loop_utils, backend->timer, &ts, NULL, false); - return -EIO; + + ret = -EIO; + goto finish; } td->broken = false; @@ -243,6 +245,11 @@ ret = 0; finish: + if (ret < 0) + spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ERROR); + else + spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ACTIVE); + return ret; } @@ -254,6 +261,8 @@ spa_log_debug(backend->log, "transport %p: Release %s", transport, transport->path); + spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_IDLE); + if (transport->sco_io) { spa_bt_sco_io_destroy(transport->sco_io); transport->sco_io = NULL;
View file
pipewire-0.3.67.tar.gz/spa/plugins/bluez5/bluez5-dbus.c -> pipewire-0.3.68.tar.gz/spa/plugins/bluez5/bluez5-dbus.c
Changed
@@ -35,6 +35,7 @@ #include "config.h" #include "codec-loader.h" #include "player.h" +#include "iso-io.h" #include "defs.h" static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5"); @@ -65,6 +66,12 @@ #define CODEC_SWITCH_RETRIES 1 +/* How many times to retry acquire on errors, and how long delay to require before we can + * try again. + */ +#define TRANSPORT_ERROR_MAX_RETRY 3 +#define TRANSPORT_ERROR_TIMEOUT (2*BLUEZ_ACTION_RATE_MSEC*SPA_NSEC_PER_MSEC) + struct spa_bt_monitor { struct spa_handle handle; @@ -72,7 +79,9 @@ struct spa_log *log; struct spa_loop *main_loop; + struct spa_loop *data_loop; struct spa_system *main_system; + struct spa_system *data_system; struct spa_plugin_loader *plugin_loader; struct spa_dbus *dbus; struct spa_dbus_connection *dbus_connection; @@ -103,6 +112,8 @@ struct spa_dict enabled_codecs; + enum spa_bt_profile enabled_profiles; + unsigned int connection_info_supported:1; unsigned int dummy_avrcp_player:1; @@ -178,11 +189,12 @@ * SCO socket connect may fail with ECONNABORTED if it is done too soon after * previous close. To avoid this in cases where nodes are toggled between * stopped/started rapidly, postpone release until the transport has remained - * unused for a time. Since this appears common to multiple SCO backends, we do - * it for all SCO backends here. + * unused for a time. + * + * Avoiding unnecessary release+reacquire also makes sense for other transports, + * so we use the release timeout for all of them. */ -#define SCO_TRANSPORT_RELEASE_TIMEOUT_MSEC 1000 -#define SPA_BT_TRANSPORT_IS_SCO(transport) (transport->backend != NULL) +#define TRANSPORT_RELEASE_TIMEOUT_MSEC 1000 #define TRANSPORT_VOLUME_TIMEOUT_MSEC 200 @@ -508,6 +520,19 @@ } } +static enum spa_bt_profile get_codec_profile(const struct media_codec *codec, + enum spa_bt_media_direction direction) +{ + switch (direction) { + case SPA_BT_MEDIA_SOURCE: + return codec->bap ? SPA_BT_PROFILE_BAP_SOURCE : SPA_BT_PROFILE_A2DP_SOURCE; + case SPA_BT_MEDIA_SINK: + return codec->bap ? SPA_BT_PROFILE_BAP_SINK : SPA_BT_PROFILE_A2DP_SINK; + default: + spa_assert_not_reached(); + } +} + static bool endpoint_should_be_registered(struct spa_bt_monitor *monitor, const struct media_codec *codec, enum spa_bt_media_direction direction) @@ -517,7 +542,8 @@ */ return is_media_codec_enabled(monitor, codec) && codec_has_direction(codec, direction) && - codec->fill_caps; + codec->fill_caps && + (get_codec_profile(codec, direction) & monitor->enabled_profiles); } static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) @@ -1209,11 +1235,17 @@ return NULL; } -void spa_bt_device_update_last_bluez_action_time(struct spa_bt_device *device) +static uint64_t get_time_now(struct spa_bt_monitor *monitor) { struct timespec ts; - spa_system_clock_gettime(device->monitor->main_system, CLOCK_MONOTONIC, &ts); - device->last_bluez_action_time = SPA_TIMESPEC_TO_NSEC(&ts); + + spa_system_clock_gettime(monitor->main_system, CLOCK_MONOTONIC, &ts); + return SPA_TIMESPEC_TO_NSEC(&ts); +} + +void spa_bt_device_update_last_bluez_action_time(struct spa_bt_device *device) +{ + device->last_bluez_action_time = get_time_now(device->monitor); } static struct spa_bt_device *device_create(struct spa_bt_monitor *monitor, const char *path) @@ -2199,6 +2231,8 @@ t->sco_io = NULL; t->delay_us = SPA_BT_UNKNOWN_DELAY; t->latency_us = SPA_BT_UNKNOWN_DELAY; + t->bap_cig = 0xff; + t->bap_cis = 0xff; t->user_data = SPA_PTROFF(t, sizeof(struct spa_bt_transport), void); spa_hook_list_init(&t->listener_list); spa_list_init(&t->bap_transport_linked); @@ -2236,6 +2270,18 @@ spa_bt_transport_emit_state_changed(transport, old, state); if (state >= SPA_BT_TRANSPORT_STATE_PENDING && old < SPA_BT_TRANSPORT_STATE_PENDING) transport_sync_volume(transport); + + if (state == SPA_BT_TRANSPORT_STATE_ERROR) { + uint64_t now = get_time_now(monitor); + + if (now > transport->last_error_time + TRANSPORT_ERROR_TIMEOUT) { + spa_log_error(monitor->log, "Failure in Bluetooth audio transport %s", + transport->path); + } + + transport->last_error_time = now; + ++transport->error_count; + } } } @@ -2261,8 +2307,16 @@ transport->sco_io = NULL; } + if (transport->iso_io) + spa_bt_iso_io_destroy(transport->iso_io); + spa_bt_transport_destroy(transport); + if (transport->acquire_call) { + dbus_pending_call_cancel(transport->acquire_call); + transport->acquire_call = NULL; + } + if (transport->fd >= 0) { spa_bt_player_set_state(transport->device->adapter->dummy_player, SPA_BT_PLAYER_STOPPED); @@ -2313,10 +2367,17 @@ if (transport->acquire_refcount > 0) { spa_log_debug(monitor->log, "transport %p: incref %s", transport, transport->path); transport->acquire_refcount += 1; + spa_bt_transport_emit_state_changed(transport, transport->state, transport->state); return 0; } spa_assert(transport->acquire_refcount == 0); + /* If we are getting into error state too often, stop trying */ + if (get_time_now(monitor) > transport->last_error_time + TRANSPORT_ERROR_TIMEOUT) + transport->error_count = 0; + if (transport->error_count >= TRANSPORT_ERROR_MAX_RETRY) + return -EIO; + if (!transport->acquired) res = spa_bt_transport_impl(transport, acquire, 0, optional); else @@ -2333,11 +2394,11 @@ int spa_bt_transport_release(struct spa_bt_transport *transport) { struct spa_bt_monitor *monitor = transport->monitor; - int res; if (transport->acquire_refcount > 1) { spa_log_debug(monitor->log, "transport %p: decref %s", transport, transport->path); transport->acquire_refcount -= 1; + spa_bt_transport_emit_state_changed(transport, transport->state, transport->state); return 0; } else if (transport->acquire_refcount == 0) { @@ -2347,23 +2408,8 @@ spa_assert(transport->acquire_refcount == 1); spa_assert(transport->acquired); - if (SPA_BT_TRANSPORT_IS_SCO(transport)) { - /* Postpone SCO transport releases, since we might need it again soon */ - res = spa_bt_transport_start_release_timer(transport); - } else if (transport->keepalive) { - res = 0; - transport->acquire_refcount = 0; - spa_log_debug(monitor->log, "transport %p: keepalive %s on release", - transport, transport->path); - } else { - res = spa_bt_transport_impl(transport, release, 0); - if (res >= 0) { - transport->acquire_refcount = 0; - transport->acquired = false; - } - } - - return res; + /* Postpone transport releases, since we might need it again soon */ + return spa_bt_transport_start_release_timer(transport); } static int spa_bt_transport_release_now(struct spa_bt_transport *transport) @@ -2460,7 +2506,7 @@ return start_timeout_timer(transport->monitor, &transport->release_timer, spa_bt_transport_release_timer_event, - SCO_TRANSPORT_RELEASE_TIMEOUT_MSEC, transport); + TRANSPORT_RELEASE_TIMEOUT_MSEC, transport); } static int spa_bt_transport_stop_release_timer(struct spa_bt_transport *transport) @@ -2623,7 +2669,16 @@ } } else if (spa_streq(key, "State")) { - spa_bt_transport_set_state(transport, spa_bt_transport_state_from_string(value)); + enum spa_bt_transport_state state = spa_bt_transport_state_from_string(value); + + /* Emit transition to active only for transports with + * acquired fd. If the acquire completes after prop + * update, we set the state in acquire completion. BlueZ + * currently sends events in the order where this never + * happens, but let's not rely on that. + */ + if (state != SPA_BT_TRANSPORT_STATE_ACTIVE || transport->fd >= 0) + spa_bt_transport_set_state(transport, state); } else if (spa_streq(key, "Device")) { struct spa_bt_device *device = spa_bt_device_find(monitor, value); @@ -2809,6 +2864,20 @@ spa_log_debug(monitor->log, "transport %p: %s=%d", transport, key, (int)value); } + else if (spa_streq(key, "CIG") || spa_streq(key, "CIS")) { + uint8_t value; + + if (type != DBUS_TYPE_BYTE) + goto next; + dbus_message_iter_get_basic(&it1, &value); + + spa_log_debug(monitor->log, "transport %p: %s=%d", transport, key, (int)value); + + if (spa_streq(key, "CIG")) + transport->bap_cig = value; + else + transport->bap_cis = value; + } next: dbus_message_iter_next(props_iter); } @@ -2888,76 +2957,95 @@ return 0; } -static int transport_acquire(void *data, bool optional) +static int transport_create_iso_io(struct spa_bt_transport *transport) { - struct spa_bt_transport *transport = data; struct spa_bt_monitor *monitor = transport->monitor; - DBusMessage *m, *r = NULL; - DBusError err; - int ret = 0; - const char *method = optional ? "TryAcquire" : "Acquire"; - struct spa_bt_transport *t_linked; + struct spa_bt_transport *t; + bool sink = (transport->profile & SPA_BT_PROFILE_BAP_SINK) != 0; - /* For LE Audio, multiple transport from the same device may share the same - * stream (CIS) and group (CIG) but for different direction, e.g. a speaker and - * a microphone. In this case they are linked. - * If one of them has already been acquired this function should not call Acquire - * or TryAcquire but re-use values from the previously acquired transport. - */ - spa_list_for_each(t_linked, &transport->bap_transport_linked, bap_transport_linked) { - if (t_linked->acquired && t_linked->device == transport->device) { - transport->fd = t_linked->fd; - transport->read_mtu = t_linked->read_mtu; - transport->write_mtu = t_linked->write_mtu; - spa_log_debug(monitor->log, "transport %p: linked transport %s", transport, t_linked->path); - goto done; - } - } + if (!(transport->profile & (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE))) + return 0; - m = dbus_message_new_method_call(BLUEZ_SERVICE, - transport->path, - BLUEZ_MEDIA_TRANSPORT_INTERFACE, - method); - if (m == NULL) - return -ENOMEM; + if (transport->bap_cig == 0xff || transport->bap_cis == 0xff) + return -EINVAL; - dbus_error_init(&err); + if (transport->iso_io) { + spa_log_debug(monitor->log, "transport %p: remove ISO IO", transport); + spa_bt_iso_io_destroy(transport->iso_io); + transport->iso_io = NULL; + } - r = dbus_connection_send_with_reply_and_block(monitor->conn, m, -1, &err); - dbus_message_unref(m); - m = NULL; + /* Transports in same connected iso group share the same i/o */ + spa_list_for_each(t, &monitor->transport_list, link) { + if (!(t->profile & (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE))) + continue; + if (t->bap_cig != transport->bap_cig) + continue; - if (r == NULL) { - if (optional && spa_streq(err.name, "org.bluez.Error.NotAvailable")) { - spa_log_info(monitor->log, "Failed optional acquire of unavailable transport %s", - transport->path); - } - else { - spa_log_error(monitor->log, "Transport %s() failed for transport %s (%s)", - method, transport->path, err.message); + if (t->iso_io) { + spa_log_debug(monitor->log, "transport %p: attach ISO IO to %p", + transport, t); + transport->iso_io = spa_bt_iso_io_attach(t->iso_io, transport->fd, sink); + return 0; } - dbus_error_free(&err); - return -EIO; } + spa_log_debug(monitor->log, "transport %p: new ISO IO", transport); + transport->iso_io = spa_bt_iso_io_create(transport->fd, sink, + monitor->log, monitor->data_loop, monitor->data_system); + if (transport->iso_io == NULL) + return -errno; + + return 0; +} + +static void transport_acquire_reply(DBusPendingCall *pending, void *user_data) +{ + struct spa_bt_transport *transport = user_data; + struct spa_bt_monitor *monitor = transport->monitor; + struct spa_bt_device *device = transport->device; + int ret = 0; + DBusError err; + DBusMessage *r; + struct spa_bt_transport *t_linked; + + r = dbus_pending_call_steal_reply(pending); + + spa_assert(transport->acquire_call == pending); + dbus_pending_call_unref(pending); + transport->acquire_call = NULL; + + spa_bt_device_update_last_bluez_action_time(device); + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { - spa_log_error(monitor->log, "%s returned error: %s", method, dbus_message_get_error_name(r)); + spa_log_error(monitor->log, "Acquire %s returned error: %s", + transport->path, + dbus_message_get_error_name(r)); ret = -EIO; goto finish; } + dbus_error_init(&err); + + if (transport->fd >= 0) { + spa_log_error(monitor->log, "transport %p: invalid duplicate acquire", transport); + ret = -EINVAL; + goto finish; + } + if (!dbus_message_get_args(r, &err, DBUS_TYPE_UNIX_FD, &transport->fd, DBUS_TYPE_UINT16, &transport->read_mtu, DBUS_TYPE_UINT16, &transport->write_mtu, DBUS_TYPE_INVALID)) { - spa_log_error(monitor->log, "Failed to parse %s() reply: %s", method, err.message); + spa_log_error(monitor->log, "Failed to parse Acquire %s reply: %s", + transport->path, err.message); dbus_error_free(&err); ret = -EIO; goto finish; } -done: - spa_log_debug(monitor->log, "transport %p: %s %s, fd %d MTU %d:%d", transport, method, + + spa_log_debug(monitor->log, "transport %p: Acquired %s, fd %d MTU %d:%d", transport, transport->path, transport->fd, transport->read_mtu, transport->write_mtu); spa_bt_player_set_state(transport->device->adapter->dummy_player, SPA_BT_PLAYER_PLAYING); @@ -2967,17 +3055,153 @@ finish: if (r) dbus_message_unref(r); - return ret; + if (ret < 0) + spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ERROR); + else { + if (transport_create_iso_io(transport) < 0) + spa_log_error(monitor->log, "transport %p: transport_create_iso_io failed", + transport); + + spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ACTIVE); + } + + /* For LE Audio, multiple transport from the same device may share the same + * stream (CIS) and group (CIG) but for different direction, e.g. a speaker and + * a microphone. In this case they are linked, and we need to set the values + * for all of them here. + */ + spa_list_for_each(t_linked, &transport->bap_transport_linked, bap_transport_linked) { + if (ret < 0) { + spa_bt_transport_set_state(t_linked, SPA_BT_TRANSPORT_STATE_ERROR); + continue; + } + + t_linked->fd = transport->fd; + t_linked->read_mtu = transport->read_mtu; + t_linked->write_mtu = transport->write_mtu; + spa_log_debug(monitor->log, "transport %p: linked Acquired %s, fd %d MTU %d:%d", t_linked, + t_linked->path, t_linked->fd, t_linked->read_mtu, t_linked->write_mtu); + + if (transport_create_iso_io(t_linked) < 0) + spa_log_error(monitor->log, "transport %p: transport_create_iso_io failed", + t_linked); + + spa_bt_transport_set_state(t_linked, SPA_BT_TRANSPORT_STATE_ACTIVE); + } } -static int transport_release(void *data) +static int do_transport_acquire(struct spa_bt_transport *transport) { - struct spa_bt_transport *transport = data; struct spa_bt_monitor *monitor = transport->monitor; - DBusMessage *m, *r; + DBusMessage *m; DBusError err; - bool is_idle = (transport->state == SPA_BT_TRANSPORT_STATE_IDLE); + dbus_bool_t ret; struct spa_bt_transport *t_linked; + + spa_list_for_each(t_linked, &transport->bap_transport_linked, bap_transport_linked) { + /* If a linked transport has been acquired, it will do all the work */ + if (t_linked->acquire_call || t_linked->acquired) { + spa_log_debug(monitor->log, "Acquiring %s: use linked transport %s", + transport->path, t_linked->path); + spa_bt_transport_emit_state_changed(transport, transport->state, transport->state); + return 0; + } + } + + if (transport->acquire_call) + return -EBUSY; + + spa_log_info(monitor->log, "Acquiring transport %s", transport->path); + + m = dbus_message_new_method_call(BLUEZ_SERVICE, + transport->path, + BLUEZ_MEDIA_TRANSPORT_INTERFACE, + "Acquire"); + if (m == NULL) + return -ENOMEM; + + dbus_error_init(&err); + + ret = dbus_connection_send_with_reply(monitor->conn, m, &transport->acquire_call, -1); + dbus_message_unref(m); + + if (!ret || transport->acquire_call == NULL) + return -EIO; + + ret = dbus_pending_call_set_notify(transport->acquire_call, transport_acquire_reply, transport, NULL); + if (!ret) + return -EIO; + + return 0; +} + +static bool transport_in_same_cig(struct spa_bt_transport *transport, struct spa_bt_transport *other) +{ + return (other->profile & (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE)) && + other->bap_cig == transport->bap_cig && + other->bap_initiator; +} + +static bool another_cig_transport_active(struct spa_bt_transport *transport) +{ + struct spa_bt_monitor *monitor = transport->monitor; + struct spa_bt_transport *t; + + spa_list_for_each(t, &monitor->transport_list, link) { + if (!transport_in_same_cig(transport, t) || t == transport) + continue; + if (t->acquired) + return true; + } + + return false; +} + +static int transport_acquire(void *data, bool optional) +{ + struct spa_bt_transport *transport = data; + struct spa_bt_monitor *monitor = transport->monitor; + + /* + * XXX: When as BAP Central, all CIS in a CIG must be acquired at the same time. + * XXX: This is because of kernel ISO socket limitations, which does not handle + * XXX: currently starting streams in the group one by one. + */ + if (transport->bap_initiator && !another_cig_transport_active(transport)) { + struct spa_bt_transport *t; + + spa_list_for_each(t, &monitor->transport_list, link) { + if (!transport_in_same_cig(transport, t) || t == transport) + continue; + + spa_log_debug(monitor->log, "Acquire CIG %d: transport %s", + transport->bap_cig, t->path); + + do_transport_acquire(t); + } + + spa_log_debug(monitor->log, "Acquire CIG %d: transport %s", + transport->bap_cig, transport->path); + } + if (transport->bap_initiator && + (transport->fd >= 0 || transport->acquire_call)) { + /* Already acquired/acquiring */ + spa_log_debug(monitor->log, "Acquiring %s: was in acquired CIG", transport->path); + spa_bt_transport_emit_state_changed(transport, transport->state, transport->state); + return 0; + } + + return do_transport_acquire(data); +} + +static int do_transport_release(struct spa_bt_transport *transport) +{ + struct spa_bt_monitor *monitor = transport->monitor; + DBusMessage *m; + struct spa_bt_transport *t_linked; + bool is_idle = (transport->state == SPA_BT_TRANSPORT_STATE_IDLE); + DBusMessage *r; + DBusError err; bool linked = false; spa_log_debug(monitor->log, "transport %p: Release %s", @@ -2985,12 +3209,25 @@ spa_bt_player_set_state(transport->device->adapter->dummy_player, SPA_BT_PLAYER_STOPPED); + spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_IDLE); + + if (transport->acquire_call) { + dbus_pending_call_cancel(transport->acquire_call); + transport->acquire_call = NULL; + } + + if (transport->iso_io) { + spa_log_debug(monitor->log, "transport %p: remove ISO IO", transport); + spa_bt_iso_io_destroy(transport->iso_io); + transport->iso_io = NULL; + } + /* For LE Audio, multiple transport stream (CIS) can be linked together (CIG). * If they are part of the same device they re-use the same fd, and call to * release should be done for the last one only. */ spa_list_for_each(t_linked, &transport->bap_transport_linked, bap_transport_linked) { - if (t_linked->acquired && t_linked->device == transport->device) { + if (t_linked->acquire_call || t_linked->acquired) { linked = true; break; } @@ -3001,8 +3238,12 @@ return 0; } - close(transport->fd); - transport->fd = -1; + if (transport->fd >= 0) { + close(transport->fd); + transport->fd = -1; + } + + spa_log_info(monitor->log, "Releasing transport %s", transport->path); m = dbus_message_new_method_call(BLUEZ_SERVICE, transport->path, @@ -3012,15 +3253,10 @@ return -ENOMEM; dbus_error_init(&err); - r = dbus_connection_send_with_reply_and_block(monitor->conn, m, -1, &err); dbus_message_unref(m); - m = NULL; - - if (r != NULL) - dbus_message_unref(r); - if (dbus_error_is_set(&err)) { + if (r == NULL) { if (is_idle) { /* XXX: The fd always needs to be closed. However, Release() * XXX: apparently doesn't need to be called on idle transports @@ -3034,13 +3270,52 @@ transport->path, err.message); } dbus_error_free(&err); - } - else + } else { spa_log_info(monitor->log, "Transport %s released", transport->path); + dbus_message_unref(r); + } return 0; } +static int transport_release(void *data) +{ + struct spa_bt_transport *transport = data; + struct spa_bt_monitor *monitor = transport->monitor; + struct spa_bt_transport *t; + + /* + * XXX: When as BAP Central, release CIS in a CIG when the last transport + * XXX: goes away. + */ + if (transport->bap_initiator) { + /* Check if another transport is alive */ + if (another_cig_transport_active(transport)) { + spa_log_debug(monitor->log, "Releasing %s: wait for CIG %d", + transport->path, transport->bap_cig); + return 0; + } + + /* Release remaining transports in CIG */ + spa_list_for_each(t, &monitor->transport_list, link) { + if (!transport_in_same_cig(transport, t) || t == transport) + continue; + + spa_log_debug(monitor->log, "Release CIG %d: transport %s", + transport->bap_cig, t->path); + + if (t->fd >= 0) + do_transport_release(t); + } + + spa_log_debug(monitor->log, "Release CIG %d: transport %s", + transport->bap_cig, transport->path); + } + + return do_transport_release(data); +} + + static const struct spa_bt_transport_implementation transport_impl = { SPA_VERSION_BT_TRANSPORT_IMPLEMENTATION, .acquire = transport_acquire, @@ -3230,12 +3505,10 @@ static void media_codec_switch_process(struct spa_bt_media_codec_switch *sw) { while (*sw->codec_iter != NULL && *sw->path_iter != NULL) { - struct timespec ts; uint64_t now, threshold; /* Rate limit BlueZ calls */ - spa_system_clock_gettime(sw->device->monitor->main_system, CLOCK_MONOTONIC, &ts); - now = SPA_TIMESPEC_TO_NSEC(&ts); + now = get_time_now(sw->device->monitor); threshold = sw->device->last_bluez_action_time + BLUEZ_ACTION_RATE_MSEC * SPA_NSEC_PER_MSEC; if (now < threshold) { /* Wait for timeout */ @@ -5000,6 +5273,30 @@ return profiles; } +static int parse_roles(struct spa_bt_monitor *monitor, const struct spa_dict *info) +{ + const char *str; + int res = 0; + int profiles = SPA_BT_PROFILE_MEDIA_SINK | SPA_BT_PROFILE_MEDIA_SOURCE; + + /* HSP/HFP backends parse this property separately */ + if (info && (str = spa_dict_lookup(info, "bluez5.roles"))) { + res = spa_bt_profiles_from_json_array(str); + if (res < 0) { + spa_log_warn(monitor->log, "malformed bluez5.roles setting ignored"); + goto done; + } + + profiles &= res; + } + + res = 0; + +done: + monitor->enabled_profiles = profiles; + return res; +} + static int parse_codec_array(struct spa_bt_monitor *this, const struct spa_dict *info) { const struct media_codec * const * const media_codecs = this->media_codecs; @@ -5118,7 +5415,9 @@ this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus); this->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); + this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); this->main_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System); + this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); this->plugin_loader = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_PluginLoader); spa_log_topic_init(this->log, &log_topic); @@ -5185,6 +5484,8 @@ if ((res = parse_codec_array(this, info)) < 0) goto fail; + parse_roles(this, info); + this->default_audio_info.rate = A2DP_CODEC_DEFAULT_RATE; this->default_audio_info.channels = A2DP_CODEC_DEFAULT_CHANNELS;
View file
pipewire-0.3.67.tar.gz/spa/plugins/bluez5/bluez5-device.c -> pipewire-0.3.68.tar.gz/spa/plugins/bluez5/bluez5-device.c
Changed
@@ -1038,13 +1038,16 @@ static void set_initial_profile(struct impl *this); -static void device_connected(void *userdata, bool connected) { +static void device_connected(void *userdata, bool connected) +{ struct impl *this = userdata; spa_log_debug(this->log, "connected: %d", connected); - if (connected ^ (this->profile != DEVICE_PROFILE_OFF)) + if (connected ^ (this->profile != DEVICE_PROFILE_OFF)) { + emit_remove_nodes(this); set_initial_profile(this); + } } static const struct spa_bt_device_events bt_dev_events = {
View file
pipewire-0.3.67.tar.gz/spa/plugins/bluez5/decode-buffer.h -> pipewire-0.3.68.tar.gz/spa/plugins/bluez5/decode-buffer.h
Changed
@@ -38,10 +38,13 @@ #include <spa/utils/defs.h> #include <spa/support/log.h> +#include "rate-control.h" + #define BUFFERING_LONG_MSEC (2*60000) #define BUFFERING_SHORT_MSEC 1000 #define BUFFERING_RATE_DIFF_MAX 0.005 + /** * Safety margin. * @@ -51,131 +54,6 @@ #define BUFFERING_TARGET(spike,packet_size,max_buf) \ SPA_CLAMP((spike)*3/2, (packet_size), (max_buf) - 2*(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 @@ -463,7 +341,8 @@ } this->corr = spa_bt_rate_control_update(&this->ctl, - level, target, this->prev_consumed, avg_period); + level, target, this->prev_consumed, avg_period, + BUFFERING_RATE_DIFF_MAX); spa_bt_decode_buffer_get_read(this, &avail);
View file
pipewire-0.3.67.tar.gz/spa/plugins/bluez5/defs.h -> pipewire-0.3.68.tar.gz/spa/plugins/bluez5/defs.h
Changed
@@ -522,6 +522,8 @@ #define spa_bt_device_add_listener(d,listener,events,data) \ spa_hook_list_append(&(d)->listener_list, listener, events, data) +struct spa_bt_iso_io; + struct spa_bt_sco_io; struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_loop *data_loop, int fd, uint16_t read_mtu, uint16_t write_mtu); @@ -539,9 +541,10 @@ #define SPA_BT_VOLUME_A2DP_MAX 127 enum spa_bt_transport_state { - SPA_BT_TRANSPORT_STATE_IDLE, - SPA_BT_TRANSPORT_STATE_PENDING, - SPA_BT_TRANSPORT_STATE_ACTIVE, + SPA_BT_TRANSPORT_STATE_ERROR = -1, + SPA_BT_TRANSPORT_STATE_IDLE = 0, + SPA_BT_TRANSPORT_STATE_PENDING = 1, + SPA_BT_TRANSPORT_STATE_ACTIVE = 2, }; struct spa_bt_transport_events { @@ -600,16 +603,22 @@ int acquire_refcount; bool acquired; bool keepalive; + int error_count; + uint64_t last_error_time; int fd; uint16_t read_mtu; uint16_t write_mtu; unsigned int delay_us; unsigned int latency_us; + uint8_t bap_cig; + uint8_t bap_cis; + struct spa_bt_iso_io *iso_io; struct spa_bt_sco_io *sco_io; struct spa_source volume_timer; struct spa_source release_timer; + DBusPendingCall *acquire_call; struct spa_hook_list listener_list; struct spa_callbacks impl;
View file
pipewire-0.3.68.tar.gz/spa/plugins/bluez5/iso-io.c
Added
@@ -0,0 +1,377 @@ +/* Spa ISO I/O */ +/* SPDX-FileCopyrightText: Copyright © 2023 Pauli Virtanen. */ +/* SPDX-License-Identifier: MIT */ + +#include <unistd.h> +#include <stddef.h> +#include <stdio.h> +#include <errno.h> +#include <limits.h> +#include <sys/socket.h> + +#include <spa/support/loop.h> +#include <spa/support/log.h> +#include <spa/utils/list.h> +#include <spa/utils/string.h> +#include <spa/utils/result.h> +#include <spa/node/io.h> + +#include <bluetooth/bluetooth.h> + +#include "config.h" +#include "iso-io.h" + +static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.iso"); +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT &log_topic + +#define IDLE_TIME (100 * SPA_NSEC_PER_MSEC) + +struct group { + struct spa_log *log; + struct spa_log_topic log_topic; + struct spa_loop *data_loop; + struct spa_system *data_system; + struct spa_source source; + struct spa_list streams; + int timerfd; + uint8_t cig; + uint64_t next; + uint64_t duration; + uint32_t paused; +}; + +struct stream { + struct spa_bt_iso_io this; + struct spa_list link; + struct group *group; + int fd; + bool sink; + bool idle; + + spa_bt_iso_io_pull_t pull; +}; + +struct modify_info +{ + struct stream *stream; + struct spa_list *streams; +}; + +static int do_modify(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct modify_info *info = user_data; + + if (info->streams) + spa_list_append(info->streams, &info->stream->link); + else + spa_list_remove(&info->stream->link); + + return 0; +} + +static void stream_link(struct group *group, struct stream *stream) +{ + struct modify_info info = { .stream = stream, .streams = &group->streams }; + int res; + + res = spa_loop_invoke(group->data_loop, do_modify, 0, NULL, 0, true, &info); + spa_assert_se(res == 0); +} + +static void stream_unlink(struct stream *stream) +{ + struct modify_info info = { .stream = stream, .streams = NULL }; + int res; + + res = spa_loop_invoke(stream->group->data_loop, do_modify, 0, NULL, 0, true, &info); + spa_assert_se(res == 0); +} + +static int set_timeout(struct group *group, uint64_t time) +{ + struct itimerspec ts; + ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; + ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; + ts.it_interval.tv_sec = 0; + ts.it_interval.tv_nsec = 0; + return spa_system_timerfd_settime(group->data_system, + group->timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); +} + +static int set_timers(struct group *group) +{ + struct timespec now; + + spa_system_clock_gettime(group->data_system, CLOCK_MONOTONIC, &now); + group->next = SPA_ROUND_UP(SPA_TIMESPEC_TO_NSEC(&now) + group->duration, + group->duration); + + return set_timeout(group, group->next); +} + +static void group_on_timeout(struct spa_source *source) +{ + struct group *group = source->data; + struct stream *stream; + uint64_t exp; + int res; + bool active = false; + + if ((res = spa_system_timerfd_read(group->data_system, group->timerfd, &exp)) < 0) { + if (res != -EAGAIN) + spa_log_warn(group->log, "%p: ISO group:%u error reading timerfd: %s", + group, group->cig, spa_strerror(res)); + return; + } + + /* + * If an idle stream activates when another stream is already active, + * pause output of all streams for a while to avoid desynchronization. + */ + spa_list_for_each(stream, &group->streams, link) { + if (!stream->sink) + continue; + if (!stream->idle) { + active = true; + break; + } + } + + spa_list_for_each(stream, &group->streams, link) { + if (!stream->sink) + continue; + + if (stream->idle && stream->this.size > 0 && active && !group->paused) + group->paused = 1u + IDLE_TIME / group->duration; + + stream->idle = (stream->this.size == 0); + } + + if (group->paused) { + --group->paused; + spa_log_debug(group->log, "%p: ISO group:%d paused:%u", group, group->cig, group->paused); + } + + /* Produce output */ + spa_list_for_each(stream, &group->streams, link) { + int res; + + if (!stream->sink) + continue; + if (stream->idle) + continue; + if (group->paused) { + stream->this.size = 0; + continue; + } + + res = send(stream->fd, stream->this.buf, stream->this.size, MSG_DONTWAIT | MSG_NOSIGNAL); + if (res < 0) + res = -errno; + + spa_log_trace(group->log, "%p: ISO group:%u sent fd:%d size:%u ts:%u res:%d", + group, group->cig, stream->fd, (unsigned)stream->this.size, + (unsigned)stream->this.timestamp, res); + + stream->this.size = 0; + } + + /* Pull data for the next interval */ + group->next += exp * group->duration; + + spa_list_for_each(stream, &group->streams, link) { + if (!stream->sink) + continue; + + if (stream->pull) { + stream->this.now = group->next; + stream->pull(&stream->this); + } else { + stream->this.size = 0; + } + } + + set_timeout(group, group->next); +} + +static struct group *group_create(int fd, struct spa_log *log, struct spa_loop *data_loop, + struct spa_system *data_system) +{ +#if defined(HAVE_BLUETOOTH_BAP) && defined(BT_ISO_QOS) + struct group *group; + struct bt_iso_qos qos; + socklen_t len; + + len = sizeof(qos); + if (getsockopt(fd, SOL_BLUETOOTH, BT_ISO_QOS, &qos, &len) < 0) + return NULL; + + if (qos.out.interval <= 5000) { + errno = EINVAL; + return NULL; + } + + group = calloc(1, sizeof(struct group)); + if (group == NULL) + return NULL; + + spa_log_topic_init(log, &log_topic); + + group->cig = qos.cig; + group->log = log; + group->data_loop = data_loop; + group->data_system = data_system; + group->duration = qos.out.interval * SPA_NSEC_PER_USEC; + + spa_list_init(&group->streams); + + group->timerfd = spa_system_timerfd_create(group->data_system, + CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); + if (group->timerfd < 0) { + int err = errno; + free(group); + errno = err; + return NULL; + } + + group->source.data = group; + group->source.fd = group->timerfd; + group->source.func = group_on_timeout; + group->source.mask = SPA_IO_IN; + group->source.rmask = 0; + spa_loop_add_source(group->data_loop, &group->source); + + return group; +#else + errno = EOPNOTSUPP; + return NULL; +#endif +} + +static int do_remove_source(struct spa_loop *loop, bool async, uint32_t seq, + const void *data, size_t size, void *user_data) +{ + struct group *group = user_data; + + if (group->source.loop) + spa_loop_remove_source(group->data_loop, &group->source); + + set_timeout(group, 0); + + return 0; +} + +static void group_destroy(struct group *group) +{ + int res; + + spa_assert(spa_list_is_empty(&group->streams)); + + res = spa_loop_invoke(group->data_loop, do_remove_source, 0, NULL, 0, true, group); + spa_assert_se(res == 0); + + close(group->timerfd); + free(group); +} + +struct stream *stream_create(int fd, bool sink, struct group *group) +{ + struct stream *stream; + + stream = calloc(1, sizeof(struct stream)); + if (stream == NULL) + return NULL; + + stream->fd = fd; + stream->sink = sink; + stream->group = group; + stream->this.duration = group->duration; + + stream_link(group, stream); + + return stream; +} + +struct spa_bt_iso_io *spa_bt_iso_io_create(int fd, bool sink, struct spa_log *log, + struct spa_loop *data_loop, struct spa_system *data_system) +{ + struct stream *stream; + struct group *group; + + group = group_create(fd, log, data_loop, data_system); + if (group == NULL) + return NULL; + + stream = stream_create(fd, sink, group); + if (stream == NULL) { + int err = errno; + group_destroy(group); + errno = err; + return NULL; + } + + return &stream->this; +} + +struct spa_bt_iso_io *spa_bt_iso_io_attach(struct spa_bt_iso_io *this, int fd, bool sink) +{ + struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this); + + stream = stream_create(fd, sink, stream->group); + if (stream == NULL) + return NULL; + + return &stream->this; +} + +void spa_bt_iso_io_destroy(struct spa_bt_iso_io *this) +{ + struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this); + + stream_unlink(stream); + + if (spa_list_is_empty(&stream->group->streams)) + group_destroy(stream->group); + + free(stream); +} + +static bool group_is_enabled(struct group *group) +{ + struct stream *stream; + + spa_list_for_each(stream, &group->streams, link) + if (stream->pull) + return true; + + return false; +} + +/** Must be called from data thread */ +void spa_bt_iso_io_set_cb(struct spa_bt_iso_io *this, spa_bt_iso_io_pull_t pull, void *user_data) +{ + struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this); + bool was_enabled, enabled; + + was_enabled = group_is_enabled(stream->group); + + stream->pull = pull; + stream->this.user_data = user_data; + + enabled = group_is_enabled(stream->group); + + if (!enabled && was_enabled) + set_timeout(stream->group, 0); + else if (enabled && !was_enabled) + set_timers(stream->group); + + if (pull == NULL) { + stream->this.size = 0; + return; + } + + /* Pull data now for the next interval */ + stream->this.now = stream->group->next; + stream->pull(&stream->this); +}
View file
pipewire-0.3.68.tar.gz/spa/plugins/bluez5/iso-io.h
Added
@@ -0,0 +1,39 @@ +/* Spa Bluez5 ISO I/O */ +/* SPDX-FileCopyrightText: Copyright © 2023 Pauli Virtanen */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_BLUEZ5_ISO_IO_H +#define SPA_BLUEZ5_ISO_IO_H + +#include <spa/utils/defs.h> +#include <spa/support/loop.h> +#include <spa/support/log.h> +#include <spa/node/io.h> + +/** + * ISO I/O. + * + * Synchronizes related writes from different streams in the same group + * to occur at same real time instant (or not at all). + */ +struct spa_bt_iso_io +{ + uint64_t now; + uint64_t duration; + + uint32_t timestamp; + uint8_t buf4096; + size_t size; + + void *user_data; +}; + +typedef void (*spa_bt_iso_io_pull_t)(struct spa_bt_iso_io *io); + +struct spa_bt_iso_io *spa_bt_iso_io_create(int fd, bool sink, struct spa_log *log, + struct spa_loop *data_loop, struct spa_system *data_system); +struct spa_bt_iso_io *spa_bt_iso_io_attach(struct spa_bt_iso_io *io, int fd, bool sink); +void spa_bt_iso_io_destroy(struct spa_bt_iso_io *io); +void spa_bt_iso_io_set_cb(struct spa_bt_iso_io *io, spa_bt_iso_io_pull_t pull, void *user_data); + +#endif
View file
pipewire-0.3.67.tar.gz/spa/plugins/bluez5/media-sink.c -> pipewire-0.3.68.tar.gz/spa/plugins/bluez5/media-sink.c
Changed
@@ -31,11 +31,15 @@ #include <spa/debug/mem.h> #include <spa/debug/log.h> +#include <bluetooth/bluetooth.h> + #include <sbc/sbc.h> #include "defs.h" #include "rtp.h" #include "media-codecs.h" +#include "rate-control.h" +#include "iso-io.h" static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.sink.media"); #undef SPA_LOG_TOPIC_DEFAULT @@ -52,6 +56,7 @@ #define MIN_BUFFERS 2 #define MAX_BUFFERS 32 #define BUFFER_SIZE (8192*8) +#define RATE_CTL_DIFF_MAX 0.005 struct buffer { uint32_t id; @@ -70,6 +75,7 @@ uint64_t info_all; struct spa_port_info info; struct spa_io_buffers *io; + struct spa_io_rate_match *rate_match; struct spa_latency_info latency; #define IDX_EnumFormat 0 #define IDX_Meta 1 @@ -87,6 +93,8 @@ struct spa_list ready; size_t ready_offset; + + struct spa_bt_rate_control ratectl; }; struct impl { @@ -116,6 +124,8 @@ struct port port; unsigned int started:1; + unsigned int start_ready:1; + unsigned int transport_started:1; unsigned int following:1; unsigned int is_output:1; unsigned int flush_pending:1; @@ -135,6 +145,8 @@ uint64_t next_time; uint64_t last_error; uint64_t process_time; + uint64_t process_duration; + uint64_t process_rate; uint64_t prev_flush_time; uint64_t next_flush_time; @@ -147,6 +159,8 @@ int need_flush; bool fragment; + bool resync; + bool have_iso_packet; uint32_t block_size; uint8_t bufferBUFFER_SIZE; uint32_t buffer_used; @@ -270,52 +284,75 @@ return set_timeout(this, this->following ? 0 : this->next_time); } -static int do_reassign_follower(struct spa_loop *loop, +static inline bool is_following(struct impl *this) +{ + return this->position && this->clock && this->position->clock.id != this->clock->id; +} + +struct reassign_io_info { + struct impl *this; + struct spa_io_position *position; + struct spa_io_clock *clock; +}; + +static int do_reassign_io(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { - struct impl *this = user_data; - set_timers(this); - return 0; -} + struct reassign_io_info *info = user_data; + struct impl *this = info->this; + bool following; -static inline bool is_following(struct impl *this) -{ - return this->position && this->clock && this->position->clock.id != this->clock->id; + if (this->position != info->position || this->clock != info->clock) + this->resync = true; + + this->position = info->position; + this->clock = info->clock; + + following = is_following(this); + + if (following != this->following) { + spa_log_debug(this->log, "%p: reassign follower %d->%d", this, this->following, following); + this->following = following; + set_timers(this); + } + + return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; - bool following; + struct reassign_io_info info = { .this = this, .position = this->position, .clock = this->clock }; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: - this->clock = data; - if (this->clock != NULL) { - spa_scnprintf(this->clock->name, - sizeof(this->clock->name), + info.clock = data; + if (info.clock != NULL) { + spa_scnprintf(info.clock->name, + sizeof(info.clock->name), "%s", this->props.clock_name); } break; case SPA_IO_Position: - this->position = data; + info.position = data; break; default: return -ENOENT; } - following = is_following(this); - if (this->started && following != this->following) { - spa_log_debug(this->log, "%p: reassign follower %d->%d", this, this->following, following); - this->following = following; - spa_loop_invoke(this->data_loop, do_reassign_follower, 0, NULL, 0, true, this); + if (this->started) { + spa_loop_invoke(this->data_loop, do_reassign_io, 0, NULL, 0, true, &info); + } else { + this->clock = info.clock; + this->position = info.position; } + return 0; } @@ -395,6 +432,62 @@ return 0; } +static uint32_t get_queued_frames(struct impl *this) +{ + struct port *port = &this->port; + uint32_t bytes = 0; + struct buffer *b; + + spa_list_for_each(b, &port->ready, link) { + struct spa_data *d = b->buf->datas; + + bytes += d0.chunk->size; + } + + if (bytes > port->ready_offset) + bytes -= port->ready_offset; + else + bytes = 0; + + /* Count (partially) encoded packet */ + bytes += this->tmp_buffer_used; + bytes += this->block_count * this->block_size; + + return bytes / port->frame_size; +} + +static uint64_t get_reference_time(struct impl *this, uint64_t *duration_ns_ret) +{ + struct port *port = &this->port; + uint64_t t, duration_ns; + + if (!this->process_rate || !this->process_duration) { + if (this->position) { + this->process_duration = this->position->clock.duration; + this->process_rate = this->position->clock.rate.denom; + } else { + this->process_duration = 1024; + this->process_rate = 48000; + } + } + + duration_ns = ((uint64_t)this->process_duration * SPA_NSEC_PER_SEC / this->process_rate); + if (duration_ns_ret) + *duration_ns_ret = duration_ns; + + /* Time at the first sample in the current packet. */ + t = this->process_time + duration_ns; + t -= ((uint64_t)get_queued_frames(this) * SPA_NSEC_PER_SEC + / port->current_format.info.raw.rate); + + /* Account for resampling delay */ + if (port->rate_match && this->clock && SPA_FLAG_IS_SET(port->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE)) + t -= (uint64_t)port->rate_match->delay * SPA_NSEC_PER_SEC + / this->clock->rate.denom; + + return t; +} + static int reset_buffer(struct impl *this) { if (this->codec_props_changed && this->codec_props @@ -405,11 +498,28 @@ this->need_flush = 0; this->block_count = 0; this->fragment = false; + this->timestamp = this->codec->bap ? (get_reference_time(this, NULL) / SPA_NSEC_PER_USEC) + : this->sample_count; this->buffer_used = this->codec->start_encode(this->codec_data, this->buffer, sizeof(this->buffer), - this->seqnum++, this->timestamp); + ++this->seqnum, this->timestamp); this->header_size = this->buffer_used; - this->timestamp = this->sample_count; + return 0; +} + +static int setup_matching(struct impl *this) +{ + struct port *port = &this->port; + + if (!this->transport_started) + port->ratectl.corr = 1.0; + + if (port->rate_match) { + port->rate_match->rate = 1 / port->ratectl.corr; + + SPA_FLAG_UPDATE(port->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE, this->following); + } + return 0; } @@ -590,26 +700,6 @@ this->flush_pending = enabled; } -static uint32_t get_queued_frames(struct impl *this) -{ - struct port *port = &this->port; - uint32_t bytes = 0; - struct buffer *b; - - spa_list_for_each(b, &port->ready, link) { - struct spa_data *d = b->buf->datas; - - bytes += d0.chunk->size; - } - - if (bytes > port->ready_offset) - bytes -= port->ready_offset; - else - bytes = 0; - - return bytes / port->frame_size; -} - static int flush_data(struct impl *this, uint64_t now_time) { int written; @@ -617,10 +707,13 @@ struct port *port = &this->port; int unused_buffer; - if (!this->flush_source.loop) { - /* I/O in error state */ + spa_assert(this->transport_started); + + /* I/O in error state? */ + if (this->transport == NULL || !this->flush_source.loop) + return -EIO; + if (!this->flush_timer_source.loop && !this->transport->iso_io) return -EIO; - } total_frames = 0; again: @@ -691,6 +784,27 @@ spa_log_trace(this->log, "%p: written %u frames", this, total_frames); } + if (this->transport->iso_io) { + struct spa_bt_iso_io *iso_io = this->transport->iso_io; + + if (this->need_flush && !this->have_iso_packet) { + size_t avail = SPA_MIN(this->buffer_used, sizeof(iso_io->buf)); + + spa_log_trace(this->log, "%p: ISO put fd:%d size:%u sn:%u ts:%u now:%"PRIu64, + this, this->transport->fd, (unsigned)avail, + (unsigned)this->seqnum, (unsigned)this->timestamp, + iso_io->now); + + memcpy(iso_io->buf, this->buffer, avail); + iso_io->size = avail; + iso_io->timestamp = this->timestamp; + this->have_iso_packet = true; + + reset_buffer(this); + } + return 0; + } + if (this->flush_pending) { spa_log_trace(this->log, "%p: wait for flush timer", this); return 0; @@ -741,17 +855,13 @@ / port->current_format.info.raw.rate; if (SPA_LIKELY(this->position)) { - uint32_t frames = get_queued_frames(this); uint64_t duration_ns; /* * Flush at the time position of the next buffered sample. */ - duration_ns = ((uint64_t)this->position->clock.duration * SPA_NSEC_PER_SEC - / this->position->clock.rate.denom); - this->next_flush_time = this->process_time + duration_ns - - ((uint64_t)frames * SPA_NSEC_PER_SEC - / port->current_format.info.raw.rate); + this->next_flush_time = get_reference_time(this, &duration_ns) + + packet_time; /* * We can delay the output by one packet to avoid waiting @@ -804,6 +914,98 @@ return 0; } +static void drop_frames(struct impl *this, uint32_t req) +{ + struct port *port = &this->port; + + while (req > 0 && !spa_list_is_empty(&port->ready)) { + struct buffer *b; + struct spa_data *d; + uint32_t avail; + + b = spa_list_first(&port->ready, struct buffer, link); + d = b->buf->datas; + + avail = d0.chunk->size - port->ready_offset; + avail /= port->frame_size; + + avail = SPA_MIN(avail, req); + port->ready_offset += avail * port->frame_size; + req -= avail; + + if (port->ready_offset >= d0.chunk->size) { + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); + spa_log_trace(this->log, "%p: reuse buffer %u", this, b->id); + this->port.io->buffer_id = b->id; + + spa_node_call_reuse_buffer(&this->callbacks, 0, b->id); + port->ready_offset = 0; + } + + spa_log_trace(this->log, "%p: skipped %u frames", this, avail); + } +} + +static void media_iso_pull(struct spa_bt_iso_io *iso_io) +{ + struct impl *this = iso_io->user_data; + struct port *port = &this->port; + const double period = 0.1 * SPA_NSEC_PER_SEC; + uint64_t duration_ns; + double value, target, err; + + this->have_iso_packet = false; + + if (this->resync || !this->position) { + spa_bt_rate_control_init(&port->ratectl, 0); + goto done; + } + + /* + * Rate match sample position so that the graph is 3/2 ISO interval + * ahead of the time instant we have to send data. + * + * Being 1 ISO interval ahead is unavoidable otherwise we underrun, + * and the 1/2 is safety margin for the graph to deliver data + * in time. + * + * This is then the part of the TX latency on PipeWire side. There is + * another part of TX latency on kernel/controller side before the + * controller starts processing the packet. + */ + + value = (int64_t)iso_io->now - (int64_t)get_reference_time(this, &duration_ns); + target = iso_io->duration * 3/2; + err = value - target; + + if (err > iso_io->duration) { + uint32_t req = err * port->current_format.info.raw.rate / SPA_NSEC_PER_SEC; + + spa_log_debug(this->log, "%p: ISO sync reset frames:%u", this, (unsigned int)req); + + spa_bt_rate_control_init(&port->ratectl, 0); + drop_frames(this, req); + } else if (-err > iso_io->duration) { + uint32_t req = -err * port->current_format.info.raw.rate / SPA_NSEC_PER_SEC; + + spa_log_debug(this->log, "%p: ISO sync skip flush frames:%u", this, (unsigned int)req); + return; + } else { + spa_bt_rate_control_update(&port->ratectl, err, 0, + iso_io->duration, period, RATE_CTL_DIFF_MAX); + spa_log_trace(this->log, "%p: ISO sync err:%+.3f value:%.3f target:%.3f (ms) corr:%g", + this, + port->ratectl.avg / SPA_NSEC_PER_MSEC, + value / SPA_NSEC_PER_MSEC, + target / SPA_NSEC_PER_MSEC, + port->ratectl.corr); + } + +done: + flush_data(this, this->current_time); +} + static void media_on_flush_error(struct spa_source *source) { struct impl *this = source->data; @@ -814,6 +1016,11 @@ spa_log_warn(this->log, "%p: error %d", this, source->rmask); if (this->flush_source.loop) spa_loop_remove_source(this->data_loop, &this->flush_source); + enable_flush_timer(this, false); + if (this->flush_timer_source.loop) + spa_loop_remove_source(this->data_loop, &this->flush_timer_source); + if (this->transport && this->transport->iso_io) + spa_bt_iso_io_set_cb(this->transport->iso_io, NULL, NULL); return; } } @@ -853,9 +1060,6 @@ uint64_t prev_time, now_time; int res; - if (this->transport == NULL) - return; - if (this->started) { if ((res = spa_system_timerfd_read(this->data_system, this->timerfd, &exp)) < 0) { if (res != -EAGAIN) @@ -872,25 +1076,29 @@ now_time, now_time - prev_time); if (SPA_LIKELY(this->position)) { - duration = this->position->clock.duration; - rate = this->position->clock.rate.denom; + duration = this->position->clock.target_duration; + rate = this->position->clock.target_rate.denom; } else { duration = 1024; rate = 48000; } - this->next_time = now_time + duration * SPA_NSEC_PER_SEC / rate; + setup_matching(this); + + this->next_time = now_time + duration * SPA_NSEC_PER_SEC / rate * port->ratectl.corr; if (SPA_LIKELY(this->clock)) { - int64_t delay_nsec; + int64_t delay_nsec = 0; this->clock->nsec = now_time; - this->clock->position += duration; + this->clock->rate = this->clock->target_rate; + this->clock->position += this->clock->duration; this->clock->duration = duration; - this->clock->rate_diff = 1.0f; + this->clock->rate_diff = 1 / port->ratectl.corr; this->clock->next_nsec = this->next_time; - delay_nsec = spa_bt_transport_get_delay_nsec(this->transport); + if (this->transport) + delay_nsec = spa_bt_transport_get_delay_nsec(this->transport); /* Negative delay doesn't work properly, so disallow it */ delay_nsec += SPA_CLAMP(this->props.latency_offset, -delay_nsec, INT64_MAX / 2); @@ -906,25 +1114,31 @@ set_timeout(this, this->next_time); } -static int do_start(struct impl *this) +static int do_start_iso_io(struct spa_loop *loop, bool async, uint32_t seq, + const void *data, size_t size, void *user_data) { - int res, val, size; + struct impl *this = user_data; + + spa_bt_iso_io_set_cb(this->transport->iso_io, media_iso_pull, this); + return 0; +} + +static int transport_start(struct impl *this) +{ + int val, size; struct port *port; socklen_t len; uint8_t *conf; uint32_t flags; - if (this->started) + if (this->transport_started) return 0; + if (!this->start_ready) + return -EIO; spa_return_val_if_fail(this->transport, -EIO); - this->following = is_following(this); - - spa_log_debug(this->log, "%p: start following:%d", this, this->following); - - if ((res = spa_bt_transport_acquire(this->transport, false)) < 0) - return res; + spa_log_debug(this->log, "%p: start transport", this); port = &this->port; @@ -950,7 +1164,7 @@ this->codec->bap ? "BAP" : "A2DP", this->codec->description, (int64_t)(spa_bt_transport_get_delay_nsec(this->transport) / SPA_NSEC_PER_MSEC)); - this->seqnum = 0; + this->seqnum = UINT16_MAX; this->block_size = this->codec->get_block_size(this->codec_data); if (this->block_size > sizeof(this->tmp_buffer)) { @@ -983,19 +1197,16 @@ reset_buffer(this); - this->source.data = this; - this->source.fd = this->timerfd; - this->source.func = media_on_timeout; - this->source.mask = SPA_IO_IN; - this->source.rmask = 0; - spa_loop_add_source(this->data_loop, &this->source); + spa_bt_rate_control_init(&port->ratectl, 0); - this->flush_timer_source.data = this; - this->flush_timer_source.fd = this->flush_timerfd; - this->flush_timer_source.func = media_on_flush_timeout; - this->flush_timer_source.mask = SPA_IO_IN; - this->flush_timer_source.rmask = 0; - spa_loop_add_source(this->data_loop, &this->flush_timer_source); + if (!this->transport->iso_io) { + this->flush_timer_source.data = this; + this->flush_timer_source.fd = this->flush_timerfd; + this->flush_timer_source.func = media_on_flush_timeout; + this->flush_timer_source.mask = SPA_IO_IN; + this->flush_timer_source.rmask = 0; + spa_loop_add_source(this->data_loop, &this->flush_timer_source); + } this->flush_source.data = this; this->flush_source.fd = this->transport->fd; @@ -1004,9 +1215,49 @@ this->flush_source.rmask = 0; spa_loop_add_source(this->data_loop, &this->flush_source); + this->resync = true; + this->flush_pending = false; + this->transport_started = true; + + if (this->transport->iso_io) + spa_loop_invoke(this->data_loop, do_start_iso_io, 0, NULL, 0, true, this); + + return 0; +} + +static int do_start(struct impl *this) +{ + int res; + + if (this->started) + return 0; + + spa_return_val_if_fail(this->transport, -EIO); + + this->following = is_following(this); + + spa_log_debug(this->log, "%p: start following:%d", this, this->following); + + this->start_ready = true; + + if ((res = spa_bt_transport_acquire(this->transport, false)) < 0) { + this->start_ready = false; + return res; + } + + this->source.data = this; + this->source.fd = this->timerfd; + this->source.func = media_on_timeout; + this->source.mask = SPA_IO_IN; + this->source.rmask = 0; + spa_loop_add_source(this->data_loop, &this->source); + + setup_matching(this); + set_timers(this); + this->started = true; return 0; @@ -1020,30 +1271,51 @@ void *user_data) { struct impl *this = user_data; - struct itimerspec ts; if (this->source.loop) spa_loop_remove_source(this->data_loop, &this->source); - ts.it_value.tv_sec = 0; - ts.it_value.tv_nsec = 0; - ts.it_interval.tv_sec = 0; - ts.it_interval.tv_nsec = 0; - spa_system_timerfd_settime(this->data_system, this->timerfd, 0, &ts, NULL); + set_timeout(this, 0); + return 0; +} + +static int do_remove_transport_source(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) +{ + struct impl *this = user_data; + + this->transport_started = false; if (this->flush_source.loop) spa_loop_remove_source(this->data_loop, &this->flush_source); if (this->flush_timer_source.loop) spa_loop_remove_source(this->data_loop, &this->flush_timer_source); - ts.it_value.tv_sec = 0; - ts.it_value.tv_nsec = 0; - ts.it_interval.tv_sec = 0; - ts.it_interval.tv_nsec = 0; - spa_system_timerfd_settime(this->data_system, this->flush_timerfd, 0, &ts, NULL); + enable_flush_timer(this, false); + + if (this->transport->iso_io) + spa_bt_iso_io_set_cb(this->transport->iso_io, NULL, NULL); return 0; } +static void transport_stop(struct impl *this) +{ + if (!this->transport_started) + return; + + spa_log_trace(this->log, "%p: stop transport", this); + + spa_loop_invoke(this->data_loop, do_remove_transport_source, 0, NULL, 0, true, this); + + if (this->codec_data) + this->codec->deinit(this->codec_data); + this->codec_data = NULL; +} + static int do_stop(struct impl *this) { int res = 0; @@ -1051,18 +1323,18 @@ if (!this->started) return 0; - spa_log_trace(this->log, "%p: stop", this); + spa_log_debug(this->log, "%p: stop", this); + + this->start_ready = false; spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, this); - this->started = false; + transport_stop(this); if (this->transport) res = spa_bt_transport_release(this->transport); - if (this->codec_data) - this->codec->deinit(this->codec_data); - this->codec_data = NULL; + this->started = false; return res; } @@ -1281,6 +1553,14 @@ 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: + if (!this->codec->bap) + return 0; + 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; } @@ -1430,7 +1710,7 @@ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; - spa_log_debug(this->log, "use buffers %d", n_buffers); + spa_log_debug(this->log, "%p: use buffers %d", this, n_buffers); clear_buffers(this, port); @@ -1477,6 +1757,11 @@ case SPA_IO_Buffers: port->io = data; break; + case SPA_IO_RateMatch: + if (!this->codec->bap) + return -ENOENT; + port->rate_match = data; + break; default: return -ENOENT; } @@ -1493,6 +1778,7 @@ struct impl *this = object; struct port *port; struct spa_io_buffers *io; + int res; spa_return_val_if_fail(this != NULL, -EINVAL); @@ -1505,6 +1791,9 @@ return SPA_STATUS_HAVE_DATA; } + if (!this->started || !this->transport_started) + return SPA_STATUS_OK; + if (io->status == SPA_STATUS_HAVE_DATA && io->buffer_id < port->n_buffers) { struct buffer *b = &port->buffersio->buffer_id; @@ -1533,11 +1822,23 @@ } } + if (this->position) { + this->process_duration = this->position->clock.duration; + this->process_rate = this->position->clock.rate.denom; + } else { + this->process_duration = 1024; + this->process_rate = 48000; + } + this->process_time = this->current_time; + this->resync = false; - if (!spa_list_is_empty(&port->ready)) { - spa_log_trace(this->log, "%p: flush on process", this); - flush_data(this, this->current_time); + setup_matching(this); + + spa_log_trace(this->log, "%p: on process time:%"PRIu64, this, this->process_time); + if ((res = flush_data(this, this->current_time)) < 0) { + io->status = res; + return SPA_STATUS_STOPPED; } return SPA_STATUS_HAVE_DATA; @@ -1593,26 +1894,35 @@ enum spa_bt_transport_state state) { struct impl *this = data; + bool was_started = this->transport_started; spa_log_debug(this->log, "%p: transport %p state %d->%d", this, this->transport, old, state); - if (state < SPA_BT_TRANSPORT_STATE_ACTIVE && old == SPA_BT_TRANSPORT_STATE_ACTIVE && - this->started) { - uint8_t buffer1024; - struct spa_pod_builder b = { 0 }; - - spa_log_debug(this->log, "%p: transport %p becomes inactive: stop and indicate error", - this, this->transport); + if (state == SPA_BT_TRANSPORT_STATE_ACTIVE) + transport_start(this); + else + transport_stop(this); + if (state < SPA_BT_TRANSPORT_STATE_ACTIVE && was_started) { /* * If establishing connection fails due to remote end not activating * the transport, we won't get a write error, but instead see a transport * state change. * - * Stop and emit a node error, to let upper levels handle it. + * Treat this as a transport error, so that upper levels don't try to + * retry too often. */ - do_stop(this); + spa_log_debug(this->log, "%p: transport %p becomes inactive: stop and indicate error", + this, this->transport); + + spa_bt_transport_set_state(this->transport, SPA_BT_TRANSPORT_STATE_ERROR); + return; + } + + if (state == SPA_BT_TRANSPORT_STATE_ERROR) { + uint8_t buffer1024; + struct spa_pod_builder b = { 0 }; spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_node_emit_event(&this->hooks,
View file
pipewire-0.3.67.tar.gz/spa/plugins/bluez5/media-source.c -> pipewire-0.3.68.tar.gz/spa/plugins/bluez5/media-source.c
Changed
@@ -114,7 +114,8 @@ struct port port; unsigned int started:1; - unsigned int transport_acquired:1; + unsigned int start_ready:1; + unsigned int transport_started:1; unsigned int following:1; unsigned int matching:1; unsigned int resampling:1; @@ -258,7 +259,8 @@ struct port *port = &this->port; set_timers(this); - spa_bt_decode_buffer_recover(&port->buffer); + if (this->transport_started) + spa_bt_decode_buffer_recover(&port->buffer); return 0; } @@ -561,12 +563,15 @@ { struct port *port = &this->port; + if (!this->transport_started) + port->buffer.corr = 1.0; + if (this->position && port->rate_match) { port->rate_match->rate = 1 / port->buffer.corr; this->matching = this->following; this->resampling = this->matching || - (port->current_format.info.raw.rate != this->position->clock.rate.denom); + (port->current_format.info.raw.rate != this->position->clock.target_rate.denom); } else { this->matching = false; this->resampling = false; @@ -607,8 +612,8 @@ now_time, now_time - prev_time); if (SPA_LIKELY(this->position)) { - duration = this->position->clock.duration; - rate = this->position->clock.rate.denom; + duration = this->position->clock.target_duration; + rate = this->position->clock.target_rate.denom; } else { duration = 1024; rate = 48000; @@ -620,7 +625,8 @@ if (SPA_LIKELY(this->clock)) { this->clock->nsec = now_time; - this->clock->position += duration; + this->clock->rate = this->clock->target_rate; + this->clock->position += this->clock->duration; this->clock->duration = duration; this->clock->rate_diff = port->buffer.corr; this->clock->next_nsec = this->next_time; @@ -643,15 +649,15 @@ struct port *port = &this->port; uint32_t flags; - if (this->transport_acquired) + if (this->transport_started) return 0; + if (!this->start_ready) + return -EIO; - spa_log_debug(this->log, "%p: transport %p acquire", this, - this->transport); - if ((res = spa_bt_transport_acquire(this->transport, false)) < 0) - return res; + spa_return_val_if_fail(this->transport != NULL, -EIO); - this->transport_acquired = true; + spa_log_debug(this->log, "%p: start transport state:%d", + this, this->transport->state); flags = this->is_duplex ? 0 : MEDIA_CODEC_FLAG_SINK; @@ -724,25 +730,16 @@ set_duplex_timeout(this, this->duplex_timeout); } - this->timer_source.data = this; - this->timer_source.fd = this->timerfd; - this->timer_source.func = media_on_timeout; - this->timer_source.mask = SPA_IO_IN; - this->timer_source.rmask = 0; - spa_loop_add_source(this->data_loop, &this->timer_source); - this->sample_count = 0; - setup_matching(this); - - set_timers(this); + this->transport_started = true; return 0; } static int do_start(struct impl *this) { - int res = 0; + int res; if (this->started) return 0; @@ -751,16 +748,31 @@ this->following = is_following(this); - spa_log_debug(this->log, "%p: start state:%d following:%d", - this, this->transport->state, this->following); + this->start_ready = true; - if (this->transport->state >= SPA_BT_TRANSPORT_STATE_PENDING || - this->is_duplex || this->codec->bap) - res = transport_start(this); + spa_log_debug(this->log, "%p: start following:%d", this, this->following); + + spa_log_debug(this->log, "%p: transport %p acquire", this, + this->transport); + if ((res = spa_bt_transport_acquire(this->transport, false)) < 0) { + this->start_ready = false; + return res; + } + + this->timer_source.data = this; + this->timer_source.fd = this->timerfd; + this->timer_source.func = media_on_timeout; + this->timer_source.mask = SPA_IO_IN; + this->timer_source.rmask = 0; + spa_loop_add_source(this->data_loop, &this->timer_source); + + setup_matching(this); + + set_timers(this); this->started = true; - return res; + return 0; } static int do_remove_source(struct spa_loop *loop, @@ -771,54 +783,58 @@ void *user_data) { struct impl *this = user_data; - struct itimerspec ts; spa_log_debug(this->log, "%p: remove source", this); + if (this->timer_source.loop) + spa_loop_remove_source(this->data_loop, &this->timer_source); + set_timeout(this, 0); + + return 0; +} + +static int do_remove_transport_source(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) +{ + struct impl *this = user_data; + + spa_log_debug(this->log, "%p: remove transport source", this); + + this->transport_started = false; + set_duplex_timeout(this, 0); if (this->source.loop) spa_loop_remove_source(this->data_loop, &this->source); - if (this->timer_source.loop) - spa_loop_remove_source(this->data_loop, &this->timer_source); - ts.it_value.tv_sec = 0; - ts.it_value.tv_nsec = 0; - ts.it_interval.tv_sec = 0; - ts.it_interval.tv_nsec = 0; - spa_system_timerfd_settime(this->data_system, this->timerfd, 0, &ts, NULL); - return 0; } -static int transport_stop(struct impl *this) +static void transport_stop(struct impl *this) { struct port *port = &this->port; - int res; + + if (!this->transport_started) + return; spa_log_debug(this->log, "%p: transport stop", this); - spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, this); + spa_loop_invoke(this->data_loop, do_remove_transport_source, 0, NULL, 0, true, this); if (this->fd >= 0) { close(this->fd); this->fd = -1; } - if (this->transport && this->transport_acquired) - res = spa_bt_transport_release(this->transport); - else - res = 0; - - this->transport_acquired = false; - if (this->codec_data) this->codec->deinit(this->codec_data); this->codec_data = NULL; spa_bt_decode_buffer_clear(&port->buffer); - - return res; } static int do_stop(struct impl *this) @@ -830,7 +846,16 @@ spa_log_debug(this->log, "%p: stop", this); - res = transport_stop(this); + this->start_ready = false; + + spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, this); + + transport_stop(this); + + if (this->transport) + res = spa_bt_transport_release(this->transport); + else + res = 0; this->started = false; @@ -1294,30 +1319,27 @@ return 0; } -static uint32_t get_samples(struct impl *this, uint32_t *duration) +static uint32_t get_samples(struct impl *this, uint32_t *result_duration) { struct port *port = &this->port; - uint32_t samples; + uint32_t samples, rate_denom; + uint64_t duration; - if (SPA_LIKELY(port->rate_match) && this->resampling) { - samples = port->rate_match->size; + if (SPA_LIKELY(this->position)) { + duration = this->position->clock.duration; + rate_denom = this->position->clock.rate.denom; } else { - if (SPA_LIKELY(this->position)) - samples = this->position->clock.duration * port->current_format.info.raw.rate - / this->position->clock.rate.denom; - else - samples = 1024; + duration = 1024; + rate_denom = port->current_format.info.raw.rate; } - if (SPA_LIKELY(this->position)) - *duration = this->position->clock.duration * port->current_format.info.raw.rate - / this->position->clock.rate.denom; - else if (SPA_LIKELY(this->clock)) - *duration = this->clock->duration * port->current_format.info.raw.rate - / this->clock->rate.denom; - else - *duration = 1024 * port->current_format.info.raw.rate / 48000; + *result_duration = duration * port->current_format.info.raw.rate / rate_denom; + if (SPA_LIKELY(port->rate_match) && this->resampling) { + samples = port->rate_match->size; + } else { + samples = *result_duration; + } return samples; } @@ -1444,8 +1466,14 @@ io->buffer_id = SPA_ID_INVALID; } + if (!this->source.loop) { + io->status = -EIO; + return SPA_STATUS_STOPPED; + } + /* Handle buffering */ - process_buffering(this); + if (this->transport_started) + process_buffering(this); /* Return if there are no buffers ready to be processed */ if (spa_list_is_empty(&port->ready)) @@ -1476,6 +1504,9 @@ if ((io = port->io) == NULL) return -EIO; + if (!this->started || !this->transport_started) + return SPA_STATUS_OK; + spa_log_trace(this->log, "%p status:%d", this, io->status); /* Return if we already have a buffer */ @@ -1514,6 +1545,30 @@ .process = impl_node_process, }; +static void transport_state_changed(void *data, + enum spa_bt_transport_state old, + enum spa_bt_transport_state state) +{ + struct impl *this = data; + + spa_log_debug(this->log, "%p: transport %p state %d->%d", this, this->transport, old, state); + + if (state == SPA_BT_TRANSPORT_STATE_ACTIVE) + transport_start(this); + else + transport_stop(this); + + if (state == SPA_BT_TRANSPORT_STATE_ERROR) { + uint8_t buffer1024; + struct spa_pod_builder b = { 0 }; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_node_emit_event(&this->hooks, + spa_pod_builder_add_object(&b, + SPA_TYPE_EVENT_Node, SPA_NODE_EVENT_Error)); + } +} + static void transport_delay_changed(void *data) { struct impl *this = data; @@ -1531,7 +1586,6 @@ { struct impl *this = user_data; this->transport = NULL; - this->transport_acquired = false; return 0; } @@ -1545,6 +1599,7 @@ static const struct spa_bt_transport_events transport_events = { SPA_VERSION_BT_TRANSPORT_EVENTS, .delay_changed = transport_delay_changed, + .state_changed = transport_state_changed, .destroy = transport_destroy, };
View file
pipewire-0.3.67.tar.gz/spa/plugins/bluez5/meson.build -> pipewire-0.3.68.tar.gz/spa/plugins/bluez5/meson.build
Changed
@@ -19,6 +19,7 @@ 'sco-sink.c', 'sco-source.c', 'sco-io.c', + 'iso-io.c', 'quirks.c', 'player.c', 'bluez5-device.c',
View file
pipewire-0.3.67.tar.gz/spa/plugins/bluez5/midi-node.c -> pipewire-0.3.68.tar.gz/spa/plugins/bluez5/midi-node.c
Changed
@@ -868,13 +868,20 @@ spa_log_trace(this->log, "%p: timer %"PRIu64" %"PRIu64"", this, now_time, now_time - prev_time); - update_position(this); + if (SPA_LIKELY(this->position)) { + this->duration = this->position->clock.target_duration; + this->rate = this->position->clock.target_rate.denom; + } else { + this->duration = 1024; + this->rate = 48000; + } this->next_time = now_time + this->duration * SPA_NSEC_PER_SEC / this->rate; if (SPA_LIKELY(this->clock)) { this->clock->nsec = now_time; - this->clock->position += this->duration; + this->clock->rate = this->clock->target_rate; + this->clock->position += this->clock->duration; this->clock->duration = this->duration; this->clock->rate_diff = 1.0f; this->clock->next_nsec = this->next_time; @@ -1103,17 +1110,10 @@ void *user_data) { struct impl *this = user_data; - struct itimerspec ts; if (this->timer_source.loop) spa_loop_remove_source(this->data_loop, &this->timer_source); - - ts.it_value.tv_sec = 0; - ts.it_value.tv_nsec = 0; - ts.it_interval.tv_sec = 0; - ts.it_interval.tv_nsec = 0; - spa_system_timerfd_settime(this->data_system, this->timerfd, 0, &ts, NULL); - + set_timeout(this, 0); return 0; } @@ -1217,7 +1217,6 @@ void *user_data) { struct impl *this = user_data; - set_timers(this); return 0; }
View file
pipewire-0.3.68.tar.gz/spa/plugins/bluez5/rate-control.h
Added
@@ -0,0 +1,133 @@ +/* Spa Bluez5 rate control */ +/* SPDX-FileCopyrightText: Copyright © 2022 Pauli Virtanen */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_BLUEZ5_RATE_CONTROL_H +#define SPA_BLUEZ5_RATE_CONTROL_H + +#include <spa/utils/defs.h> + +/** + * 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, double rate_diff_max) +{ + /* + * 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 - rate_diff_max, 1 + rate_diff_max); + + return this->corr; +} + +#endif
View file
pipewire-0.3.67.tar.gz/spa/plugins/bluez5/sco-io.c -> pipewire-0.3.68.tar.gz/spa/plugins/bluez5/sco-io.c
Changed
@@ -89,7 +89,7 @@ int res; read_again: - res = read(io->fd, io->read_buffer, SPA_MIN(io->read_mtu, MAX_MTU)); + res = recv(io->fd, io->read_buffer, SPA_MIN(io->read_mtu, MAX_MTU), MSG_DONTWAIT); if (res <= 0) { if (errno == EINTR) { /* retry if interrupted */ @@ -165,7 +165,7 @@ do { int written; - written = write(io->fd, buf, packet_size); + written = send(io->fd, buf, packet_size, MSG_DONTWAIT | MSG_NOSIGNAL); if (written < 0) { if (errno == EINTR) { /* retry if interrupted */
View file
pipewire-0.3.67.tar.gz/spa/plugins/bluez5/sco-sink.c -> pipewire-0.3.68.tar.gz/spa/plugins/bluez5/sco-sink.c
Changed
@@ -116,6 +116,8 @@ /* Flags */ unsigned int started:1; + unsigned int start_ready:1; + unsigned int transport_started:1; unsigned int following:1; unsigned int flush_pending:1; @@ -360,9 +362,17 @@ return bytes / port->frame_size; } -static void flush_data(struct impl *this) +static int flush_data(struct impl *this) { struct port *port = &this->port; + int processed = 0; + int written; + + spa_assert(this->transport_started); + + if (this->transport == NULL || this->transport->sco_io == NULL || !this->flush_timer_source.loop) + return -EIO; + const uint32_t min_in_size = (this->transport->codec == HFP_AUDIO_CODEC_MSBC) ? MSBC_DECODED_SIZE : this->transport->write_mtu; @@ -372,18 +382,13 @@ const uint32_t packet_samples = min_in_size / port->frame_size; const uint64_t packet_time = (uint64_t)packet_samples * SPA_NSEC_PER_SEC / port->current_format.info.raw.rate; - int processed = 0; - int written; - - if (this->transport == NULL || this->transport->sco_io == NULL) - return; while (!spa_list_is_empty(&port->ready) && port->write_buffer_size < min_in_size) { struct spa_data *datas; /* get buffer */ if (!port->current_buffer) { - spa_return_if_fail(!spa_list_is_empty(&port->ready)); + spa_return_val_if_fail(!spa_list_is_empty(&port->ready), -EIO); port->current_buffer = spa_list_first(&port->ready, struct buffer, link); port->ready_offset = 0; } @@ -418,14 +423,14 @@ if (this->flush_pending) { spa_log_trace(this->log, "%p: wait for flush timer", this); - return; + return 0; } if (port->write_buffer_size < min_in_size) { /* wait for more data */ spa_log_trace(this->log, "%p: skip flush", this); enable_flush_timer(this, false); - return; + return 0; } if (this->transport->codec == HFP_AUDIO_CODEC_MSBC) { @@ -445,7 +450,7 @@ this->buffer_next + 2, MSBC_ENCODED_SIZE - 3, &out_encoded); if (processed < 0) { spa_log_warn(this->log, "sbc_encode failed: %d", processed); - return; + return -EINVAL; } this->buffer_next += out_encoded + 3; port->write_buffer_size = 0; @@ -539,18 +544,19 @@ } enable_flush_timer(this, true); - return; + return 0; stop: - if (this->source.loop) - spa_loop_remove_source(this->data_loop, &this->source); enable_flush_timer(this, false); + if (this->flush_timer_source.loop) + spa_loop_remove_source(this->data_loop, &this->flush_timer_source); + return -EIO; } static void sco_on_flush_timeout(struct spa_source *source) { struct impl *this = source->data; - uint64_t exp; + uint64_t exp = 0; int res; spa_log_trace(this->log, "%p: flush on timeout", this); @@ -582,9 +588,6 @@ uint64_t prev_time, now_time; int res; - if (this->transport == NULL) - return; - if (this->started) { if ((res = spa_system_timerfd_read(this->data_system, this->timerfd, &exp)) < 0) { if (res != -EAGAIN) @@ -600,8 +603,8 @@ now_time, now_time - prev_time); if (SPA_LIKELY(this->position)) { - duration = this->position->clock.duration; - rate = this->position->clock.rate.denom; + duration = this->position->clock.target_duration; + rate = this->position->clock.target_rate.denom; } else { duration = 1024; rate = 48000; @@ -611,7 +614,8 @@ if (SPA_LIKELY(this->clock)) { this->clock->nsec = now_time; - this->clock->position += duration; + this->clock->rate = this->clock->target_rate; + this->clock->position += this->clock->duration; this->clock->duration = duration; this->clock->rate_diff = 1.0f; this->clock->next_nsec = this->next_time; @@ -639,28 +643,22 @@ return (a*b)/gcd(a,b); } -static int do_start(struct impl *this) +static int transport_start(struct impl *this) { - bool do_accept; int res; /* Don't do anything if the node has already started */ - if (this->started) + if (this->transport_started) return 0; + if (!this->start_ready) + return -EIO; /* Make sure the transport is valid */ spa_return_val_if_fail(this->transport != NULL, -EIO); this->following = is_following(this); - spa_log_debug(this->log, "%p: start following:%d", this, this->following); - - /* Do accept if Gateway; otherwise do connect for Head Unit */ - do_accept = this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY; - - /* acquire the socket fd (false -> connect | true -> accept) */ - if ((res = spa_bt_transport_acquire(this->transport, do_accept)) < 0) - return res; + spa_log_debug(this->log, "%p: start transport", this); /* Init mSBC if needed */ if (this->transport->codec == HFP_AUDIO_CODEC_MSBC) { @@ -689,14 +687,6 @@ if ((res = spa_bt_transport_ensure_sco_io(this->transport, this->data_loop)) < 0) goto fail; - /* Add the timeout callback */ - this->source.data = this; - this->source.fd = this->timerfd; - this->source.func = sco_on_timeout; - this->source.mask = SPA_IO_IN; - this->source.rmask = 0; - spa_loop_add_source(this->data_loop, &this->source); - this->flush_timer_source.data = this; this->flush_timer_source.fd = this->flush_timerfd; this->flush_timer_source.func = sco_on_flush_timeout; @@ -706,20 +696,58 @@ /* start processing */ this->flush_pending = false; - set_timers(this); /* Set the started flag */ - this->started = true; + this->transport_started = true; return 0; fail: free(this->buffer); this->buffer = NULL; - spa_bt_transport_release(this->transport); return res; } +static int do_start(struct impl *this) +{ + bool do_accept; + int res; + + if (this->started) + return 0; + + spa_return_val_if_fail(this->transport, -EIO); + + this->following = is_following(this); + + this->start_ready = true; + + spa_log_debug(this->log, "%p: start following:%d", this, this->following); + + /* Do accept if Gateway; otherwise do connect for Head Unit */ + do_accept = this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY; + + /* acquire the socket fd (false -> connect | true -> accept) */ + if ((res = spa_bt_transport_acquire(this->transport, do_accept)) < 0) { + this->start_ready = false; + return res; + } + + /* Add the timeout callback */ + this->source.data = this; + this->source.fd = this->timerfd; + this->source.func = sco_on_timeout; + this->source.mask = SPA_IO_IN; + this->source.rmask = 0; + spa_loop_add_source(this->data_loop, &this->source); + + set_timers(this); + + this->started = true; + + return 0; +} + /* Drop any buffered data remaining in the port */ static void drop_port_output(struct impl *this) { @@ -748,19 +776,28 @@ void *user_data) { struct impl *this = user_data; - struct itimerspec ts; - set_timeout(this, 0); if (this->source.loop) spa_loop_remove_source(this->data_loop, &this->source); + set_timeout(this, 0); + + return 0; +} + +static int do_remove_transport_source(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) +{ + struct impl *this = user_data; + + this->transport_started = false; if (this->flush_timer_source.loop) spa_loop_remove_source(this->data_loop, &this->flush_timer_source); - ts.it_value.tv_sec = 0; - ts.it_value.tv_nsec = 0; - ts.it_interval.tv_sec = 0; - ts.it_interval.tv_nsec = 0; - spa_system_timerfd_settime(this->data_system, this->flush_timerfd, 0, &ts, NULL); + enable_flush_timer(this, false); /* Drop buffered data in the ready queue. Ideally there shouldn't be any. */ drop_port_output(this); @@ -768,29 +805,43 @@ return 0; } -static int do_stop(struct impl *this) +static void transport_stop(struct impl *this) { - int res = 0; - - if (!this->started) - return 0; - - spa_log_trace(this->log, "sco-sink %p: stop", this); + if (!this->transport_started) + return; - spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, this); + spa_log_trace(this->log, "sco-sink %p: transport stop", this); - this->started = false; + spa_loop_invoke(this->data_loop, do_remove_transport_source, 0, NULL, 0, true, this); if (this->buffer) { free(this->buffer); this->buffer = NULL; this->buffer_head = this->buffer_next = this->buffer; } +} - if (this->transport) { - /* Release the transport; it is responsible for closing the fd */ +static int do_stop(struct impl *this) +{ + int res; + + if (!this->started) + return 0; + + spa_log_debug(this->log, "%p: stop", this); + + this->start_ready = false; + + spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, this); + + transport_stop(this); + + if (this->transport) res = spa_bt_transport_release(this->transport); - } + else + res = 0; + + this->started = false; return res; } @@ -1242,6 +1293,9 @@ return SPA_STATUS_HAVE_DATA; } + if (!this->started || !this->transport_started) + return SPA_STATUS_OK; + if (io->status == SPA_STATUS_HAVE_DATA && io->buffer_id < port->n_buffers) { struct buffer *b = &port->buffersio->buffer_id; @@ -1272,8 +1326,12 @@ this->process_time = this->current_time; if (!spa_list_is_empty(&port->ready)) { + int res; spa_log_trace(this->log, "%p: flush on process", this); - flush_data(this); + if ((res = flush_data(this)) < 0) { + io->status = res; + return SPA_STATUS_STOPPED; + } } return SPA_STATUS_HAVE_DATA; @@ -1298,6 +1356,30 @@ .process = impl_node_process, }; +static void transport_state_changed(void *data, + enum spa_bt_transport_state old, + enum spa_bt_transport_state state) +{ + struct impl *this = data; + + spa_log_debug(this->log, "%p: transport %p state %d->%d", this, this->transport, old, state); + + if (state == SPA_BT_TRANSPORT_STATE_ACTIVE) + transport_start(this); + else if (state < SPA_BT_TRANSPORT_STATE_ACTIVE) + transport_stop(this); + + if (state == SPA_BT_TRANSPORT_STATE_ERROR) { + uint8_t buffer1024; + struct spa_pod_builder b = { 0 }; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_node_emit_event(&this->hooks, + spa_pod_builder_add_object(&b, + SPA_TYPE_EVENT_Node, SPA_NODE_EVENT_Error)); + } +} + static int do_transport_destroy(struct spa_loop *loop, bool async, uint32_t seq, @@ -1319,6 +1401,7 @@ static const struct spa_bt_transport_events transport_events = { SPA_VERSION_BT_TRANSPORT_EVENTS, + .state_changed = transport_state_changed, .destroy = transport_destroy, };
View file
pipewire-0.3.67.tar.gz/spa/plugins/bluez5/sco-source.c -> pipewire-0.3.68.tar.gz/spa/plugins/bluez5/sco-source.c
Changed
@@ -113,9 +113,12 @@ struct port port; unsigned int started:1; + unsigned int start_ready:1; + unsigned int transport_started:1; unsigned int following:1; unsigned int matching:1; unsigned int resampling:1; + unsigned int io_error:1; struct spa_source timer_source; int timerfd; @@ -230,7 +233,8 @@ struct port *port = &this->port; set_timers(this); - spa_bt_decode_buffer_recover(&port->buffer); + if (this->transport_started) + spa_bt_decode_buffer_recover(&port->buffer); return 0; } @@ -495,6 +499,10 @@ uint32_t decoded; uint64_t dt; + /* Drop data when not started */ + if (!this->started) + return 0; + if (this->transport == NULL) { spa_log_debug(this->log, "no transport, stop reading"); goto stop; @@ -548,6 +556,7 @@ return 0; stop: + this->io_error = true; return 1; } @@ -555,12 +564,15 @@ { struct port *port = &this->port; + if (!this->transport_started) + port->buffer.corr = 1.0; + if (this->position && port->rate_match) { port->rate_match->rate = 1 / port->buffer.corr; this->matching = this->following; this->resampling = this->matching || - (port->current_format.info.raw.rate != this->position->clock.rate.denom); + (port->current_format.info.raw.rate != this->position->clock.target_rate.denom); } else { this->matching = false; this->resampling = false; @@ -583,9 +595,6 @@ uint64_t prev_time, now_time; int res; - if (this->transport == NULL) - return; - if (this->started) { if ((res = spa_system_timerfd_read(this->data_system, this->timerfd, &exp)) < 0) { if (res != -EAGAIN) @@ -602,8 +611,8 @@ now_time, now_time - prev_time); if (SPA_LIKELY(this->position)) { - duration = this->position->clock.duration; - rate = this->position->clock.rate.denom; + duration = this->position->clock.target_duration; + rate = this->position->clock.target_rate.denom; } else { duration = 1024; rate = 48000; @@ -615,7 +624,8 @@ if (SPA_LIKELY(this->clock)) { this->clock->nsec = now_time; - this->clock->position += duration; + this->clock->rate = this->clock->target_rate; + this->clock->position += this->clock->duration; this->clock->duration = duration; this->clock->rate_diff = port->buffer.corr; this->clock->next_nsec = this->next_time; @@ -646,31 +656,22 @@ return 0; } -static int do_start(struct impl *this) +static int transport_start(struct impl *this) { struct port *port = &this->port; - bool do_accept; int res; /* Don't do anything if the node has already started */ - if (this->started) + if (this->transport_started) return 0; + if (!this->start_ready) + return -EIO; - this->following = is_following(this); - - spa_log_debug(this->log, "%p: start following:%d", - this, this->following); + spa_log_debug(this->log, "%p: start transport", this); /* Make sure the transport is valid */ spa_return_val_if_fail (this->transport != NULL, -EIO); - /* Do accept if Gateway; otherwise do connect for Head Unit */ - do_accept = this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY; - - /* acquire the socket fd (false -> connect | true -> accept) */ - if ((res = spa_bt_transport_acquire(this->transport, do_accept)) < 0) - return res; - /* Reset the buffers and sample count */ reset_buffers(port); @@ -694,11 +695,47 @@ this->msbc_buffer_pos = 0; } + this->io_error = false; + /* Start socket i/o */ if ((res = spa_bt_transport_ensure_sco_io(this->transport, this->data_loop)) < 0) goto fail; spa_loop_invoke(this->data_loop, do_add_source, 0, NULL, 0, true, this); + /* Set the started flag */ + this->transport_started = true; + + return 0; + +fail: + return res; +} + +static int do_start(struct impl *this) +{ + bool do_accept; + int res; + + if (this->started) + return 0; + + spa_return_val_if_fail(this->transport, -EIO); + + this->following = is_following(this); + + this->start_ready = true; + + spa_log_debug(this->log, "%p: start following:%d", this, this->following); + + /* Do accept if Gateway; otherwise do connect for Head Unit */ + do_accept = this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY; + + /* acquire the socket fd (false -> connect | true -> accept) */ + if ((res = spa_bt_transport_acquire(this->transport, do_accept)) < 0) { + this->start_ready = false; + return res; + } + /* Start timer */ this->timer_source.data = this; this->timer_source.fd = this->timerfd; @@ -708,16 +745,12 @@ spa_loop_add_source(this->data_loop, &this->timer_source); setup_matching(this); + set_timers(this); - /* Set the started flag */ this->started = true; return 0; - -fail: - spa_bt_transport_release(this->transport); - return res; } static int do_remove_source(struct spa_loop *loop, @@ -728,42 +761,66 @@ void *user_data) { struct impl *this = user_data; - struct itimerspec ts; - - if (this->transport && this->transport->sco_io) - spa_bt_sco_io_set_source_cb(this->transport->sco_io, NULL, NULL); if (this->timer_source.loop) spa_loop_remove_source(this->data_loop, &this->timer_source); - ts.it_value.tv_sec = 0; - ts.it_value.tv_nsec = 0; - ts.it_interval.tv_sec = 0; - ts.it_interval.tv_nsec = 0; - spa_system_timerfd_settime(this->data_system, this->timerfd, 0, &ts, NULL); + set_timeout(this, 0); return 0; } -static int do_stop(struct impl *this) +static int do_remove_transport_source(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) +{ + struct impl *this = user_data; + + this->transport_started = false; + + if (this->transport && this->transport->sco_io) + spa_bt_sco_io_set_source_cb(this->transport->sco_io, NULL, NULL); + + return 0; +} + +static void transport_stop(struct impl *this) { struct port *port = &this->port; - int res = 0; + + if (!this->transport_started) + return; + + spa_log_debug(this->log, "sco-source %p: transport stop", this); + + spa_loop_invoke(this->data_loop, do_remove_transport_source, 0, NULL, 0, true, this); + + spa_bt_decode_buffer_clear(&port->buffer); +} + +static int do_stop(struct impl *this) +{ + int res; if (!this->started) return 0; - spa_log_debug(this->log, "sco-source %p: stop", this); + spa_log_debug(this->log, "%p: stop", this); + + this->start_ready = false; spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, this); - this->started = false; + transport_stop(this); - if (this->transport) { - /* Release the transport; it is responsible for closing the fd */ + if (this->transport) res = spa_bt_transport_release(this->transport); - } + else + res = 0; - spa_bt_decode_buffer_clear(&port->buffer); + this->started = false; return res; } @@ -1217,29 +1274,26 @@ return 0; } -static uint32_t get_samples(struct impl *this, uint32_t *duration) +static uint32_t get_samples(struct impl *this, uint32_t *result_duration) { struct port *port = &this->port; - uint32_t samples; + uint32_t samples, rate_denom; + uint64_t duration; - if (SPA_LIKELY(port->rate_match) && this->resampling) { - samples = port->rate_match->size; + if (SPA_LIKELY(this->position)) { + duration = this->position->clock.duration; + rate_denom = this->position->clock.rate.denom; } else { - if (SPA_LIKELY(this->position)) - samples = this->position->clock.duration * port->current_format.info.raw.rate - / this->position->clock.rate.denom; - else - samples = 1024; + duration = 1024; + rate_denom = port->current_format.info.raw.rate; } - if (SPA_LIKELY(this->position)) - *duration = this->position->clock.duration * port->current_format.info.raw.rate - / this->position->clock.rate.denom; - else if (SPA_LIKELY(this->clock)) - *duration = this->clock->duration * port->current_format.info.raw.rate - / this->clock->rate.denom; + *result_duration = duration * port->current_format.info.raw.rate / rate_denom; + + if (SPA_LIKELY(port->rate_match) && this->resampling) + samples = port->rate_match->size; else - *duration = 1024 * port->current_format.info.raw.rate / 48000; + samples = *result_duration; return samples; } @@ -1315,8 +1369,14 @@ io->buffer_id = SPA_ID_INVALID; } + if (this->io_error) { + io->status = -EIO; + return SPA_STATUS_STOPPED; + } + /* Handle buffering */ - process_buffering(this); + if (this->transport_started) + process_buffering(this); /* Return if there are no buffers ready to be processed */ if (spa_list_is_empty(&port->ready)) @@ -1347,6 +1407,9 @@ if ((io = port->io) == NULL) return -EIO; + if (!this->started || !this->transport_started) + return SPA_STATUS_OK; + spa_log_trace(this->log, "%p status:%d", this, io->status); /* Return if we already have a buffer */ @@ -1385,6 +1448,30 @@ .process = impl_node_process, }; +static void transport_state_changed(void *data, + enum spa_bt_transport_state old, + enum spa_bt_transport_state state) +{ + struct impl *this = data; + + spa_log_debug(this->log, "%p: transport %p state %d->%d", this, this->transport, old, state); + + if (state == SPA_BT_TRANSPORT_STATE_ACTIVE) + transport_start(this); + else if (state < SPA_BT_TRANSPORT_STATE_ACTIVE) + transport_stop(this); + + if (state == SPA_BT_TRANSPORT_STATE_ERROR) { + uint8_t buffer1024; + struct spa_pod_builder b = { 0 }; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_node_emit_event(&this->hooks, + spa_pod_builder_add_object(&b, + SPA_TYPE_EVENT_Node, SPA_NODE_EVENT_Error)); + } +} + static int do_transport_destroy(struct spa_loop *loop, bool async, uint32_t seq, @@ -1406,6 +1493,7 @@ static const struct spa_bt_transport_events transport_events = { SPA_VERSION_BT_TRANSPORT_EVENTS, + .state_changed = transport_state_changed, .destroy = transport_destroy, };
View file
pipewire-0.3.67.tar.gz/spa/plugins/libcamera/libcamera-utils.cpp -> pipewire-0.3.68.tar.gz/spa/plugins/libcamera/libcamera-utils.cpp
Changed
@@ -885,9 +885,12 @@ } const FrameMetadata &fmd = buffer->metadata(); - - if (impl->clock) { + /* FIXME, we should follow the driver clock and target_ values. + * for now we ignore and use our own. */ + impl->clock->target_rate = port->rate; + impl->clock->target_duration = 1; + impl->clock->nsec = fmd.timestamp; impl->clock->rate = port->rate; impl->clock->position = fmd.sequence;
View file
pipewire-0.3.67.tar.gz/spa/plugins/meson.build -> pipewire-0.3.68.tar.gz/spa/plugins/meson.build
Changed
@@ -1,4 +1,4 @@ -if alsa_dep.found() +if alsa_dep.found() and host_machine.system() == 'linux' subdir('alsa') endif if get_option('avb').require(host_machine.system() == 'linux', error_message: 'AVB support is only available on Linux').allowed()
View file
pipewire-0.3.67.tar.gz/spa/plugins/support/loop.c -> pipewire-0.3.68.tar.gz/spa/plugins/support/loop.c
Changed
@@ -333,7 +333,7 @@ impl->enter_count = 1; } else { spa_return_if_fail(impl->enter_count > 0); - spa_return_if_fail(impl->thread == thread_id); + spa_return_if_fail(pthread_equal(impl->thread, thread_id)); impl->enter_count++; } spa_log_trace(impl->log, "%p: enter %p", impl, (void *) impl->thread); @@ -345,7 +345,7 @@ pthread_t thread_id = pthread_self(); spa_return_if_fail(impl->enter_count > 0); - spa_return_if_fail(impl->thread == thread_id); + spa_return_if_fail(pthread_equal(impl->thread, thread_id)); spa_log_trace(impl->log, "%p: leave %p", impl, (void *) impl->thread); @@ -356,6 +356,13 @@ } } +static int loop_check(void *object) +{ + struct impl *impl = object; + pthread_t thread_id = pthread_self(); + return (impl->thread == 0 || pthread_equal(impl->thread, thread_id)) ? 1 : 0; +} + static inline void free_source(struct source_impl *s) { detach_source(&s->source); @@ -826,6 +833,7 @@ .enter = loop_enter, .leave = loop_leave, .iterate = loop_iterate, + .check = loop_check, }; static const struct spa_loop_utils_methods impl_loop_utils = {
View file
pipewire-0.3.67.tar.gz/spa/plugins/support/node-driver.c -> pipewire-0.3.68.tar.gz/spa/plugins/support/node-driver.c
Changed
@@ -88,9 +88,13 @@ clockid_t id; } clock_info = { { "realtime", CLOCK_REALTIME }, +#ifdef CLOCK_TAI { "tai", CLOCK_TAI }, +#endif { "monotonic", CLOCK_MONOTONIC }, +#ifdef CLOCK_MONOTONIC_RAW { "monotonic-raw", CLOCK_MONOTONIC_RAW }, +#endif { "boottime", CLOCK_BOOTTIME }, }; @@ -145,7 +149,7 @@ spa_log_debug(this->log, "%p now:%"PRIu64, this, this->next_time); - if (this->following) { + if (this->following || !this->started) { set_timeout(this, 0); } else { set_timeout(this, this->next_time); @@ -158,7 +162,7 @@ return this->position && this->clock && this->position->clock.id != this->clock->id; } -static int do_reassign_follower(struct spa_loop *loop, +static int do_set_timers(struct spa_loop *loop, bool async, uint32_t seq, const void *data, @@ -185,7 +189,7 @@ if (following != this->following) { spa_log_debug(this->log, NAME" %p: reassign follower %d->%d", this, this->following, following); this->following = following; - spa_loop_invoke(this->data_loop, do_reassign_follower, 0, NULL, 0, true, this); + spa_loop_invoke(this->data_loop, do_set_timers, 0, NULL, 0, true, this); } return 0; } @@ -243,8 +247,8 @@ return; } if (SPA_LIKELY(this->position)) { - duration = this->position->clock.duration; - rate = this->position->clock.rate.denom; + duration = this->position->clock.target_duration; + rate = this->position->clock.target_rate.denom; } else { duration = 1024; rate = 48000; @@ -259,15 +263,14 @@ current_position = scale_u64(current_time, rate, SPA_NSEC_PER_SEC); - if (SPA_LIKELY(this->clock)) - position = this->clock->position; - else - position = current_position; - if (this->last_time == 0) { spa_dll_set_bw(&this->dll, SPA_DLL_BW_MIN, duration, rate); this->max_error = rate * MAX_ERROR_MS / 1000; position = current_position; + } else if (SPA_LIKELY(this->clock)) { + position = this->clock->position + this->clock->duration; + } else { + position = current_position; } /* check the elapsed time of the other clock against @@ -279,7 +282,6 @@ else if (err < -this->max_error) err = -this->max_error; - position += duration; this->last_time = current_time; if (this->tracking) { @@ -287,7 +289,7 @@ this->next_time = nsec + duration / corr * 1e9 / rate; } else { corr = 1.0; - this->next_time = scale_u64(position, SPA_NSEC_PER_SEC, rate); + this->next_time = scale_u64(position + duration, SPA_NSEC_PER_SEC, rate); } if (SPA_UNLIKELY((this->next_time - this->base_time) > BW_PERIOD)) { @@ -300,6 +302,7 @@ if (SPA_LIKELY(this->clock)) { this->clock->nsec = nsec; + this->clock->rate = this->clock->target_rate; this->clock->position = position; this->clock->duration = duration; this->clock->delay = 0; @@ -319,9 +322,9 @@ return 0; this->following = is_following(this); - set_timers(this); this->started = true; this->last_time = 0; + spa_loop_invoke(this->data_loop, do_set_timers, 0, NULL, 0, true, this); return 0; } @@ -330,7 +333,7 @@ if (!this->started) return 0; this->started = false; - set_timeout(this, 0); + spa_loop_invoke(this->data_loop, do_set_timers, 0, NULL, 0, true, this); return 0; }
View file
pipewire-0.3.67.tar.gz/spa/plugins/support/null-audio-sink.c -> pipewire-0.3.68.tar.gz/spa/plugins/support/null-audio-sink.c
Changed
@@ -191,7 +191,7 @@ return res; this->next_time = SPA_TIMESPEC_TO_NSEC(&now); - if (this->following) { + if (this->following || !this->started) { set_timeout(this, 0); } else { set_timeout(this, this->next_time); @@ -204,7 +204,7 @@ return this->position && this->clock && this->position->clock.id != this->clock->id; } -static int do_reassign_follower(struct spa_loop *loop, +static int do_set_timers(struct spa_loop *loop, bool async, uint32_t seq, const void *data, @@ -227,7 +227,7 @@ if (following != this->following) { spa_log_debug(this->log, NAME" %p: reassign follower %d->%d", this, this->following, following); this->following = following; - spa_loop_invoke(this->data_loop, do_reassign_follower, 0, NULL, 0, true, this); + spa_loop_invoke(this->data_loop, do_set_timers, 0, NULL, 0, true, this); } return 0; } @@ -280,8 +280,8 @@ nsec = this->next_time; if (SPA_LIKELY(this->position)) { - duration = this->position->clock.duration; - rate = this->position->clock.rate.denom; + duration = this->position->clock.target_duration; + rate = this->position->clock.target_rate.denom; } else { duration = 1024; rate = 48000; @@ -291,7 +291,8 @@ if (SPA_LIKELY(this->clock)) { this->clock->nsec = nsec; - this->clock->position += duration; + this->clock->rate = this->clock->target_rate; + this->clock->position += this->clock->duration; this->clock->duration = duration; this->clock->delay = 0; this->clock->rate_diff = 1.0; @@ -309,8 +310,8 @@ return 0; this->following = is_following(this); - set_timers(this); this->started = true; + spa_loop_invoke(this->data_loop, do_set_timers, 0, NULL, 0, true, this); return 0; } @@ -319,7 +320,7 @@ if (!this->started) return 0; this->started = false; - set_timeout(this, 0); + spa_loop_invoke(this->data_loop, do_set_timers, 0, NULL, 0, true, this); return 0; }
View file
pipewire-0.3.67.tar.gz/spa/plugins/v4l2/v4l2-utils.c -> pipewire-0.3.68.tar.gz/spa/plugins/v4l2/v4l2-utils.c
Changed
@@ -1289,6 +1289,11 @@ spa_log_trace(this->log, "v4l2 %p: have output %d", this, buf.index); if (this->clock) { + /* FIXME, we should follow the driver clock and target_ values. + * for now we ignore and use our own. */ + this->clock->target_rate = port->rate; + this->clock->target_duration = 1; + this->clock->nsec = pts; this->clock->rate = port->rate; this->clock->position = buf.sequence;
View file
pipewire-0.3.67.tar.gz/spa/plugins/volume/volume.c -> pipewire-0.3.68.tar.gz/spa/plugins/volume/volume.c
Changed
@@ -525,7 +525,7 @@ b->flags = direction == SPA_DIRECTION_INPUT ? BUFFER_FLAG_OUT : 0; b->h = spa_buffer_find_meta_data(buffersi, SPA_META_Header, sizeof(*b->h)); - if (d0.data == NULL) { + if (d0.data != NULL) { b->ptr = d0.data; b->size = d0.maxsize; } else {
View file
pipewire-0.3.68.tar.gz/src/daemon/client-rt.conf.avail
Added
+(directory)
View file
pipewire-0.3.68.tar.gz/src/daemon/client-rt.conf.avail/20-upmix.conf.in
Added
@@ -0,0 +1,8 @@ +# Enables upmixing +stream.properties = { + channelmix.upmix = true + channelmix.upmix-method = psd # none, simple + channelmix.lfe-cutoff = 150 + channelmix.fc-cutoff = 12000 + channelmix.rear-delay = 12.0 +}
View file
pipewire-0.3.68.tar.gz/src/daemon/client-rt.conf.avail/meson.build
Added
@@ -0,0 +1,11 @@ +conf_files = + '20-upmix.conf', + + +foreach c : conf_files + configure_file(input : '@0@.in'.format(c), + output : c, + configuration : conf_config, + install_dir : pipewire_confdatadir / 'client-rt.conf.avail') +endforeach +
View file
pipewire-0.3.68.tar.gz/src/daemon/client.conf.avail
Added
+(directory)
View file
pipewire-0.3.68.tar.gz/src/daemon/client.conf.avail/20-upmix.conf.in
Added
@@ -0,0 +1,8 @@ +# Enables upmixing +stream.properties = { + channelmix.upmix = true + channelmix.upmix-method = psd # none, simple + channelmix.lfe-cutoff = 150 + channelmix.fc-cutoff = 12000 + channelmix.rear-delay = 12.0 +}
View file
pipewire-0.3.68.tar.gz/src/daemon/client.conf.avail/meson.build
Added
@@ -0,0 +1,11 @@ +conf_files = + '20-upmix.conf', + + +foreach c : conf_files + configure_file(input : '@0@.in'.format(c), + output : c, + configuration : conf_config, + install_dir : pipewire_confdatadir / 'client.conf.avail') +endforeach +
View file
pipewire-0.3.67.tar.gz/src/daemon/meson.build -> pipewire-0.3.68.tar.gz/src/daemon/meson.build
Changed
@@ -86,31 +86,18 @@ output : 'pipewire-uninstalled.conf', configuration : conf_config_uninstalled) -pipewire_exec = executable('pipewire', - pipewire_daemon_sources, - install: true, - c_args : pipewire_c_args, - include_directories : configinc , - dependencies : spa_dep, pipewire_dep, , -) - -executable('pipewire-pulse', - pipewire_daemon_sources, - install: true, - c_args : pipewire_c_args, - include_directories : configinc , - dependencies : spa_dep, pipewire_dep, , -) +conf_avail_folders = + 'pipewire.conf.avail', + 'client.conf.avail', + 'client-rt.conf.avail', + 'pipewire-pulse.conf.avail', + -executable('pipewire-avb', - pipewire_daemon_sources, - install: true, - c_args : pipewire_c_args, - include_directories : configinc , - dependencies : spa_dep, pipewire_dep, , -) +foreach c : conf_avail_folders + subdir(c) +endforeach -executable('pipewire-aes67', +pipewire_exec = executable('pipewire', pipewire_daemon_sources, install: true, c_args : pipewire_c_args, @@ -120,6 +107,22 @@ ln = find_program('ln') +foreach alias : 'pipewire-pulse', 'pipewire-avb', 'pipewire-aes67' + custom_target( + alias, + build_by_default: true, + install: false, + command: ln, '-sf', meson.project_build_root() + '/@INPUT@', '@OUTPUT@', + input: pipewire_exec, + output: alias, + ) + install_symlink( + alias, + pointing_to: pipewire_exec.name(), + install_dir: pipewire_bindir, + ) +endforeach + custom_target('pipewire-uninstalled', build_by_default: true, install: false,
View file
pipewire-0.3.67.tar.gz/src/daemon/minimal.conf.in -> pipewire-0.3.68.tar.gz/src/daemon/minimal.conf.in
Changed
@@ -326,7 +326,6 @@ # link.output.port = capture_1 # link.input.node = my-mic # link.input.port = input_FL - # link.passive = true # } #} #{ factory = link-factory @@ -335,7 +334,6 @@ # link.output.port = capture_2 # link.input.node = my-mic # link.input.port = input_FR - # link.passive = true # } #}
View file
pipewire-0.3.68.tar.gz/src/daemon/pipewire-pulse.conf.avail
Added
+(directory)
View file
pipewire-0.3.68.tar.gz/src/daemon/pipewire-pulse.conf.avail/20-upmix.conf.in
Added
@@ -0,0 +1,8 @@ +# Enables upmixing +stream.properties = { + channelmix.upmix = true + channelmix.upmix-method = psd # none, simple + channelmix.lfe-cutoff = 150 + channelmix.fc-cutoff = 12000 + channelmix.rear-delay = 12.0 +}
View file
pipewire-0.3.68.tar.gz/src/daemon/pipewire-pulse.conf.avail/meson.build
Added
@@ -0,0 +1,11 @@ +conf_files = + '20-upmix.conf', + + +foreach c : conf_files + configure_file(input : '@0@.in'.format(c), + output : c, + configuration : conf_config, + install_dir : pipewire_confdatadir / 'pipewire-pulse.conf.avail') +endforeach +
View file
pipewire-0.3.68.tar.gz/src/daemon/pipewire.conf.avail
Added
+(directory)
View file
pipewire-0.3.68.tar.gz/src/daemon/pipewire.conf.avail/10-rates.conf.in
Added
@@ -0,0 +1,4 @@ +# Adds more common rates +context.properties = { + default.clock.allowed-rates = 44100 48000 88200 96000 +}
View file
pipewire-0.3.68.tar.gz/src/daemon/pipewire.conf.avail/20-upmix.conf.in
Added
@@ -0,0 +1,8 @@ +# Enables upmixing +stream.properties = { + channelmix.upmix = true + channelmix.upmix-method = psd # none, simple + channelmix.lfe-cutoff = 150 + channelmix.fc-cutoff = 12000 + channelmix.rear-delay = 12.0 +}
View file
pipewire-0.3.68.tar.gz/src/daemon/pipewire.conf.avail/meson.build
Added
@@ -0,0 +1,12 @@ +conf_files = + '10-rates.conf', + '20-upmix.conf', + + +foreach c : conf_files + configure_file(input : '@0@.in'.format(c), + output : c, + configuration : conf_config, + install_dir : pipewire_confdatadir / 'pipewire.conf.avail') +endforeach +
View file
pipewire-0.3.67.tar.gz/src/examples/export-spa.c -> pipewire-0.3.68.tar.gz/src/examples/export-spa.c
Changed
@@ -36,7 +36,7 @@ uint32_t id; }; -static void proxy_event_bound(void *_data, uint32_t global_id) +static void proxy_event_bound_props(void *_data, uint32_t global_id, const struct spa_dict *props) { struct data *data = _data; if (data->id != global_id) { @@ -47,7 +47,7 @@ static const struct pw_proxy_events proxy_events = { PW_VERSION_PROXY_EVENTS, - .bound = proxy_event_bound, + .bound_props = proxy_event_bound_props, }; static int make_node(struct data *data)
View file
pipewire-0.3.67.tar.gz/src/gst/gstpipewiredeviceprovider.c -> pipewire-0.3.68.tar.gz/src/gst/gstpipewiredeviceprovider.c
Changed
@@ -210,6 +210,7 @@ const struct pw_node_info *info = data->info; const gchar *element = NULL; GstPipeWireDevice *gstdev; + int priority = 0; if (info->max_input_ports > 0 && info->max_output_ports == 0) { type = GST_PIPEWIRE_DEVICE_TYPE_SINK; @@ -224,11 +225,16 @@ props = gst_structure_new_empty ("pipewire-proplist"); if (info->props) { const struct spa_dict_item *item; + const char *str; + spa_dict_for_each (item, info->props) gst_structure_set (props, item->key, G_TYPE_STRING, item->value, NULL); klass = spa_dict_lookup (info->props, PW_KEY_MEDIA_CLASS); name = spa_dict_lookup (info->props, PW_KEY_NODE_DESCRIPTION); + + if ((str = spa_dict_lookup(info->props, PW_KEY_PRIORITY_SESSION))) + priority = atoi(str); } if (klass == NULL) klass = "unknown/unknown"; @@ -244,26 +250,56 @@ gstdev->serial = data->serial; gstdev->type = type; gstdev->element = element; + gstdev->priority = priority; if (props) gst_structure_free (props); return GST_DEVICE (gstdev); } +static int +compare_device_session_priority (const void *a, + const void *b) +{ + const GstPipeWireDevice *dev_a = a; + const GstPipeWireDevice *dev_b = b; + + if (dev_a->priority < dev_b->priority) + return 1; + else if (dev_a->priority > dev_b->priority) + return -1; + else + return 0; +} + static void do_add_nodes(GstPipeWireDeviceProvider *self) { struct node_data *nd; + GList *new_devices = NULL; + GList *l; spa_list_for_each(nd, &self->nodes, link) { if (nd->dev != NULL) continue; pw_log_info("add node %d", nd->id); nd->dev = new_node (self, nd); - if (nd->dev) { - if(self->list_only) - self->devices = g_list_prepend (self->devices, gst_object_ref_sink (nd->dev)); - else - gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), nd->dev); + if (nd->dev) + new_devices = g_list_prepend (new_devices, nd->dev); + } + if (!new_devices) + return; + + new_devices = g_list_sort (new_devices, + compare_device_session_priority); + for (l = new_devices; l != NULL; l = l->next) { + GstDevice *device = l->data; + + if(self->list_only) { + self->devices = g_list_insert_sorted (self->devices, + gst_object_ref_sink (device), + compare_device_session_priority); + } else { + gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), device); } } }
View file
pipewire-0.3.67.tar.gz/src/gst/gstpipewiredeviceprovider.h -> pipewire-0.3.68.tar.gz/src/gst/gstpipewiredeviceprovider.h
Changed
@@ -39,6 +39,7 @@ uint64_t serial; int fd; const gchar *element; + int priority; }; struct _GstPipeWireDeviceClass {
View file
pipewire-0.3.67.tar.gz/src/modules/meson.build -> pipewire-0.3.68.tar.gz/src/modules/meson.build
Changed
@@ -28,6 +28,8 @@ 'module-rt.c', 'module-raop-discover.c', 'module-raop-sink.c', + 'module-rtp-sap.c', + 'module-rtp-session.c', 'module-rtp-source.c', 'module-rtp-sink.c', 'module-session-manager.c', @@ -153,7 +155,7 @@ install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, - dependencies : mathlib, dl_lib, pipewire_dep, + dependencies : mathlib, dl_lib, pipewire_dep, audioconvert_dep, ) pipewire_module_profiler = shared_library('pipewire-module-profiler', @@ -519,16 +521,41 @@ summary({'ROC': roc_dep.found()}, bool_yn: true, section: 'Streaming between daemons') pipewire_module_rtp_source = shared_library('pipewire-module-rtp-source', - 'module-rtp-source.c' , + 'module-rtp-source.c', + 'module-rtp/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, + dependencies : mathlib, dl_lib, rt_lib, pipewire_dep, opus_dep, ) pipewire_module_rtp_sink = shared_library('pipewire-module-rtp-sink', - 'module-rtp-sink.c' , + 'module-rtp-sink.c', + 'module-rtp/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, opus_dep, +) + +build_module_rtp_session = avahi_dep.found() +if build_module_rtp_session + pipewire_module_rtp_session = shared_library('pipewire-module-rtp-session', + 'module-rtp/stream.c', + 'module-zeroconf-discover/avahi-poll.c', + 'module-rtp-session.c' , + include_directories : configinc, + install : true, + install_dir : modules_install_dir, + install_rpath: modules_install_dir, + dependencies : mathlib, dl_lib, rt_lib, pipewire_dep, avahi_dep, opus_dep, + ) +endif + +pipewire_module_rtp_sap = shared_library('pipewire-module-rtp-sap', + 'module-rtp-sap.c' , include_directories : configinc, install : true, install_dir : modules_install_dir,
View file
pipewire-0.3.67.tar.gz/src/modules/module-access.c -> pipewire-0.3.68.tar.gz/src/modules/module-access.c
Changed
@@ -111,10 +111,10 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic -#define MODULE_USAGE " access.force=flatpak " \ - " access.allowed=<cmd-line> " \ - " access.rejected=<cmd-line> " \ - " access.restricted=<cmd-line> " \ +#define MODULE_USAGE "( access.force=flatpak ) " \ + "( access.allowed= <cmd-line>,.. ) " \ + "( access.rejected= <cmd-line>,.. ) " \ + "( access.restricted= <cmd-line>,.. ) " \ static const struct spa_dict_item module_props = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
View file
pipewire-0.3.67.tar.gz/src/modules/module-adapter.c -> pipewire-0.3.68.tar.gz/src/modules/module-adapter.c
Changed
@@ -26,7 +26,7 @@ #define PW_LOG_TOPIC_DEFAULT mod_topic #define FACTORY_USAGE SPA_KEY_FACTORY_NAME"=<factory-name> " \ - ""SPA_KEY_LIBRARY_NAME"=<library-name> " \ + "("SPA_KEY_LIBRARY_NAME"=<library-name>) " \ ADAPTER_USAGE static const struct spa_dict_item module_props = { @@ -203,7 +203,7 @@ handle = pw_context_load_spa_handle(d->context, factory_name, - properties ? &properties->dict : NULL); + &properties->dict); if (handle == NULL) goto error_errno;
View file
pipewire-0.3.67.tar.gz/src/modules/module-avb/avdecc.c -> pipewire-0.3.68.tar.gz/src/modules/module-avb/avdecc.c
Changed
@@ -240,6 +240,7 @@ struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props) { struct server *server; + const char *str; int res = 0; server = calloc(1, sizeof(*server)); @@ -248,7 +249,8 @@ server->impl = impl; spa_list_append(&impl->servers, &server->link); - server->ifname = strdup(spa_dict_lookup(props, "ifname")); + str = spa_dict_lookup(props, "ifname"); + server->ifname = str ? strdup(str) : NULL; spa_hook_list_init(&server->listener_list); spa_list_init(&server->descriptors); spa_list_init(&server->streams); @@ -309,7 +311,7 @@ if (server->source) pw_loop_destroy_source(impl->loop, server->source); if (server->timer) - pw_loop_destroy_source(impl->loop, server->source); + pw_loop_destroy_source(impl->loop, server->timer); spa_hook_list_clean(&server->listener_list); free(server); }
View file
pipewire-0.3.67.tar.gz/src/modules/module-client-node/remote-node.c -> pipewire-0.3.68.tar.gz/src/modules/module-client-node/remote-node.c
Changed
@@ -1143,18 +1143,20 @@ client_node_removed(_data); } -static void client_node_bound(void *_data, uint32_t global_id) +static void client_node_bound_props(void *_data, uint32_t global_id, const struct spa_dict *props) { struct node_data *data = _data; pw_log_debug("%p: bound %u", data, global_id); data->remote_id = global_id; + if (props) + pw_properties_update(data->node->properties, props); } static const struct pw_proxy_events proxy_client_node_events = { PW_VERSION_PROXY_EVENTS, .removed = client_node_removed, .destroy = client_node_destroy, - .bound = client_node_bound, + .bound_props = client_node_bound_props, }; static int node_ready(void *d, int status)
View file
pipewire-0.3.67.tar.gz/src/modules/module-combine-stream.c -> pipewire-0.3.68.tar.gz/src/modules/module-combine-stream.c
Changed
@@ -35,6 +35,10 @@ * - a new virtual sink that forwards audio to other sinks * - a new virtual source that combines audio from other sources * + * The sources and sink that need to be combined can be selected using generic match + * rules. This makes it possible to combine static nodes or nodes based on certain + * properties. + * * ## Module Options * * - `node.name`: a unique name for the stream @@ -42,6 +46,7 @@ * - `combine.mode` = capture | playback | sink | source, default sink * - `combine.props = {}`: properties to be passed to the sink/source * - `stream.props = {}`: properties to be passed to the streams + * - `stream.rules = {}`: rules for matching streams, use create-stream actions * * ## General options * @@ -198,15 +203,15 @@ #define DEFAULT_CHANNELS 2 #define DEFAULT_POSITION " FL FR " -#define MODULE_USAGE " node.latency=<latency as fraction> " \ - " combine.mode=<mode of stream, playback|capture|sink|source>, default:sink " \ - " node.name=<name of the stream> " \ - " node.description=<description of the stream> " \ - " audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS) "> " \ - " audio.position=<channel map, default:"DEFAULT_POSITION"> " \ - " combine.props=<properties> " \ - " stream.props=<properties> " \ - " stream.rules=<properties> " +#define MODULE_USAGE "( node.latency=<latency as fraction> ) " \ + "( combine.mode=<mode of stream, playback|capture|sink|source>, default:sink ) " \ + "( node.name=<name of the stream> ) " \ + "( node.description=<description of the stream> ) " \ + "( audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS) "> ) " \ + "( audio.position=<channel map, default:"DEFAULT_POSITION"> ) " \ + "( combine.props=<properties> ) " \ + "( stream.props=<properties> ) " \ + "( stream.rules=<properties> ) " static const struct spa_dict_item module_props = { @@ -576,7 +581,7 @@ if (impl->mode == MODE_CAPTURE || impl->mode == MODE_SINK) str = " { matches = { media.class = \"Audio/Sink\" } " " actions = { create-stream = {} } } "; - else if (impl->mode == MODE_PLAYBACK || impl->mode == MODE_SOURCE) + else str = " { matches = { media.class = \"Audio/Source\" } " " actions = { create-stream = {} } } "; }
View file
pipewire-0.3.67.tar.gz/src/modules/module-echo-cancel.c -> pipewire-0.3.68.tar.gz/src/modules/module-echo-cancel.c
Changed
@@ -33,6 +33,8 @@ #include <spa/support/plugin-loader.h> #include <spa/interfaces/audio/aec.h> +#include <spa/plugins/audioconvert/wavfile.h> + #include <pipewire/impl.h> #include <pipewire/pipewire.h> @@ -155,19 +157,19 @@ static const struct spa_dict_item module_props = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "Echo Cancellation" }, - { PW_KEY_MODULE_USAGE, " remote.name=<remote> " - " node.latency=<latency as fraction> " - " audio.rate=<sample rate> " - " audio.channels=<number of channels> " - " audio.position=<channel map> " - " buffer.max_size=<max buffer size in ms> " - " buffer.play_delay=<delay as fraction> " - " library.name =<library name> " - " aec.args=<aec arguments> " - " capture.props=<properties> " - " source.props=<properties> " - " sink.props=<properties> " - " playback.props=<properties> " }, + { PW_KEY_MODULE_USAGE, " ( remote.name=<remote> ) " + "( node.latency=<latency as fraction> ) " + "( audio.rate=<sample rate> ) " + "( audio.channels=<number of channels> ) " + "( audio.position=<channel map> ) " + "( buffer.max_size=<max buffer size in ms> ) " + "( buffer.play_delay=<delay as fraction> ) " + "( library.name =<library name> ) " + "( aec.args=<aec arguments> ) " + "( capture.props=<properties> ) " + "( source.props=<properties> ) " + "( sink.props=<properties> ) " + "( playback.props=<properties> ) " }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; @@ -231,8 +233,50 @@ struct spa_plugin_loader *loader; bool monitor_mode; + + char wav_path512; + struct wav_file *wav_file; }; +static inline void aec_run(struct impl *impl, const float *rec, const float *play, + float *out, uint32_t n_samples) +{ + spa_audio_aec_run(impl->aec, rec, play, out, n_samples); + + if (SPA_UNLIKELY(impl->wav_path0)) { + if (impl->wav_file == NULL) { + struct wav_file_info info; + + info.info.info.raw = impl->info; + info.info.info.raw.channels *= 3; + info.info.media_type = SPA_MEDIA_TYPE_audio; + info.info.media_subtype = SPA_MEDIA_SUBTYPE_raw; + + impl->wav_file = wav_file_open(impl->wav_path, + "w", &info); + if (impl->wav_file == NULL) + pw_log_warn("can't open wav path '%s': %m", + impl->wav_path); + } + if (impl->wav_file) { + uint32_t i, c = impl->info.channels; + const float *datac * 3; + + for (i = 0; i < c; i++) { + datai = playi; + datai + c = reci; + datai + 2*c = outi; + } + wav_file_write(impl->wav_file, (void*)data, n_samples); + } else { + spa_zero(impl->wav_path); + } + } else if (impl->wav_file != NULL) { + wav_file_close(impl->wav_file); + impl->wav_file = NULL; + } +} + static void process(struct impl *impl) { struct pw_buffer *cout; @@ -326,11 +370,11 @@ pdi = play_delayedi + delay_left; oi = outi + delay_left; } - spa_audio_aec_run(impl->aec, rec, pd, o, size / sizeof(float) - delay_left); + aec_run(impl, rec, pd, o, size / sizeof(float) - delay_left); } } else { /* run the canceller */ - spa_audio_aec_run(impl->aec, rec, play_delayed, out, size / sizeof(float)); + aec_run(impl, rec, play_delayed, out, size / sizeof(float)); } /* Next, copy over the output to the output ringbuffer */ @@ -520,6 +564,27 @@ } } +static void reset_buffers(struct impl *impl) +{ + uint32_t index, i; + + spa_ringbuffer_init(&impl->rec_ring); + spa_ringbuffer_init(&impl->play_ring); + spa_ringbuffer_init(&impl->play_delayed_ring); + spa_ringbuffer_init(&impl->out_ring); + + for (i = 0; i < impl->info.channels; i++) { + memset(impl->rec_bufferi, 0, impl->rec_ringsize); + memset(impl->play_bufferi, 0, impl->play_ringsize); + memset(impl->out_bufferi, 0, impl->out_ringsize); + } + + spa_ringbuffer_get_write_index(&impl->play_ring, &index); + spa_ringbuffer_write_update(&impl->play_ring, index + (sizeof(float) * (impl->buffer_delay))); + spa_ringbuffer_get_read_index(&impl->play_ring, &index); + spa_ringbuffer_read_update(&impl->play_ring, index + (sizeof(float) * (impl->buffer_delay))); +} + static void input_param_latency_changed(struct impl *impl, const struct spa_pod *param) { struct spa_latency_info latency; @@ -538,21 +603,64 @@ else pw_stream_update_params(impl->capture, params, 1); } + static struct spa_pod* get_props_param(struct impl* impl, struct spa_pod_builder* b) { - if (spa_audio_aec_get_params(impl->aec, NULL) > 0) { - struct spa_pod_frame f2; - spa_pod_builder_push_object( - b, &f0, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); - spa_pod_builder_prop(b, SPA_PROP_params, 0); - spa_pod_builder_push_struct(b, &f1); + struct spa_pod_frame f2; + spa_pod_builder_push_object( + b, &f0, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); + spa_pod_builder_prop(b, SPA_PROP_params, 0); + spa_pod_builder_push_struct(b, &f1); + + spa_pod_builder_string(b, "debug.aec.wav-path"); + spa_pod_builder_string(b, impl->wav_path); + + if (spa_audio_aec_get_params(impl->aec, NULL) > 0) spa_audio_aec_get_params(impl->aec, b); - spa_pod_builder_pop(b, &f1); - return spa_pod_builder_pop(b, &f0); + spa_pod_builder_pop(b, &f1); + return spa_pod_builder_pop(b, &f0); +} + +static int set_params(struct impl* impl, const struct spa_pod *params) +{ + struct spa_pod_parser prs; + struct spa_pod_frame f; + int changed = 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_none(pod)) { + spa_zero(value); + } else + continue; + + pw_log_info("key:'%s' val:'%s'", name, value); + + if (spa_streq(name, "debug.aec.wav-path")) { + spa_scnprintf(impl->wav_path, + sizeof(impl->wav_path), "%s", value); + changed++; + } } - return NULL; + spa_audio_aec_set_params(impl->aec, params); + return 1; } static void input_param_changed(void *data, uint32_t id, const struct spa_pod* param) @@ -560,7 +668,12 @@ struct spa_pod_object* obj = (struct spa_pod_object*)param; const struct spa_pod_prop* prop; struct impl* impl = data; + switch (id) { + case SPA_PARAM_Format: + if (param == NULL) + reset_buffers(impl); + break; case SPA_PARAM_Latency: input_param_latency_changed(impl, param); break; @@ -569,18 +682,18 @@ uint8_t buffer1024; struct spa_pod_dynamic_builder b; const struct spa_pod* params1; - SPA_POD_OBJECT_FOREACH(obj, prop) - { - if (prop->key == SPA_PROP_params) { - spa_audio_aec_set_params(impl->aec, &prop->value); - } + + SPA_POD_OBJECT_FOREACH(obj, prop) { + if (prop->key == SPA_PROP_params) + set_params(impl, &prop->value); } spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); params0 = get_props_param(impl, &b.b); if (params0) { pw_stream_update_params(impl->capture, params, 1); - pw_stream_update_params(impl->playback, params, 1); + if (impl->playback != NULL) + pw_stream_update_params(impl->playback, params, 1); } spa_pod_dynamic_builder_clean(&b); } else { @@ -661,7 +774,12 @@ struct spa_pod_object *obj = (struct spa_pod_object *) param; const struct spa_pod_prop *prop; struct impl *impl = data; + switch (id) { + case SPA_PARAM_Format: + if (param == NULL) + reset_buffers(impl); + break; case SPA_PARAM_Latency: output_param_latency_changed(impl, param); break; @@ -681,7 +799,8 @@ params0 = get_props_param(impl, &b.b); if (params0 != NULL) { pw_stream_update_params(impl->capture, params, 1); - pw_stream_update_params(impl->playback, params, 1); + if (impl->playback != NULL) + pw_stream_update_params(impl->playback, params, 1); } spa_pod_dynamic_builder_clean(&b); } @@ -789,7 +908,6 @@ uint32_t offsets512; const struct spa_pod *params512; struct spa_pod_dynamic_builder b; - uint32_t index; impl->capture = pw_stream_new(impl->core, "Echo-Cancel Capture", impl->capture_props); @@ -924,15 +1042,8 @@ impl->play_bufferi = malloc(impl->play_ringsize); impl->out_bufferi = malloc(impl->out_ringsize); } - spa_ringbuffer_init(&impl->rec_ring); - spa_ringbuffer_init(&impl->play_ring); - spa_ringbuffer_init(&impl->play_delayed_ring); - spa_ringbuffer_init(&impl->out_ring); - spa_ringbuffer_get_write_index(&impl->play_ring, &index); - spa_ringbuffer_write_update(&impl->play_ring, index + (sizeof(float) * (impl->buffer_delay))); - spa_ringbuffer_get_read_index(&impl->play_ring, &index); - spa_ringbuffer_read_update(&impl->play_ring, index + (sizeof(float) * (impl->buffer_delay))); + reset_buffers(impl); return 0; }
View file
pipewire-0.3.67.tar.gz/src/modules/module-example-sink.c -> pipewire-0.3.68.tar.gz/src/modules/module-example-sink.c
Changed
@@ -83,14 +83,14 @@ #define DEFAULT_CHANNELS 2 #define DEFAULT_POSITION " FL FR " -#define MODULE_USAGE " node.latency=<latency as fraction> " \ - " node.name=<name of the nodes> " \ - " node.description=<description of the nodes> " \ - " audio.format=<format, default:"DEFAULT_FORMAT"> " \ - " audio.rate=<sample rate, default: "SPA_STRINGIFY(DEFAULT_RATE)"> " \ - " audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS) "> " \ - " audio.position=<channel map, default:"DEFAULT_POSITION"> " \ - " stream.props=<properties> " +#define MODULE_USAGE "( node.latency=<latency as fraction> ) " \ + "( node.name=<name of the nodes> ) " \ + "( node.description=<description of the nodes> ) " \ + "( audio.format=<format, default:"DEFAULT_FORMAT"> ) " \ + "( audio.rate=<sample rate, default: "SPA_STRINGIFY(DEFAULT_RATE)"> ) " \ + "( audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS) "> ) " \ + "( audio.position=<channel map, default:"DEFAULT_POSITION"> " \ + "( stream.props=<properties> ) " static const struct spa_dict_item module_props = {
View file
pipewire-0.3.67.tar.gz/src/modules/module-example-source.c -> pipewire-0.3.68.tar.gz/src/modules/module-example-source.c
Changed
@@ -83,14 +83,14 @@ #define DEFAULT_CHANNELS 2 #define DEFAULT_POSITION " FL FR " -#define MODULE_USAGE " node.latency=<latency as fraction> " \ - " node.name=<name of the nodes> " \ - " node.description=<description of the nodes> " \ - " audio.format=<format, default:"DEFAULT_FORMAT"> " \ - " audio.rate=<sample rate, default: "SPA_STRINGIFY(DEFAULT_RATE)"> " \ - " audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS) "> " \ - " audio.position=<channel map, default:"DEFAULT_POSITION"> " \ - " stream.props=<properties> " +#define MODULE_USAGE "( node.latency=<latency as fraction> ) " \ + "( node.name=<name of the nodes> ) " \ + "( node.description=<description of the nodes> ) " \ + "( audio.format=<format, default:"DEFAULT_FORMAT"> ) " \ + "( audio.rate=<sample rate, default: "SPA_STRINGIFY(DEFAULT_RATE)"> ) " \ + "( audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS) "> ) " \ + "( audio.position=<channel map, default:"DEFAULT_POSITION"> ) " \ + "( stream.props=<properties> ) " static const struct spa_dict_item module_props = {
View file
pipewire-0.3.67.tar.gz/src/modules/module-fallback-sink.c -> pipewire-0.3.68.tar.gz/src/modules/module-fallback-sink.c
Changed
@@ -33,8 +33,8 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic -#define MODULE_USAGE (" sink.name=<str> " \ - " sink.description=<str> ") +#define MODULE_USAGE ("( sink.name=<str> ) " \ + "( sink.description=<str> ) ") static const struct spa_dict_item module_props = { { PW_KEY_MODULE_AUTHOR, "Pauli Virtanen <pav@iki.fi>" }, @@ -162,7 +162,7 @@ pw_proxy_destroy(impl->sink); } -static void sink_proxy_bound(void *data, uint32_t id) +static void sink_proxy_bound_props(void *data, uint32_t id, const struct spa_dict *props) { struct impl *impl = data; @@ -186,7 +186,7 @@ static const struct pw_proxy_events sink_proxy_events = { PW_VERSION_PROXY_EVENTS, .removed = sink_proxy_removed, - .bound = sink_proxy_bound, + .bound_props = sink_proxy_bound_props, .destroy = sink_proxy_destroy, };
View file
pipewire-0.3.67.tar.gz/src/modules/module-filter-chain.c -> pipewire-0.3.68.tar.gz/src/modules/module-filter-chain.c
Changed
@@ -62,7 +62,7 @@ * filter.graph = { * nodes = * { - * type = <ladspa | lv2 | builtin> + * type = <ladspa | lv2 | builtin | sofa> * name = <name> * plugin = <plugin> * label = <label> @@ -89,22 +89,24 @@ * Nodes describe the processing filters in the graph. Use a tool like lv2ls * or listplugins to get a list of available plugins, labels and the port names. * - * - `type` is one of `ladspa`, `lv2` or `builtin` + * - `type` is one of `ladspa`, `lv2`, `builtin` or `sofa`. * - `name` is the name for this node, you might need this later to refer to this node * and its ports when setting controls or making links. * - `plugin` is the type specific plugin name. * - For LADSPA plugins it will append `.so` to find the shared object with that * name in the LADSPA plugin path. * - For LV2, this is the plugin URI obtained with lv2ls. - * - For builtin this is ignored + * - For builtin and sofa this is ignored * - `label` is the type specific filter inside the plugin. * - For LADSPA this is the label * - For LV2 this is unused - * - For builtin this is the name of the filter to use + * - For builtin and sofa this is the name of the filter to use * - * - `config` contains a filter specific configuration section. The convolver - * plugin needs this. + * - `config` contains a filter specific configuration section. Some plugins need + * this. (convolver, sofa, delay, ...) * - `control` contains the initial values for the control ports of the filter. + * normally these are given with the port name but it is also possible + * to give the control index as the key. * * ### Links * @@ -196,6 +198,7 @@ * offset = ... * length = ... * channel = ... + * resample_quality = ... * } * ... * } @@ -218,9 +221,13 @@ * can be used as gain. * - A filename to load as the IR. This needs to be a file format supported * by sndfile. + * - filename, ... an array of filenames. The file with the closest samplerate match + * with the graph samplerate will be used. * - `offset` The sample offset in the file as the start of the IR. * - `length` The number of samples to use as the IR. * - `channel` The channel to use from the file as the IR. + * - `resample_quality` The resample quality in case the IR does not match the graph + * samplerate. * * ### Delay * @@ -259,6 +266,61 @@ * * It has an input port "In" and an output port "Out". * + * ## SOFA filter + * + * There is an optional builtin SOFA filter available. + * + * ### Spatializer + * + * The spatializer can be used to place the sound in a 3D space. + * + * The spatializer has an input port "In" and a stereo pair of output ports + * called "Out L" and "Out R". It requires a config section in the node + * declaration in this format: + * + * The control can be changed at runtime to move the sounds around in the + * 3D space. + * + *\code{.unparsed} + * filter.graph = { + * nodes = + * { + * type = sofa + * name = ... + * label = spatializer + * config = { + * blocksize = ... + * tailsize = ... + * filename = ... + * } + * control = { + * "Azimuth" = ... + * "Elevation" = ... + * "Radius" = ... + * } + * ... + * } + * } + * ... + * } + *\endcode + * + * - `blocksize` specifies the size of the blocks to use in the FFT. It is a value + * between 64 and 256. When not specified, this value is + * computed automatically from the number of samples in the file. + * - `tailsize` specifies the size of the tail blocks to use in the FFT. + * - `filename` The SOFA file to load. SOFA files usually end in the .sofa extension + * and contain the HRTF for the various spatial positions. + * + * - `Azimuth` controls the azimuth, this is the direction the sound is coming from + * in degrees between 0 and 360. 0 is straight ahead. 90 is left, 180 + * behind, 270 right. + * - `Elevation` controls the elevation, this is how high/low the signal is in degrees + * between -90 and 90. 0 is straight in front, 90 is directly above + * and -90 directly below. + * - `Radius` controls how far away the signal is as a value between 0 and 100. + * default is 1.0. + * * ## General options * * Options with well-known behavior. Most options can be added to the global @@ -375,16 +437,16 @@ static const struct spa_dict_item module_props = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "Create filter chain streams" }, - { PW_KEY_MODULE_USAGE, " remote.name=<remote> " - " node.latency=<latency as fraction> " - " node.description=<description of the nodes> " - " audio.rate=<sample rate> " - " audio.channels=<number of channels> " - " audio.position=<channel map> " + { PW_KEY_MODULE_USAGE, " ( remote.name=<remote> ) " + "( node.latency=<latency as fraction> ) " + "( node.description=<description of the nodes> ) " + "( audio.rate=<sample rate> ) " + "( audio.channels=<number of channels> ) " + "( audio.position=<channel map> ) " "filter.graph = " " nodes = " " { " - " type = <ladspa | lv2 | builtin> " + " type = <ladspa | lv2 | builtin | sofa> " " name = <name> " " plugin = <plugin> " " label = <label> " @@ -402,8 +464,8 @@ " inputs = <portname> ... " " outputs = <portname> ... " " " - " capture.props=<properties> " - " playback.props=<properties> " }, + "( capture.props=<properties> ) " + "( playback.props=<properties> ) " }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, };
View file
pipewire-0.3.67.tar.gz/src/modules/module-filter-chain/ladspa_plugin.c -> pipewire-0.3.68.tar.gz/src/modules/module-filter-chain/ladspa_plugin.c
Changed
@@ -214,7 +214,7 @@ struct fc_plugin *pl = NULL; if (plugin0 != '/') { - const char *search_dirs, *p; + const char *search_dirs, *p, *state = NULL; char pathPATH_MAX; size_t len; @@ -229,7 +229,7 @@ */ errno = ENAMETOOLONG; - while ((p = pw_split_walk(NULL, ":", &len, &search_dirs))) { + while ((p = pw_split_walk(search_dirs, ":", &len, &state))) { int pathlen; if (len >= sizeof(path))
View file
pipewire-0.3.67.tar.gz/src/modules/module-link-factory.c -> pipewire-0.3.68.tar.gz/src/modules/module-link-factory.c
Changed
@@ -22,22 +22,29 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic -#define FACTORY_USAGE PW_KEY_LINK_OUTPUT_NODE"=<output-node> " \ - ""PW_KEY_LINK_OUTPUT_PORT"=<output-port> " \ - PW_KEY_LINK_INPUT_NODE"=<input-node> " \ - ""PW_KEY_LINK_INPUT_PORT"=<input-port> " \ - ""PW_KEY_OBJECT_LINGER"=<bool> " \ - ""PW_KEY_LINK_PASSIVE"=<bool>" +#define FACTORY_USAGE "("PW_KEY_LINK_OUTPUT_NODE"=<output-node>) " \ + "("PW_KEY_LINK_OUTPUT_PORT"=<output-port>) " \ + "("PW_KEY_LINK_INPUT_NODE"=<input-node>) " \ + "("PW_KEY_LINK_INPUT_PORT"=<input-port>) " \ + "("PW_KEY_OBJECT_LINGER"=<bool>) " \ + "("PW_KEY_LINK_PASSIVE"=<bool>)" + +#define MODULE_USAGE "( allow.link.passive=<bool, default false> ) " static const struct spa_dict_item module_props = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "Allow clients to create links" }, + { PW_KEY_MODULE_USAGE, MODULE_USAGE }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; struct factory_data { struct pw_context *context; + struct pw_properties *props; + + unsigned int allow_passive:1; + struct pw_impl_module *module; struct spa_hook module_listener; @@ -396,6 +403,8 @@ pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", pw_impl_client_get_info(client)->id); + if (!d->allow_passive) + pw_properties_set(properties, PW_KEY_LINK_PASSIVE, NULL); link = pw_context_create_link(context, outport, inport, NULL, properties, sizeof(struct link_data)); properties = NULL; @@ -462,6 +471,7 @@ d->factory = NULL; if (d->module) pw_impl_module_destroy(d->module); + pw_properties_free(d->props); } static const struct pw_impl_factory_events factory_events = { @@ -527,6 +537,12 @@ data->module = module; data->context = context; data->work = pw_context_get_work_queue(context); + data->props = args ? pw_properties_new_string(args) : NULL; + + if (data->props) { + data->allow_passive = pw_properties_get_bool(data->props, + "allow.link.passive", false); + } spa_list_init(&data->link_list);
View file
pipewire-0.3.67.tar.gz/src/modules/module-loopback.c -> pipewire-0.3.68.tar.gz/src/modules/module-loopback.c
Changed
@@ -130,15 +130,15 @@ static const struct spa_dict_item module_props = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "Create loopback streams" }, - { PW_KEY_MODULE_USAGE, " remote.name=<remote> " - " node.latency=<latency as fraction> " - " node.description=<description of the nodes> " - " audio.rate=<sample rate> " - " audio.channels=<number of channels> " - " audio.position=<channel map> " - " target.delay.sec=<delay as seconds in float> " - " capture.props=<properties> " - " playback.props=<properties> " }, + { PW_KEY_MODULE_USAGE, " ( remote.name=<remote> ) " + "( node.latency=<latency as fraction> ) " + "( node.description=<description of the nodes> ) " + "( audio.rate=<sample rate> ) " + "( audio.channels=<number of channels> ) " + "( audio.position=<channel map> ) " + "( target.delay.sec=<delay as seconds in float> ) " + "( capture.props=<properties> ) " + "( playback.props=<properties> ) " }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, };
View file
pipewire-0.3.67.tar.gz/src/modules/module-pipe-tunnel.c -> pipewire-0.3.68.tar.gz/src/modules/module-pipe-tunnel.c
Changed
@@ -114,18 +114,18 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic -#define MODULE_USAGE " remote.name=<remote> " \ - " node.latency=<latency as fraction> " \ - " node.name=<name of the nodes> " \ - " node.description=<description of the nodes> " \ - " target.object=<remote node target name or serial> "\ - " audio.format=<sample format> " \ - " audio.rate=<sample rate> " \ - " audio.channels=<number of channels> " \ - " audio.position=<channel map> " \ - " tunnel.mode=capture|playback|sink|source " \ - " pipe.filename=<filename> " \ - " stream.props=<properties> " +#define MODULE_USAGE "( remote.name=<remote> ) " \ + "( node.latency=<latency as fraction> ) " \ + "( node.name=<name of the nodes> ) " \ + "( node.description=<description of the nodes> ) " \ + "( target.object=<remote node target name or serial> ) "\ + "( audio.format=<sample format> ) " \ + "( audio.rate=<sample rate> ) " \ + "( audio.channels=<number of channels> ) " \ + "( audio.position=<channel map> ) " \ + "( tunnel.mode=capture|playback|sink|source )" \ + "( pipe.filename=<filename> )" \ + "( stream.props=<properties> ) " static const struct spa_dict_item module_props = { @@ -294,7 +294,7 @@ .process = capture_stream_process }; -static int create_stream(struct impl *impl) +static int create_stream(struct impl *impl) { int res; uint32_t n_params;
View file
pipewire-0.3.67.tar.gz/src/modules/module-protocol-native/protocol-native.c -> pipewire-0.3.68.tar.gz/src/modules/module-protocol-native/protocol-native.c
Changed
@@ -385,6 +385,7 @@ struct pw_proxy *proxy = data; struct spa_pod_parser prs; uint32_t id, global_id; + struct spa_dict props = SPA_DICT_INIT(NULL, 0); spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, @@ -392,7 +393,33 @@ SPA_POD_Int(&global_id)) < 0) return -EINVAL; - return pw_proxy_notify(proxy, struct pw_core_events, bound_id, 0, id, global_id); + /* old client / old/new server -> bound_id + * new client / old server -> bound_id + bound_props (in case it's using bound_props only) */ + pw_proxy_notify(proxy, struct pw_core_events, bound_id, 0, id, global_id); + return pw_proxy_notify(proxy, struct pw_core_events, bound_props, 1, id, global_id, &props); +} + +static int core_event_demarshal_bound_props(void *data, const struct pw_protocol_native_message *msg) +{ + struct pw_proxy *proxy = data; + struct spa_pod_parser prs; + uint32_t id, global_id; + struct spa_pod_frame f2; + struct spa_dict props = SPA_DICT_INIT(NULL, 0); + + spa_pod_parser_init(&prs, msg->data, msg->size); + if (spa_pod_parser_push_struct(&prs, &f0) < 0) + return -EINVAL; + if (spa_pod_parser_get(&prs, + SPA_POD_Int(&id), + SPA_POD_Int(&global_id), NULL) < 0) + return -EINVAL; + + parse_dict_struct(&prs, &f1, &props); + + /* new client / new server -> bound_props + bound_id (in case it's not using bound_props yet) */ + pw_proxy_notify(proxy, struct pw_core_events, bound_id, 0, id, global_id); + return pw_proxy_notify(proxy, struct pw_core_events, bound_props, 1, id, global_id, &props); } static int core_event_demarshal_add_mem(void *data, const struct pw_protocol_native_message *msg) @@ -526,6 +553,25 @@ pw_protocol_native_end_resource(resource, b); } +static void core_event_marshal_bound_props(void *data, uint32_t id, uint32_t global_id, const struct spa_dict *props) +{ + struct pw_resource *resource = data; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_protocol_native_begin_resource(resource, PW_CORE_EVENT_BOUND_PROPS, NULL); + + spa_pod_builder_push_struct(b, &f); + spa_pod_builder_add(b, + SPA_POD_Int(id), + SPA_POD_Int(global_id), + NULL); + push_dict(b, props); + spa_pod_builder_pop(b, &f); + + pw_protocol_native_end_resource(resource, b); +} + static void core_event_marshal_add_mem(void *data, uint32_t id, uint32_t type, int fd, uint32_t flags) { struct pw_resource *resource = data; @@ -1863,6 +1909,7 @@ .bound_id = &core_event_marshal_bound_id, .add_mem = &core_event_marshal_add_mem, .remove_mem = &core_event_marshal_remove_mem, + .bound_props = &core_event_marshal_bound_props, }; static const struct pw_protocol_native_demarshal @@ -1876,6 +1923,7 @@ PW_CORE_EVENT_BOUND_ID = { &core_event_demarshal_bound_id, 0, }, PW_CORE_EVENT_ADD_MEM = { &core_event_demarshal_add_mem, 0, }, PW_CORE_EVENT_REMOVE_MEM = { &core_event_demarshal_remove_mem, 0, }, + PW_CORE_EVENT_BOUND_PROPS = { &core_event_demarshal_bound_props, 0, }, }; static const struct pw_protocol_marshal pw_protocol_native_core_marshal = {
View file
pipewire-0.3.67.tar.gz/src/modules/module-protocol-pulse/cmd.c -> pipewire-0.3.68.tar.gz/src/modules/module-protocol-pulse/cmd.c
Changed
@@ -17,9 +17,9 @@ struct module *module; char *a2 = { NULL }; - n = pw_split_ip(args, WHITESPACE, 2, a); + n = args != NULL ? pw_split_ip(args, WHITESPACE, 2, a) : 0; if (n < 1) { - pw_log_info("load-module expects module name"); + pw_log_info("load-module expects module name got '%s'", args); return -EINVAL; }
View file
pipewire-0.3.67.tar.gz/src/modules/module-protocol-pulse/defs.h -> pipewire-0.3.68.tar.gz/src/modules/module-protocol-pulse/defs.h
Changed
@@ -116,6 +116,44 @@ return ERR_UNKNOWN; } +static inline int err_to_res(int err) +{ + switch (err) { + case ERR_OK: return 0; + case ERR_ACCESS: return -EACCES; + case ERR_COMMAND: return -ENOTTY; + case ERR_INVALID: return -EINVAL; + case ERR_EXIST: return -EEXIST; + case ERR_NOENTITY: return -ENOENT; + case ERR_CONNECTIONREFUSED: return -ECONNREFUSED; + case ERR_PROTOCOL: return -EPROTO; + case ERR_TIMEOUT: return -ETIMEDOUT; +#ifdef ENOKEY + case ERR_AUTHKEY: return -ENOKEY; +#endif + case ERR_INTERNAL: return -ENFILE; + case ERR_CONNECTIONTERMINATED: return -ECONNRESET; + case ERR_KILLED: return -EFAULT; + case ERR_INVALIDSERVER: return -EINVAL; + case ERR_MODINITFAILED: return -EIO; +#ifdef EBADFD + case ERR_BADSTATE: return -EBADFD; +#endif + case ERR_NODATA: return -ENODATA; + case ERR_VERSION: return -EPROTO; + case ERR_TOOLARGE: return -E2BIG; + case ERR_NOTSUPPORTED: return -ENOTSUP; + case ERR_UNKNOWN: return -EIO; + case ERR_NOEXTENSION: return -ENOTTY; + case ERR_OBSOLETE: return -ENOTSUP; + case ERR_NOTIMPLEMENTED: return -ENOSYS; + case ERR_FORKED: return -EIO; + case ERR_IO: return -EIO; + case ERR_BUSY: return -EBUSY; + } + return -EIO; +} + enum { SUBSCRIPTION_MASK_NULL = 0x0000U, SUBSCRIPTION_MASK_SINK = 0x0001U,
View file
pipewire-0.3.67.tar.gz/src/modules/module-protocol-pulse/modules/module-null-sink.c -> pipewire-0.3.68.tar.gz/src/modules/module-protocol-pulse/modules/module-null-sink.c
Changed
@@ -40,7 +40,7 @@ module_schedule_unload(module); } -static void module_null_sink_proxy_bound(void *data, uint32_t global_id) +static void module_null_sink_proxy_bound_props(void *data, uint32_t global_id, const struct spa_dict *props) { struct module *module = data; struct module_null_sink_data *d = module->user_data; @@ -63,7 +63,7 @@ static const struct pw_proxy_events proxy_events = { PW_VERSION_PROXY_EVENTS, .removed = module_null_sink_proxy_removed, - .bound = module_null_sink_proxy_bound, + .bound_props = module_null_sink_proxy_bound_props, .error = module_null_sink_proxy_error, .destroy = module_null_sink_proxy_destroy, };
View file
pipewire-0.3.67.tar.gz/src/modules/module-protocol-pulse/modules/module-rtp-recv.c -> pipewire-0.3.68.tar.gz/src/modules/module-protocol-pulse/modules/module-rtp-recv.c
Changed
@@ -51,13 +51,16 @@ fprintf(f, "{"); pw_properties_serialize_dict(f, &data->global_props->dict, 0); - fprintf(f, " stream.props = {"); + fprintf(f, " stream.rules = "); + fprintf(f, " { matches = { rtp.session = \"~.*\" } "), + fprintf(f, " actions = { create-stream = { "); pw_properties_serialize_dict(f, &data->stream_props->dict, 0); - fprintf(f, " } }"); + fprintf(f, " } } } "); + fprintf(f, " }"); fclose(f); data->mod = pw_context_load_module(module->impl->context, - "libpipewire-module-rtp-source", + "libpipewire-module-rtp-sap", args, NULL); free(args); @@ -113,14 +116,13 @@ res = -errno; goto out; } - if ((str = pw_properties_get(props, "sink")) != NULL) - pw_properties_set(stream_props, PW_KEY_TARGET_OBJECT, str); - if ((str = pw_properties_get(props, "sap_address")) != NULL) pw_properties_set(global_props, "sap.ip", str); + if ((str = pw_properties_get(props, "sink")) != NULL) + pw_properties_set(stream_props, PW_KEY_TARGET_OBJECT, str); if ((str = pw_properties_get(props, "latency_msec")) != NULL) - pw_properties_set(global_props, "sess.latency.msec", str); + pw_properties_set(stream_props, "sess.latency.msec", str); d->module = module; d->stream_props = stream_props;
View file
pipewire-0.3.67.tar.gz/src/modules/module-protocol-pulse/modules/module-rtp-send.c -> pipewire-0.3.68.tar.gz/src/modules/module-protocol-pulse/modules/module-rtp-send.c
Changed
@@ -19,8 +19,13 @@ struct spa_hook mod_listener; struct pw_impl_module *mod; + struct spa_hook sap_listener; + struct pw_impl_module *sap; + struct pw_properties *stream_props; struct pw_properties *global_props; + struct pw_properties *sap_props; + struct spa_audio_info_raw info; }; @@ -37,6 +42,19 @@ .destroy = module_destroy }; +static void sap_module_destroy(void *data) +{ + struct module_rtp_send_data *d = data; + spa_hook_remove(&d->sap_listener); + d->sap = NULL; + module_schedule_unload(d->module); +} + +static const struct pw_impl_module_events sap_module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = sap_module_destroy +}; + static int module_rtp_send_load(struct module *module) { struct module_rtp_send_data *data = module->user_data; @@ -75,7 +93,6 @@ data->mod = pw_context_load_module(module->impl->context, "libpipewire-module-rtp-sink", args, NULL); - free(args); if (data->mod == NULL) @@ -85,6 +102,29 @@ &data->mod_listener, &module_events, data); + if ((f = open_memstream(&args, &size)) == NULL) + return -errno; + + fprintf(f, "{"); + pw_properties_serialize_dict(f, &data->sap_props->dict, 0); + fprintf(f, " stream.rules = "); + fprintf(f, " { matches = { pulse.module.id = %u } ", module->index); + fprintf(f, " actions = { announce-stream = { } } "); + fprintf(f, " } }"); + fclose(f); + + data->sap = pw_context_load_module(module->impl->context, + "libpipewire-module-rtp-sap", + args, NULL); + free(args); + + if (data->sap == NULL) + return -errno; + + pw_impl_module_add_listener(data->sap, + &data->sap_listener, + &sap_module_events, data); + return 0; } @@ -92,6 +132,11 @@ { struct module_rtp_send_data *d = module->user_data; + if (d->sap) { + spa_hook_remove(&d->sap_listener); + pw_impl_module_destroy(d->sap); + d->sap = NULL; + } if (d->mod) { spa_hook_remove(&d->mod_listener); pw_impl_module_destroy(d->mod); @@ -100,6 +145,7 @@ pw_properties_free(d->global_props); pw_properties_free(d->stream_props); + pw_properties_free(d->sap_props); return 0; } @@ -127,7 +173,7 @@ { struct module_rtp_send_data * const d = module->user_data; struct pw_properties * const props = module->props; - struct pw_properties *stream_props = NULL, *global_props = NULL; + struct pw_properties *stream_props = NULL, *global_props = NULL, *sap_props = NULL; struct spa_audio_info_raw info = { 0 }; const char *str; int res; @@ -136,7 +182,8 @@ stream_props = pw_properties_new(NULL, NULL); global_props = pw_properties_new(NULL, NULL); - if (!stream_props || !global_props) { + sap_props = pw_properties_new(NULL, NULL); + if (!stream_props || !global_props || !sap_props) { res = -errno; goto out; } @@ -165,31 +212,46 @@ } } - if ((str = pw_properties_get(props, "destination_ip")) != NULL) - pw_properties_set(global_props, "destination.ip", str); - if ((str = pw_properties_get(props, "source_ip")) != NULL) + pw_properties_set(global_props, "sess.media", "audio"); + if ((str = pw_properties_get(props, "enable_opus")) != NULL) { + if (module_args_parse_bool(str)) + pw_properties_set(global_props, "sess.media", "opus"); + } + if ((str = pw_properties_get(props, "source_ip")) != NULL) { pw_properties_set(global_props, "source.ip", str); + pw_properties_set(sap_props, "source.ip", str); + } + if ((str = pw_properties_get(props, "destination_ip")) != NULL) { + pw_properties_set(global_props, "destination.ip", str); + pw_properties_set(sap_props, "sap.ip", str); + } if ((str = pw_properties_get(props, "port")) != NULL) pw_properties_set(global_props, "destination.port", str); if ((str = pw_properties_get(props, "mtu")) != NULL) pw_properties_set(global_props, "net.mtu", str); - if ((str = pw_properties_get(props, "loop")) != NULL) - pw_properties_set(global_props, "net.loop", - module_args_parse_bool(str) ? "true" : "false"); - if ((str = pw_properties_get(props, "ttl")) != NULL) + if ((str = pw_properties_get(props, "loop")) != NULL) { + const char *b = module_args_parse_bool(str) ? "true" : "false"; + pw_properties_set(global_props, "net.loop", b); + pw_properties_set(sap_props, "net.loop", b); + } + if ((str = pw_properties_get(props, "ttl")) != NULL) { pw_properties_set(global_props, "net.ttl", str); + pw_properties_set(sap_props, "net.ttl", str); + } if ((str = pw_properties_get(props, "stream_name")) != NULL) pw_properties_set(global_props, "sess.name", str); d->module = module; d->stream_props = stream_props; d->global_props = global_props; + d->sap_props = sap_props; d->info = info; return 0; out: pw_properties_free(stream_props); pw_properties_free(global_props); + pw_properties_free(sap_props); return res; }
View file
pipewire-0.3.67.tar.gz/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c -> pipewire-0.3.68.tar.gz/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c
Changed
@@ -5,6 +5,7 @@ #include <sys/utsname.h> #include <arpa/inet.h> +#include <netinet/in.h> #include <pipewire/pipewire.h>
View file
pipewire-0.3.67.tar.gz/src/modules/module-protocol-pulse/pulse-server.c -> pipewire-0.3.68.tar.gz/src/modules/module-protocol-pulse/pulse-server.c
Changed
@@ -1154,30 +1154,25 @@ struct buffer_attr *attr, struct spa_pod_builder *b) { const struct spa_pod *param; - uint32_t blocks, buffers, size, maxsize, stride; + uint32_t blocks, size, stride; struct defs *defs = &s->impl->defs; blocks = 1; stride = s->frame_size; - maxsize = defs->quantum_limit * 32 * s->frame_size; - if (s->direction == PW_DIRECTION_OUTPUT) { - size = attr->minreq; - } else { - size = attr->fragsize; - } - buffers = SPA_CLAMP(maxsize / size, MIN_BUFFERS, MAX_BUFFERS); + size = defs->quantum_limit * s->frame_size; - pw_log_info("%s stride %d maxsize %d size %u buffers %d", s->client->name, - stride, maxsize, size, buffers); + pw_log_info("%s stride %d size %u", s->client->name, stride, size); param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(buffers, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, MIN_BUFFERS, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( - size, size, maxsize), + size, + 16 * s->frame_size, + INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride)); return param; } @@ -1411,7 +1406,20 @@ if (avail < (int32_t)minreq || stream->corked) { /* underrun, produce a silence buffer */ size = SPA_MIN(d->maxsize, minreq); - memset(p, 0, size); + switch (stream->ss.format) { + case SPA_AUDIO_FORMAT_U8: + memset(p, 0x80, size); + break; + case SPA_AUDIO_FORMAT_ALAW: + memset(p, 0x80 ^ 0x55, size); + break; + case SPA_AUDIO_FORMAT_ULAW: + memset(p, 0x00 ^ 0xff, size); + break; + default: + memset(p, 0, size); + break; + } if (stream->draining && !stream->corked) { stream->draining = false;
View file
pipewire-0.3.67.tar.gz/src/modules/module-protocol-simple.c -> pipewire-0.3.68.tar.gz/src/modules/module-protocol-simple.c
Changed
@@ -49,7 +49,7 @@ * - `capture.node`: an optional node serial or name to use for capture. * - `playback.node`: an optional node serial or name to use for playback. * - `server.address = `: an array of server addresses to listen on as - * tcp:<ip>:<port>. + * tcp:(<ip>:)<port>. * * ## General options * @@ -119,18 +119,18 @@ #define MAX_CLIENTS 10 -#define MODULE_USAGE " capture=<bool> " \ - " playback=<bool> " \ - " remote.name=<remote> " \ - " node.latency=<num/denom, default:"DEFAULT_LATENCY"> " \ - " node.rate=<1/rate, default:1/"SPA_STRINGIFY(DEFAULT_RATE)"> " \ - " capture.node=<source-target> stream.capture.sink=true " \ - " playback.node=<sink-target> " \ - " audio.rate=<sample-rate, default:"SPA_STRINGIFY(DEFAULT_RATE)"> " \ - " audio.format=<format, default:"DEFAULT_FORMAT"> " \ - " audio.channels=<channels, default: "SPA_STRINGIFY(DEFAULT_CHANNELS)"> " \ - " audio.position=<position, default:"DEFAULT_POSITION"> " \ - " server.address=< tcp:<ip>:<port>,... , default:"DEFAULT_SERVER">" \ +#define MODULE_USAGE "( capture=<bool> ) " \ + "( playback=<bool> ) " \ + "( remote.name=<remote> ) " \ + "( node.latency=<num/denom, default:"DEFAULT_LATENCY"> ) " \ + "( node.rate=<1/rate, default:1/"SPA_STRINGIFY(DEFAULT_RATE)"> ) " \ + "( capture.node=<source-target> ( stream.capture.sink=true )) " \ + "( playback.node=<sink-target> ) " \ + "( audio.rate=<sample-rate, default:"SPA_STRINGIFY(DEFAULT_RATE)"> ) " \ + "( audio.format=<format, default:"DEFAULT_FORMAT"> ) " \ + "( audio.channels=<channels, default: "SPA_STRINGIFY(DEFAULT_CHANNELS)"> ) " \ + "( audio.position=<position, default:"DEFAULT_POSITION"> ) " \ + "( server.address=< tcp:(<ip>:)<port>(,...) , default:"DEFAULT_SERVER"> )" \ static const struct spa_dict_item module_props = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
View file
pipewire-0.3.67.tar.gz/src/modules/module-pulse-tunnel.c -> pipewire-0.3.68.tar.gz/src/modules/module-pulse-tunnel.c
Changed
@@ -32,6 +32,7 @@ #include <pipewire/private.h> #include <pulse/pulseaudio.h> +#include "module-protocol-pulse/defs.h" #include "module-protocol-pulse/format.h" /** \page page_module_pulse_tunnel PipeWire Module: Pulse Tunnel @@ -105,19 +106,19 @@ #define DEFAULT_CHANNELS 2 #define DEFAULT_POSITION " FL FR " -#define MODULE_USAGE " remote.name=<remote> " \ - " node.latency=<latency as fraction> " \ - " node.name=<name of the nodes> " \ - " node.description=<description of the nodes> " \ - " node.target=<remote node target name or serial> " \ - " audio.format=<sample format> " \ - " audio.rate=<sample rate> " \ - " audio.channels=<number of channels> " \ - " audio.position=<channel map> " \ +#define MODULE_USAGE "( remote.name=<remote> " \ + "( node.latency=<latency as fraction> " \ + "( node.name=<name of the nodes> " \ + "( node.description=<description of the nodes> " \ + "( node.target=<remote node target name or serial> " \ + "( audio.format=<sample format> " \ + "( audio.rate=<sample rate> " \ + "( audio.channels=<number of channels> " \ + "( audio.position=<channel map> " \ "pulse.server.address=<address> " \ - "pulse.latency=<latency in msec> " \ - " tunnel.mode=source|sink " \ - " stream.props=<properties> " + "( pulse.latency=<latency in msec, default 200> ) " \ + "( tunnel.mode=source|sink, default sink ) " \ + "( stream.props=<properties> ) " static const struct spa_dict_item module_props = { @@ -134,6 +135,7 @@ struct impl { struct pw_context *context; + struct pw_loop *main_loop; #define MODE_SINK 0 #define MODE_SOURCE 1 @@ -231,23 +233,22 @@ } } -static void update_rate(struct impl *impl, bool playback) +static void update_rate(struct impl *impl, uint32_t filled) { float error, corr; + uint32_t current_latency; if (impl->rate_match == NULL) return; - if (playback) - error = (float)impl->target_latency - (float)impl->current_latency; - else - error = (float)impl->current_latency - (float)impl->target_latency; + current_latency = impl->current_latency + filled; + error = (float)impl->target_latency - (float)(current_latency); error = SPA_CLAMP(error, -impl->max_error, impl->max_error); corr = spa_dll_update(&impl->dll, error); pw_log_debug("error:%f corr:%f current:%u target:%u", error, corr, - impl->current_latency, impl->target_latency); + current_latency, impl->target_latency); SPA_FLAG_SET(impl->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE); impl->rate_match->rate = 1.0f / corr; @@ -282,7 +283,7 @@ size, RINGBUFFER_SIZE); impl->resync = true; } else { - update_rate(impl, true); + update_rate(impl, filled / impl->frame_size); } spa_ringbuffer_write_data(&impl->ring, impl->buffer, RINGBUFFER_SIZE, @@ -323,7 +324,7 @@ avail = impl->target_buffer; index += avail - impl->target_buffer; } else { - update_rate(impl, false); + update_rate(impl, avail / impl->frame_size); } spa_ringbuffer_read_data(&impl->ring, impl->buffer, RINGBUFFER_SIZE, @@ -415,6 +416,20 @@ return 0; } +static int +do_schedule_destroy(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct impl *impl = user_data; + pw_impl_module_schedule_destroy(impl->module); + return 0; +} + +void module_schedule_destroy(struct impl *impl) +{ + pw_loop_invoke(impl->main_loop, do_schedule_destroy, 1, NULL, 0, false, impl); +} + static void context_state_cb(pa_context *c, void *userdata) { struct impl *impl = userdata; @@ -436,7 +451,7 @@ break; } if (do_destroy) - pw_impl_module_schedule_destroy(impl->module); + module_schedule_destroy(impl); } static void stream_state_cb(pa_stream *s, void * userdata) @@ -458,7 +473,7 @@ break; } if (do_destroy) - pw_impl_module_schedule_destroy(impl->module); + module_schedule_destroy(impl); } static void stream_read_request_cb(pa_stream *s, size_t length, void *userdata) @@ -511,7 +526,6 @@ pa_stream_get_latency(impl->pa_stream, &latency, &negative); impl->current_latency = latency * impl->info.rate / SPA_USEC_PER_SEC; - impl->current_latency += filled / impl->frame_size; spa_ringbuffer_write_update(&impl->ring, index); } @@ -536,7 +550,6 @@ pa_stream_get_latency(impl->pa_stream, &latency, &negative); impl->current_latency = latency * impl->info.rate / SPA_USEC_PER_SEC; - impl->current_latency += avail / impl->frame_size; while (avail < (int32_t)length) { uint32_t maxsize = SPA_ROUND_DOWN(sizeof(impl->empty), impl->frame_size); @@ -753,7 +766,7 @@ pa_threaded_mainloop_unlock(impl->pa_mainloop); error: pw_log_error("failed to connect: %s", pa_strerror(res)); - return -res; + return err_to_res(res); } @@ -960,6 +973,7 @@ impl->module = module; impl->context = context; + impl->main_loop = pw_context_get_main_loop(context); spa_ringbuffer_init(&impl->ring); impl->buffer = calloc(1, RINGBUFFER_SIZE);
View file
pipewire-0.3.67.tar.gz/src/modules/module-raop-discover.c -> pipewire-0.3.68.tar.gz/src/modules/module-raop-discover.c
Changed
@@ -31,19 +31,56 @@ * Automatically creates RAOP (Airplay) sink devices based on zeroconf * information. * - * This module will load module-raop-sink for each discovered sink - * with the right parameters. + * This module will load module-raop-sink for each announced stream that matches + * the rule with the create-stream action. + * + * If no stream.rules are given, it will create a sink for all announced + * streams. * * ## Module Options * - * This module has no options. + * Options specific to the behavior of this module + * + * - `stream.rules` = <rules>: match rules, use create-stream actions. See + * \ref page_module_raop_sink for module properties. * * ## Example configuration * *\code{.unparsed} * context.modules = * { name = libpipewire-raop-discover - * args = { } + * args = { + * stream.rules = + * { matches = + * { raop.ip = "~.*" + * #raop.ip.version = 4 | 6 + * #raop.ip.version = 4 + * #raop.port = 1000 + * #raop.name = "" + * #raop.hostname = "" + * #raop.domain = "" + * #raop.device = "" + * #raop.transport = "udp" | "tcp" + * #raop.encryption.type = "RSA" | "auth_setup" | "none" + * #raop.audio.codec = "PCM" | "ALAC" | "AAC" | "AAC-ELD" + * #audio.channels = 2 + * #audio.format = "S16" | "S24" | "S32" + * #audio.rate = 44100 + * #device.model = "" + * } + * + * actions = { + * create-stream = { + * #raop.password = "" + * stream.props = { + * #target.object = "" + * #media.class = "Audio/Sink" + * } + * } + * } + * } + * + * } * } * *\endcode @@ -58,7 +95,10 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic -#define MODULE_USAGE " " +#define MODULE_USAGE "( stream.rules=<rules>, use create-stream actions )" + +#define DEFAULT_CREATE_RULES \ + " { matches = { raop.ip = \"~.*\" } actions = { create-stream = { } } } " static const struct spa_dict_item module_props = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, @@ -88,6 +128,7 @@ AvahiIfIndex interface; AvahiProtocol protocol; const char *name; + const char *host_name; const char *type; const char *domain; }; @@ -114,6 +155,7 @@ t->info.interface = info->interface; t->info.protocol = info->protocol; t->info.name = strdup(info->name); + t->info.host_name = strdup(info->host_name); t->info.type = strdup(info->type); t->info.domain = strdup(info->domain); spa_list_append(&impl->tunnel_list, &t->link); @@ -255,6 +297,7 @@ spa_hook_remove(&t->module_listener); free((char *) t->info.name); + free((char *) t->info.host_name); free((char *) t->info.type); free((char *) t->info.domain); @@ -266,22 +309,87 @@ .destroy = submodule_destroy, }; +struct match_info { + struct impl *impl; + struct pw_properties *props; + struct tunnel_info *tinfo; + bool matched; +}; + +static int create_stream(struct impl *impl, struct pw_properties *props, + struct tunnel_info *tinfo) +{ + FILE *f; + char *args; + size_t size; + int res = 0; + struct pw_impl_module *mod; + struct tunnel *t; + + if ((f = open_memstream(&args, &size)) == NULL) { + res = -errno; + pw_log_error("Can't open memstream: %m"); + goto done; + } + + fprintf(f, "{"); + pw_properties_serialize_dict(f, &props->dict, 0); + fprintf(f, "}"); + fclose(f); + + pw_log_info("loading module args:'%s'", args); + mod = pw_context_load_module(impl->context, + "libpipewire-module-raop-sink", + args, NULL); + free(args); + + if (mod == NULL) { + res = -errno; + pw_log_error("Can't load module: %m"); + goto done; + } + + t = make_tunnel(impl, tinfo); + if (t == NULL) { + res = -errno; + pw_log_error("Can't make tunnel: %m"); + pw_impl_module_destroy(mod); + goto done; + } + + pw_impl_module_add_listener(mod, &t->module_listener, &submodule_events, t); + + t->module = mod; +done: + return res; +} + +static int rule_matched(void *data, const char *location, const char *action, + const char *str, size_t len) +{ + struct match_info *i = data; + int res = 0; + + i->matched = true; + if (spa_streq(action, "create-stream")) { + pw_properties_update_string(i->props, str, len); + create_stream(i->impl, i->props, i->tinfo); + } + return res; +} + static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *a, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void *userdata) { struct impl *impl = userdata; - struct tunnel *t; struct tunnel_info tinfo; const char *str; AvahiStringList *l; - FILE *f; - char *args; - size_t size; - struct pw_impl_module *mod; struct pw_properties *props = NULL; char atAVAHI_ADDRESS_STR_MAX; + int ipv; if (event != AVAHI_RESOLVER_FOUND) { pw_log_error("Resolving of '%s' failed: %s", name, @@ -290,6 +398,7 @@ } tinfo = TUNNEL_INFO(.interface = interface, .protocol = protocol, + .host_name = host_name, .name = name, .type = type, .domain = domain); @@ -301,18 +410,14 @@ } avahi_address_snprint(at, sizeof(at), a); + ipv = protocol == AVAHI_PROTO_INET ? 4 : 6; - pw_properties_setf(props, "raop.hostname", "%s", at); + pw_properties_setf(props, "raop.ip", "%s", at); + pw_properties_setf(props, "raop.ip.version", "%d", ipv); pw_properties_setf(props, "raop.port", "%u", port); - - if ((str = strstr(name, "@"))) { - str++; - if (strlen(str) > 0) - pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, str); - else - pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, - "RAOP on %s", host_name); - } + pw_properties_setf(props, "raop.name", "%s", name); + pw_properties_setf(props, "raop.hostname", "%s", host_name); + pw_properties_setf(props, "raop.domain", "%s", domain); for (l = txt; l; l = l->next) { char *key, *value; @@ -325,45 +430,24 @@ avahi_free(value); } - - if ((f = open_memstream(&args, &size)) == NULL) { - pw_log_error("Can't open memstream: %m"); - goto done; + if ((str = pw_properties_get(impl->properties, "stream.rules")) == NULL) + str = DEFAULT_CREATE_RULES; + if (str != NULL) { + struct match_info minfo = { + .impl = impl, + .props = props, + .tinfo = &tinfo, + }; + pw_conf_match_rules(str, strlen(str), NAME, &props->dict, + rule_matched, &minfo); + + if (!minfo.matched) + pw_log_info("unmatched service found %s", str); } - fprintf(f, "{"); - pw_properties_serialize_dict(f, &props->dict, 0); - fprintf(f, " stream.props = {"); - fprintf(f, " }"); - fprintf(f, "}"); - fclose(f); - - pw_properties_free(props); - - pw_log_info("loading module args:'%s'", args); - mod = pw_context_load_module(impl->context, - "libpipewire-module-raop-sink", - args, NULL); - free(args); - - if (mod == NULL) { - pw_log_error("Can't load module: %m"); - goto done; - } - - t = make_tunnel(impl, &tinfo); - if (t == NULL) { - pw_log_error("Can't make tunnel: %m"); - pw_impl_module_destroy(mod); - goto done; - } - - pw_impl_module_add_listener(mod, &t->module_listener, &submodule_events, t); - - t->module = mod; - done: avahi_service_resolver_free(r); + pw_properties_free(props); }
View file
pipewire-0.3.67.tar.gz/src/modules/module-raop-sink.c -> pipewire-0.3.68.tar.gz/src/modules/module-raop-sink.c
Changed
@@ -18,6 +18,9 @@ #include <netinet/in.h> #include <openssl/err.h> +#if OPENSSL_API_LEVEL >= 30000 +#include <openssl/core_names.h> +#endif #include <openssl/rand.h> #include <openssl/rsa.h> #include <openssl/engine.h> @@ -53,8 +56,10 @@ * * Options specific to the behavior of this module * - * - `raop.hostname`: The hostname of the remote end. + * - `raop.ip`: The ip address of the remote end. * - `raop.port`: The port of the remote end. + * - `raop.name`: The name of the remote end. + * - `raop.hostname`: The hostname of the remote end. * - `raop.transport`: The data transport to use, one of "udp" or "tcp". Defaults * to "udp". * - `raop.encryption.type`: The encryption type to use. One of "none", "RSA" or @@ -84,8 +89,10 @@ * { name = libpipewire-module-raop-sink * args = { * # Set the remote address to tunnel to - * raop.hostname = "my-raop-device" + * raop.ip = "127.0.0.1" * raop.port = 8190 + * raop.name = "my-raop-device" + * raop.hostname = "My Service" * #raop.transport = "udp" * raop.encryption.type = "RSA" * #raop.audio.codec = "PCM" @@ -138,20 +145,22 @@ #define DEFAULT_LATENCY 22050 -#define MODULE_USAGE " raop.hostname=<name of host> " \ - " raop.port=<remote port> " \ - " raop.transport=<transport, default:udp> " \ - " raop.encryption.type=<encryption, default:none> " \ - " raop.audio.codec=PCM " \ - " raop.password=<password for auth> " \ - " node.latency=<latency as fraction> " \ - " node.name=<name of the nodes> " \ - " node.description=<description of the nodes> " \ - " audio.format=<format, default:"DEFAULT_FORMAT"> " \ - " audio.rate=<sample rate, default: "SPA_STRINGIFY(DEFAULT_RATE)"> " \ - " audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS)"> " \ - " audio.position=<channel map, default:"DEFAULT_POSITION"> " \ - " stream.props=<properties> " +#define MODULE_USAGE "( raop.ip=<ip address of host> ) " \ + "( raop.port=<remote port> ) " \ + "( raop.name=<name of host> ) " \ + "( raop.hostname=<hostname of host> ) " \ + "( raop.transport=<transport, default:udp> ) " \ + "( raop.encryption.type=<encryption, default:none> ) " \ + "( raop.audio.codec=PCM ) " \ + "( raop.password=<password for auth> ) " \ + "( node.latency=<latency as fraction> ) " \ + "( node.name=<name of the nodes> ) " \ + "( node.description=<description of the nodes> ) " \ + "( audio.format=<format, default:"DEFAULT_FORMAT"> ) " \ + "( audio.rate=<sample rate, default: "SPA_STRINGIFY(DEFAULT_RATE)"> ) " \ + "( audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS)"> ) " \ + "( audio.position=<channel map, default:"DEFAULT_POSITION"> ) " \ + "( stream.props=<properties> ) " static const struct spa_dict_item module_props = { @@ -207,12 +216,15 @@ char session_id32; char *password; + char *auth_method; + char *realm; + char *nonce; unsigned int do_disconnect:1; uint8_t keyAES_CHUNK_SIZE; /* Key for aes-cbc */ uint8_t ivAES_CHUNK_SIZE; /* Initialization vector for cbc */ - AES_KEY aes; /* AES encryption */ + EVP_CIPHER_CTX *ctx; uint16_t control_port; int control_fd; @@ -266,21 +278,10 @@ static int aes_encrypt(struct impl *impl, uint8_t *data, int len) { - uint8_t nvAES_CHUNK_SIZE; - uint8_t *buffer; - int i, j; - - memcpy(nv, impl->iv, AES_CHUNK_SIZE); - for (i = 0; i + AES_CHUNK_SIZE <= len; i += AES_CHUNK_SIZE) { - buffer = data + i; - for (j = 0; j < AES_CHUNK_SIZE; j++) - bufferj ^= nvj; - - AES_encrypt(buffer, buffer, &impl->aes); - - memcpy(nv, buffer, AES_CHUNK_SIZE); - } - return i; + int i = len & ~0xf, clen = i; + EVP_EncryptInit(impl->ctx, EVP_aes_128_cbc(), impl->key, impl->iv); + EVP_EncryptUpdate(impl->ctx, data, &clen, data, i); + return i; } static inline uint64_t timespec_to_ntp(struct timespec *ts) @@ -587,7 +588,7 @@ size_t salen; int res, af; - host = pw_properties_get(impl->props, "raop.hostname"); + host = pw_properties_get(impl->props, "raop.ip"); if (host == NULL) return -EINVAL; @@ -703,6 +704,125 @@ } } +static void base64_encode(const uint8_t *data, size_t len, char *enc, char pad) +{ + static const char tab = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + size_t i; + for (i = 0; i < len; i += 3) { + uint32_t v; + v = datai+0 << 16; + v |= (i+1 < len ? datai+1 : 0) << 8; + v |= (i+2 < len ? datai+2 : 0); + *enc++ = tab(v >> (3*6)) & 0x3f; + *enc++ = tab(v >> (2*6)) & 0x3f; + *enc++ = i+1 < len ? tab(v >> (1*6)) & 0x3f : pad; + *enc++ = i+2 < len ? tab(v >> (0*6)) & 0x3f : pad; + } + *enc = '\0'; +} + +static size_t base64_decode(const char *data, size_t len, uint8_t *dec) +{ + uint8_t tab = { + 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, + -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, + -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 }; + size_t i, j; + for (i = 0, j = 0; i < len; i += 4) { + uint32_t v; + v = tabdatai+0-43 << (3*6); + v |= tabdatai+1-43 << (2*6); + v |= (datai+2 == '=' ? 0 : tabdatai+2-43) << (1*6); + v |= (datai+3 == '=' ? 0 : tabdatai+3-43); + decj++ = (v >> 16) & 0xff; + if (datai+2 != '=') decj++ = (v >> 8) & 0xff; + if (datai+3 != '=') decj++ = v & 0xff; + } + return j; +} + +SPA_PRINTF_FUNC(2,3) +static int MD5_hash(char hashMD5_HASH_LENGTH+1, const char *fmt, ...) +{ + unsigned char dMD5_DIGEST_LENGTH; + int i; + va_list args; + char buffer1024; + unsigned int size; + + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + + size = MD5_DIGEST_LENGTH; + EVP_Digest(buffer, strlen(buffer), d, &size, EVP_md5(), NULL); + for (i = 0; i < MD5_DIGEST_LENGTH; i++) + sprintf(&hash2*i, "%02x", (uint8_t) di); + hashMD5_HASH_LENGTH = '\0'; + return 0; +} + +static int rtsp_add_auth(struct impl *impl, const char *method) +{ + char auth1024; + + if (impl->auth_method == NULL) + return 0; + + if (spa_streq(impl->auth_method, "Basic")) { + char buf256; + char enc512; + spa_scnprintf(buf, sizeof(buf), "%s:%s", DEFAULT_USER_NAME, impl->password); + base64_encode((uint8_t*)buf, strlen(buf), enc, '='); + spa_scnprintf(auth, sizeof(auth), "Basic %s", enc); + } + else if (spa_streq(impl->auth_method, "Digest")) { + const char *url; + char h1MD5_HASH_LENGTH+1; + char h2MD5_HASH_LENGTH+1; + char respMD5_HASH_LENGTH+1; + + url = pw_rtsp_client_get_url(impl->rtsp); + + MD5_hash(h1, "%s:%s:%s", DEFAULT_USER_NAME, impl->realm, impl->password); + MD5_hash(h2, "%s:%s", method, url); + MD5_hash(resp, "%s:%s:%s", h1, impl->nonce, h2); + + spa_scnprintf(auth, sizeof(auth), + "username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"", + DEFAULT_USER_NAME, impl->realm, impl->nonce, url, resp); + } + else + goto error; + + pw_properties_setf(impl->headers, "Authorization", "%s %s", + impl->auth_method, auth); + + return 0; +error: + pw_log_error("error adding auth"); + return -EINVAL; +} + +static int rtsp_send(struct impl *impl, const char *method, + const char *content_type, const char *content, + int (*reply) (void *data, int status, const struct spa_dict *headers)) +{ + int res; + + rtsp_add_auth(impl, method); + + res = pw_rtsp_client_send(impl->rtsp, method, &impl->headers->dict, + content_type, content, reply, impl); + return res; +} + static int rtsp_flush_reply(void *data, int status, const struct spa_dict *headers) { pw_log_info("reply %d", status); @@ -722,8 +842,7 @@ impl->recording = false; - res = pw_rtsp_client_send(impl->rtsp, "FLUSH", &impl->headers->dict, - NULL, NULL, rtsp_flush_reply, impl); + res = rtsp_send(impl, "FLUSH", NULL, NULL, rtsp_flush_reply); pw_properties_set(impl->headers, "Range", NULL); pw_properties_set(impl->headers, "RTP-Info", NULL); @@ -767,8 +886,7 @@ impl->recording = true; snprintf(progress, sizeof(progress), "progress: %s/%s/%s\r\n", "0", "0", "0"); - return pw_rtsp_client_send(impl->rtsp, "SET_PARAMETER", NULL, - "text/parameters", progress, NULL, NULL); + return rtsp_send(impl, "SET_PARAMETER", "text/parameters", progress, NULL); } static int rtsp_do_record(struct impl *impl) @@ -782,8 +900,7 @@ pw_properties_setf(impl->headers, "RTP-Info", "seq=%u;rtptime=%u", impl->seq, impl->rtptime); - res = pw_rtsp_client_send(impl->rtsp, "RECORD", &impl->headers->dict, - NULL, NULL, rtsp_record_reply, impl); + res = rtsp_send(impl, "RECORD", NULL, NULL, rtsp_record_reply); pw_properties_set(impl->headers, "Range", NULL); pw_properties_set(impl->headers, "RTP-Info", NULL); @@ -878,7 +995,7 @@ break; case PROTO_UDP: - if (control_port == 0 || timing_port == 0) { + if (control_port == 0) { pw_log_error("missing UDP ports in Transport"); return 0; } @@ -888,11 +1005,17 @@ return impl->server_fd; if ((impl->control_fd = connect_socket(impl, SOCK_DGRAM, impl->control_fd, control_port)) < 0) return impl->control_fd; - if ((impl->timing_fd = connect_socket(impl, SOCK_DGRAM, impl->timing_fd, timing_port)) < 0) - return impl->timing_fd; - - ntp = ntp_now(CLOCK_MONOTONIC); - send_udp_timing_packet(impl, ntp, ntp, NULL, 0); + if (timing_port != 0) { + /* it is possible that there is no timing_port. We simply don't + * connect then and don't send an initial timing packet. + * We will reply to received timing packets on the same address we + * received the packet from so we don't really need this. */ + if ((impl->timing_fd = connect_socket(impl, SOCK_DGRAM, impl->timing_fd, timing_port)) < 0) + return impl->timing_fd; + + ntp = ntp_now(CLOCK_MONOTONIC); + send_udp_timing_packet(impl, ntp, ntp, NULL, 0); + } impl->control_source = pw_loop_add_io(impl->loop, impl->control_fd, SPA_IO_IN, false, on_control_source_io, impl); @@ -939,8 +1062,7 @@ return -ENOTSUP; } - res = pw_rtsp_client_send(impl->rtsp, "SETUP", &impl->headers->dict, - NULL, NULL, rtsp_setup_reply, impl); + res = rtsp_send(impl, "SETUP", NULL, NULL, rtsp_setup_reply); pw_properties_set(impl->headers, "Transport", NULL); @@ -966,57 +1088,19 @@ return rtsp_do_setup(impl); } -static void base64_encode(const uint8_t *data, size_t len, char *enc, char pad) -{ - static const char tab = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - size_t i; - for (i = 0; i < len; i += 3) { - uint32_t v; - v = datai+0 << 16; - v |= (i+1 < len ? datai+1 : 0) << 8; - v |= (i+2 < len ? datai+2 : 0); - *enc++ = tab(v >> (3*6)) & 0x3f; - *enc++ = tab(v >> (2*6)) & 0x3f; - *enc++ = i+1 < len ? tab(v >> (1*6)) & 0x3f : pad; - *enc++ = i+2 < len ? tab(v >> (0*6)) & 0x3f : pad; - } - *enc = '\0'; -} - -static size_t base64_decode(const char *data, size_t len, uint8_t *dec) +static inline void swap_bytes(uint8_t *data, size_t size) { - uint8_t tab = { - 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, - 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, - -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, - 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, - -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, - 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 }; - size_t i, j; - for (i = 0, j = 0; i < len; i += 4) { - uint32_t v; - v = tabdatai+0-43 << (3*6); - v |= tabdatai+1-43 << (2*6); - v |= (datai+2 == '=' ? 0 : tabdatai+2-43) << (1*6); - v |= (datai+3 == '=' ? 0 : tabdatai+3-43); - decj++ = (v >> 16) & 0xff; - if (datai+2 != '=') decj++ = (v >> 8) & 0xff; - if (datai+3 != '=') decj++ = v & 0xff; - } - return j; + int i, j; + for (i = 0, j = size-1; i < j; i++, j--) + SPA_SWAP(datai, dataj); } -static int rsa_encrypt(uint8_t *data, int len, uint8_t *res) +static int rsa_encrypt(uint8_t *data, int len, uint8_t *enc) { - RSA *rsa; uint8_t modulus256; uint8_t exponent8; - size_t size; - BIGNUM *n_bn = NULL; - BIGNUM *e_bn = NULL; + size_t msize, esize; + int res = 0; char n = "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC" "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR" @@ -1026,19 +1110,71 @@ "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew=="; char e = "AQAB"; - rsa = RSA_new(); + msize = base64_decode(n, strlen(n), modulus); + esize = base64_decode(e, strlen(e), exponent); - size = base64_decode(n, strlen(n), modulus); - n_bn = BN_bin2bn(modulus, size, NULL); +#if OPENSSL_API_LEVEL >= 30000 + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *ctx = NULL; + OSSL_PARAM params5; + int err = 0; + size_t size; - size = base64_decode(e, strlen(e), exponent); - e_bn = BN_bin2bn(exponent, size, NULL); +#if __BYTE_ORDER == __LITTLE_ENDIAN + swap_bytes(modulus, msize); + swap_bytes(exponent, esize); +#endif + params0 = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, modulus, msize); + params1 = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, exponent, esize); + params2 = OSSL_PARAM_construct_end(); + + ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); + if (ctx == NULL || + (err = EVP_PKEY_fromdata_init(ctx)) <= 0 || + (err = EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params)) <= 0) + goto error; - RSA_set0_key(rsa, n_bn, e_bn, NULL); + EVP_PKEY_CTX_free(ctx); + + params0 = OSSL_PARAM_construct_utf8_string(OSSL_ASYM_CIPHER_PARAM_PAD_MODE, + OSSL_PKEY_RSA_PAD_MODE_OAEP, 0); + params1 = OSSL_PARAM_construct_end(); + + if ((ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL)) == NULL || + (err = EVP_PKEY_encrypt_init_ex(ctx, params)) <= 0 || + (err = EVP_PKEY_encrypt(ctx, enc, &size, data, len)) <= 0) + goto error; - size = RSA_public_encrypt(len, data, res, rsa, RSA_PKCS1_OAEP_PADDING); - RSA_free(rsa); - return size; + res = size; +done: + if (ctx) + EVP_PKEY_CTX_free(ctx); + if (pkey) + EVP_PKEY_free(pkey); + return res; +#else + RSA *rsa = RSA_new(); + BIGNUM *n_bn = BN_bin2bn(modulus, msize, NULL); + BIGNUM *e_bn = BN_bin2bn(exponent, esize, NULL); + if (rsa == NULL || n_bn == NULL || e_bn == NULL) + goto error; + RSA_set0_key(rsa, n_bn, e_bn, NULL); + n_bn = e_bn = NULL; + if ((res = RSA_public_encrypt(len, data, enc, rsa, RSA_PKCS1_OAEP_PADDING)) <= 0) + goto error; +done: + if (rsa != NULL) + RSA_free(rsa); + if (n_bn != NULL) + BN_free(n_bn); + if (e_bn != NULL) + BN_free(e_bn); + return res; +#endif +error: + ERR_print_errors_fp(stdout); + res = -EIO; + goto done; } static int rtsp_do_announce(struct impl *impl) @@ -1047,12 +1183,12 @@ uint8_t rsakey512; char key512*2; char iv16*2; - int res, frames, i, ip_version; + int res, frames, rsa_len, ip_version; char *sdp; char local_ip256; int min_latency; min_latency = DEFAULT_LATENCY; - host = pw_properties_get(impl->props, "raop.hostname"); + host = pw_properties_get(impl->props, "raop.ip"); if (impl->protocol == PROTO_TCP) frames = FRAMES_PER_TCP_PACKET; @@ -1073,9 +1209,9 @@ "t=0 0\r\n" "m=audio 0 RTP/AVP 96\r\n" "a=rtpmap:96 AppleLossless\r\n" - "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n", + "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 %u\r\n", impl->session_id, ip_version, local_ip, - ip_version, host, frames); + ip_version, host, frames, impl->info.rate); break; case CRYPTO_AUTH_SETUP: @@ -1086,10 +1222,11 @@ "t=0 0\r\n" "m=audio 0 RTP/AVP 96\r\n" "a=rtpmap:96 AppleLossless\r\n" - "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n" + "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 %u\r\n" "a=min-latency:%d", impl->session_id, ip_version, local_ip, - ip_version, host, frames, min_latency); + ip_version, host, frames, impl->info.rate, + min_latency); break; case CRYPTO_RSA: @@ -1097,10 +1234,11 @@ pw_getrandom(impl->iv, sizeof(impl->iv), 0) < 0) return -errno; - AES_set_encrypt_key(impl->key, 128, &impl->aes); + rsa_len = rsa_encrypt(impl->key, 16, rsakey); + if (rsa_len < 0) + return -rsa_len; - i = rsa_encrypt(impl->key, 16, rsakey); - base64_encode(rsakey, i, key, '='); + base64_encode(rsakey, rsa_len, key, '='); base64_encode(impl->iv, 16, iv, '='); asprintf(&sdp, "v=0\r\n" @@ -1110,17 +1248,17 @@ "t=0 0\r\n" "m=audio 0 RTP/AVP 96\r\n" "a=rtpmap:96 AppleLossless\r\n" - "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n" + "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 %u\r\n" "a=rsaaeskey:%s\r\n" "a=aesiv:%s\r\n", impl->session_id, ip_version, local_ip, - ip_version, host, frames, key, iv); + ip_version, host, frames, impl->info.rate, + key, iv); break; default: return -ENOTSUP; } - res = pw_rtsp_client_send(impl->rtsp, "ANNOUNCE", &impl->headers->dict, - "application/sdp", sdp, rtsp_announce_reply, impl); + res = rtsp_send(impl, "ANNOUNCE", "application/sdp", sdp, rtsp_announce_reply); free(sdp); return res; @@ -1147,24 +1285,6 @@ rtsp_auth_setup_reply, impl); } -static const char *find_attr(char **tokens, const char *key) -{ - int i; - char *p, *s; - for (i = 0; tokensi; i++) { - if (!spa_strstartswith(tokensi, key)) - continue; - p = tokensi + strlen(key); - if ((s = rindex(p, '"')) == NULL) - continue; - *s = '\0'; - if ((s = index(p, '"')) == NULL) - continue; - return s+1; - } - return NULL; -} - static int rtsp_auth_reply(void *data, int status, const struct spa_dict *headers) { struct impl *impl = data; @@ -1183,37 +1303,37 @@ return res; } -SPA_PRINTF_FUNC(2,3) -static int MD5_hash(char hashMD5_HASH_LENGTH+1, const char *fmt, ...) +static const char *find_attr(char **tokens, const char *key) { - unsigned char dMD5_DIGEST_LENGTH; int i; - va_list args; - char buffer1024; - - va_start(args, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, args); - va_end(args); - - MD5((unsigned char*) buffer, strlen(buffer), d); - for (i = 0; i < MD5_DIGEST_LENGTH; i++) - sprintf(&hash2*i, "%02x", (uint8_t) di); - hashMD5_HASH_LENGTH = '\0'; - return 0; + char *p, *s; + for (i = 0; tokensi; i++) { + if (!spa_strstartswith(tokensi, key)) + continue; + p = tokensi + strlen(key); + if ((s = rindex(p, '"')) == NULL) + continue; + *s = '\0'; + if ((s = index(p, '"')) == NULL) + continue; + return s+1; + } + return NULL; } static int rtsp_do_auth(struct impl *impl, const struct spa_dict *headers) { - const char *str; + const char *str, *realm, *nonce; char **tokens; int n_tokens; - char auth1024; - - if (impl->password == NULL) - return -ENOTSUP; if ((str = spa_dict_lookup(headers, "WWW-Authenticate")) == NULL) - return -ENOENT; + return -EINVAL; + + if (impl->password == NULL) { + pw_log_warn("authentication required but no raop.password property was given"); + return -ENOTSUP; + } pw_log_info("Auth: %s", str); @@ -1221,45 +1341,23 @@ if (tokens == NULL || tokens0 == NULL) goto error; - if (spa_streq(tokens0, "Basic")) { - char buf256; - char enc512; - spa_scnprintf(buf, sizeof(buf), "%s:%s", DEFAULT_USER_NAME, impl->password); - base64_encode((uint8_t*)buf, strlen(buf), enc, '='); - spa_scnprintf(auth, sizeof(auth), "Basic %s", enc); - } - else if (spa_streq(tokens0, "Digest")) { - const char *realm, *nonce, *url; - char h1MD5_HASH_LENGTH+1; - char h2MD5_HASH_LENGTH+1; - char respMD5_HASH_LENGTH+1; + impl->auth_method = strdup(tokens0); + if (spa_streq(impl->auth_method, "Digest")) { realm = find_attr(tokens, "realm"); nonce = find_attr(tokens, "nonce"); if (realm == NULL || nonce == NULL) goto error; - url = pw_rtsp_client_get_url(impl->rtsp); - - MD5_hash(h1, "%s:%s:%s", DEFAULT_USER_NAME, realm, impl->password); - MD5_hash(h2, "OPTIONS:%s", url); - MD5_hash(resp, "%s:%s:%s", h1, nonce, h2); - - spa_scnprintf(auth, sizeof(auth), - "username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"", - DEFAULT_USER_NAME, realm, nonce, url, resp); + impl->realm = strdup(realm); + impl->nonce = strdup(nonce); } - else - goto error; - pw_properties_setf(impl->headers, "Authorization", "%s %s", - tokens0, auth); pw_free_strv(tokens); - pw_rtsp_client_send(impl->rtsp, "OPTIONS", &impl->headers->dict, - NULL, NULL, rtsp_auth_reply, impl); - + rtsp_send(impl, "OPTIONS", NULL, NULL, rtsp_auth_reply); return 0; + error: pw_free_strv(tokens); return -EINVAL; @@ -1342,6 +1440,12 @@ close(impl->timing_fd); impl->timing_fd = -1; } + free(impl->auth_method); + impl->auth_method = NULL; + free(impl->realm); + impl->realm = NULL; + free(impl->nonce); + impl->nonce = NULL; } static void rtsp_disconnected(void *data) @@ -1406,7 +1510,7 @@ return 0; } - hostname = pw_properties_get(impl->props, "raop.hostname"); + hostname = pw_properties_get(impl->props, "raop.ip"); port = pw_properties_get(impl->props, "raop.port"); if (hostname == NULL || port == NULL) return -EINVAL; @@ -1440,8 +1544,7 @@ if (!impl->ready) return 0; - return pw_rtsp_client_send(impl->rtsp, "TEARDOWN", NULL, - NULL, NULL, rtsp_teardown_reply, impl); + return rtsp_send(impl, "TEARDOWN", NULL, NULL, rtsp_teardown_reply); } static void stream_param_changed(void *data, uint32_t id, const struct spa_pod *param) @@ -1549,6 +1652,9 @@ if (impl->rtsp) pw_rtsp_client_destroy(impl->rtsp); + if (impl->ctx) + EVP_CIPHER_CTX_free(impl->ctx); + pw_properties_free(impl->headers); pw_properties_free(impl->stream_props); pw_properties_free(impl->props); @@ -1673,10 +1779,8 @@ { struct pw_context *context = pw_impl_module_get_context(module); struct pw_properties *props = NULL; - uint32_t id = pw_global_get_id(pw_impl_module_get_global(module)); - uint32_t pid = getpid(); struct impl *impl; - const char *str; + const char *str, *name, *hostname, *ipv; int res; PW_LOG_TOPIC_INIT(mod_topic); @@ -1689,7 +1793,12 @@ impl->server_fd = -1; impl->control_fd = -1; impl->timing_fd = -1; - + impl->ctx = EVP_CIPHER_CTX_new(); + if (impl->ctx == NULL) { + res = -errno; + pw_log_error( "can't create cipher context: %m"); + goto error; + } if (args == NULL) args = ""; @@ -1718,11 +1827,25 @@ if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL) pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); - if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL) - pw_properties_setf(props, PW_KEY_NODE_NAME, "raop-sink-%u-%u", pid, id); + if ((name = pw_properties_get(props, "raop.name")) == NULL) + name = "RAOP"; + + if ((str = strstr(name, "@"))) { + str++; + if (strlen(str) > 0) + name = str; + } + if ((ipv = pw_properties_get(props, "raop.ip.version")) == NULL) + ipv = "4"; + if ((hostname = pw_properties_get(props, "raop.hostname")) == NULL) + hostname = name; + if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) - pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, - pw_properties_get(props, PW_KEY_NODE_NAME)); + pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, + "%s (IPv%s)", name, ipv); + if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL) + pw_properties_setf(props, PW_KEY_NODE_NAME, "raop_sink.%s.ipv%s", + hostname, ipv); if (pw_properties_get(props, PW_KEY_NODE_LATENCY) == NULL) pw_properties_set(props, PW_KEY_NODE_LATENCY, "352/44100");
View file
pipewire-0.3.67.tar.gz/src/modules/module-raop/rtsp-client.c -> pipewire-0.3.68.tar.gz/src/modules/module-raop/rtsp-client.c
Changed
@@ -530,6 +530,8 @@ int pw_rtsp_client_disconnect(struct pw_rtsp_client *client) { + struct message *msg; + if (client->source == NULL) return 0; @@ -539,6 +541,11 @@ client->url = NULL; free(client->session_id); client->session_id = NULL; + + spa_list_consume(msg, &client->messages, link) { + spa_list_remove(&msg->link); + free(msg); + } pw_rtsp_client_emit_disconnected(client); return 0; }
View file
pipewire-0.3.67.tar.gz/src/modules/module-roc-sink.c -> pipewire-0.3.68.tar.gz/src/modules/module-roc-sink.c
Changed
@@ -54,7 +54,6 @@ * context.modules = * { name = libpipewire-module-roc-sink * args = { - * local.ip = 0.0.0.0 * fec.code = disable * remote.ip = 192.168.0.244 * remote.source.port = 10001 @@ -350,13 +349,12 @@ static const struct spa_dict_item module_roc_sink_info = { { PW_KEY_MODULE_AUTHOR, "Sanchayan Maity <sanchayan@asymptotic.io>" }, { PW_KEY_MODULE_DESCRIPTION, "roc sink" }, - { PW_KEY_MODULE_USAGE, "sink.name=<name for the sink> " - "local.ip=<local sender ip> " - "fec.code=<empty>|disable|rs8m|ldpc " + { PW_KEY_MODULE_USAGE, "( sink.name=<name for the sink> ) " + "( fec.code=<empty>|disable|rs8m|ldpc ) " "remote.ip=<remote receiver ip> " - "remote.source.port=<remote receiver port for source packets> " - "remote.repair.port=<remote receiver port for repair packets> " - "sink.props= { key=val ... } " }, + "( remote.source.port=<remote receiver port for source packets> ) " + "( remote.repair.port=<remote receiver port for repair packets> ) " + "( sink.props= { key=val ... } ) " }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, };
View file
pipewire-0.3.67.tar.gz/src/modules/module-roc-source.c -> pipewire-0.3.68.tar.gz/src/modules/module-roc-source.c
Changed
@@ -371,14 +371,14 @@ static const struct spa_dict_item module_roc_source_info = { { PW_KEY_MODULE_AUTHOR, "Sanchayan Maity <sanchayan@asymptotic.io>" }, { PW_KEY_MODULE_DESCRIPTION, "roc source" }, - { PW_KEY_MODULE_USAGE, "source.name=<name for the source> " - "resampler.profile=<empty>|disable|high|medium|low " - "fec.code=<empty>|disable|rs8m|ldpc " - "sess.latency.msec=<target network latency in milliseconds> " - "local.ip=<local receiver ip> " - "local.source.port=<local receiver port for source packets> " - "local.repair.port=<local receiver port for repair packets> " - "source.props= { key=value ... }" }, + { PW_KEY_MODULE_USAGE, "( source.name=<name for the source> ) " + "( resampler.profile=<empty>|disable|high|medium|low ) " + "( fec.code=<empty>|disable|rs8m|ldpc ) " + "( sess.latency.msec=<target network latency in milliseconds> ) " + "( local.ip=<local receiver ip> ) " + "( local.source.port=<local receiver port for source packets> ) " + "( local.repair.port=<local receiver port for repair packets> ) " + "( source.props= { key=value ... } ) " }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, };
View file
pipewire-0.3.67.tar.gz/src/modules/module-rt.c -> pipewire-0.3.68.tar.gz/src/modules/module-rt.c
Changed
@@ -119,10 +119,10 @@ #define DEFAULT_RT_TIME_SOFT -1 #define DEFAULT_RT_TIME_HARD -1 -#define MODULE_USAGE "nice.level=<priority: default "SPA_STRINGIFY(DEFAULT_NICE_LEVEL)"(don't change)> " \ - "rt.prio=<priority: default "SPA_STRINGIFY(DEFAULT_RT_PRIO)"> " \ - "rt.time.soft=<in usec: default "SPA_STRINGIFY(DEFAULT_RT_TIME_SOFT)" " \ - "rt.time.hard=<in usec: default "SPA_STRINGIFY(DEFAULT_RT_TIME_HARD)" " +#define MODULE_USAGE "( nice.level=<priority: default "SPA_STRINGIFY(DEFAULT_NICE_LEVEL)"(don't change)> ) " \ + "( rt.prio=<priority: default "SPA_STRINGIFY(DEFAULT_RT_PRIO)"> ) " \ + "( rt.time.soft=<in usec: default "SPA_STRINGIFY(DEFAULT_RT_TIME_SOFT)" ) " \ + "( rt.time.hard=<in usec: default "SPA_STRINGIFY(DEFAULT_RT_TIME_HARD)" ) " static const struct spa_dict_item module_props = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, @@ -574,6 +574,7 @@ return false; } if (try == 2) { +#ifdef RLIMIT_RTPRIO struct rlimit rlim; /* second try, try to clamp to RLIMIT_RTPRIO */ if (getrlimit(RLIMIT_RTPRIO, &rlim) == 0 && max > (int)rlim.rlim_max) { @@ -581,6 +582,7 @@ max = (int)rlim.rlim_max; } else +#endif break; } if (max < DEFAULT_RT_PRIO_MIN) {
View file
pipewire-0.3.68.tar.gz/src/modules/module-rtp-sap.c
Added
@@ -0,0 +1,1509 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans <wim.taymans@gmail.com> */ +/* SPDX-License-Identifier: MIT */ + +#include "config.h" + +#include <limits.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> +#include <netinet/ip.h> +#include <netinet/in.h> +#include <net/if.h> +#include <ctype.h> + +#include <spa/utils/hook.h> +#include <spa/utils/result.h> +#include <spa/debug/types.h> + +#include <pipewire/pipewire.h> +#include <pipewire/impl.h> + +#include <module-rtp/sap.h> + +#ifdef __FreeBSD__ +#define ifr_ifindex ifr_index +#endif + +/** \page page_module_rtp_sap PipeWire Module: Announce and create RTP streams + * + * The `rtp-sap` module announces RTP streams that match the rules with the + * announce-stream action. + * + * It will create source RTP streams that are announced with SAP when they + * match the rule with the create-stream action. + * + * If no stream.rules are given, it will announce all streams with + * sess.sap.announce = true and it will create a receiver for all announced + * streams. + * + * ## Module Options + * + * Options specific to the behavior of this module + * + * - `local.ifname = <str>`: interface name to use + * - `sap.ip = <str>`: IP address of the SAP messages, default "224.0.0.56" + * - `sap.port = <int>`: port of the SAP messages, default 9875 + * - `sap.cleanup.sec = <int>`: cleanup interval in seconds, default 90 seconds + * - `source.ip =<str>`: source IP address, default "0.0.0.0" + * - `net.ttl = <int>`: TTL to use, default 1 + * - `net.loop = <bool>`: loopback multicast, default false + * - `stream.rules` = <rules>: match rules, use create-stream and announce-stream actions + * + * ## General options + * + * Options with well-known behavior: + * + * - \ref PW_KEY_REMOTE_NAME + * + * ## Example configuration + *\code{.unparsed} + * context.modules = + * { name = libpipewire-module-rtp-sap + * args = { + * #local.ifname = "eth0" + * #sap.ip = "224.0.0.56" + * #sap.port = 9875 + * #sap.cleanup.sec = 5 + * #source.ip = "0.0.0.0" + * #net.ttl = 1 + * #net.loop = false + * stream.rules = + * { matches = + * # any of the items in matches needs to match, if one does, + * # actions are emited. + * { # all keys must match the value. ~ in value starts regex. + * #rtp.origin = "wim 3883629975 0 IN IP4 0.0.0.0" + * #rtp.payload = "127" + * #rtp.fmt = "L16/48000/2" + * #rtp.session = "PipeWire RTP Stream on fedora" + * #rtp.ts-offset = 0 + * #rtp.ts-refclk = "private" + * sess.sap.announce = true + * } + * + * actions = { + * announce-stream = { + * } + * } + * } + * { matches = + * { # all keys must match the value. ~ in value starts regex. + * #rtp.origin = "wim 3883629975 0 IN IP4 0.0.0.0" + * #rtp.payload = "127" + * #rtp.fmt = "L16/48000/2" + * #rtp.session = "PipeWire RTP Stream on fedora" + * #rtp.ts-offset = 0 + * #rtp.ts-refclk = "private" + * rtp.session = "~.*" + * } + * + * actions = { + * create-stream = { + * #sess.latency.msec = 100 + * #sess.ts-direct = false + * #target.object = "" + * } + * } + * } + * + * } + * } + * + *\endcode + * + * \since 0.3.67 + */ + +#define NAME "rtp-sap" + +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); +#define PW_LOG_TOPIC_DEFAULT mod_topic + +#define MAX_SESSIONS 64 + +#define DEFAULT_ANNOUNCE_RULES \ + " { matches = { sess.sap.announce = true } actions = { announce-stream = { } } } " +#define DEFAULT_CREATE_RULES \ + " { matches = { rtp.session = \"~.*\" } actions = { create-stream = { } } } " + +#define DEFAULT_CLEANUP_SEC 90 +#define SAP_INTERVAL_SEC 5 +#define SAP_MIME_TYPE "application/sdp" + +#define DEFAULT_SAP_IP "224.0.0.56" +#define DEFAULT_SAP_PORT 9875 + +#define DEFAULT_SOURCE_IP "0.0.0.0" +#define DEFAULT_TTL 1 +#define DEFAULT_LOOP false + +#define USAGE "( local.ifname=<local interface name to use> ) " \ + "( sap.ip=<SAP IP address to send announce, default:"DEFAULT_SAP_IP"> ) " \ + "( sap.port=<SAP port to send on, default:"SPA_STRINGIFY(DEFAULT_SAP_PORT)"> ) " \ + "( sap.cleanup.sec=<cleanup interval in seconds, default 90> ) " \ + "( source.ip=<source IP address, default:"DEFAULT_SOURCE_IP"> ) " \ + "( net.ttl=<desired TTL, default:"SPA_STRINGIFY(DEFAULT_TTL)"> ) " \ + "( net.loop=<desired loopback, default:"SPA_STRINGIFY(DEFAULT_LOOP)"> ) " \ + "( stream.rules=<rules>, use announce-stream and create-stream actions )" + +static const struct spa_dict_item module_info = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { PW_KEY_MODULE_DESCRIPTION, "RTP SAP announce/listen" }, + { PW_KEY_MODULE_USAGE, USAGE }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + +struct sdp_info { + uint16_t hash; + uint32_t ntp; + + char *origin; + char *session_name; + char *media_type; + char *mime_type; + char channelmap512; + + uint16_t dst_port; + struct sockaddr_storage dst_addr; + socklen_t dst_len; + uint16_t ttl; + + uint16_t port; + uint8_t payload; + + uint32_t rate; + uint32_t channels; + + float ptime; + + uint32_t ts_offset; + char *ts_refclk; +}; + +struct session { + struct spa_list link; + + bool announce; + uint64_t timestamp; + + struct impl *impl; + struct node *node; + + struct sdp_info info; + + unsigned has_sent_sap:1; + + struct pw_properties *props; + + struct pw_impl_module *module; + struct spa_hook module_listener; +}; + +struct node { + struct impl *impl; + + uint32_t id; + + struct pw_proxy *proxy; + struct spa_hook proxy_listener; + struct spa_hook node_listener; + + struct pw_node_info *info; + struct session *session; +}; + +struct impl { + struct pw_properties *props; + + struct pw_loop *loop; + + struct pw_impl_module *module; + struct spa_hook module_listener; + + struct pw_core *core; + struct spa_hook core_listener; + struct spa_hook core_proxy_listener; + unsigned int do_disconnect:1; + + struct pw_registry *registry; + struct spa_hook registry_listener; + + struct spa_source *timer; + + char *ifname; + bool ttl; + bool mcast_loop; + + struct sockaddr_storage src_addr; + socklen_t src_len; + + uint16_t sap_port; + struct sockaddr_storage sap_addr; + socklen_t sap_len; + int sap_fd; + struct spa_source *sap_source; + uint32_t cleanup_interval; + + uint32_t n_sessions; + struct spa_list sessions; +}; + +struct format_info { + uint32_t media_subtype; + uint32_t format; + uint32_t size; + const char *mime; + const char *media_type; + const char *format_str; +}; + +static const struct format_info audio_format_info = { + { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_U8, 1, "L8", "audio", "U8" }, + { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_ALAW, 1, "PCMA", "audio", "ALAW" }, + { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_ULAW, 1, "PCMU", "audio", "ULAW" }, + { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_S16_BE, 2, "L16", "audio", "S16BE" }, + { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_S24_BE, 3, "L24", "audio", "S16LE" }, + { SPA_MEDIA_SUBTYPE_control, 0, 1, "rtp-midi", "midi", NULL }, + { SPA_MEDIA_SUBTYPE_opus, 0, 1, "opus", "opus", NULL }, +}; + +static const struct format_info *find_audio_format_info(const char *mime) +{ + SPA_FOR_EACH_ELEMENT_VAR(audio_format_info, f) + if (spa_streq(f->mime, mime)) + return f; + return NULL; +} + +static int send_sap(struct impl *impl, struct session *sess, bool bye); + + +static void clear_sdp_info(struct sdp_info *info) +{ + free(info->origin); + free(info->session_name); + free(info->media_type); + free(info->mime_type); + free(info->ts_refclk); + spa_zero(*info); +} + +static void session_touch(struct session *sess) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + sess->timestamp = SPA_TIMESPEC_TO_NSEC(&ts); +} + +static void session_free(struct session *sess) +{ + struct impl *impl = sess->impl; + + if (sess->impl) { + if (sess->announce) + send_sap(impl, sess, 1); + spa_list_remove(&sess->link); + impl->n_sessions++; + } + if (sess->node && sess->node->session != NULL) + sess->node->session = NULL; + + if (sess->module) { + spa_hook_remove(&sess->module_listener); + pw_impl_module_destroy(sess->module); + } + + pw_properties_free(sess->props); + clear_sdp_info(&sess->info); + free(sess); +} + +static int parse_address(const char *address, uint16_t port, + struct sockaddr_storage *addr, socklen_t *len) +{ + struct sockaddr_in *sa4 = (struct sockaddr_in*)addr; + struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)addr; + + if (inet_pton(AF_INET, address, &sa4->sin_addr) > 0) { + sa4->sin_family = AF_INET; + sa4->sin_port = htons(port); + *len = sizeof(*sa4); + } else if (inet_pton(AF_INET6, address, &sa6->sin6_addr) > 0) { + sa6->sin6_family = AF_INET6; + sa6->sin6_port = htons(port); + *len = sizeof(*sa6); + } else + return -EINVAL; + + return 0; +} + +static bool is_multicast(struct sockaddr *sa, socklen_t salen) +{ + if (sa->sa_family == AF_INET) { + static const uint32_t ipv4_mcast_mask = 0xe0000000; + struct sockaddr_in *sa4 = (struct sockaddr_in*)sa; + return (ntohl(sa4->sin_addr.s_addr) & ipv4_mcast_mask) == ipv4_mcast_mask; + } else if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)sa; + return sa6->sin6_addr.s6_addr0 == 0xff; + } + return false; +} + +static int make_send_socket(struct sockaddr_storage *sa, socklen_t salen, + bool loop, int ttl, char *ifname) +{ + int af, fd, val, res; + + af = sa->ss_family; + if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) { + pw_log_error("socket failed: %m"); + return -errno; + } + if (connect(fd, (struct sockaddr*)sa, salen) < 0) { + res = -errno; + pw_log_error("connect() failed: %m"); + goto error; + } + if (is_multicast((struct sockaddr*)sa, salen)) { + val = loop; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &val, sizeof(val)) < 0) + pw_log_warn("setsockopt(IP_MULTICAST_LOOP) failed: %m"); + + val = ttl; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &val, sizeof(val)) < 0) + pw_log_warn("setsockopt(IP_MULTICAST_TTL) failed: %m"); + } + return fd; +error: + close(fd); + return res; +} + +static int make_recv_socket(struct sockaddr_storage *sa, socklen_t salen, + char *ifname) +{ + int af, fd, val, res; + struct ifreq req; + + af = sa->ss_family; + if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) { + pw_log_error("socket failed: %m"); + return -errno; + } + val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) { + res = -errno; + pw_log_error("setsockopt failed: %m"); + goto error; + } + spa_zero(req); + if (ifname) { + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", ifname); + res = ioctl(fd, SIOCGIFINDEX, &req); + if (res < 0) + pw_log_warn("SIOCGIFINDEX %s failed: %m", ifname); + } + res = 0; + if (af == AF_INET) { + static const uint32_t ipv4_mcast_mask = 0xe0000000; + struct sockaddr_in *sa4 = (struct sockaddr_in*)sa; + if ((ntohl(sa4->sin_addr.s_addr) & ipv4_mcast_mask) == ipv4_mcast_mask) { + struct ip_mreqn mr4; + memset(&mr4, 0, sizeof(mr4)); + mr4.imr_multiaddr = sa4->sin_addr; + mr4.imr_ifindex = req.ifr_ifindex; + res = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mr4, sizeof(mr4)); + } else { + sa4->sin_addr.s_addr = INADDR_ANY; + } + } else if (af == AF_INET6) { + struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)sa; + if (sa6->sin6_addr.s6_addr0 == 0xff) { + struct ipv6_mreq mr6; + memset(&mr6, 0, sizeof(mr6)); + mr6.ipv6mr_multiaddr = sa6->sin6_addr; + mr6.ipv6mr_interface = req.ifr_ifindex; + res = setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mr6, sizeof(mr6)); + } else { + sa6->sin6_addr = in6addr_any; + } + } else { + res = -EINVAL; + goto error; + } + + if (res < 0) { + res = -errno; + pw_log_error("join mcast failed: %m"); + goto error; + } + + if (bind(fd, (struct sockaddr*)sa, salen) < 0) { + res = -errno; + pw_log_error("bind() failed: %m"); + goto error; + } + return fd; +error: + close(fd); + return res; +} + +static int get_ip(const struct sockaddr_storage *sa, char *ip, size_t len, bool *ip4) +{ + if (sa->ss_family == AF_INET) { + struct sockaddr_in *in = (struct sockaddr_in*)sa; + inet_ntop(sa->ss_family, &in->sin_addr, ip, len); + } else if (sa->ss_family == AF_INET6) { + struct sockaddr_in6 *in = (struct sockaddr_in6*)sa; + inet_ntop(sa->ss_family, &in->sin6_addr, ip, len); + *ip4 = false; + } else + return -EIO; + if (ip4) + *ip4 = sa->ss_family == AF_INET; + return 0; +} + +static int send_sap(struct impl *impl, struct session *sess, bool bye) +{ + char buffer2048, src_addr64, dst_addr64, dst_ttl8; + const char *user_name; + struct sockaddr *sa = (struct sockaddr*)&impl->src_addr; + struct sap_header header; + struct iovec iov4; + struct msghdr msg; + struct spa_strbuf buf; + struct sdp_info *sdp = &sess->info; + bool src_ip4, dst_ip4; + int res; + + if (!sess->has_sent_sap && bye) + return 0; + + spa_zero(header); + header.v = 1; + header.t = bye; + header.msg_id_hash = sdp->hash; + + iov0.iov_base = &header; + iov0.iov_len = sizeof(header); + + if ((res = get_ip(&impl->src_addr, src_addr, sizeof(src_addr), &src_ip4)) < 0) + return res; + + if (src_ip4) { + iov1.iov_base = &((struct sockaddr_in*) sa)->sin_addr; + iov1.iov_len = 4U; + } else { + iov1.iov_base = &((struct sockaddr_in6*) sa)->sin6_addr; + iov1.iov_len = 16U; + header.a = 1; + } + iov2.iov_base = SAP_MIME_TYPE; + iov2.iov_len = sizeof(SAP_MIME_TYPE); + + if ((res = get_ip(&sdp->dst_addr, dst_addr, sizeof(dst_addr), &dst_ip4)) < 0) + return res; + + if ((user_name = pw_get_user_name()) == NULL) + user_name = "-"; + + spa_zero(dst_ttl); + if (is_multicast((struct sockaddr*)&sdp->dst_addr, sdp->dst_len)) + snprintf(dst_ttl, sizeof(dst_ttl), "/%d", sdp->ttl); + + spa_strbuf_init(&buf, buffer, sizeof(buffer)); + spa_strbuf_append(&buf, + "v=0\n" + "o=%s %u 0 IN %s %s\n" + "s=%s\n" + "c=IN %s %s%s\n" + "t=%u 0\n" + "a=recvonly\n" + "a=tool:PipeWire %s\n" + "a=type:broadcast\n", + user_name, sdp->ntp, src_ip4 ? "IP4" : "IP6", src_addr, + sdp->session_name, + dst_ip4 ? "IP4" : "IP6", dst_addr, dst_ttl, + sdp->ntp, + pw_get_library_version()); + spa_strbuf_append(&buf, + "m=%s %u RTP/AVP %i\n", + sdp->media_type, + sdp->dst_port, sdp->payload); + + if (sdp->channels) { + spa_strbuf_append(&buf, + "a=rtpmap:%i %s/%u/%u\n", + sdp->payload, sdp->mime_type, + sdp->rate, sdp->channels); + if (sdp->channelmap0 != 0) { + spa_strbuf_append(&buf, + "i=%d channels: %s\n", sdp->channels, + sdp->channelmap); + } + } else { + spa_strbuf_append(&buf, + "a=rtpmap:%i %s/%u\n", + sdp->payload, sdp->mime_type, sdp->rate); + } + if (sdp->ptime != 0) + spa_strbuf_append(&buf, + "a=ptime:%f\n", sdp->ptime); + + if (sdp->ts_refclk != NULL) { + spa_strbuf_append(&buf, + "a=ts-refclk:%s\n" + "a=mediaclk:direct=%u\n", + sdp->ts_refclk, + sdp->ts_offset); + } else { + spa_strbuf_append(&buf, "a=mediaclk:sender\n"); + } + + pw_log_debug("sending SAP for %u %s", sess->node->id, buffer); + + iov3.iov_base = buffer; + iov3.iov_len = strlen(buffer); + + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = iov; + msg.msg_iovlen = 4; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + res = sendmsg(impl->sap_fd, &msg, MSG_NOSIGNAL); + if (res < 0) + res = -errno; + else + sess->has_sent_sap = true; + + return res; +} + +static void on_timer_event(void *data, uint64_t expirations) +{ + struct impl *impl = data; + struct session *sess, *tmp; + struct timespec ts; + uint64_t timestamp, interval; + + clock_gettime(CLOCK_MONOTONIC, &ts); + timestamp = SPA_TIMESPEC_TO_NSEC(&ts); + interval = impl->cleanup_interval * SPA_NSEC_PER_SEC; + + spa_list_for_each_safe(sess, tmp, &impl->sessions, link) { + if (sess->announce) { + send_sap(impl, sess, 0); + } else { + if (sess->timestamp + interval < timestamp) { + pw_log_info("session %s timeout", + sess->info.session_name); + session_free(sess); + } + + } + } +} + +static struct session *session_find(struct impl *impl, const struct sdp_info *info) +{ + struct session *sess; + spa_list_for_each(sess, &impl->sessions, link) { + if (info->hash == sess->info.hash && + spa_streq(info->origin, sess->info.origin)) + return sess; + } + return NULL; +} + +static struct session *session_new_announce(struct impl *impl, struct node *node, + struct pw_properties *props) +{ + struct session *sess = NULL; + struct sdp_info *sdp; + const char *str; + uint32_t port; + int res; + + if (impl->n_sessions >= MAX_SESSIONS) { + pw_log_warn("too many sessions (%u >= %u)", impl->n_sessions, MAX_SESSIONS); + errno = EMFILE; + return NULL; + } + + sess = calloc(1, sizeof(struct session)); + if (sess == NULL) + return NULL; + + sdp = &sess->info; + + sess->announce = true; + + sdp->hash = pw_rand32(); + sdp->ntp = (uint32_t) time(NULL) + 2208988800U; + sess->props = props; + + if ((str = pw_properties_get(props, "sess.name")) == NULL) + str = pw_get_host_name(); + sdp->session_name = strdup(str); + + if ((str = pw_properties_get(props, "rtp.destination.port")) == NULL) + goto error_free; + if (!spa_atou32(str, &port, 0)) + goto error_free; + sdp->dst_port = port; + + if ((str = pw_properties_get(props, "rtp.destination.ip")) == NULL) + goto error_free; + if ((res = parse_address(str, sdp->dst_port, &sdp->dst_addr, &sdp->dst_len)) < 0) { + pw_log_error("invalid destination.ip %s: %s", str, spa_strerror(res)); + goto error_free; + } + sdp->ttl = pw_properties_get_int32(props, "rtp.ttl", DEFAULT_TTL); + sdp->payload = pw_properties_get_int32(props, "rtp.payload", 127); + + if ((str = pw_properties_get(props, "rtp.media")) != NULL) + sdp->media_type = strdup(str); + if ((str = pw_properties_get(props, "rtp.mime")) != NULL) + sdp->mime_type = strdup(str); + if ((str = pw_properties_get(props, "rtp.rate")) != NULL) + sdp->rate = atoi(str); + if ((str = pw_properties_get(props, "rtp.channels")) != NULL) + sdp->channels = atoi(str); + if ((str = pw_properties_get(props, "rtp.ts-offset")) != NULL) + sdp->ts_offset = atoi(str); + if ((str = pw_properties_get(props, "rtp.ts-refclk")) != NULL) + sdp->ts_refclk = strdup(str); + if ((str = pw_properties_get(props, "rtp.channel-names")) != NULL) + snprintf(sdp->channelmap, sizeof(sdp->channelmap), "%s", str); + + pw_log_info("created new session for node:%u", node->id); + node->session = sess; + sess->node = node; + + sess->impl = impl; + spa_list_append(&impl->sessions, &sess->link); + impl->n_sessions++; + + send_sap(impl, sess, 0); + + return sess; + +error_free: + pw_log_warn("invalid session props"); + session_free(sess); + return NULL; +} + +static void session_module_destroy(void *d) +{ + struct session *sess = d; + spa_hook_remove(&sess->module_listener); + sess->module = NULL; + session_free(sess); +} + +static const struct pw_impl_module_events session_module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = session_module_destroy, +}; + +static int session_load_source(struct session *session, struct pw_properties *props) +{ + struct impl *impl = session->impl; + struct pw_context *context = pw_impl_module_get_context(impl->module); + FILE *f = NULL; + char *args = NULL; + size_t size; + const char *str, *media; + int res; + + if ((f = open_memstream(&args, &size)) == NULL) { + res = -errno; + pw_log_error("Can't open memstream: %m"); + goto done; + } + fprintf(f, "{"); + + if ((str = pw_properties_get(props, "rtp.destination.ip")) != NULL) + fprintf(f, "\"source.ip\" = \"%s\", ", str); + if ((str = pw_properties_get(props, "rtp.destination.port")) != NULL) + fprintf(f, "\"source.port\" = %s, ", str); + if ((str = pw_properties_get(props, "rtp.session")) != NULL) + fprintf(f, "\"sess.name\" = \"%s\", ", str); + + if ((media = pw_properties_get(props, "sess.media")) == NULL) + media = "audio"; + + if (spa_streq(media, "audio")) { + const char *mime; + const struct format_info *format_info; + + if ((mime = pw_properties_get(props, "rtp.mime")) == NULL) { + pw_log_error("missing rtp.mime property"); + res = -EINVAL; + goto done; + } + format_info = find_audio_format_info(mime); + if (format_info == NULL) { + pw_log_error("unknown rtp.mime type %s", mime); + res = -ENOTSUP; + goto done; + } + fprintf(f, "\"sess.media\" = \"%s\", ", format_info->media_type); + if (format_info->format_str != NULL) { + pw_properties_set(props, "audio.format", format_info->format_str); + if ((str = pw_properties_get(props, "rtp.rate")) != NULL) + pw_properties_set(props, "audio.rate", str); + if ((str = pw_properties_get(props, "rtp.channels")) != NULL) + pw_properties_set(props, "audio.channels", str); + } + } else { + pw_log_error("Unhandled media %s", media); + res = -EINVAL; + goto done; + } + if ((str = pw_properties_get(props, "rtp.ts-offset")) != NULL) + fprintf(f, "\"sess.ts-offset\" = %s, ", str); + + fprintf(f, " stream.props = {"); + pw_properties_serialize_dict(f, &props->dict, 0); + fprintf(f, " }"); + fprintf(f, "}"); + fclose(f); + f = NULL; + + pw_log_info("loading new RTP source"); + session->module = pw_context_load_module(context, + "libpipewire-module-rtp-source", + args, NULL); + + if (session->module == NULL) { + res = -errno; + pw_log_error("Can't load module: %m"); + goto done; + } + + pw_impl_module_add_listener(session->module, + &session->module_listener, + &session_module_events, session); + + res = 0; +done: + if (f != NULL) + fclose(f); + free(args); + return res; +} + +struct match_info { + struct impl *impl; + struct session *session; + struct node *node; + struct pw_properties *props; + bool matched; +}; + +static int rule_matched(void *data, const char *location, const char *action, + const char *str, size_t len) +{ + struct match_info *i = data; + int res = 0; + + i->matched = true; + if (i->session && spa_streq(action, "create-stream")) { + pw_properties_update_string(i->props, str, len); + + session_load_source(i->session, i->props); + } + else if (i->node && spa_streq(action, "announce-stream")) { + struct pw_properties *props; + + if ((props = pw_properties_new_dict(i->node->info->props)) == NULL) + return -errno; + + pw_properties_update_string(props, str, len); + + session_new_announce(i->impl, i->node, props); + } + return res; +} + +static struct session *session_new(struct impl *impl, struct sdp_info *info) +{ + struct session *session; + struct pw_properties *props; + const char *str; + char dst_addr64; + + if (impl->n_sessions >= MAX_SESSIONS) { + pw_log_warn("too many sessions (%u >= %u)", impl->n_sessions, MAX_SESSIONS); + errno = EMFILE; + return NULL; + } + + session = calloc(1, sizeof(struct session)); + if (session == NULL) + return NULL; + + session->announce = false; + session->info = *info; + spa_zero(*info); + info = &session->info; + + props = pw_properties_new(NULL, NULL); + if (props == NULL) + goto error; + + session->impl = impl; + spa_list_append(&impl->sessions, &session->link); + impl->n_sessions++; + + pw_properties_set(props, "rtp.origin", info->origin); + if (info->session_name != NULL) { + pw_properties_set(props, "rtp.session", info->session_name); + pw_properties_setf(props, PW_KEY_MEDIA_NAME, "RTP Stream (%s)", + info->session_name); + pw_properties_setf(props, PW_KEY_NODE_NAME, "%s", + info->session_name); + } else { + pw_properties_set(props, PW_KEY_MEDIA_NAME, "RTP Stream"); + } + + get_ip(&info->dst_addr, dst_addr, sizeof(dst_addr), NULL); + pw_properties_setf(props, "rtp.destination.ip", "%s", dst_addr); + pw_properties_setf(props, "rtp.destination.port", "%u", info->dst_port); + pw_properties_setf(props, "rtp.payload", "%u", info->payload); + pw_properties_setf(props, "rtp.media", "%s", info->media_type); + pw_properties_setf(props, "rtp.mime", "%s", info->mime_type); + pw_properties_setf(props, "rtp.rate", "%u", info->rate); + pw_properties_setf(props, "rtp.channels", "%u", info->channels); + + pw_properties_setf(props, "rtp.ts-offset", "%u", info->ts_offset); + pw_properties_set(props, "rtp.ts-refclk", info->ts_refclk); + + if (info->channelmap0) + pw_properties_set(props, PW_KEY_NODE_CHANNELNAMES, info->channelmap); + + if ((str = pw_properties_get(impl->props, "stream.rules")) == NULL) + str = DEFAULT_CREATE_RULES; + if (str != NULL) { + struct match_info minfo = { + .impl = impl, + .session = session, + .props = props, + }; + pw_conf_match_rules(str, strlen(str), NAME, &props->dict, + rule_matched, &minfo); + } + session->props = props; + session_touch(session); + + return NULL; +error: + session_free(session); + return NULL; +} + +static int parse_sdp_c(struct impl *impl, char *c, struct sdp_info *info) +{ + int res; + + cstrcspn(c, "/") = 0; + if (spa_strstartswith(c, "c=IN IP4 ")) { + struct sockaddr_in *sa = (struct sockaddr_in*) &info->dst_addr; + + c += strlen("c=IN IP4 "); + if (inet_pton(AF_INET, c, &sa->sin_addr) <= 0) { + res = -errno; + pw_log_warn("inet_pton(%s) failed: %m", c); + goto error; + } + sa->sin_family = AF_INET; + info->dst_len = sizeof(struct sockaddr_in); + } + else if (spa_strstartswith(c, "c=IN IP6 ")) { + struct sockaddr_in6 *sa = (struct sockaddr_in6*) &info->dst_addr; + + c += strlen("c=IN IP6 "); + if (inet_pton(AF_INET6, c, &sa->sin6_addr) <= 0) { + res = -errno; + pw_log_warn("inet_pton(%s) failed: %m", c); + goto error; + } + + sa->sin6_family = AF_INET6; + info->dst_len = sizeof(struct sockaddr_in6); + } else + return -EINVAL; + + + res= 0; +error: + return res; +} + +static int parse_sdp_m(struct impl *impl, char *c, struct sdp_info *info) +{ + int port, payload; + char media_type12; + + if (!spa_strstartswith(c, "m=")) + return -EINVAL; + + c += strlen("m="); + if (sscanf(c, "%11s %i RTP/AVP %i", media_type, &port, &payload) != 3) + return -EINVAL; + + if (port <= 0 || port > 0xFFFF) + return -EINVAL; + + if (payload < 0 || payload > 127) + return -EINVAL; + + info->media_type = strdup(media_type); + info->dst_port = (uint16_t) port; + info->payload = (uint8_t) payload; + + return 0; +} + +/* some AES67 devices have channelmap encoded in i=* + * if `i` record is found, it matches the template + * and channel count matches, name the channels respectively + * `i=2 channels: 01, 08` is the format */ +static int parse_sdp_i(struct impl *impl, char *c, struct sdp_info *info) +{ + if (!strstr(c, " channels: ")) { + return 0; + } + + c += strlen("i="); + cstrcspn(c, " ") = '\0'; + + uint32_t channels; + if (sscanf(c, "%u", &channels) != 1 || channels <= 0 || channels > SPA_AUDIO_MAX_CHANNELS) + return 0; + + c += strcspn(c, "\0"); + c += strlen(" channels: "); + + strncpy(info->channelmap, c, sizeof(info->channelmap) - 1); + + return 0; +} + +static int parse_sdp_a_rtpmap(struct impl *impl, char *c, struct sdp_info *info) +{ + int payload, len, rate, channels; + + if (!spa_strstartswith(c, "a=rtpmap:")) + return 0; + + c += strlen("a=rtpmap:"); + + if (sscanf(c, "%i %n", &payload, &len) != 1) + return -EINVAL; + + if (payload < 0 || payload > 127) + return -EINVAL; + + if (payload != info->payload) + return 0; + + c += len; + cstrcspn(c, "/") = 0; + info->mime_type = strdup(c); + c += strlen(c) + 1; + + if (sscanf(c, "%u/%u", &rate, &channels) == 2) { + info->channels = channels; + info->rate = rate; + } else if (sscanf(c, "%u", &rate) == 1) { + info->rate = rate; + info->channels = 1; + } else + return -EINVAL; + + pw_log_debug("rate: %d, ch: %d", info->rate, info->channels); + + return 0; +} + +static int parse_sdp_a_mediaclk(struct impl *impl, char *c, struct sdp_info *info) +{ + if (!spa_strstartswith(c, "a=mediaclk:")) + return 0; + + c += strlen("a=mediaclk:"); + + if (spa_strstartswith(c, "direct=")) { + int offset; + c += strlen("direct="); + if (sscanf(c, "%i", &offset) != 1) + return -EINVAL; + info->ts_offset = offset; + } else if (spa_strstartswith(c, "sender")) { + info->ts_offset = 0; + } + return 0; +} + +static int parse_sdp_a_ts_refclk(struct impl *impl, char *c, struct sdp_info *info) +{ + if (!spa_strstartswith(c, "a=ts-refclk:")) + return 0; + + c += strlen("a=ts-refclk:"); + info->ts_refclk = strdup(c); + return 0; +} + +static int parse_sdp(struct impl *impl, char *sdp, struct sdp_info *info) +{ + char *s = sdp; + int count = 0, res = 0; + size_t l; + + while (*s) { + if ((l = strcspn(s, "\r\n")) < 2) + goto too_short; + + sl = 0; + pw_log_debug("%d: %s", count, s); + + if (count++ == 0 && strcmp(s, "v=0") != 0) + goto invalid_version; + + if (spa_strstartswith(s, "o=")) + info->origin = strdup(&s2); + else if (spa_strstartswith(s, "s=")) + info->session_name = strdup(&s2); + else if (spa_strstartswith(s, "c=")) + res = parse_sdp_c(impl, s, info); + else if (spa_strstartswith(s, "m=")) + res = parse_sdp_m(impl, s, info); + else if (spa_strstartswith(s, "a=rtpmap:")) + res = parse_sdp_a_rtpmap(impl, s, info); + else if (spa_strstartswith(s, "a=mediaclk:")) + res = parse_sdp_a_mediaclk(impl, s, info); + else if (spa_strstartswith(s, "a=ts-refclk:")) + res = parse_sdp_a_ts_refclk(impl, s, info); + else if (spa_strstartswith(s, "i=")) + res = parse_sdp_i(impl, s, info); + + if (res < 0) + goto error; + s += l + 1; + while (isspace(*s)) + s++; + } + if (((struct sockaddr*) &info->dst_addr)->sa_family == AF_INET) + ((struct sockaddr_in*) &info->dst_addr)->sin_port = htons(info->dst_port); + else + ((struct sockaddr_in6*) &info->dst_addr)->sin6_port = htons(info->dst_port); + + return 0; +too_short: + pw_log_warn("SDP: line starting with `%.6s...' too short", s); + return -EINVAL; +invalid_version: + pw_log_warn("SDP: invalid first version line `%*s'", (int)l, s); + return -EINVAL; +error: + pw_log_warn("SDP: error: %s", spa_strerror(res)); + return res; +} + +static int parse_sap(struct impl *impl, void *data, size_t len) +{ + struct sap_header *header; + char *mime, *sdp; + struct sdp_info info; + struct session *sess; + int res; + size_t offs; + bool bye; + + if (len < 8) + return -EINVAL; + + header = (struct sap_header*) data; + if (header->v != 1) + return -EINVAL; + + if (header->e) + return -ENOTSUP; + if (header->c) + return -ENOTSUP; + + offs = header->a ? 12 : 8; + offs += header->auth_len * 4; + if (len <= offs) + return -EINVAL; + + mime = SPA_PTROFF(data, offs, char); + if (spa_strstartswith(mime, "v=0")) { + sdp = mime; + mime = SAP_MIME_TYPE; + } else if (spa_streq(mime, SAP_MIME_TYPE)) + sdp = SPA_PTROFF(mime, strlen(mime)+1, char); + else + return -EINVAL; + + pw_log_debug("got SAP: %s %s", mime, sdp); + + spa_zero(info); + if ((res = parse_sdp(impl, sdp, &info)) < 0) + return res; + + bye = header->t; + + sess = session_find(impl, &info); + if (sess == NULL) { + if (!bye) + session_new(impl, &info); + } else { + if (bye) + session_free(sess); + else + session_touch(sess); + } + clear_sdp_info(&info); + return res; +} + +static void +on_sap_io(void *data, int fd, uint32_t mask) +{ + struct impl *impl = data; + + if (mask & SPA_IO_IN) { + uint8_t buffer2048; + ssize_t len; + + if ((len = recv(fd, buffer, sizeof(buffer), 0)) < 0) { + pw_log_warn("recv error: %m"); + return; + } + if ((size_t)len >= sizeof(buffer)) + return; + + bufferlen = 0; + parse_sap(impl, buffer, len); + } +} + +static int start_sap(struct impl *impl) +{ + int fd, res; + struct timespec value, interval; + + if ((fd = make_send_socket(&impl->sap_addr, impl->sap_len, + impl->mcast_loop, impl->ttl, + impl->ifname)) < 0) + return fd; + + impl->sap_fd = fd; + + pw_log_info("starting SAP timer"); + impl->timer = pw_loop_add_timer(impl->loop, on_timer_event, impl); + if (impl->timer == NULL) { + res = -errno; + pw_log_error("can't create timer source: %m"); + goto error; + } + value.tv_sec = 0; + value.tv_nsec = 1; + interval.tv_sec = SAP_INTERVAL_SEC; + interval.tv_nsec = 0; + pw_loop_update_timer(impl->loop, impl->timer, &value, &interval, false); + + if ((fd = make_recv_socket(&impl->sap_addr, impl->sap_len, impl->ifname)) < 0) + return fd; + + pw_log_info("starting SAP listener"); + impl->sap_source = pw_loop_add_io(impl->loop, fd, + SPA_IO_IN, true, on_sap_io, impl); + if (impl->sap_source == NULL) { + res = -errno; + goto error; + } + + return 0; +error: + close(fd); + return res; +} + +static void node_event_info(void *data, const struct pw_node_info *info) +{ + struct node *n = data; + struct impl *impl = n->impl; + const char *str; + + if (n->session != NULL || info == NULL) + return; + + n->info = pw_node_info_merge(n->info, info, true); + if (n->info == NULL) + return; + + pw_log_debug("node %d changed", n->id); + + if ((str = pw_properties_get(impl->props, "stream.rules")) == NULL) + str = DEFAULT_ANNOUNCE_RULES; + if (str != NULL) { + struct match_info minfo = { + .impl = impl, + .node = n, + }; + pw_conf_match_rules(str, strlen(str), NAME, n->info->props, + rule_matched, &minfo); + } +} + +static const struct pw_node_events node_events = { + PW_VERSION_NODE_EVENTS, + .info = node_event_info, +}; + +static void +proxy_removed(void *data) +{ + struct node *n = data; + pw_log_debug("node %d removed", n->id); + pw_proxy_destroy(n->proxy); +} + +static void +proxy_destroy(void *data) +{ + struct node *n = data; + pw_log_debug("node %d destroy", n->id); + spa_hook_remove(&n->node_listener); + spa_hook_remove(&n->proxy_listener); + n->proxy = NULL; + if (n->session != NULL) { + session_free(n->session); + n->session = NULL; + } + if (n->info) { + pw_node_info_free(n->info); + n->info = NULL; + } +} + +static const struct pw_proxy_events proxy_events = { + PW_VERSION_PROXY_EVENTS, + .removed = proxy_removed, + .destroy = proxy_destroy, +}; + +static void registry_event_global(void *data, uint32_t id, + uint32_t permissions, const char *type, uint32_t version, + const struct spa_dict *props) +{ + struct impl *impl = data; + struct pw_proxy *proxy; + struct node *node; + + if (!spa_streq(type, PW_TYPE_INTERFACE_Node)) + return; + + proxy = pw_registry_bind(impl->registry, id, type, PW_VERSION_NODE, sizeof(struct node)); + if (proxy == NULL) + return; + + node = pw_proxy_get_user_data(proxy); + node->impl = impl; + node->id = id; + node->proxy = proxy; + + pw_proxy_add_object_listener(proxy, + &node->node_listener, &node_events, node); + pw_proxy_add_listener(proxy, + &node->proxy_listener, &proxy_events, node); +} + +static const struct pw_registry_events registry_events = { + PW_VERSION_REGISTRY_EVENTS, + .global = registry_event_global, +}; + +static void core_destroy(void *d) +{ + struct impl *impl = d; + spa_hook_remove(&impl->core_listener); + impl->core = NULL; + pw_impl_module_schedule_destroy(impl->module); +} + +static const struct pw_proxy_events core_proxy_events = { + .destroy = core_destroy, +}; + +static void impl_destroy(struct impl *impl) +{ + struct session *sess; + + spa_list_consume(sess, &impl->sessions, link) + session_free(sess); + + if (impl->registry) { + spa_hook_remove(&impl->registry_listener); + pw_proxy_destroy((struct pw_proxy*)impl->registry); + impl->registry = NULL; + } + if (impl->core && impl->do_disconnect) + pw_core_disconnect(impl->core); + + if (impl->timer) + pw_loop_destroy_source(impl->loop, impl->timer); + if (impl->sap_source) + pw_loop_destroy_source(impl->loop, impl->sap_source); + + if (impl->sap_fd != -1) + close(impl->sap_fd); + + pw_properties_free(impl->props); + + free(impl->ifname); + free(impl); +} + +static void module_destroy(void *d) +{ + struct impl *impl = d; + spa_hook_remove(&impl->module_listener); + impl_destroy(impl); +} + +static const struct pw_impl_module_events module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = module_destroy, +}; + +static void on_core_error(void *d, uint32_t id, int seq, int res, const char *message) +{ + struct impl *impl = d; + + pw_log_error("error id:%u seq:%d res:%d (%s): %s", + id, seq, res, spa_strerror(res), message); + + if (id == PW_ID_CORE && res == -EPIPE) + pw_impl_module_schedule_destroy(impl->module); +} + +static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .error = on_core_error, +}; + +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 impl *impl; + struct pw_properties *props = NULL; + uint32_t port; + const char *str; + int res = 0; + + PW_LOG_TOPIC_INIT(mod_topic); + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + return -errno; + + impl->sap_fd = -1; + spa_list_init(&impl->sessions); + + if (args == NULL) + args = ""; + + props = pw_properties_new_string(args); + if (props == NULL) { + res = -errno; + pw_log_error( "can't create properties: %m"); + goto out; + } + impl->props = props; + + impl->module = module; + impl->loop = pw_context_get_main_loop(context); + + str = pw_properties_get(props, "local.ifname"); + impl->ifname = str ? strdup(str) : NULL; + + if ((str = pw_properties_get(props, "sap.ip")) == NULL) + str = DEFAULT_SAP_IP; + port = pw_properties_get_uint32(props, "sap.port", DEFAULT_SAP_PORT); + if ((res = parse_address(str, port, &impl->sap_addr, &impl->sap_len)) < 0) { + pw_log_error("invalid sap.ip %s: %s", str, spa_strerror(res)); + goto out; + } + impl->cleanup_interval = pw_properties_get_uint32(impl->props, + "sap.cleanup.sec", DEFAULT_CLEANUP_SEC); + + if ((str = pw_properties_get(props, "source.ip")) == NULL) + str = DEFAULT_SOURCE_IP; + if ((res = parse_address(str, port, &impl->src_addr, &impl->src_len)) < 0) { + pw_log_error("invalid source.ip %s: %s", str, spa_strerror(res)); + goto out; + } + + impl->ttl = pw_properties_get_uint32(props, "net.ttl", DEFAULT_TTL); + impl->mcast_loop = pw_properties_get_bool(props, "net.loop", DEFAULT_LOOP); + + 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 out; + } + + pw_proxy_add_listener((struct pw_proxy*)impl->core, + &impl->core_proxy_listener, + &core_proxy_events, impl); + pw_core_add_listener(impl->core, + &impl->core_listener, + &core_events, impl); + + if ((res = start_sap(impl)) < 0) + goto out; + + impl->registry = pw_core_get_registry(impl->core, PW_VERSION_REGISTRY, 0); + pw_registry_add_listener(impl->registry, &impl->registry_listener, + ®istry_events, impl); + + pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); + + pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_info)); + + pw_log_info("Successfully loaded module-rtp-sink"); + + return 0; +out: + impl_destroy(impl); + return res; +}
View file
pipewire-0.3.68.tar.gz/src/modules/module-rtp-session.c
Added
@@ -0,0 +1,1850 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans <wim.taymans@gmail.com> */ +/* SPDX-License-Identifier: MIT */ + +#include "config.h" + +#include <limits.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> +#include <netinet/ip.h> +#include <netinet/in.h> +#include <net/if.h> +#include <ctype.h> + +#include <spa/utils/hook.h> +#include <spa/utils/result.h> +#include <spa/utils/ringbuffer.h> +#include <spa/utils/json.h> +#include <spa/param/audio/format-utils.h> +#include <spa/debug/types.h> +#include <spa/debug/mem.h> + +#include <pipewire/pipewire.h> +#include <pipewire/impl.h> + +#include <avahi-client/publish.h> +#include <avahi-client/lookup.h> +#include <avahi-common/error.h> +#include <avahi-common/malloc.h> + +#include "module-zeroconf-discover/avahi-poll.h" + +#include <module-rtp/rtp.h> +#include <module-rtp/apple-midi.h> +#include <module-rtp/stream.h> + +#ifdef __FreeBSD__ +#define ifr_ifindex ifr_index +#endif + +/** \page page_module_rtp_session PipeWire Module: RTP session + * + * The `rtp-session` module creates a media session that is announced + * with avahi/mDNS/Bonjour. + * + * Other machines on the network that run a compatible session will see + * eachother and will be able to send audio/midi between eachother. + * + * The session setup is based on apple-midi and is compatible with + * apple-midi when the session is using midi. + * + * ## Module Options + * + * Options specific to the behavior of this module + * + * - `local.ifname = <str>`: interface name to use + * - `control.ip =<str>`: control IP address, default "0.0.0.0" + * - `control.port =<int>`: control port, default "0" + * - `net.mtu = <int>`: MTU to use, default 1280 + * - `net.ttl = <int>`: TTL to use, default 1 + * - `net.loop = <bool>`: loopback multicast, default false + * - `sess.min-ptime = <int>`: minimum packet time in milliseconds, default 2 + * - `sess.max-ptime = <int>`: maximum packet time in milliseconds, default 20 + * - `sess.latency.msec = <int>`: receiver latency in milliseconds, default 100 + * - `sess.name = <str>`: a session name + * - `sess.ts-offset = <int>`: an offset to apply to the timestamp, default -1 = random offset + * - `sess.ts-refclk = <string>`: the name of a reference clock + * - `sess.media = <string>`: the media type audio|midi|opus, default midi + * - `stream.props = {}`: properties to be passed to the stream + * + * ## General options + * + * Options with well-known behavior: + * + * - \ref PW_KEY_REMOTE_NAME + * - \ref PW_KEY_AUDIO_FORMAT + * - \ref PW_KEY_AUDIO_RATE + * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_POSITION + * - \ref PW_KEY_NODE_NAME + * - \ref PW_KEY_NODE_DESCRIPTION + * - \ref PW_KEY_MEDIA_NAME + * - \ref PW_KEY_NODE_GROUP + * - \ref PW_KEY_NODE_LATENCY + * - \ref PW_KEY_NODE_VIRTUAL + * - \ref PW_KEY_MEDIA_CLASS + * + * ## Example configuration + *\code{.unparsed} + * context.modules = + * { name = libpipewire-module-rtp-session + * args = { + * #local.ifname = "eth0" + * #control.ip = "0.0.0.0" + * #control.port = 0 + * #net.mtu = 1280 + * #net.ttl = 1 + * #net.loop = false + * #sess.min-ptime = 2 + * #sess.max-ptime = 20 + * #sess.name = "PipeWire RTP stream" + * #sess.media = "audio" + * stream.props = { + * node.name = "rtp-sink" + * #audio.format = "S16BE" + * #audio.rate = 48000 + * #audio.channels = 2 + * #audio.position = FL FR + * } + * } + *} + * + *\endcode + * + * \since 0.3.60 + */ + +#define NAME "rtp-session" + +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); +#define PW_LOG_TOPIC_DEFAULT mod_topic + +#define DEFAULT_CONTROL_IP "0.0.0.0" +#define DEFAULT_CONTROL_PORT 0 +#define DEFAULT_TTL 1 +#define DEFAULT_LOOP false + +#define USAGE "( control.ip=<destination IP address, default:"DEFAULT_CONTROL_IP"> ) " \ + "( control.port=<int, default:"SPA_STRINGIFY(DEFAULT_CONTROL_PORT)"> ) " \ + "( local.ifname=<local interface name to use> ) " \ + "( net.mtu=<desired MTU, default:"SPA_STRINGIFY(DEFAULT_MTU)"> ) " \ + "( net.ttl=<desired TTL, default:"SPA_STRINGIFY(DEFAULT_TTL)"> ) " \ + "( net.loop=<desired loopback, default:"SPA_STRINGIFY(DEFAULT_LOOP)"> ) " \ + "( sess.name=<a name for the session> ) " \ + "( sess.min-ptime=<minimum packet time in milliseconds, default:2> ) " \ + "( sess.max-ptime=<maximum packet time in milliseconds, default:20> ) " \ + "( sess.media=<string, the media type audio|midi|opus, default midi> ) " \ + "( audio.format=<format, default:"DEFAULT_FORMAT"> ) " \ + "( audio.rate=<sample rate, default:"SPA_STRINGIFY(DEFAULT_RATE)"> ) " \ + "( audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS)"> ) "\ + "( audio.position=<channel map, default:"DEFAULT_POSITION"> ) " \ + "( stream.props= { key=value ... } ) " + +static const struct spa_dict_item module_info = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { PW_KEY_MODULE_DESCRIPTION, "RTP Sink" }, + { PW_KEY_MODULE_USAGE, USAGE }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + +struct service_info { + AvahiIfIndex interface; + AvahiProtocol protocol; + const char *name; + const char *type; + const char *domain; + const char *host_name; + AvahiAddress address; + uint16_t port; +}; + +#define SERVICE_INFO(...) ((struct service_info){ __VA_ARGS__ }) + +struct service { + struct service_info info; + + struct spa_list link; + struct impl *impl; + + struct session *sess; +}; + +struct session { + struct impl *impl; + struct spa_list link; + + struct sockaddr_storage ctrl_addr; + socklen_t ctrl_len; + struct sockaddr_storage data_addr; + socklen_t data_len; + + struct rtp_stream *send; + struct spa_hook send_listener; + struct rtp_stream *recv; + struct spa_hook recv_listener; + + char *name; + + unsigned we_initiated:1; + +#define SESSION_STATE_INIT 0 +#define SESSION_STATE_SENDING_CTRL_IN 1 +#define SESSION_STATE_SENDING_DATA_IN 2 +#define SESSION_STATE_ESTABLISHING 3 +#define SESSION_STATE_ESTABLISHED 4 + int state; + int ck_count; + uint64_t next_time; + + uint32_t ctrl_initiator; + uint32_t data_initiator; + uint32_t remote_ssrc; + + uint32_t ssrc; + + unsigned sending:1; + unsigned receiving:1; + + unsigned ctrl_ready:1; + unsigned data_ready:1; +}; + +struct impl { + struct pw_context *context; + + struct pw_impl_module *module; + struct spa_hook module_listener; + struct pw_properties *props; + + AvahiPoll *avahi_poll; + AvahiClient *client; + AvahiServiceBrowser *browser; + AvahiEntryGroup *group; + struct spa_list service_list; + + struct pw_properties *stream_props; + + struct pw_loop *loop; + struct pw_loop *data_loop; + + struct pw_core *core; + struct spa_hook core_listener; + struct spa_hook core_proxy_listener; + unsigned int do_disconnect:1; + + struct spa_source *timer; + uint64_t next_time; + + struct spa_source *ctrl_source; + struct spa_source *data_source; + + char *ifname; + char *session_name; + uint32_t ttl; + bool mcast_loop; + int32_t ts_offset; + char *ts_refclk; + int payload; + + uint16_t ctrl_port; + struct sockaddr_storage ctrl_addr; + socklen_t ctrl_len; + struct sockaddr_storage data_addr; + socklen_t data_len; + + struct spa_list sessions; + uint32_t n_sessions; +}; + +static ssize_t send_packet(int fd, struct msghdr *msg) +{ + ssize_t n; + n = sendmsg(fd, msg, MSG_NOSIGNAL); + if (n < 0) { + switch (errno) { + case ECONNREFUSED: + case ECONNRESET: + pw_log_debug("remote end not listening"); + break; + default: + pw_log_debug("sendmsg() failed: %m"); + break; + } + } + return n; +} + +static uint64_t current_time_ns(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return SPA_TIMESPEC_TO_NSEC(&ts); +} + +static void set_timeout(struct impl *impl, uint64_t time) +{ + struct itimerspec ts; + ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; + ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; + ts.it_interval.tv_sec = 0; + ts.it_interval.tv_nsec = 0; + pw_loop_update_timer(impl->loop, impl->timer, &ts.it_value, &ts.it_interval, true); + impl->next_time = time; +} + +static void schedule_timeout(struct impl *impl) +{ + struct session *sess; + uint64_t next_time = 0; + spa_list_for_each(sess, &impl->sessions, link) { + if (next_time == 0 || + (sess->next_time != 0 && sess->next_time < next_time)) + next_time = sess->next_time; + } + set_timeout(impl, next_time); +} + +static void send_apple_midi_cmd_ck0(struct session *sess) +{ + struct impl *impl = sess->impl; + struct iovec iov3; + struct msghdr msg; + struct rtp_apple_midi_ck hdr; + uint64_t current_time, ts; + + spa_zero(hdr); + hdr.cmd = htonl(APPLE_MIDI_CMD_CK); + hdr.ssrc = htonl(sess->ssrc); + + current_time = current_time_ns(); + ts = current_time / 10000; + hdr.ts1_h = htonl(ts >> 32); + hdr.ts1_l = htonl(ts); + + iov0.iov_base = &hdr; + iov0.iov_len = sizeof(hdr); + + spa_zero(msg); + msg.msg_name = &sess->data_addr; + msg.msg_namelen = sess->data_len; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + send_packet(impl->data_source->fd, &msg); + + if (sess->ck_count++ < 8) + sess->next_time = current_time + SPA_NSEC_PER_SEC; + else if (sess->ck_count++ < 16) + sess->next_time = current_time + 2 * SPA_NSEC_PER_SEC; + else + sess->next_time = current_time + 5 * SPA_NSEC_PER_SEC; +} + +static void session_update_state(struct session *sess, int state) +{ + if (sess->state == state) + return; + + pw_log_info("session ssrc:%08x state:%d", sess->ssrc, state); + + sess->state = state; + switch (state) { + case SESSION_STATE_ESTABLISHED: + if (sess->we_initiated) { + sess->ck_count = 0; + send_apple_midi_cmd_ck0(sess); + schedule_timeout(sess->impl); + } + break; + case SESSION_STATE_INIT: + sess->next_time = 0; + schedule_timeout(sess->impl); + break; + default: + break; + } +} + +static void send_apple_midi_cmd_in(struct session *sess, bool ctrl) +{ + struct impl *impl = sess->impl; + struct iovec iov3; + struct msghdr msg; + struct rtp_apple_midi hdr; + int fd; + + spa_zero(hdr); + hdr.cmd = htonl(APPLE_MIDI_CMD_IN); + hdr.protocol = htonl(2); + hdr.initiator = htonl(ctrl ? sess->ctrl_initiator : sess->data_initiator); + hdr.ssrc = htonl(sess->ssrc); + + iov0.iov_base = &hdr; + iov0.iov_len = sizeof(hdr); + iov1.iov_base = impl->session_name; + iov1.iov_len = strlen(impl->session_name)+1; + + spa_zero(msg); + if (ctrl) { + msg.msg_name = &sess->ctrl_addr; + msg.msg_namelen = sess->ctrl_len; + fd = impl->ctrl_source->fd; + session_update_state(sess, SESSION_STATE_SENDING_CTRL_IN); + } else { + msg.msg_name = &sess->data_addr; + msg.msg_namelen = sess->data_len; + fd = impl->data_source->fd; + session_update_state(sess, SESSION_STATE_SENDING_DATA_IN); + } + msg.msg_iov = iov; + msg.msg_iovlen = 2; + + send_packet(fd, &msg); +} + +static void send_apple_midi_cmd_by(struct session *sess, bool ctrl) +{ + struct impl *impl = sess->impl; + struct iovec iov3; + struct msghdr msg; + struct rtp_apple_midi hdr; + + spa_zero(hdr); + hdr.cmd = htonl(APPLE_MIDI_CMD_BY); + hdr.protocol = htonl(2); + hdr.initiator = htonl(ctrl ? sess->ctrl_initiator : sess->data_initiator); + hdr.ssrc = htonl(sess->ssrc); + + iov0.iov_base = &hdr; + iov0.iov_len = sizeof(hdr); + + spa_zero(msg); + msg.msg_name = ctrl ? &sess->ctrl_addr : &sess->data_addr; + msg.msg_namelen = ctrl ? sess->ctrl_len : sess->data_len; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + send_packet(ctrl ? impl->ctrl_source->fd : impl->data_source->fd, &msg); +} + +static void session_establish(struct session *sess) +{ + switch (sess->state) { + case SESSION_STATE_INIT: + /* we initiate */ + sess->we_initiated = true; + sess->ctrl_initiator = pw_rand32(); + sess->data_initiator = pw_rand32(); + + pw_log_info("start session SSRC:%08x %u %u", sess->ssrc, + sess->ctrl_ready, sess->data_ready); + + if (!sess->ctrl_ready) + send_apple_midi_cmd_in(sess, true); + else if (!sess->data_ready) + send_apple_midi_cmd_in(sess, false); + break; + case SESSION_STATE_ESTABLISHING: + case SESSION_STATE_ESTABLISHED: + /* we're done or waiting for other initiator */ + break; + case SESSION_STATE_SENDING_CTRL_IN: + case SESSION_STATE_SENDING_DATA_IN: + /* we're busy initiating */ + break; + } +} + +static void session_stop(struct session *sess) +{ + if (!sess->we_initiated) + return; + pw_log_info("stop session SSRC:%08x %u %u", sess->ssrc, + sess->ctrl_ready, sess->data_ready); + if (sess->ctrl_ready) { + send_apple_midi_cmd_by(sess, true); + sess->ctrl_ready = false; + } + if (sess->data_ready) { + send_apple_midi_cmd_by(sess, false); + sess->data_ready = false; + } + session_update_state(sess, SESSION_STATE_INIT); +} + +static void send_destroy(void *data) +{ +} + +static void send_state_changed(void *data, bool started, const char *error) +{ + struct session *sess = data; + + if (started) { + sess->sending = true; + session_establish(sess); + } else { + sess->sending = false; + if (!sess->receiving) + session_stop(sess); + } +} + +static void send_send_packet(void *data, struct iovec *iov, size_t iovlen) +{ + struct session *sess = data; + struct impl *impl = sess->impl; + struct msghdr msg; + + if (!sess->data_ready || !sess->sending) + return; + + spa_zero(msg); + msg.msg_name = &sess->data_addr; + msg.msg_namelen = sess->data_len; + msg.msg_iov = iov; + msg.msg_iovlen = iovlen; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + send_packet(impl->data_source->fd, &msg); +} + +static void recv_destroy(void *data) +{ +} +static void recv_state_changed(void *data, bool started, const char *error) +{ + struct session *sess = data; + if (started) { + sess->receiving = true; + session_establish(sess); + } else { + sess->receiving = false; + if (!sess->sending) + session_stop(sess); + } +} + +static void recv_send_feedback(void *data, uint32_t seqnum) +{ + struct session *sess = data; + struct impl *impl = sess->impl; + struct iovec iov1; + struct msghdr msg; + struct rtp_apple_midi_rs hdr; + + if (!sess->ctrl_ready || !sess->receiving) + return; + + spa_zero(hdr); + hdr.cmd = htonl(APPLE_MIDI_CMD_RS); + hdr.ssrc = htonl(sess->ssrc); + hdr.seqnum = htonl(seqnum); + + iov0.iov_base = &hdr; + iov0.iov_len = sizeof(hdr); + + spa_zero(msg); + msg.msg_name = &sess->ctrl_addr; + msg.msg_namelen = sess->ctrl_len; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + send_packet(impl->ctrl_source->fd, &msg); +} + +static const struct rtp_stream_events send_stream_events = { + RTP_VERSION_STREAM_EVENTS, + .destroy = send_destroy, + .state_changed = send_state_changed, + .send_packet = send_send_packet, +}; + +static const struct rtp_stream_events recv_stream_events = { + RTP_VERSION_STREAM_EVENTS, + .destroy = recv_destroy, + .state_changed = recv_state_changed, + .send_feedback = recv_send_feedback, +}; + +static void free_session(struct session *sess) +{ + spa_list_remove(&sess->link); + sess->impl->n_sessions--; + + if (sess->send) + rtp_stream_destroy(sess->send); + if (sess->recv) + rtp_stream_destroy(sess->recv); + free(sess->name); + free(sess); +} + +static bool cmp_ip(const struct sockaddr_storage *sa, const struct sockaddr_storage *sb) +{ + if (sa->ss_family == AF_INET && sb->ss_family == AF_INET) { + struct sockaddr_in *ia = (struct sockaddr_in*)sa; + struct sockaddr_in *ib = (struct sockaddr_in*)sb; + return ia->sin_addr.s_addr == ib->sin_addr.s_addr; + } else if (sa->ss_family == AF_INET6 && sb->ss_family == AF_INET6) { + struct sockaddr_in6 *ia = (struct sockaddr_in6*)sa; + struct sockaddr_in6 *ib = (struct sockaddr_in6*)sb; + return ia->sin6_addr.s6_addr == ib->sin6_addr.s6_addr; + } + return false; +} + +static int get_ip(const struct sockaddr_storage *sa, char *ip, size_t len, uint16_t *port) +{ + if (sa->ss_family == AF_INET) { + struct sockaddr_in *in = (struct sockaddr_in*)sa; + inet_ntop(sa->ss_family, &in->sin_addr, ip, len); + *port = ntohs(in->sin_port); + } else if (sa->ss_family == AF_INET6) { + struct sockaddr_in6 *in = (struct sockaddr_in6*)sa; + inet_ntop(sa->ss_family, &in->sin6_addr, ip, len); + *port = ntohs(in->sin6_port); + } else + return -EIO; + return 0; +} + +static struct session *make_session(struct impl *impl, struct pw_properties *props) +{ + struct session *sess; + const char *str; + struct pw_properties *copy; + + sess = calloc(1, sizeof(struct session)); + if (sess == NULL) + goto error; + + spa_list_append(&impl->sessions, &sess->link); + impl->n_sessions++; + + sess->impl = impl; + sess->ssrc = pw_rand32(); + + str = pw_properties_get(props, "sess.name"); + sess->name = str ? strdup(str) : strdup("RTP Session"); + + if (impl->ts_refclk != NULL) + pw_properties_setf(props, "rtp.sender-ts-offset", "%u", impl->ts_offset); + pw_properties_setf(props, "rtp.sender-ssrc", "%u", sess->ssrc); + pw_properties_set(props, "rtp.session", sess->name); + + if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL) + pw_properties_set(props, PW_KEY_NODE_GROUP, impl->session_name); + + copy = pw_properties_copy(props); + + if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL) { + const char *media = NULL; + + str = pw_properties_get(props, "sess.media"); + if (spa_streq(str, "midi")) + media = "Midi"; + else if (spa_streq(str, "audio") || spa_streq(str, "opus")) + media = "Audio"; + + if (media != NULL) { + pw_properties_setf(copy, PW_KEY_MEDIA_CLASS, "%s/Sink", media); + pw_properties_setf(props, PW_KEY_MEDIA_CLASS, "%s/Source", media); + } + } + + sess->send = rtp_stream_new(impl->core, + PW_DIRECTION_INPUT, copy, + &send_stream_events, sess); + sess->recv = rtp_stream_new(impl->core, + PW_DIRECTION_OUTPUT, props, + &recv_stream_events, sess); + + return sess; +error: + pw_properties_free(props); + return NULL; +} + +static struct session *find_session_by_addr_name(struct impl *impl, + const struct sockaddr_storage *sa, const char *name) +{ + struct session *sess; + spa_list_for_each(sess, &impl->sessions, link) { + pw_log_info("%p '%s' '%s'", sess, name, sess->name); + if (cmp_ip(sa, &sess->ctrl_addr) && + spa_streq(sess->name, name)) + return sess; + } + return NULL; +} +static struct session *find_session_by_initiator(struct impl *impl, uint32_t initiator, bool ctrl) +{ + struct session *sess; + spa_list_for_each(sess, &impl->sessions, link) { + uint32_t target = ctrl ? sess->ctrl_initiator : sess->data_initiator; + if (target == initiator) + return sess; + } + return NULL; +} + +static struct session *find_session_by_ssrc(struct impl *impl, uint32_t ssrc) +{ + struct session *sess; + spa_list_for_each(sess, &impl->sessions, link) { + if (sess->remote_ssrc == ssrc) + return sess; + } + return NULL; +} + +static void parse_apple_midi_cmd_in(struct impl *impl, bool ctrl, uint8_t *buffer, + ssize_t len, struct sockaddr_storage *sa, socklen_t salen) +{ + struct rtp_apple_midi *hdr = (struct rtp_apple_midi*)buffer; + struct iovec iov3; + struct msghdr msg; + struct rtp_apple_midi reply; + struct session *sess; + bool success = true; + uint32_t initiator, ssrc; + char addr128; + uint16_t port = 0; + + initiator = ntohl(hdr->initiator); + ssrc = ntohl(hdr->ssrc); + + get_ip(sa, addr, sizeof(addr), &port); + pw_log_info("IN from %s:%d %s ssrc:%08x initiator:%08x", + addr, port, hdr->name, ssrc, initiator); + + if (ctrl) { + sess = find_session_by_addr_name(impl, sa, hdr->name); + if (sess == NULL) { + pw_log_warn("receive ctrl IN from nonexisting session %s", hdr->name); + success = false; + } else { + if (sess->ctrl_ready && + (sess->remote_ssrc != ssrc || sess->ctrl_initiator != initiator)) { + pw_log_warn("receive ctrl IN from existing initiator:%08x", initiator); + } + } + if (success) { + sess->we_initiated = false; + sess->remote_ssrc = ssrc; + sess->ctrl_initiator = initiator; + sess->ctrl_addr = *sa; + sess->ctrl_len = salen; + sess->ctrl_ready = true; + session_update_state(sess, SESSION_STATE_ESTABLISHING); + } + } + else { + sess = find_session_by_ssrc(impl, ssrc); + if (sess == NULL) { + pw_log_warn("receive data IN from nonexisting ssrc:%08x", ssrc); + success = false; + } else { + if (sess->data_ready) { + pw_log_warn("receive data IN from existing initiator:%08x", initiator); + } + } + if (success) { + pw_log_info("got data IN initiator:%08x, session established", initiator); + sess->data_initiator = initiator; + sess->data_addr = *sa; + sess->data_len = salen; + sess->data_ready = true; + session_update_state(sess, SESSION_STATE_ESTABLISHED); + } + } + + reply = *hdr; + if (success) { + reply.cmd = htonl(APPLE_MIDI_CMD_OK); + reply.ssrc = htonl(sess->ssrc); + } else + reply.cmd = htonl(APPLE_MIDI_CMD_NO); + + iov0.iov_base = &reply; + iov0.iov_len = sizeof(reply); + iov1.iov_base = impl->session_name; + iov1.iov_len = strlen(impl->session_name)+1; + + spa_zero(msg); + msg.msg_name = sa; + msg.msg_namelen = salen; + msg.msg_iov = iov; + msg.msg_iovlen = 2; + + pw_log_debug("send %p %u", msg.msg_name, msg.msg_namelen); + + send_packet(ctrl ? impl->ctrl_source->fd : impl->data_source->fd, &msg); +} + +static void parse_apple_midi_cmd_ok(struct impl *impl, bool ctrl, uint8_t *buffer, + ssize_t len, struct sockaddr_storage *sa, socklen_t salen) +{ + struct rtp_apple_midi *hdr = (struct rtp_apple_midi*)buffer; + uint32_t initiator = ntohl(hdr->initiator); + struct session *sess; + + sess = find_session_by_initiator(impl, initiator, ctrl); + if (sess == NULL || !sess->we_initiated) { + pw_log_warn("received OK from nonexisting session %u", initiator); + return; + } + + if (ctrl) { + pw_log_info("got ctrl OK %08x %u", initiator, sess->data_ready); + sess->ctrl_ready = true; + if (!sess->data_ready) + send_apple_midi_cmd_in(sess, false); + } else { + pw_log_info("got data OK %08x %u, session established", initiator, + sess->ctrl_ready); + sess->remote_ssrc = ntohl(hdr->ssrc); + sess->data_ready = true; + if (sess->ctrl_ready) + session_update_state(sess, SESSION_STATE_ESTABLISHED); + } +} + +static void parse_apple_midi_cmd_no(struct impl *impl, bool ctrl, uint8_t *buffer, + ssize_t len, struct sockaddr_storage *sa, socklen_t salen) +{ + struct rtp_apple_midi *hdr = (struct rtp_apple_midi*)buffer; + uint32_t initiator = ntohl(hdr->initiator); + struct session *sess; + + sess = find_session_by_initiator(impl, initiator, ctrl); + if (sess == NULL || !sess->we_initiated) { + pw_log_warn("received NO from nonexisting session %u", initiator); + return; + } + + if (ctrl) { + pw_log_info("got ctrl NO %08x %u", initiator, sess->data_ready); + sess->ctrl_ready = false; + } else { + pw_log_info("got data NO %08x %u, session canceled", initiator, + sess->ctrl_ready); + sess->data_ready = false; + if (!sess->ctrl_ready) + session_update_state(sess, SESSION_STATE_INIT); + } +} + +static void parse_apple_midi_cmd_ck(struct impl *impl, bool ctrl, uint8_t *buffer, + ssize_t len, struct sockaddr_storage *sa, socklen_t salen) +{ + struct rtp_apple_midi_ck *hdr = (struct rtp_apple_midi_ck*)buffer; + struct iovec iov3; + struct msghdr msg; + struct rtp_apple_midi_ck reply; + struct session *sess; + uint64_t now, t1, t2, t3; + uint32_t ssrc = ntohl(hdr->ssrc); + + sess = find_session_by_ssrc(impl, ssrc); + if (sess == NULL) { + pw_log_warn("unknown SSRC %u", ssrc); + return; + } + + pw_log_debug("got CK count %d", hdr->count); + + now = current_time_ns() / 10000; + reply = *hdr; + reply.ssrc = htonl(sess->ssrc); + reply.count++; + iov0.iov_base = &reply; + iov0.iov_len = sizeof(reply); + + t1 = ((uint64_t)ntohl(hdr->ts1_h) << 32) | ntohl(hdr->ts1_l); + t2 = t3 = 0; + + switch (hdr->count) { + case 0: + t2 = now; + break; + case 1: + t2 = ((uint64_t)ntohl(hdr->ts2_h) << 32) | ntohl(hdr->ts2_l); + t3 = now; + break; + case 2: + t3 = ((uint64_t)ntohl(hdr->ts3_h) << 32) | ntohl(hdr->ts3_l); + return; + } + + if (hdr->count >= 1) { + int64_t latency, offset; + latency = t3 - t1; + offset = ((t3 + t1) / 2) - t2; + + pw_log_info("latency:%f offset:%f", latency / 1e5, offset / 1e5); + if (hdr->count >= 2) + return; + } + + reply.ts2_h = htonl(t2 >> 32); + reply.ts2_l = htonl(t2); + reply.ts3_h = htonl(t3 >> 32); + reply.ts3_l = htonl(t3); + + spa_zero(msg); + msg.msg_name = sa; + msg.msg_namelen = salen; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + pw_log_debug("send %p %u", msg.msg_name, msg.msg_namelen); + + send_packet(ctrl ? impl->ctrl_source->fd : impl->data_source->fd, &msg); +} + +static void parse_apple_midi_cmd_by(struct impl *impl, bool ctrl, uint8_t *buffer, + ssize_t len, struct sockaddr_storage *sa, socklen_t salen) +{ + struct rtp_apple_midi *hdr = (struct rtp_apple_midi*)buffer; + uint32_t initiator = ntohl(hdr->initiator); + struct session *sess; + + sess = find_session_by_initiator(impl, initiator, ctrl); + if (sess == NULL || sess->we_initiated) { + pw_log_warn("received BY from nonexisting initiator %08x", initiator); + return; + } + + if (ctrl) { + pw_log_info("%p: got ctrl BY %08x %u", sess, initiator, sess->data_ready); + sess->ctrl_ready = false; + if (!sess->data_ready) + session_update_state(sess, SESSION_STATE_INIT); + } else { + pw_log_info("%p: got data BY %08x %u", sess, initiator, sess->ctrl_ready); + sess->data_ready = false; + if (!sess->ctrl_ready) + session_update_state(sess, SESSION_STATE_INIT); + } +} + +static void parse_apple_midi_cmd_rs(struct impl *impl, bool ctrl, uint8_t *buffer, + ssize_t len, struct sockaddr_storage *sa, socklen_t salen) +{ + struct rtp_apple_midi_rs *hdr = (struct rtp_apple_midi_rs*)buffer; + struct session *sess; + uint32_t ssrc, seqnum; + + ssrc = ntohl(hdr->ssrc); + sess = find_session_by_ssrc(impl, ssrc); + if (sess == NULL) { + pw_log_warn("unknown SSRC %u", ssrc); + return; + } + + seqnum = ntohl(hdr->seqnum); + pw_log_debug("got RS seqnum %u", seqnum); +} + +static void parse_apple_midi_cmd(struct impl *impl, bool ctrl, uint8_t *buffer, + ssize_t len, struct sockaddr_storage *sa, socklen_t salen) +{ + struct rtp_apple_midi *hdr = (struct rtp_apple_midi*)buffer; + switch (ntohl(hdr->cmd)) { + case APPLE_MIDI_CMD_IN: + parse_apple_midi_cmd_in(impl, ctrl, buffer, len, sa, salen); + break; + case APPLE_MIDI_CMD_OK: + parse_apple_midi_cmd_ok(impl, ctrl, buffer, len, sa, salen); + break; + case APPLE_MIDI_CMD_NO: + parse_apple_midi_cmd_no(impl, ctrl, buffer, len, sa, salen); + break; + case APPLE_MIDI_CMD_CK: + parse_apple_midi_cmd_ck(impl, ctrl, buffer, len, sa, salen); + break; + case APPLE_MIDI_CMD_BY: + parse_apple_midi_cmd_by(impl, ctrl, buffer, len, sa, salen); + break; + case APPLE_MIDI_CMD_RS: + parse_apple_midi_cmd_rs(impl, ctrl, buffer, len, sa, salen); + break; + default: + break; + } +} + +static void +on_ctrl_io(void *data, int fd, uint32_t mask) +{ + struct impl *impl = data; + ssize_t len; + uint8_t buffer2048; + + if (mask & SPA_IO_IN) { + struct sockaddr_storage sa; + socklen_t salen = sizeof(sa); + + if ((len = recvfrom(fd, buffer, sizeof(buffer), 0, + (struct sockaddr*)&sa, &salen)) < 0) + goto receive_error; + + if (len < 12) + goto short_packet; + + if (buffer0 == 0xff && buffer1 == 0xff) { + parse_apple_midi_cmd(impl, true, buffer, len, &sa, salen); + } else { + spa_debug_mem(0, buffer, len); + } + } + return; + +receive_error: + pw_log_warn("recv error: %m"); + return; +short_packet: + pw_log_warn("short packet received"); + spa_debug_mem(0, buffer, len); + return; +} + +static void +on_data_io(void *data, int fd, uint32_t mask) +{ + struct impl *impl = data; + ssize_t len; + uint8_t buffer2048; + uint32_t ssrc; + + if (mask & SPA_IO_IN) { + struct sockaddr_storage sa; + socklen_t salen = sizeof(sa); + + if ((len = recvfrom(fd, buffer, sizeof(buffer), 0, + (struct sockaddr*)&sa, &salen)) < 0) + goto receive_error; + + if (len < 12) + goto short_packet; + + if (buffer0 == 0xff && buffer1 == 0xff) { + parse_apple_midi_cmd(impl, false, buffer, len, &sa, salen); + } else { + struct rtp_header *hdr = (struct rtp_header*)buffer; + struct session *sess; + + ssrc = ntohl(hdr->ssrc); + sess = find_session_by_ssrc(impl, ssrc); + if (sess == NULL) + goto unknown_ssrc; + + if (sess->data_ready && sess->receiving) + rtp_stream_receive_packet(sess->recv, buffer, len); + } + } + return; + +receive_error: + pw_log_warn("recv error: %m"); + return; +short_packet: + pw_log_warn("short packet received"); + spa_debug_mem(0, buffer, len); + return; +unknown_ssrc: + pw_log_debug("unknown SSRC %08x", ssrc); + return; +} + +static int make_socket(const struct sockaddr_storage* sa, socklen_t salen, + bool loop, int ttl, char *ifname) +{ + int af, fd, val, res; + struct ifreq req; + struct sockaddr_storage src = *sa; + bool is_multicast = false; + + af = sa->ss_family; + if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) { + pw_log_error("socket failed: %m"); + return -errno; + } +#ifdef SO_TIMESTAMP + val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &val, sizeof(val)) < 0) { + res = -errno; + pw_log_error("setsockopt failed: %m"); + goto error; + } +#endif + val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) { + res = -errno; + pw_log_error("setsockopt failed: %m"); + goto error; + } + + spa_zero(req); + if (ifname) { + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", ifname); + res = ioctl(fd, SIOCGIFINDEX, &req); + if (res < 0) + pw_log_warn("SIOCGIFINDEX %s failed: %m", ifname); + } + res = 0; + if (af == AF_INET) { + static const uint32_t ipv4_mcast_mask = 0xe0000000; + struct sockaddr_in *sa4 = (struct sockaddr_in*)&src; + if ((ntohl(sa4->sin_addr.s_addr) & ipv4_mcast_mask) == ipv4_mcast_mask) { + struct ip_mreqn mr4; + memset(&mr4, 0, sizeof(mr4)); + mr4.imr_multiaddr = sa4->sin_addr; + mr4.imr_ifindex = req.ifr_ifindex; + res = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mr4, sizeof(mr4)); + is_multicast = true; + } else { + sa4->sin_addr.s_addr = INADDR_ANY; + } + } else if (af == AF_INET6) { + struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)&src; + if (sa6->sin6_addr.s6_addr0 == 0xff) { + struct ipv6_mreq mr6; + memset(&mr6, 0, sizeof(mr6)); + mr6.ipv6mr_multiaddr = sa6->sin6_addr; + mr6.ipv6mr_interface = req.ifr_ifindex; + res = setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mr6, sizeof(mr6)); + is_multicast = true; + } else { + sa6->sin6_addr = in6addr_any; + } + } else { + res = -EINVAL; + goto error; + } + + if (res < 0) { + res = -errno; + pw_log_error("join mcast failed: %m"); + goto error; + } + if (is_multicast) { + val = loop; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &val, sizeof(val)) < 0) + pw_log_warn("setsockopt(IP_MULTICAST_LOOP) failed: %m"); + + val = ttl; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &val, sizeof(val)) < 0) + pw_log_warn("setsockopt(IP_MULTICAST_TTL) failed: %m"); + } + + if (bind(fd, (struct sockaddr*)&src, salen) < 0) { + res = -errno; + pw_log_error("bind() failed: %m"); + goto error; + } + /* FIXME AES67 wants IPTOS_DSCP_AF41 */ + val = IPTOS_LOWDELAY; + if (setsockopt(fd, IPPROTO_IP, IP_TOS, &val, sizeof(val)) < 0) + pw_log_warn("setsockopt(IP_TOS) failed: %m"); + + pw_log_debug("new socket fd:%d", fd); + + return fd; +error: + close(fd); + return res; +} + +static int setup_apple_session(struct impl *impl) +{ + int fd; + + if ((fd = make_socket(&impl->ctrl_addr, impl->ctrl_len, + impl->mcast_loop, impl->ttl, impl->ifname)) < 0) { + return fd; + } + impl->ctrl_source = pw_loop_add_io(impl->loop, fd, + SPA_IO_IN, true, on_ctrl_io, impl); + + if (impl->ctrl_source == NULL) { + close(fd); + return -errno; + } + + if ((fd = make_socket(&impl->data_addr, impl->data_len, + impl->mcast_loop, impl->ttl, impl->ifname)) < 0) + return fd; + + impl->data_source = pw_loop_add_io(impl->data_loop, fd, + SPA_IO_IN, true, on_data_io, impl); + if (impl->data_source == NULL) { + close(fd); + return -errno; + } + return 0; +} + +static void core_destroy(void *d) +{ + struct impl *impl = d; + spa_hook_remove(&impl->core_listener); + impl->core = NULL; + pw_impl_module_schedule_destroy(impl->module); +} + +static const struct pw_proxy_events core_proxy_events = { + .destroy = core_destroy, +}; + +static void impl_destroy(struct impl *impl) +{ + struct session *sess; + + spa_list_consume(sess, &impl->sessions, link) + free_session(sess); + + if (impl->core && impl->do_disconnect) + pw_core_disconnect(impl->core); + + if (impl->timer) + pw_loop_destroy_source(impl->loop, impl->timer); + if (impl->ctrl_source) + pw_loop_destroy_source(impl->loop, impl->ctrl_source); + if (impl->data_source) + pw_loop_destroy_source(impl->data_loop, impl->data_source); + + if (impl->client) + avahi_client_free(impl->client); + + pw_properties_free(impl->stream_props); + pw_properties_free(impl->props); + + free(impl->ifname); + free(impl->ts_refclk); + free(impl->session_name); + free(impl); +} + +static void module_destroy(void *d) +{ + struct impl *impl = d; + spa_hook_remove(&impl->module_listener); + impl_destroy(impl); +} + +static const struct pw_impl_module_events module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = module_destroy, +}; + +static void on_core_error(void *d, uint32_t id, int seq, int res, const char *message) +{ + struct impl *impl = d; + + pw_log_error("error id:%u seq:%d res:%d (%s): %s", + id, seq, res, spa_strerror(res), message); + + if (id == PW_ID_CORE && res == -EPIPE) + pw_impl_module_schedule_destroy(impl->module); +} + +static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .error = on_core_error, +}; + +static int parse_address(const char *address, uint16_t port, + struct sockaddr_storage *addr, socklen_t *len) +{ + struct sockaddr_in *sa4 = (struct sockaddr_in*)addr; + struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)addr; + + if (inet_pton(AF_INET, address, &sa4->sin_addr) > 0) { + sa4->sin_family = AF_INET; + sa4->sin_port = htons(port); + *len = sizeof(*sa4); + } else if (inet_pton(AF_INET6, address, &sa6->sin6_addr) > 0) { + sa6->sin6_family = AF_INET6; + sa6->sin6_port = htons(port); + *len = sizeof(*sa6); + } else + return -EINVAL; + + return 0; +} + +static void free_service(struct service *s) +{ + spa_list_remove(&s->link); + + if (s->sess) + free_session(s->sess); + + free((char *) s->info.name); + free((char *) s->info.type); + free((char *) s->info.domain); + free((char *) s->info.host_name); + free(s); +} + +static const char *get_service_name(struct impl *impl) +{ + const char *str; + str = pw_properties_get(impl->props, "sess.media"); + if (spa_streq(str, "midi")) + return "_apple-midi._udp"; + else if (spa_streq(str, "audio") || spa_streq(str, "opus")) + return "_pipewire-audio._udp"; + return NULL; +} + +static struct service *make_service(struct impl *impl, const struct service_info *info, + AvahiStringList *txt) +{ + struct service *s = NULL; + char atAVAHI_ADDRESS_STR_MAX; + struct session *sess; + int res, ipv; + struct pw_properties *props = NULL; + const char *service_name, *str; + AvahiStringList *l; + bool compatible = true; + + /* check for compatible session */ + service_name = get_service_name(impl); + compatible = spa_streq(service_name, info->type); + + props = pw_properties_copy(impl->stream_props); + if (props == NULL) { + res = -errno; + goto error; + } + + if (spa_streq(service_name, "_pipewire-audio._udp")) { + uint32_t mask = 0; + for (l = txt; l && compatible; l = l->next) { + char *key, *value, *k = NULL; + + if (avahi_string_list_get_pair(l, &key, &value, NULL) != 0) + break; + + if (spa_streq(key, "subtype")) { + k = "sess.media"; + mask |= 1<<0; + } else if (spa_streq(key, "format")) { + k = PW_KEY_AUDIO_FORMAT; + mask |= 1<<1; + } else if (spa_streq(key, "rate")) { + k = PW_KEY_AUDIO_RATE; + mask |= 1<<2; + } else if (spa_streq(key, "channels")) { + k = PW_KEY_AUDIO_CHANNELS; + mask |= 1<<3; + } else if (spa_streq(key, "channelnames")) { + pw_properties_set(props, + PW_KEY_NODE_CHANNELNAMES, value); + } else if (spa_streq(key, "ts-refclk")) { + pw_properties_set(props, + "sess.ts-refclk", value); + if (spa_streq(value, impl->ts_refclk)) + pw_properties_set(props, + "sess.ts-direct", "true"); + } else if (spa_streq(key, "ts-offset")) { + uint32_t v; + if (spa_atou32(value, &v, 0)) + pw_properties_setf(props, + "rtp.receiver-ts-offset", "%u", v); + } + if (k != NULL) { + str = pw_properties_get(props, k); + if (str == NULL || !spa_streq(str, value)) + compatible = false; + } + avahi_free(key); + avahi_free(value); + } + str = pw_properties_get(props, "sess.media"); + if (spa_streq(str, "opus") && mask != 0xd) + compatible = false; + if (spa_streq(str, "audio") && mask != 0xf) + compatible = false; + } + if (!compatible) { + pw_log_info("found incompatible session IP%d:%s", + info->protocol == AVAHI_PROTO_INET ? 4 : 6, + info->name); + res = 0; + goto error; + } + + s = calloc(1, sizeof(*s)); + if (s == NULL) { + res = -errno; + goto error; + } + + s->impl = impl; + spa_list_append(&impl->service_list, &s->link); + + s->info.interface = info->interface; + s->info.protocol = info->protocol; + s->info.name = strdup(info->name); + s->info.type = strdup(info->type); + s->info.domain = strdup(info->domain); + s->info.host_name = strdup(info->host_name); + s->info.address = info->address; + s->info.port = info->port; + + avahi_address_snprint(at, sizeof(at), &s->info.address); + pw_log_info("create session: %s %s:%u %s", s->info.name, at, s->info.port, s->info.type); + + ipv = s->info.protocol == AVAHI_PROTO_INET ? 4 : 6; + pw_properties_set(props, "sess.name", s->info.name); + pw_properties_setf(props, "destination.ip", "%s", at); + pw_properties_setf(props, "destination.port", "%u", s->info.port); + + if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL) + pw_properties_setf(props, PW_KEY_NODE_NAME, "rtp_session.%s.%s.ipv%d", + s->info.name, s->info.host_name, ipv); + if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) + pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, "%s (IPv%d)", + s->info.name, ipv); + if (pw_properties_get(props, PW_KEY_MEDIA_NAME) == NULL) + pw_properties_setf(props, PW_KEY_MEDIA_NAME, "RTP Session with %s (IPv%d)", + s->info.name, ipv); + + sess = make_session(impl, props); + props = NULL; + if (sess == NULL) { + res = -errno; + pw_log_error("can't create session: %m"); + goto error; + } + s->sess = sess; + + if ((res = parse_address(at, s->info.port, &sess->ctrl_addr, &sess->ctrl_len)) < 0) { + pw_log_error("invalid address %s: %s", at, spa_strerror(res)); + } + if ((res = parse_address(at, s->info.port+1, &sess->data_addr, &sess->data_len)) < 0) { + pw_log_error("invalid address %s: %s", at, spa_strerror(res)); + } + return s; +error: + pw_properties_free(props); + if (s != NULL) + free_service(s); + errno = -res; + return NULL; +} + +static struct service *find_service(struct impl *impl, const struct service_info *info) +{ + struct service *s; + spa_list_for_each(s, &impl->service_list, link) { + if (s->info.interface == info->interface && + s->info.protocol == info->protocol && + spa_streq(s->info.name, info->name) && + spa_streq(s->info.type, info->type) && + spa_streq(s->info.domain, info->domain)) + return s; + } + return NULL; +} + +static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, + AvahiResolverEvent event, const char *name, const char *type, const char *domain, + const char *host_name, const AvahiAddress *a, uint16_t port, AvahiStringList *txt, + AvahiLookupResultFlags flags, void *userdata) +{ + struct impl *impl = userdata; + struct service_info sinfo; + + if (event != AVAHI_RESOLVER_FOUND) { + pw_log_error("Resolving of '%s' failed: %s", name, + avahi_strerror(avahi_client_errno(impl->client))); + goto done; + } + + sinfo = SERVICE_INFO(.interface = interface, + .protocol = protocol, + .name = name, + .type = type, + .domain = domain, + .host_name = host_name, + .address = *a, + .port = port); + + make_service(impl, &sinfo, txt); +done: + avahi_service_resolver_free(r); +} + +static void browser_cb(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, + AvahiBrowserEvent event, const char *name, const char *type, const char *domain, + AvahiLookupResultFlags flags, void *userdata) +{ + struct impl *impl = userdata; + struct service_info info; + struct service *s; + + if (flags & AVAHI_LOOKUP_RESULT_LOCAL) + return; + + info = SERVICE_INFO(.interface = interface, + .protocol = protocol, + .name = name, + .type = type, + .domain = domain); + + s = find_service(impl, &info); + + switch (event) { + case AVAHI_BROWSER_NEW: + if (s != NULL) + return; + if (!(avahi_service_resolver_new(impl->client, + interface, protocol, + name, type, domain, + AVAHI_PROTO_UNSPEC, 0, + resolver_cb, impl))) + pw_log_error("can't make service resolver: %s", + avahi_strerror(avahi_client_errno(impl->client))); + break; + case AVAHI_BROWSER_REMOVE: + if (s == NULL) + return; + free_service(s); + break; + default: + break; + } +} + +static int make_browser(struct impl *impl) +{ + const char *service_name; + + service_name = get_service_name(impl); + if (service_name == NULL) + return -EINVAL; + + if (impl->browser == NULL) { + impl->browser = avahi_service_browser_new(impl->client, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + service_name, NULL, 0, + browser_cb, impl); + } + if (impl->browser == NULL) { + pw_log_error("can't make browser: %s", + avahi_strerror(avahi_client_errno(impl->client))); + return -EIO; + } + return 0; +} + +static void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) +{ + switch (state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + pw_log_info("Service successfully established"); + break; + case AVAHI_ENTRY_GROUP_COLLISION: + pw_log_error("Service name collision"); + break; + case AVAHI_ENTRY_GROUP_FAILURE: + pw_log_error("Entry group failure: %s", + avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); + break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING:; + break; + } +} + +static int make_announce(struct impl *impl) +{ + int res; + const char *service_name, *str; + AvahiStringList *txt = NULL; + + if ((service_name = get_service_name(impl)) == NULL) + return -ENOTSUP; + + if (impl->group == NULL) { + impl->group = avahi_entry_group_new(impl->client, + entry_group_callback, impl); + } + if (impl->group == NULL) { + pw_log_error("can't make group: %s", + avahi_strerror(avahi_client_errno(impl->client))); + return -EIO; + } + avahi_entry_group_reset(impl->group); + + if (spa_streq(service_name, "_pipewire-audio._udp")) { + str = pw_properties_get(impl->props, "sess.media"); + txt = avahi_string_list_add_pair(txt, "subtype", str); + if ((str = pw_properties_get(impl->stream_props, PW_KEY_AUDIO_FORMAT)) != NULL) + txt = avahi_string_list_add_pair(txt, "format", str); + if ((str = pw_properties_get(impl->stream_props, PW_KEY_AUDIO_RATE)) != NULL) + txt = avahi_string_list_add_pair(txt, "rate", str); + if ((str = pw_properties_get(impl->stream_props, PW_KEY_AUDIO_CHANNELS)) != NULL) + txt = avahi_string_list_add_pair(txt, "channels", str); + if ((str = pw_properties_get(impl->stream_props, SPA_KEY_AUDIO_POSITION)) != NULL) + txt = avahi_string_list_add_pair(txt, "position", str); + if ((str = pw_properties_get(impl->stream_props, PW_KEY_NODE_CHANNELNAMES)) != NULL) + txt = avahi_string_list_add_pair(txt, "channelnames", str); + if (impl->ts_refclk != NULL) { + txt = avahi_string_list_add_pair(txt, "ts-refclk", impl->ts_refclk); + txt = avahi_string_list_add_printf(txt, "ts-offset=%u", impl->ts_offset); + } + } + res = avahi_entry_group_add_service_strlst(impl->group, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + (AvahiPublishFlags)0, impl->session_name, + service_name, NULL, NULL, + impl->ctrl_port, txt); + + avahi_string_list_free(txt); + + if (res < 0) { + pw_log_error("can't add service: %s", + avahi_strerror(avahi_client_errno(impl->client))); + return -EIO; + } + if ((res = avahi_entry_group_commit(impl->group)) < 0) { + pw_log_error("can't commit group: %s", + avahi_strerror(avahi_client_errno(impl->client))); + return -EIO; + } + return 0; +} +static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) +{ + struct impl *impl = userdata; + impl->client = c; + + switch (state) { + case AVAHI_CLIENT_S_REGISTERING: + case AVAHI_CLIENT_S_RUNNING: + case AVAHI_CLIENT_S_COLLISION: + make_browser(impl); + make_announce(impl); + break; + case AVAHI_CLIENT_FAILURE: + case AVAHI_CLIENT_CONNECTING: + break; + default: + break; + } +} + +static void on_timer_event(void *data, uint64_t expirations) +{ + struct impl *impl = data; + struct session *sess; + uint64_t current_time = impl->next_time; + + pw_log_info("timeout"); + spa_list_for_each(sess, &impl->sessions, link) { + if (sess->state != SESSION_STATE_ESTABLISHED) + continue; + if (sess->next_time < current_time) + continue; + + send_apple_midi_cmd_ck0(sess); + } + schedule_timeout(impl); +} + +static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) +{ + const char *str; + if ((str = pw_properties_get(props, key)) != NULL) { + if (pw_properties_get(impl->stream_props, key) == NULL) + pw_properties_set(impl->stream_props, key, str); + } +} + +SPA_EXPORT +int pipewire__module_init(struct pw_impl_module *module, const char *args) +{ + struct pw_context *context = pw_impl_module_get_context(module); + struct impl *impl; + struct pw_properties *props = NULL, *stream_props = NULL; + uint16_t port; + const char *str; + struct timespec value, interval; + int res = 0; + + PW_LOG_TOPIC_INIT(mod_topic); + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + return -errno; + + if (args == NULL) + args = ""; + + spa_list_init(&impl->sessions); + spa_list_init(&impl->service_list); + + props = pw_properties_new_string(args); + if (props == NULL) { + res = -errno; + pw_log_error( "can't create properties: %m"); + goto out; + } + impl->props = props; + + stream_props = pw_properties_new(NULL, NULL); + if (stream_props == NULL) { + res = -errno; + pw_log_error( "can't create properties: %m"); + goto out; + } + impl->stream_props = stream_props; + + impl->module = module; + impl->context = context; + impl->loop = pw_context_get_main_loop(context); + impl->data_loop = pw_data_loop_get_loop(pw_context_get_data_loop(context)); + + if ((str = pw_properties_get(props, "stream.props")) != NULL) + pw_properties_update_string(stream_props, str, strlen(str)); + + copy_props(impl, props, PW_KEY_AUDIO_FORMAT); + copy_props(impl, props, PW_KEY_AUDIO_RATE); + copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_POSITION); + copy_props(impl, props, PW_KEY_NODE_NAME); + copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); + copy_props(impl, props, PW_KEY_NODE_GROUP); + copy_props(impl, props, PW_KEY_NODE_LATENCY); + copy_props(impl, props, PW_KEY_NODE_VIRTUAL); + copy_props(impl, props, PW_KEY_NODE_CHANNELNAMES); + copy_props(impl, props, PW_KEY_MEDIA_NAME); + copy_props(impl, props, PW_KEY_MEDIA_CLASS); + copy_props(impl, props, "net.mtu"); + copy_props(impl, props, "sess.media"); + copy_props(impl, props, "sess.min-ptime"); + copy_props(impl, props, "sess.max-ptime"); + copy_props(impl, props, "sess.latency.msec"); + copy_props(impl, props, "sess.ts-refclk"); + + impl->ttl = pw_properties_get_uint32(props, "net.ttl", DEFAULT_TTL); + impl->mcast_loop = pw_properties_get_bool(props, "net.loop", DEFAULT_LOOP); + + if ((str = pw_properties_get(stream_props, "sess.media")) == NULL) { + str = "midi"; + pw_properties_set(stream_props, "sess.media", str); + } + if (spa_streq(str, "audio")) { + struct spa_dict_item items = { + { "audio.format", DEFAULT_FORMAT }, + { "audio.rate", SPA_STRINGIFY(DEFAULT_RATE) }, + { "audio.channels", SPA_STRINGIFY(DEFAULT_CHANNELS) }, + { "audio.position", DEFAULT_POSITION } }; + pw_properties_add(stream_props, &SPA_DICT_INIT_ARRAY(items)); + } + else if (spa_streq(str, "opus")) { + struct spa_dict_item items = { + { "audio.rate", SPA_STRINGIFY(DEFAULT_RATE) }, + { "audio.channels", SPA_STRINGIFY(DEFAULT_CHANNELS) }, + { "audio.position", DEFAULT_POSITION } }; + pw_properties_add(stream_props, &SPA_DICT_INIT_ARRAY(items)); + } + + str = pw_properties_get(props, "local.ifname"); + impl->ifname = str ? strdup(str) : NULL; + + port = pw_properties_get_uint32(props, "control.port", DEFAULT_CONTROL_PORT); + if ((str = pw_properties_get(props, "control.ip")) == NULL) + str = DEFAULT_CONTROL_IP; + + impl->ctrl_port = port; + + if ((res = parse_address(str, port, &impl->ctrl_addr, &impl->ctrl_len)) < 0) { + pw_log_error("invalid control.ip %s: %s", str, spa_strerror(res)); + goto out; + } + if ((res = parse_address(str, port ? port+1 : 0, &impl->data_addr, &impl->data_len)) < 0) { + pw_log_error("invalid data.ip %s: %s", str, spa_strerror(res)); + goto out; + } + + impl->ts_offset = pw_properties_get_int64(props, + "sess.ts-offset", pw_rand32()); + str = pw_properties_get(props, "sess.ts-refclk"); + impl->ts_refclk = str ? strdup(str) : NULL; + + if ((str = pw_properties_get(props, "sess.name")) == NULL) + pw_properties_setf(props, "sess.name", "%s", pw_get_host_name()); + str = pw_properties_get(props, "sess.name"); + impl->session_name = str ? strdup(str) : NULL; + + impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); + if (impl->core == NULL) { + str = pw_properties_get(props, PW_KEY_REMOTE_NAME); + impl->core = pw_context_connect(impl->context, + pw_properties_new( + PW_KEY_REMOTE_NAME, str, + NULL), + 0); + impl->do_disconnect = true; + } + if (impl->core == NULL) { + res = -errno; + pw_log_error("can't connect: %m"); + goto out; + } + + pw_proxy_add_listener((struct pw_proxy*)impl->core, + &impl->core_proxy_listener, + &core_proxy_events, impl); + pw_core_add_listener(impl->core, + &impl->core_listener, + &core_events, impl); + + impl->timer = pw_loop_add_timer(impl->loop, on_timer_event, impl); + if (impl->timer == NULL) { + res = -errno; + pw_log_error("can't create timer source: %m"); + goto out; + } + value.tv_sec = 0; + value.tv_nsec = 1; + interval.tv_sec = 1; + interval.tv_nsec = 0; + pw_loop_update_timer(impl->loop, impl->timer, &value, &interval, false); + + if ((res = setup_apple_session(impl)) < 0) + goto out; + + impl->avahi_poll = pw_avahi_poll_new(impl->loop); + if ((impl->client = avahi_client_new(impl->avahi_poll, + AVAHI_CLIENT_NO_FAIL, + client_callback, impl, + &res)) == NULL) { + pw_log_error("can't create avahi client: %s", avahi_strerror(res)); + goto out; + } + + pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); + + pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_info)); + + return 0; +out: + impl_destroy(impl); + return res; +}
View file
pipewire-0.3.67.tar.gz/src/modules/module-rtp-sink.c -> pipewire-0.3.68.tar.gz/src/modules/module-rtp-sink.c
Changed
@@ -25,9 +25,12 @@ #include <pipewire/pipewire.h> #include <pipewire/impl.h> -#include <module-rtp/sap.h> -#include <module-rtp/rtp.h> +#include <module-rtp/stream.h> +#ifndef IPTOS_DSCP +#define IPTOS_DSCP_MASK 0xfc +#define IPTOS_DSCP(x) ((x) & IPTOS_DSCP_MASK) +#endif /** \page page_module_rtp_sink PipeWire Module: RTP sink * @@ -38,8 +41,6 @@ * * Options specific to the behavior of this module * - * - `sap.ip = <str>`: IP address of the SAP messages, default "224.0.0.56" - * - `sap.port = <int>`: port of the SAP messages, default 9875 * - `source.ip =<str>`: source IP address, default "0.0.0.0" * - `destination.ip =<str>`: destination IP address, default "224.0.0.56" * - `destination.port =<int>`: destination port, default random beteen 46000 and 47024 @@ -52,7 +53,7 @@ * - `sess.name = <str>`: a session name * - `sess.ts-offset = <int>`: an offset to apply to the timestamp, default -1 = random offset * - `sess.ts-refclk = <string>`: the name of a reference clock - * - `sess.media = <string>`: the session media type audio|midi, default audio + * - `sess.media = <string>`: the media type audio|midi|opus, default audio * - `stream.props = {}`: properties to be passed to the stream * * ## General options @@ -77,19 +78,17 @@ * context.modules = * { name = libpipewire-module-rtp-sink * args = { - * #sap.ip = "224.0.0.56" - * #sap.port = 9875 + * #local.ifname = "eth0" * #source.ip = "0.0.0.0" * #destination.ip = "224.0.0.56" * #destination.port = 46000 - * #local.ifname = "eth0" * #net.mtu = 1280 * #net.ttl = 1 * #net.loop = false * #sess.min-ptime = 2 * #sess.max-ptime = 20 * #sess.name = "PipeWire RTP stream" - * #sess.media = audio + * #sess.media = "audio" * #audio.format = "S16BE" * #audio.rate = 48000 * #audio.channels = 2 @@ -110,52 +109,32 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic -#define SAP_INTERVAL_SEC 5 -#define SAP_MIME_TYPE "application/sdp" - -#define BUFFER_SIZE (1u<<20) -#define BUFFER_MASK (BUFFER_SIZE-1) - -#define DEFAULT_SAP_IP "224.0.0.56" -#define DEFAULT_SAP_PORT 9875 - -#define DEFAULT_SESS_MEDIA "audio" - -#define DEFAULT_FORMAT "S16BE" -#define DEFAULT_RATE 48000 -#define DEFAULT_CHANNELS 2 -#define DEFAULT_POSITION " FL FR " - #define DEFAULT_PORT 46000 #define DEFAULT_SOURCE_IP "0.0.0.0" #define DEFAULT_DESTINATION_IP "224.0.0.56" #define DEFAULT_TTL 1 -#define DEFAULT_MTU 1280 #define DEFAULT_LOOP false #define DEFAULT_DSCP 34 /* Default to AES-67 AF41 (34) */ -#define DEFAULT_MIN_PTIME 2 -#define DEFAULT_MAX_PTIME 20 #define DEFAULT_TS_OFFSET -1 -#define USAGE "sap.ip=<SAP IP address to send announce, default:"DEFAULT_SAP_IP"> " \ - "sap.port=<SAP port to send on, default:"SPA_STRINGIFY(DEFAULT_SAP_PORT)"> " \ - "source.ip=<source IP address, default:"DEFAULT_SOURCE_IP"> " \ - "destination.ip=<destination IP address, default:"DEFAULT_DESTINATION_IP"> " \ - "local.ifname=<local interface name to use> " \ - "net.mtu=<desired MTU, default:"SPA_STRINGIFY(DEFAULT_MTU)"> " \ - "net.ttl=<desired TTL, default:"SPA_STRINGIFY(DEFAULT_TTL)"> " \ - "net.loop=<desired loopback, default:"SPA_STRINGIFY(DEFAULT_LOOP)"> " \ - "net.dscp=<desired DSCP, default:"SPA_STRINGIFY(DEFAULT_DSCP)"> " \ - "sess.name=<a name for the session> " \ - "sess.min-ptime=<minimum packet time in milliseconds, default:2> " \ - "sess.max-ptime=<maximum packet time in milliseconds, default:20> " \ - "sess.media=<media type, audio or midi, default:audio> " \ - "audio.format=<format, default:"DEFAULT_FORMAT"> " \ - "audio.rate=<sample rate, default:"SPA_STRINGIFY(DEFAULT_RATE)"> " \ - "audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS)"> "\ - "audio.position=<channel map, default:"DEFAULT_POSITION"> " \ - "stream.props= { key=value ... }" +#define USAGE "( source.ip=<source IP address, default:"DEFAULT_SOURCE_IP"> ) " \ + "( destination.ip=<destination IP address, default:"DEFAULT_DESTINATION_IP"> ) " \ + "( destination.port=<int, default random beteen 46000 and 47024> ) " \ + "( local.ifname=<local interface name to use> ) " \ + "( net.mtu=<desired MTU, default:"SPA_STRINGIFY(DEFAULT_MTU)"> ) " \ + "( net.ttl=<desired TTL, default:"SPA_STRINGIFY(DEFAULT_TTL)"> ) " \ + "( net.loop=<desired loopback, default:"SPA_STRINGIFY(DEFAULT_LOOP)"> ) " \ + "( net.dscp=<desired DSCP, default:"SPA_STRINGIFY(DEFAULT_DSCP)"> ) " \ + "( sess.name=<a name for the session> ) " \ + "( sess.min-ptime=<minimum packet time in milliseconds, default:2> ) " \ + "( sess.max-ptime=<maximum packet time in milliseconds, default:20> ) " \ + "( sess.media=<string, the media type audio|midi|opus, default audio> ) " \ + "( audio.format=<format, default:"DEFAULT_FORMAT"> ) " \ + "( audio.rate=<sample rate, default:"SPA_STRINGIFY(DEFAULT_RATE)"> ) " \ + "( audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS)"> ) " \ + "( audio.position=<channel map, default:"DEFAULT_POSITION"> ) " \ + "( stream.props= { key=value ... } ) " static const struct spa_dict_item module_info = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, @@ -165,10 +144,11 @@ }; struct impl { + struct pw_context *context; + struct pw_impl_module *module; struct spa_hook module_listener; struct pw_properties *props; - struct pw_context *module_context; struct pw_loop *loop; @@ -176,110 +156,47 @@ struct spa_hook core_listener; struct spa_hook core_proxy_listener; - struct spa_source *timer; - struct pw_properties *stream_props; - struct pw_stream *stream; - struct spa_hook stream_listener; - - struct spa_io_position *io_position; + struct rtp_stream *stream; unsigned int do_disconnect:1; char *ifname; char *session_name; - uint32_t mtu; bool ttl; bool mcast_loop; uint32_t dscp; - float min_ptime; - float max_ptime; - uint32_t psamples; - uint32_t min_samples; - uint32_t max_samples; struct sockaddr_storage src_addr; socklen_t src_len; - uint16_t port; + uint16_t dst_port; struct sockaddr_storage dst_addr; socklen_t dst_len; - uint16_t sap_port; - struct sockaddr_storage sap_addr; - socklen_t sap_len; - - uint16_t msg_id_hash; - uint32_t ntp; - - struct spa_audio_info info; - const struct format_info *format_info; - uint32_t rate; - uint32_t stride; - int payload; - uint16_t seq; - uint32_t ssrc; - uint32_t ts_offset; - char ts_refclk64; - - struct spa_ringbuffer ring; - uint8_t bufferBUFFER_SIZE; - int rtp_fd; - int sap_fd; - - unsigned sync:1; - unsigned has_sent_sap:1; }; - static void stream_destroy(void *d) { struct impl *impl = d; - spa_hook_remove(&impl->stream_listener); impl->stream = NULL; } -static inline void -set_iovec(struct spa_ringbuffer *rbuf, void *buffer, uint32_t size, - uint32_t offset, struct iovec *iov, uint32_t len) +static void stream_send_packet(void *data, struct iovec *iov, size_t iovlen) { - 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; -} - -struct format_info { - uint32_t media_subtype; - uint32_t format; - uint32_t size; - const char *mime; - const char *media_type; -}; - -static const struct format_info audio_format_info = { - { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_U8, 1, "L8", "audio" }, - { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_ALAW, 1, "PCMA", "audio" }, - { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_ULAW, 1, "PCMU", "audio" }, - { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_S16_BE, 2, "L16", "audio" }, - { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_S24_BE, 3, "L24", "audio" }, - { SPA_MEDIA_SUBTYPE_control, 0, 1, "rtp-midi", "audio" }, -}; + struct impl *impl = data; + struct msghdr msg; + ssize_t n; -static const struct format_info *find_audio_format_info(const struct spa_audio_info *info) -{ - SPA_FOR_EACH_ELEMENT_VAR(audio_format_info, f) - if (f->media_subtype == info->media_subtype && - (f->format == 0 || f->format == info->info.raw.format)) - return f; - return NULL; -} + spa_zero(msg); + msg.msg_iov = iov; + msg.msg_iovlen = iovlen; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; -static ssize_t send_packet(struct impl *impl, struct msghdr *msg) -{ - ssize_t n; - n = sendmsg(impl->rtp_fd, msg, MSG_NOSIGNAL); + n = sendmsg(impl->rtp_fd, &msg, MSG_NOSIGNAL); if (n < 0) { switch (errno) { case ECONNREFUSED: @@ -287,316 +204,27 @@ pw_log_debug("remote end not listening"); break; default: - pw_log_warn("sendmsg() failed, seq:%u dropped: %m", - impl->seq); + pw_log_warn("sendmsg() failed: %m"); break; } } - impl->seq++; - return n; -} - -static void flush_audio_packets(struct impl *impl) -{ - int32_t avail; - uint32_t stride, timestamp; - struct iovec iov3; - struct msghdr msg; - struct rtp_header header; - int32_t tosend; - - avail = spa_ringbuffer_get_read_index(&impl->ring, ×tamp); - tosend = impl->psamples; - - if (avail < tosend) - return; - - stride = impl->stride; - - spa_zero(header); - header.v = 2; - header.pt = impl->payload; - header.ssrc = htonl(impl->ssrc); - - iov0.iov_base = &header; - iov0.iov_len = sizeof(header); - - msg.msg_name = NULL; - msg.msg_namelen = 0; - msg.msg_iov = iov; - msg.msg_iovlen = 3; - msg.msg_control = NULL; - msg.msg_controllen = 0; - msg.msg_flags = 0; - - while (avail >= tosend) { - header.sequence_number = htons(impl->seq); - header.timestamp = htonl(impl->ts_offset + timestamp); - - set_iovec(&impl->ring, - impl->buffer, BUFFER_SIZE, - (timestamp * stride) & BUFFER_MASK, - &iov1, tosend * stride); - - pw_log_trace("sending %d timestamp:%d", tosend, timestamp); - - send_packet(impl, &msg); - - timestamp += tosend; - avail -= tosend; - } - spa_ringbuffer_read_update(&impl->ring, timestamp); -} - -static void stream_audio_process(struct impl *impl) -{ - struct pw_buffer *buf; - struct spa_data *d; - uint32_t offs, size, timestamp, expected_timestamp, stride; - int32_t filled, wanted; - - if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) { - pw_log_debug("Out of stream buffers: %m"); - return; - } - d = buf->buffer->datas; - - offs = SPA_MIN(d0.chunk->offset, d0.maxsize); - size = SPA_MIN(d0.chunk->size, d0.maxsize - offs); - stride = impl->stride; - wanted = size / stride; - - filled = spa_ringbuffer_get_write_index(&impl->ring, &expected_timestamp); - if (SPA_LIKELY(impl->io_position)) - timestamp = impl->io_position->clock.position; - else - timestamp = expected_timestamp; - - if (impl->sync) { - if (expected_timestamp != timestamp) { - pw_log_warn("expected %u != timestamp %u", expected_timestamp, timestamp); - impl->sync = false; - } else if (filled + wanted > (int32_t)(BUFFER_SIZE / stride)) { - pw_log_warn("overrun %u + %u > %u", filled, wanted, BUFFER_SIZE / stride); - impl->sync = false; - } - } - if (!impl->sync) { - pw_log_info("sync to timestamp %u", timestamp); - impl->ring.readindex = impl->ring.writeindex = timestamp; - memset(impl->buffer, 0, BUFFER_SIZE); - impl->sync = true; - } - - spa_ringbuffer_write_data(&impl->ring, - impl->buffer, - BUFFER_SIZE, - (timestamp * stride) & BUFFER_MASK, - SPA_PTROFF(d0.data, offs, void), wanted * stride); - timestamp += wanted; - spa_ringbuffer_write_update(&impl->ring, timestamp); - - pw_stream_queue_buffer(impl->stream, buf); - - flush_audio_packets(impl); -} - -static int write_event(uint8_t *p, uint32_t value, void *ev, uint32_t size) -{ - uint64_t buffer; - uint8_t b; - int count = 0; - - buffer = value & 0x7f; - while ((value >>= 7)) { - buffer <<= 8; - buffer |= ((value & 0x7f) | 0x80); - } - do { - b = buffer & 0xff; - pcount++ = b; - buffer >>= 8; - } while (b & 0x80); - - memcpy(&pcount, ev, size); - return count + size; } -static void flush_midi_packets(struct impl *impl, struct spa_pod_sequence *sequence, uint32_t timestamp) -{ - struct spa_pod_control *c; - struct rtp_header header; - struct rtp_midi_header midi_header; - struct iovec iov3; - struct msghdr msg; - uint32_t len, prev_offset, base; - - spa_zero(header); - header.v = 2; - header.pt = impl->payload; - header.ssrc = htonl(impl->ssrc); - - spa_zero(midi_header); - midi_header.b = 1; - midi_header.z = 1; - - iov0.iov_base = &header; - iov0.iov_len = sizeof(header); - iov1.iov_base = &midi_header; - iov1.iov_len = sizeof(midi_header); - iov2.iov_base = impl->buffer; - iov2.iov_len = 0; - - msg.msg_name = NULL; - msg.msg_namelen = 0; - msg.msg_iov = iov; - msg.msg_iovlen = 3; - msg.msg_control = NULL; - msg.msg_controllen = 0; - msg.msg_flags = 0; - - prev_offset = len = base = 0; - - SPA_POD_SEQUENCE_FOREACH(sequence, c) { - void *ev; - uint32_t size, delta; - - if (c->type != SPA_CONTROL_Midi) - continue; - - ev = SPA_POD_BODY(&c->value), - size = SPA_POD_BODY_SIZE(&c->value); - - if (len > 0 && (len + size > impl->mtu || - c->offset - base > impl->max_samples)) { - /* flush packet when we have one and when it's either - * too large or has too much data. */ - midi_header.len = (len >> 8) & 0xf; - midi_header.len_b = len & 0xff; - iov2.iov_len = len; - - pw_log_debug("sending %d timestamp:%d %u %u", - len, timestamp + base, - c->offset, impl->max_samples); - send_packet(impl, &msg); - - len = 0; - } - if (len == 0) { - /* start new packet */ - base = prev_offset = c->offset; - header.sequence_number = htons(impl->seq); - header.timestamp = htonl(impl->ts_offset + timestamp + base); - } - - delta = c->offset - prev_offset; - prev_offset = c->offset; - - len += write_event(&impl->bufferlen, delta, ev, size); - } - if (len > 0) { - /* flush last packet */ - midi_header.len = (len >> 8) & 0xf; - midi_header.len_b = len & 0xff; - iov2.iov_len = len; - - pw_log_debug("sending %d timestamp:%d", len, base); - send_packet(impl, &msg); - } -} - -static void stream_midi_process(void *data) +static void stream_state_changed(void *data, bool started, const char *error) { struct impl *impl = data; - struct pw_buffer *buf; - struct spa_data *d; - uint32_t offs, size, timestamp; - struct spa_pod *pod; - void *ptr; - - if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) { - pw_log_debug("Out of stream buffers: %m"); - return; - } - d = buf->buffer->datas; - - offs = SPA_MIN(d0.chunk->offset, d0.maxsize); - size = SPA_MIN(d0.chunk->size, d0.maxsize - offs); - - if (SPA_LIKELY(impl->io_position)) - timestamp = impl->io_position->clock.position; - else - timestamp = 0; - - ptr = SPA_PTROFF(d0.data, offs, void); - - if ((pod = spa_pod_from_data(ptr, size, 0, size)) == NULL) - goto done; - if (!spa_pod_is_sequence(pod)) - goto done; - - if (!impl->sync) { - pw_log_info("sync to timestamp %u", timestamp); - impl->sync = true; - } - - flush_midi_packets(impl, (struct spa_pod_sequence*)pod, timestamp); - -done: - pw_stream_queue_buffer(impl->stream, buf); -} - -static void stream_process(void *data) -{ - struct impl *impl = data; - switch (impl->info.media_type) { - case SPA_MEDIA_TYPE_audio: - stream_audio_process(impl); - break; - case SPA_MEDIA_TYPE_application: - stream_midi_process(impl); - break; - } -} - - -static void stream_io_changed(void *data, uint32_t id, void *area, uint32_t size) -{ - struct impl *impl = data; - switch (id) { - case SPA_IO_Position: - impl->io_position = area; - break; - } -} - -static void on_stream_state_changed(void *d, enum pw_stream_state old, - enum pw_stream_state state, const char *error) -{ - struct impl *impl = d; - switch (state) { - case PW_STREAM_STATE_UNCONNECTED: - pw_log_info("stream disconnected, unloading"); - pw_impl_module_schedule_destroy(impl->module); - break; - case PW_STREAM_STATE_ERROR: + if (error) { pw_log_error("stream error: %s", error); - break; - case PW_STREAM_STATE_PAUSED: - impl->sync = false; - break; - default: - break; + pw_impl_module_schedule_destroy(impl->module); } } -static const struct pw_stream_events in_stream_events = { - PW_VERSION_STREAM_EVENTS, +static const struct rtp_stream_events stream_events = { + RTP_VERSION_STREAM_EVENTS, .destroy = stream_destroy, - .io_changed = stream_io_changed, - .state_changed = on_stream_state_changed, - .process = stream_process + .state_changed = stream_state_changed, + .send_packet = stream_send_packet, }; static int parse_address(const char *address, uint16_t port, @@ -680,73 +308,6 @@ return res; } -static int setup_stream(struct impl *impl) -{ - const struct spa_pod *params1; - struct spa_pod_builder b; - uint32_t n_params; - uint8_t buffer1024; - struct pw_properties *props; - int res, fd; - - props = pw_properties_copy(impl->stream_props); - if (props == NULL) - return -errno; - - if (pw_properties_get(props, PW_KEY_NODE_LATENCY) == NULL) { - pw_properties_setf(props, PW_KEY_NODE_LATENCY, - "%d/%d", impl->psamples, impl->rate); - } - pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%d", impl->rate); - - impl->stream = pw_stream_new(impl->core, - "rtp-sink capture", props); - if (impl->stream == NULL) - return -errno; - - pw_stream_add_listener(impl->stream, - &impl->stream_listener, - &in_stream_events, impl); - - n_params = 0; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - switch (impl->info.media_type) { - case SPA_MEDIA_TYPE_audio: - paramsn_params++ = spa_format_audio_build(&b, - SPA_PARAM_EnumFormat, &impl->info); - break; - case SPA_MEDIA_TYPE_application: - paramsn_params++ = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); - break; - default: - return -EINVAL; - } - - if ((res = pw_stream_connect(impl->stream, - PW_DIRECTION_INPUT, - PW_ID_ANY, - PW_STREAM_FLAG_MAP_BUFFERS | - PW_STREAM_FLAG_AUTOCONNECT | - PW_STREAM_FLAG_RT_PROCESS, - params, n_params)) < 0) - return res; - - - if ((fd = make_socket(&impl->src_addr, impl->src_len, - &impl->dst_addr, impl->dst_len, - impl->mcast_loop, impl->ttl, - impl->dscp)) < 0) - return fd; - - impl->rtp_fd = fd; - - return 0; -} - static int get_ip(const struct sockaddr_storage *sa, char *ip, size_t len) { if (sa->ss_family == AF_INET) { @@ -759,152 +320,6 @@ return -EIO; return 0; } -static void send_sap(struct impl *impl, bool bye) -{ - char buffer2048, src_addr64, dst_addr64, dst_ttl8; - const char *user_name, *af; - struct sockaddr *sa = (struct sockaddr*)&impl->src_addr; - struct sap_header header; - struct iovec iov4; - struct msghdr msg; - struct spa_strbuf buf; - - if (!impl->has_sent_sap && bye) - return; - - spa_zero(header); - header.v = 1; - header.t = bye; - header.msg_id_hash = impl->msg_id_hash; - - iov0.iov_base = &header; - iov0.iov_len = sizeof(header); - - if (sa->sa_family == AF_INET) { - iov1.iov_base = &((struct sockaddr_in*) sa)->sin_addr; - iov1.iov_len = 4U; - af = "IP4"; - } else { - iov1.iov_base = &((struct sockaddr_in6*) sa)->sin6_addr; - iov1.iov_len = 16U; - header.a = 1; - af = "IP6"; - } - iov2.iov_base = SAP_MIME_TYPE; - iov2.iov_len = sizeof(SAP_MIME_TYPE); - - get_ip(&impl->src_addr, src_addr, sizeof(src_addr)); - get_ip(&impl->dst_addr, dst_addr, sizeof(dst_addr)); - - if ((user_name = pw_get_user_name()) == NULL) - user_name = "-"; - - spa_zero(dst_ttl); - if (is_multicast((struct sockaddr*)&impl->dst_addr, impl->dst_len)) - snprintf(dst_ttl, sizeof(dst_ttl), "/%d", impl->ttl); - - spa_strbuf_init(&buf, buffer, sizeof(buffer)); - spa_strbuf_append(&buf, - "v=0\n" - "o=%s %u 0 IN %s %s\n" - "s=%s\n" - "c=IN %s %s%s\n" - "t=%u 0\n" - "a=recvonly\n" - "a=tool:PipeWire %s\n" - "a=type:broadcast\n", - user_name, impl->ntp, af, src_addr, - impl->session_name, - af, dst_addr, dst_ttl, - impl->ntp, - pw_get_library_version()); - spa_strbuf_append(&buf, - "m=%s %u RTP/AVP %i\n", - impl->format_info->media_type, - impl->port, impl->payload); - - switch (impl->info.media_type) { - case SPA_MEDIA_TYPE_audio: - spa_strbuf_append(&buf, - "a=rtpmap:%i %s/%u/%u\n" - "a=ptime:%d\n", - impl->payload, impl->format_info->mime, - impl->info.info.raw.rate, - impl->info.info.raw.channels, - impl->psamples * 1000 / impl->info.info.raw.rate); - break; - case SPA_MEDIA_TYPE_application: - spa_strbuf_append(&buf, - "a=rtpmap:%i %s/%u\n", - impl->payload, impl->format_info->mime, - impl->rate); - break; - - } - - if (impl->ts_refclk0 != '\0') { - spa_strbuf_append(&buf, - "a=ts-refclk:%s\n" - "a=mediaclk:direct=%u\n", - impl->ts_refclk, - impl->ts_offset); - } else { - spa_strbuf_append(&buf, "a=mediaclk:sender\n"); - } - - iov3.iov_base = buffer; - iov3.iov_len = strlen(buffer); - - msg.msg_name = NULL; - msg.msg_namelen = 0; - msg.msg_iov = iov; - msg.msg_iovlen = 4; - msg.msg_control = NULL; - msg.msg_controllen = 0; - msg.msg_flags = 0; - - sendmsg(impl->sap_fd, &msg, MSG_NOSIGNAL); - - impl->has_sent_sap = true; -} - -static void on_timer_event(void *data, uint64_t expirations) -{ - struct impl *impl = data; - send_sap(impl, 0); -} - -static int start_sap_announce(struct impl *impl) -{ - int fd, res; - struct timespec value, interval; - - if ((fd = make_socket(&impl->src_addr, impl->src_len, - &impl->sap_addr, impl->sap_len, - impl->mcast_loop, impl->ttl, 0)) < 0) - return fd; - - impl->sap_fd = fd; - - pw_log_info("starting SAP timer"); - impl->timer = pw_loop_add_timer(impl->loop, on_timer_event, impl); - if (impl->timer == NULL) { - res = -errno; - pw_log_error("can't create timer source: %m"); - goto error; - } - value.tv_sec = 0; - value.tv_nsec = 1; - interval.tv_sec = SAP_INTERVAL_SEC; - interval.tv_nsec = 0; - pw_loop_update_timer(impl->loop, impl->timer, &value, &interval, false); - - return 0; -error: - close(fd); - return res; - -} static void core_destroy(void *d) { @@ -920,21 +335,14 @@ static void impl_destroy(struct impl *impl) { - send_sap(impl, 1); - if (impl->stream) - pw_stream_destroy(impl->stream); + rtp_stream_destroy(impl->stream); if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); - if (impl->timer) - pw_loop_destroy_source(impl->loop, impl->timer); - if (impl->rtp_fd != -1) close(impl->rtp_fd); - if (impl->sap_fd != -1) - close(impl->sap_fd); pw_properties_free(impl->stream_props); pw_properties_free(impl->props); @@ -972,63 +380,6 @@ .error = on_core_error, }; -static inline uint32_t 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 uint32_t channel_from_name(const char *name) -{ - int i; - for (i = 0; spa_type_audio_channeli.name; i++) { - if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_channeli.name))) - return spa_type_audio_channeli.type; - } - return SPA_AUDIO_CHANNEL_UNKNOWN; -} - -static void parse_position(struct spa_audio_info_raw *info, const char *val, size_t len) -{ - struct spa_json it2; - char v256; - - spa_json_init(&it0, val, len); - if (spa_json_enter_array(&it0, &it1) <= 0) - spa_json_init(&it1, val, len); - - info->channels = 0; - while (spa_json_get_string(&it1, v, sizeof(v)) > 0 && - info->channels < SPA_AUDIO_MAX_CHANNELS) { - info->positioninfo->channels++ = channel_from_name(v); - } -} - -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) -{ - const char *str; - - spa_zero(*info); - if ((str = pw_properties_get(props, PW_KEY_AUDIO_FORMAT)) == NULL) - str = DEFAULT_FORMAT; - info->format = format_from_name(str, strlen(str)); - - info->rate = pw_properties_get_uint32(props, PW_KEY_AUDIO_RATE, info->rate); - if (info->rate == 0) - info->rate = DEFAULT_RATE; - - info->channels = pw_properties_get_uint32(props, PW_KEY_AUDIO_CHANNELS, info->channels); - info->channels = SPA_MIN(info->channels, SPA_AUDIO_MAX_CHANNELS); - if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) != NULL) - parse_position(info, str, strlen(str)); - if (info->channels == 0) - parse_position(info, DEFAULT_POSITION, strlen(DEFAULT_POSITION)); -} - static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) { const char *str; @@ -1044,11 +395,9 @@ struct pw_context *context = pw_impl_module_get_context(module); struct impl *impl; struct pw_properties *props = NULL, *stream_props = NULL; - uint32_t id = pw_global_get_id(pw_impl_module_get_global(module)); - uint32_t pid = getpid(), port; - int64_t ts_offset; char addr64; - const char *str; + const char *str, *sess_name; + int64_t ts_offset; int res = 0; PW_LOG_TOPIC_INIT(mod_topic); @@ -1058,7 +407,6 @@ return -errno; impl->rtp_fd = -1; - impl->sap_fd = -1; if (args == NULL) args = ""; @@ -1080,21 +428,19 @@ impl->stream_props = stream_props; impl->module = module; - impl->module_context = context; + impl->context = context; impl->loop = pw_context_get_main_loop(context); - if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) - pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); - if (pw_properties_get(stream_props, PW_KEY_NODE_NETWORK) == NULL) - pw_properties_set(stream_props, PW_KEY_NODE_NETWORK, "true"); + if ((sess_name = pw_properties_get(props, "sess.name")) == NULL) + sess_name = pw_get_host_name(); if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL) - pw_properties_setf(props, PW_KEY_NODE_NAME, "rtp-sink-%u-%u", pid, id); + pw_properties_setf(props, PW_KEY_NODE_NAME, "rtp_session.%s", sess_name); if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) - pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, - pw_properties_get(props, PW_KEY_NODE_NAME)); + pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, "%s", sess_name); if (pw_properties_get(props, PW_KEY_MEDIA_NAME) == NULL) - pw_properties_set(props, PW_KEY_MEDIA_NAME, "RTP Sender Stream"); + pw_properties_setf(props, PW_KEY_MEDIA_NAME, "RTP Session with %s", + sess_name); if ((str = pw_properties_get(props, "stream.props")) != NULL) pw_properties_update_string(stream_props, str, strlen(str)); @@ -1108,71 +454,20 @@ copy_props(impl, props, PW_KEY_NODE_GROUP); copy_props(impl, props, PW_KEY_NODE_LATENCY); copy_props(impl, props, PW_KEY_NODE_VIRTUAL); + copy_props(impl, props, PW_KEY_NODE_CHANNELNAMES); copy_props(impl, props, PW_KEY_MEDIA_NAME); copy_props(impl, props, PW_KEY_MEDIA_CLASS); - - if ((str = pw_properties_get(props, "sess.media")) == NULL) - str = DEFAULT_SESS_MEDIA; - - if (spa_streq(str, "audio")) { - impl->info.media_type = SPA_MEDIA_TYPE_audio; - impl->info.media_subtype = SPA_MEDIA_SUBTYPE_raw; - } - else if (spa_streq(str, "midi")) { - impl->info.media_type = SPA_MEDIA_TYPE_application; - impl->info.media_subtype = SPA_MEDIA_SUBTYPE_control; - } - else { - pw_log_error("unsupported media type:%s", str); - res = -EINVAL; - goto out; - } - - switch (impl->info.media_type) { - case SPA_MEDIA_TYPE_audio: - parse_audio_info(impl->stream_props, &impl->info.info.raw); - impl->format_info = find_audio_format_info(&impl->info); - if (impl->format_info == NULL) { - pw_log_error("unsupported audio format:%d channels:%d", - impl->info.info.raw.format, impl->info.info.raw.channels); - res = -EINVAL; - goto out; - } - impl->stride = impl->format_info->size * impl->info.info.raw.channels; - impl->rate = impl->info.info.raw.rate; - break; - case SPA_MEDIA_TYPE_application: - impl->format_info = find_audio_format_info(&impl->info); - if (impl->format_info == NULL) { - res = -EINVAL; - goto out; - } - pw_properties_set(impl->stream_props, PW_KEY_FORMAT_DSP, "8 bit raw midi"); - impl->stride = impl->format_info->size; - impl->rate = 48000; - break; - default: - spa_assert_not_reached(); - break; - } - impl->msg_id_hash = rand(); - impl->ntp = (uint32_t) time(NULL) + 2208988800U; - - impl->payload = 127; - impl->seq = rand(); - impl->ssrc = rand(); + copy_props(impl, props, "net.mtu"); + copy_props(impl, props, "sess.media"); + copy_props(impl, props, "sess.name"); + copy_props(impl, props, "sess.min-ptime"); + copy_props(impl, props, "sess.max-ptime"); + copy_props(impl, props, "sess.latency.msec"); + copy_props(impl, props, "sess.ts-refclk"); str = pw_properties_get(props, "local.ifname"); impl->ifname = str ? strdup(str) : NULL; - if ((str = pw_properties_get(props, "sap.ip")) == NULL) - str = DEFAULT_SAP_IP; - port = pw_properties_get_uint32(props, "sap.port", DEFAULT_SAP_PORT); - if ((res = parse_address(str, port, &impl->sap_addr, &impl->sap_len)) < 0) { - pw_log_error("invalid sap.ip %s: %s", str, spa_strerror(res)); - goto out; - } - if ((str = pw_properties_get(props, "source.ip")) == NULL) str = DEFAULT_SOURCE_IP; if ((res = parse_address(str, 0, &impl->src_addr, &impl->src_len)) < 0) { @@ -1180,62 +475,36 @@ goto out; } - impl->port = DEFAULT_PORT + ((uint32_t) (rand() % 512) << 1); - impl->port = pw_properties_get_uint32(props, "destination.port", impl->port); + impl->dst_port = DEFAULT_PORT + ((uint32_t) (pw_rand32() % 512) << 1); + impl->dst_port = pw_properties_get_uint32(props, "destination.port", impl->dst_port); if ((str = pw_properties_get(props, "destination.ip")) == NULL) str = DEFAULT_DESTINATION_IP; - if ((res = parse_address(str, impl->port, &impl->dst_addr, &impl->dst_len)) < 0) { + if ((res = parse_address(str, impl->dst_port, &impl->dst_addr, &impl->dst_len)) < 0) { pw_log_error("invalid destination.ip %s: %s", str, spa_strerror(res)); goto out; } - impl->mtu = pw_properties_get_uint32(props, "net.mtu", DEFAULT_MTU); impl->ttl = pw_properties_get_uint32(props, "net.ttl", DEFAULT_TTL); impl->mcast_loop = pw_properties_get_bool(props, "net.loop", DEFAULT_LOOP); impl->dscp = pw_properties_get_uint32(props, "net.dscp", DEFAULT_DSCP); ts_offset = pw_properties_get_int64(props, "sess.ts-offset", DEFAULT_TS_OFFSET); - impl->ts_offset = ts_offset < 0 ? rand() : ts_offset; - - str = pw_properties_get(props, "sess.ts-refclk"); - if (str != NULL) - snprintf(impl->ts_refclk, sizeof(impl->ts_refclk), "%s", str); - - str = pw_properties_get(props, "sess.min-ptime"); - if (!spa_atof(str, &impl->min_ptime)) - impl->min_ptime = DEFAULT_MIN_PTIME; - str = pw_properties_get(props, "sess.max-ptime"); - if (!spa_atof(str, &impl->max_ptime)) - impl->max_ptime = DEFAULT_MAX_PTIME; - - impl->min_samples = impl->min_ptime * impl->rate / 1000; - impl->max_samples = impl->max_ptime * impl->rate / 1000; + if (ts_offset == -1) + ts_offset = pw_rand32(); + pw_properties_setf(stream_props, "rtp.sender-ts-offset", "%u", (uint32_t)ts_offset); - impl->psamples = impl->mtu / impl->stride; - impl->psamples = SPA_CLAMP(impl->psamples, impl->min_samples, impl->max_samples); - - if ((str = pw_properties_get(props, "sess.name")) == NULL) - pw_properties_setf(props, "sess.name", "PipeWire RTP Stream on %s", - pw_get_host_name()); - str = pw_properties_get(props, "sess.name"); - impl->session_name = str ? strdup(str) : NULL; - - pw_properties_set(stream_props, "rtp.session", impl->session_name); get_ip(&impl->src_addr, addr, sizeof(addr)); pw_properties_set(stream_props, "rtp.source.ip", addr); get_ip(&impl->dst_addr, addr, sizeof(addr)); pw_properties_set(stream_props, "rtp.destination.ip", addr); - pw_properties_setf(stream_props, "rtp.destination.port", "%u", impl->port); - pw_properties_setf(stream_props, "rtp.mtu", "%u", impl->mtu); + pw_properties_setf(stream_props, "rtp.destination.port", "%u", impl->dst_port); pw_properties_setf(stream_props, "rtp.ttl", "%u", impl->ttl); - pw_properties_setf(stream_props, "rtp.ptime", "%u", - impl->psamples * 1000 / impl->rate); pw_properties_setf(stream_props, "rtp.dscp", "%u", impl->dscp); - impl->core = pw_context_get_object(impl->module_context, PW_TYPE_INTERFACE_Core); + impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { str = pw_properties_get(props, PW_KEY_REMOTE_NAME); - impl->core = pw_context_connect(impl->module_context, + impl->core = pw_context_connect(impl->context, pw_properties_new( PW_KEY_REMOTE_NAME, str, NULL), @@ -1255,11 +524,22 @@ &impl->core_listener, &core_events, impl); - if ((res = setup_stream(impl)) < 0) + if ((res = make_socket(&impl->src_addr, impl->src_len, + &impl->dst_addr, impl->dst_len, + impl->mcast_loop, impl->ttl, impl->dscp)) < 0) { + pw_log_error("can't make socket: %s", spa_strerror(res)); goto out; + } + impl->rtp_fd = res; - if ((res = start_sap_announce(impl)) < 0) + impl->stream = rtp_stream_new(impl->core, + PW_DIRECTION_INPUT, pw_properties_copy(stream_props), + &stream_events, impl); + if (impl->stream == NULL) { + res = -errno; + pw_log_error("can't create stream: %m"); goto out; + } pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
View file
pipewire-0.3.67.tar.gz/src/modules/module-rtp-source.c -> pipewire-0.3.68.tar.gz/src/modules/module-rtp-source.c
Changed
@@ -19,14 +19,16 @@ #include <spa/utils/result.h> #include <spa/utils/ringbuffer.h> #include <spa/utils/dll.h> +#include <spa/utils/json.h> #include <spa/param/audio/format-utils.h> #include <spa/control/control.h> +#include <spa/debug/types.h> +#include <spa/debug/mem.h> #include <pipewire/pipewire.h> #include <pipewire/impl.h> -#include <module-rtp/sap.h> -#include <module-rtp/rtp.h> +#include <module-rtp/stream.h> #ifdef __FreeBSD__ #define ifr_ifindex ifr_index @@ -35,63 +37,52 @@ /** \page page_module_rtp_source PipeWire Module: RTP source * * The `rtp-source` module creates a PipeWire source that receives audio - * RTP packets. + * and midi RTP packets. * * ## Module Options * * Options specific to the behavior of this module * - * - `sap.ip = <str>`: IP address of the SAP messages, default "224.0.0.56" - * - `sap.port = <str>`: port of the SAP messages, default 9875 * - `local.ifname = <str>`: interface name to use + * - `node.always-process = <bool>`: true to receive even when not running * - `sess.latency.msec = <str>`: target network latency in milliseconds, default 100 + * - `sess.media = <string>`: the media type audio|midi|opus, default audio * - `stream.props = {}`: properties to be passed to the stream * * ## General options * * Options with well-known behavior: * - * - \ref PW_KEY_NODE_NAME - * - \ref PW_KEY_NODE_DESCRIPTION + * - \ref PW_KEY_REMOTE_NAME + * - \ref PW_KEY_AUDIO_FORMAT + * - \ref PW_KEY_AUDIO_RATE + * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_MEDIA_NAME * - \ref PW_KEY_MEDIA_CLASS + * - \ref PW_KEY_NODE_NAME + * - \ref PW_KEY_NODE_DESCRIPTION + * - \ref PW_KEY_NODE_GROUP + * - \ref PW_KEY_NODE_LATENCY + * - \ref PW_KEY_NODE_VIRTUAL * * ## Example configuration *\code{.unparsed} * context.modules = * { name = libpipewire-module-rtp-source * args = { - * #sap.ip = 224.0.0.56 - * #sap.port = 9875 * #local.ifname = eth0 * sess.latency.msec = 100 - * #node.always-process = false # true to receive even when not running + * #node.always-process = false + * #sess.media = "audio" + * #audio.format = "S16BE" + * #audio.rate = 48000 + * #audio.channels = 2 + * #audio.position = FL FR * stream.props = { * #media.class = "Audio/Source" - * #node.name = "rtp-source" + * node.name = "rtp-source" * } - * stream.rules = - * { matches = - * # any of the items in matches needs to match, if one does, - * # actions are emited. - * { # all keys must match the value. ~ in value starts regex. - * #rtp.origin = "wim 3883629975 0 IN IP4 0.0.0.0" - * #rtp.payload = "127" - * #rtp.fmt = "L16/48000/2" - * #rtp.session = "PipeWire RTP Stream on fedora" - * #rtp.ts-offset = 0 - * #rtp.ts-refclk = "private" - * } - * - * actions = { - * create-stream = { - * #sess.latency.msec = 100 - * #sess.ts-direct = false - * #target.object = "" - * } - * } - * } - * * } * } * @@ -105,27 +96,21 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic -#define SAP_MIME_TYPE "application/sdp" - -#define ERROR_MSEC 2 -#define MAX_SESSIONS 16 - -#define DEFAULT_CLEANUP_INTERVAL_SEC 90 -#define DEFAULT_SAP_IP "224.0.0.56" -#define DEFAULT_SAP_PORT 9875 -#define DEFAULT_SESS_LATENCY 100 +#define DEFAULT_CLEANUP_SEC 60 +#define DEFAULT_SOURCE_IP "224.0.0.56" -#define BUFFER_SIZE (1u<<22) -#define BUFFER_MASK (BUFFER_SIZE-1) -#define BUFFER_SIZE2 (BUFFER_SIZE>>1) -#define BUFFER_MASK2 (BUFFER_SIZE2-1) +#define DEFAULT_TS_OFFSET -1 -#define USAGE "sap.ip=<SAP IP address to listen on, default "DEFAULT_SAP_IP"> " \ - "sap.port=<SAP port to listen on, default "SPA_STRINGIFY(DEFAULT_SAP_PORT)"> " \ - "local.ifname=<local interface name to use> " \ - "sess.latency.msec=<target network latency, default "SPA_STRINGIFY(DEFAULT_SESS_LATENCY)"> " \ - "stream.props= { key=value ... } " \ - "stream.rules=<rules> " +#define USAGE "( local.ifname=<local interface name to use> ) " \ + "( source.ip=<source IP address, default:"DEFAULT_SOURCE_IP"> ) " \ + "source.port=<int, source port> " \ + "( sess.latency.msec=<target network latency, default "SPA_STRINGIFY(DEFAULT_SESS_LATENCY)"> ) "\ + "( sess.media=<string, the media type audio|midi|opus, default audio> ) " \ + "( audio.format=<format, default:"DEFAULT_FORMAT"> ) " \ + "( audio.rate=<sample rate, default:"SPA_STRINGIFY(DEFAULT_RATE)"> ) " \ + "( audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS)"> ) " \ + "( audio.position=<channel map, default:"DEFAULT_POSITION"> ) " \ + "( stream.props= { key=value ... } ) " static const struct spa_dict_item module_info = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, @@ -138,7 +123,7 @@ struct pw_impl_module *module; struct spa_hook module_listener; struct pw_properties *props; - struct pw_context *module_context; + struct pw_context *context; struct pw_loop *loop; struct pw_loop *data_loop; @@ -146,577 +131,42 @@ struct pw_core *core; struct spa_hook core_listener; struct spa_hook core_proxy_listener; - - struct spa_source *timer; - struct spa_source *sap_source; - - struct pw_properties *stream_props; - unsigned int do_disconnect:1; char *ifname; - char *sap_ip; bool always_process; - int sap_port; - int sess_latency_msec; uint32_t cleanup_interval; - struct spa_list sessions; - uint32_t n_sessions; -}; - -struct format_info { - uint32_t media_subtype; - uint32_t format; - uint32_t size; - const char *mime; - const char *media_type; -}; - -static const struct format_info audio_format_info = { - { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_U8, 1, "L8", "audio" }, - { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_ALAW, 1, "PCMA", "audio" }, - { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_ULAW, 1, "PCMU", "audio" }, - { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_S16_BE, 2, "L16", "audio" }, - { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_S24_BE, 3, "L24", "audio" }, - { SPA_MEDIA_SUBTYPE_control, 0, 1, "rtp-midi", "audio" }, -}; - -static const struct format_info *find_format_info(const char *mime) -{ - SPA_FOR_EACH_ELEMENT_VAR(audio_format_info, f) - if (spa_streq(f->mime, mime)) - return f; - return NULL; -} - -struct sdp_info { - uint16_t hash; - - char origin128; - char session256; - char channelmap512; - - struct sockaddr_storage sa; - socklen_t salen; - - uint16_t port; - uint8_t payload; - - const struct format_info *format_info; - struct spa_audio_info info; - uint32_t rate; - uint32_t stride; - - uint32_t ts_offset; - char refclk64; -}; - -struct session { - struct impl *impl; - struct spa_list link; - - uint64_t timestamp; + struct spa_source *timer; - struct sdp_info info; + struct pw_properties *stream_props; + struct rtp_stream *stream; + uint16_t src_port; + struct sockaddr_storage src_addr; + socklen_t src_len; struct spa_source *source; - struct pw_stream *stream; - struct spa_hook stream_listener; - - uint32_t expected_ssrc; - uint16_t expected_seq; - unsigned have_ssrc:1; - unsigned have_seq:1; - unsigned have_sync:1; - - struct spa_ringbuffer ring; - uint8_t bufferBUFFER_SIZE; - - struct spa_io_rate_match *rate_match; - struct spa_io_position *position; - struct spa_dll dll; - double corr; - uint32_t target_buffer; - float max_error; - unsigned first:1; unsigned receiving:1; - unsigned direct_timestamp:1; - - float last_timestamp; - float last_time; }; -static void session_touch(struct session *sess) -{ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - sess->timestamp = SPA_TIMESPEC_TO_NSEC(&ts); -} - -static void process_audio(struct session *sess) -{ - struct pw_buffer *buf; - struct spa_data *d; - uint32_t wanted, timestamp, target_buffer, stride, maxsize; - int32_t avail; - - if ((buf = pw_stream_dequeue_buffer(sess->stream)) == NULL) { - pw_log_debug("Out of stream buffers: %m"); - return; - } - d = buf->buffer->datas; - - stride = sess->info.stride; - - maxsize = d0.maxsize / stride; - wanted = buf->requested ? SPA_MIN(buf->requested, maxsize) : maxsize; - - if (sess->position && sess->direct_timestamp) { - /* in direct mode, read directly from the timestamp index, - * because sender and receiver are in sync, this would keep - * target_buffer of samples available. */ - spa_ringbuffer_read_update(&sess->ring, - sess->position->clock.position); - } - avail = spa_ringbuffer_get_read_index(&sess->ring, ×tamp); - - target_buffer = sess->target_buffer; - - if (avail < (int32_t)wanted) { - enum spa_log_level level; - memset(d0.data, 0, wanted * stride); - if (sess->have_sync) { - sess->have_sync = false; - level = SPA_LOG_LEVEL_WARN; - } else { - level = SPA_LOG_LEVEL_DEBUG; - } - pw_log(level, "underrun %d/%u < %u", - avail, target_buffer, wanted); - } else { - float error, corr; - if (sess->first) { - if ((uint32_t)avail > target_buffer) { - uint32_t skip = avail - target_buffer; - pw_log_debug("first: avail:%d skip:%u target:%u", - avail, skip, target_buffer); - timestamp += skip; - avail = target_buffer; - } - sess->first = false; - } else if (avail > (int32_t)SPA_MIN(target_buffer * 8, BUFFER_SIZE / stride)) { - pw_log_warn("overrun %u > %u", avail, target_buffer * 8); - timestamp += avail - target_buffer; - avail = target_buffer; - } - if (!sess->direct_timestamp) { - /* when not using direct timestamp and clocks are not - * in sync, try to adjust our playback rate to keep the - * requested target_buffer bytes in the ringbuffer */ - error = (float)target_buffer - (float)avail; - error = SPA_CLAMP(error, -sess->max_error, sess->max_error); - - corr = spa_dll_update(&sess->dll, error); - - pw_log_debug("avail:%u target:%u error:%f corr:%f", avail, - target_buffer, error, corr); - - if (sess->rate_match) { - SPA_FLAG_SET(sess->rate_match->flags, - SPA_IO_RATE_MATCH_FLAG_ACTIVE); - sess->rate_match->rate = 1.0f / corr; - } - } - spa_ringbuffer_read_data(&sess->ring, - sess->buffer, - BUFFER_SIZE, - (timestamp * stride) & BUFFER_MASK, - d0.data, wanted * stride); - - timestamp += wanted; - spa_ringbuffer_read_update(&sess->ring, timestamp); - } - d0.chunk->size = wanted * stride; - d0.chunk->stride = stride; - d0.chunk->offset = 0; - buf->size = wanted; - - pw_stream_queue_buffer(sess->stream, buf); -} - -static void receive_audio(struct session *sess, uint8_t *packet, - uint32_t timestamp, uint32_t payload_offset, uint32_t len) -{ - uint32_t plen = len - payload_offset; - uint8_t *payload = &packetpayload_offset; - uint32_t stride = sess->info.stride; - uint32_t samples = plen / stride; - uint32_t write, expected_write; - int32_t filled; - - filled = spa_ringbuffer_get_write_index(&sess->ring, &expected_write); - - /* we always write to timestamp + delay */ - write = timestamp + sess->target_buffer; - - if (!sess->have_sync) { - pw_log_info("sync to timestamp %u direct:%d", write, sess->direct_timestamp); - /* we read from timestamp, keeping target_buffer of data - * in the ringbuffer. */ - sess->ring.readindex = timestamp; - sess->ring.writeindex = write; - filled = sess->target_buffer; - - spa_dll_init(&sess->dll); - spa_dll_set_bw(&sess->dll, SPA_DLL_BW_MIN, 128, sess->info.rate); - memset(sess->buffer, 0, BUFFER_SIZE); - sess->have_sync = true; - } else if (expected_write != write) { - pw_log_debug("unexpected write (%u != %u)", - write, expected_write); - } - - if (filled + samples > BUFFER_SIZE / stride) { - pw_log_debug("capture overrun %u + %u > %u", filled, samples, - BUFFER_SIZE / stride); - sess->have_sync = false; - } else { - pw_log_debug("got samples:%u", samples); - spa_ringbuffer_write_data(&sess->ring, - sess->buffer, - BUFFER_SIZE, - (write * stride) & BUFFER_MASK, - payload, (samples * stride)); - write += samples; - spa_ringbuffer_write_update(&sess->ring, write); - } -} - -static void process_midi(struct session *sess) -{ - struct pw_buffer *buf; - struct spa_data *d; - uint32_t timestamp, duration, maxsize, read; - struct spa_pod_builder b; - struct spa_pod_frame f1; - void *ptr; - struct spa_pod *pod; - struct spa_pod_control *c; - - if ((buf = pw_stream_dequeue_buffer(sess->stream)) == NULL) { - pw_log_debug("Out of stream buffers: %m"); - return; - } - d = buf->buffer->datas; - - maxsize = d0.maxsize; - - /* we always use the graph position to select events, the receiver side is - * responsible for smoothing out the RTP timestamps to graph time */ - duration = sess->position->clock.duration; - if (sess->position) - timestamp = sess->position->clock.position; - else - timestamp = 0; - - /* we copy events into the buffer based on the rtp timestamp + delay. */ - spa_pod_builder_init(&b, d0.data, maxsize); - spa_pod_builder_push_sequence(&b, &f0, 0); - - while (true) { - int32_t avail = spa_ringbuffer_get_read_index(&sess->ring, &read); - if (avail <= 0) - break; - - ptr = SPA_PTROFF(sess->buffer, read & BUFFER_MASK2, void); - - if ((pod = spa_pod_from_data(ptr, avail, 0, avail)) == NULL) - goto done; - if (!spa_pod_is_sequence(pod)) - goto done; - - /* the ringbuffer contains series of sequences, one for each - * received packet */ - SPA_POD_SEQUENCE_FOREACH((struct spa_pod_sequence*)pod, c) { - /* try to render with given delay */ - uint32_t target = c->offset + sess->target_buffer; - if (timestamp != 0) { - /* skip old packets */ - if (target < timestamp) - continue; - /* event for next cycle */ - if (target >= timestamp + duration) - goto complete; - } else { - timestamp = target; - } - spa_pod_builder_control(&b, target - timestamp, SPA_CONTROL_Midi); - spa_pod_builder_bytes(&b, - SPA_POD_BODY(&c->value), - SPA_POD_BODY_SIZE(&c->value)); - } - /* we completed a sequence (one RTP packet), advance ringbuffer - * and go to the next packet */ - read += SPA_PTRDIFF(c, ptr); - spa_ringbuffer_read_update(&sess->ring, read); - } -complete: - spa_pod_builder_pop(&b, &f0); - - if (b.state.offset > maxsize) { - pw_log_warn("overflow buffer %u %u", b.state.offset, maxsize); - b.state.offset = 0; - } - d0.chunk->size = b.state.offset; - d0.chunk->stride = 1; - d0.chunk->offset = 0; -done: - pw_stream_queue_buffer(sess->stream, buf); -} - -static int parse_varlen(uint8_t *p, uint32_t avail, uint32_t *result) -{ - uint32_t value = 0, offs = 0; - while (offs < avail) { - uint8_t b = poffs++; - value = (value << 7) | (b & 0x7f); - if ((b & 0x80) == 0) - break; - } - *result = value; - return offs; -} - -static int get_midi_size(uint8_t *p, uint32_t avail) -{ - int size; - uint32_t offs = 0, value; - - switch (poffs++) { - case 0xc0 ... 0xdf: - size = 2; - break; - case 0x80 ... 0xbf: - case 0xe0 ... 0xef: - size = 3; - break; - case 0xff: - case 0xf0: - case 0xf7: - size = parse_varlen(&poffs, avail - offs, &value); - size += value + 1; - break; - default: - return -EINVAL; - } - return size; -} - -static double get_time(struct session *sess) -{ - struct timespec ts; - double t; - clock_gettime(CLOCK_MONOTONIC, &ts); - t = sess->position->clock.position / (double) sess->position->clock.rate.denom; - t += (SPA_TIMESPEC_TO_NSEC(&ts) - sess->position->clock.nsec) / (double)SPA_NSEC_PER_SEC; - return t; -} - -static void receive_midi(struct session *sess, uint8_t *packet, - uint32_t timestamp, uint32_t payload_offset, uint32_t plen) -{ - uint32_t write; - struct rtp_midi_header *hdr; - int32_t filled; - struct spa_pod_builder b; - struct spa_pod_frame f1; - void *ptr; - uint32_t offs = payload_offset, len, end; - bool first = true; - - if (sess->direct_timestamp) { - /* in direct timestamp we attach the RTP timestamp directly on the - * midi events and render them in the corresponding cycle */ - if (!sess->have_sync) { - pw_log_info("sync to timestamp %u/ direct:%d", timestamp, - sess->direct_timestamp); - sess->have_sync = true; - } - } else { - /* in non-direct timestamp mode, we relate the graph clock against - * the RTP timestamps */ - double ts = timestamp / (float) sess->info.rate; - double t = get_time(sess); - double elapsed, estimated, diff; - - /* the elapsed time between RTP timestamps */ - elapsed = ts - sess->last_timestamp; - /* for that elapsed time, our clock should have advanced - * by this amount since the last estimation */ - estimated = sess->last_time + elapsed * sess->corr; - /* calculate the diff between estimated and current clock time in - * samples */ - diff = (estimated - t) * sess->info.rate; - - /* no sync or we drifted too far, resync */ - if (!sess->have_sync || fabs(diff) > sess->target_buffer) { - sess->corr = 1.0; - spa_dll_set_bw(&sess->dll, SPA_DLL_BW_MIN, 256, sess->info.rate); - - pw_log_info("sync to timestamp %u/%f direct:%d", timestamp, t, - sess->direct_timestamp); - sess->have_sync = true; - sess->ring.readindex = sess->ring.writeindex; - } else { - /* update our new rate correction */ - sess->corr = spa_dll_update(&sess->dll, diff); - /* our current time is now the estimated time */ - t = estimated; - } - pw_log_debug("%f %f %f %f", t, estimated, diff, sess->corr); - - timestamp = t * sess->info.rate; - - sess->last_timestamp = ts; - sess->last_time = t; - } - - filled = spa_ringbuffer_get_write_index(&sess->ring, &write); - if (filled > (int32_t)BUFFER_SIZE2) - return; - - hdr = (struct rtp_midi_header *)&packetoffs++; - len = hdr->len; - if (hdr->b) { - len = (len << 8) | hdr->len_b; - offs++; - } - end = len + offs; - if (end > plen) - return; - - ptr = SPA_PTROFF(sess->buffer, write & BUFFER_MASK2, void); - - /* each packet is written as a sequence of events. The offset is - * the RTP timestamp */ - spa_pod_builder_init(&b, ptr, BUFFER_SIZE2 - filled); - spa_pod_builder_push_sequence(&b, &f0, 0); - - while (offs < end) { - uint32_t delta; - int size; - - if (first && !hdr->z) - delta = 0; - else - offs += parse_varlen(&packetoffs, end - offs, &delta); - - timestamp += delta * sess->corr; - spa_pod_builder_control(&b, timestamp, SPA_CONTROL_Midi); - - size = get_midi_size(&packetoffs, end - offs); - - if (size <= 0 || offs + size > end) { - pw_log_warn("invalid size (%08x) %d (%u %u)", - packetoffs, size, offs, end); - break; - } - - spa_pod_builder_bytes(&b, &packetoffs, size); - - offs += size; - first = false; - } - spa_pod_builder_pop(&b, &f0); - - write += b.state.offset; - spa_ringbuffer_write_update(&sess->ring, write); -} - -static void stream_io_changed(void *data, uint32_t id, void *area, uint32_t size) -{ - struct session *sess = data; - switch (id) { - case SPA_IO_RateMatch: - sess->rate_match = area; - break; - case SPA_IO_Position: - sess->position = area; - break; - } -} - -static void stream_destroy(void *d) -{ - struct session *sess = d; - spa_hook_remove(&sess->stream_listener); - sess->stream = NULL; -} - -static void stream_process(void *data) -{ - struct session *sess = data; - switch (sess->info.info.media_type) { - case SPA_MEDIA_TYPE_audio: - process_audio(sess); - break; - case SPA_MEDIA_TYPE_application: - process_midi(sess); - break; - } -} - static void on_rtp_io(void *data, int fd, uint32_t mask) { - struct session *sess = data; - struct rtp_header *hdr; - ssize_t len, hlen; + struct impl *impl = data; + ssize_t len; uint8_t buffer2048; if (mask & SPA_IO_IN) { - uint16_t seq; - uint32_t timestamp; - if ((len = recv(fd, buffer, sizeof(buffer), 0)) < 0) goto receive_error; if (len < 12) goto short_packet; - hdr = (struct rtp_header*)buffer; - if (hdr->v != 2) - goto invalid_version; + rtp_stream_receive_packet(impl->stream, buffer, len); - hlen = 12 + hdr->cc * 4; - if (hlen > len) - goto invalid_len; - - if (sess->have_ssrc && sess->expected_ssrc != hdr->ssrc) - goto unexpected_ssrc; - sess->expected_ssrc = hdr->ssrc; - sess->have_ssrc = true; - - seq = ntohs(hdr->sequence_number); - if (sess->have_seq && sess->expected_seq != seq) { - pw_log_info("unexpected seq (%d != %d)", seq, sess->expected_seq); - sess->have_sync = false; - } - sess->expected_seq = seq + 1; - sess->have_seq = true; - - timestamp = ntohl(hdr->timestamp) - sess->info.ts_offset; - - switch (sess->info.info.media_type) { - case SPA_MEDIA_TYPE_audio: - receive_audio(sess, buffer, timestamp, hlen, len); - break; - case SPA_MEDIA_TYPE_application: - receive_midi(sess, buffer, timestamp, hlen, len); - } - sess->receiving = true; + impl->receiving = true; } return; @@ -726,16 +176,26 @@ short_packet: pw_log_warn("short packet received"); return; -invalid_version: - pw_log_warn("invalid RTP version"); - return; -invalid_len: - pw_log_warn("invalid RTP length"); - return; -unexpected_ssrc: - pw_log_warn("unexpected SSRC (expected %u != %u)", - sess->expected_ssrc, hdr->ssrc); - return; +} + +static int parse_address(const char *address, uint16_t port, + struct sockaddr_storage *addr, socklen_t *len) +{ + struct sockaddr_in *sa4 = (struct sockaddr_in*)addr; + struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)addr; + + if (inet_pton(AF_INET, address, &sa4->sin_addr) > 0) { + sa4->sin_family = AF_INET; + sa4->sin_port = htons(port); + *len = sizeof(*sa4); + } else if (inet_pton(AF_INET6, address, &sa6->sin6_addr) > 0) { + sa6->sin6_family = AF_INET6; + sa6->sin6_port = htons(port); + *len = sizeof(*sa6); + } else + return -EINVAL; + + return 0; } static int make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname) @@ -812,63 +272,28 @@ } return fd; error: + close(fd); return res; } -static uint32_t msec_to_samples(struct sdp_info *info, uint32_t msec) -{ - return msec * info->rate / 1000; -} - -static void session_free(struct session *sess) +static int stream_start(struct impl *impl) { - if (sess->impl) { - pw_log_info("free session %s %s", sess->info.origin, sess->info.session); - sess->impl->n_sessions--; - spa_list_remove(&sess->link); - } - if (sess->stream) - pw_stream_destroy(sess->stream); - if (sess->source) - pw_loop_destroy_source(sess->impl->data_loop, sess->source); - free(sess); -} - -struct session_info { - struct session *session; - struct pw_properties *props; - bool matched; -}; - -static int rule_matched(void *data, const char *location, const char *action, - const char *str, size_t len) -{ - struct session_info *i = data; - int res = 0; - - i->matched = true; - if (spa_streq(action, "create-stream")) { - pw_properties_update_string(i->props, str, len); - } - return res; -} - -static int session_start(struct impl *impl, struct session *session) { int fd; - if (session->source) - return 0; + + if (impl->source != NULL) + return 0; pw_log_info("starting RTP listener"); - if ((fd = make_socket((const struct sockaddr *)&session->info.sa, - session->info.salen, impl->ifname)) < 0) { + if ((fd = make_socket((const struct sockaddr *)&impl->src_addr, + impl->src_len, impl->ifname)) < 0) { pw_log_error("failed to create socket: %m"); return fd; } - session->source = pw_loop_add_io(impl->data_loop, fd, - SPA_IO_IN, true, on_rtp_io, session); - if (session->source == NULL) { + impl->source = pw_loop_add_io(impl->data_loop, fd, + SPA_IO_IN, true, on_rtp_io, impl); + if (impl->source == NULL) { pw_log_error("can't create io source: %m"); close(fd); return -errno; @@ -876,582 +301,56 @@ return 0; } -static void session_stop(struct impl *impl, struct session *session) { - if (!session->source) +static void stream_stop(struct impl *impl) +{ + if (!impl->source) return; pw_log_info("stopping RTP listener"); - pw_loop_destroy_source( - session->impl->data_loop, - session->source - ); - - session->source = NULL; -} - -static void on_stream_state_changed(void *d, enum pw_stream_state old, - enum pw_stream_state state, const char *error) -{ - struct session *sess = d; - struct impl *impl = sess->impl; - - switch (state) { - case PW_STREAM_STATE_UNCONNECTED: - pw_log_info("stream disconnected, unloading"); - pw_impl_module_schedule_destroy(impl->module); - break; - case PW_STREAM_STATE_ERROR: - pw_log_error("stream error: %s", error); - break; - case PW_STREAM_STATE_STREAMING: - if ((errno = -session_start(impl, sess)) < 0) - pw_log_error("failed to start RTP stream: %m"); - break; - case PW_STREAM_STATE_PAUSED: - if (!impl->always_process) - session_stop(impl, sess); - break; - default: - break; - } -} - -static const struct pw_stream_events out_stream_events = { - PW_VERSION_STREAM_EVENTS, - .destroy = stream_destroy, - .state_changed = on_stream_state_changed, - .io_changed = stream_io_changed, - .process = stream_process -}; - -static int session_new(struct impl *impl, struct sdp_info *info) -{ - struct session *session; - const struct spa_pod *params1; - struct spa_pod_builder b; - uint32_t n_params; - uint8_t buffer1024; - struct pw_properties *props; - int res, sess_latency_msec; - const char *str; - - if (impl->n_sessions >= MAX_SESSIONS) { - pw_log_warn("too many sessions (%u >= %u)", impl->n_sessions, MAX_SESSIONS); - return -EMFILE; - } - - session = calloc(1, sizeof(struct session)); - if (session == NULL) - return -errno; - - session->info = *info; - session->first = true; - - props = pw_properties_copy(impl->stream_props); - if (props == NULL) { - res = -errno; - goto error; - } - - pw_properties_set(props, "rtp.origin", info->origin); - pw_properties_setf(props, "rtp.payload", "%u", info->payload); - pw_properties_setf(props, "rtp.fmt", "%s/%u/%u", info->format_info->mime, - info->rate, info->info.info.raw.channels); - if (info->session0) { - pw_properties_set(props, "rtp.session", info->session); - pw_properties_setf(props, PW_KEY_MEDIA_NAME, "RTP Stream (%s)", - info->session); - pw_properties_setf(props, PW_KEY_NODE_NAME, "%s", - info->session); - } else { - pw_properties_set(props, PW_KEY_MEDIA_NAME, "RTP Stream"); - } - pw_properties_setf(props, "rtp.ts-offset", "%u", info->ts_offset); - pw_properties_set(props, "rtp.ts-refclk", info->refclk); - - if ((str = pw_properties_get(impl->props, "stream.rules")) != NULL) { - struct session_info sinfo = { - .session = session, - .props = props, - }; - pw_conf_match_rules(str, strlen(str), NAME, &props->dict, - rule_matched, &sinfo); - - if (!sinfo.matched) { - res = 0; - pw_log_info("session '%s' was not matched", info->session); - goto error; - } - } - session->direct_timestamp = pw_properties_get_bool(props, "sess.ts-direct", false); - - pw_log_info("new session %s %s direct:%d", info->origin, info->session, - session->direct_timestamp); - - sess_latency_msec = pw_properties_get_uint32(props, - "sess.latency.msec", impl->sess_latency_msec); - - session->target_buffer = msec_to_samples(info, sess_latency_msec); - session->max_error = msec_to_samples(info, ERROR_MSEC); - - pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%d", info->rate); - pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%d/%d", - session->target_buffer / 2, info->rate); - - spa_dll_init(&session->dll); - spa_dll_set_bw(&session->dll, SPA_DLL_BW_MIN, 128, info->rate); - session->corr = 1.0; - - if (info->channelmap0) { - pw_properties_set(props, PW_KEY_NODE_CHANNELNAMES, info->channelmap); - pw_log_info("channelmap: %s", info->channelmap); - } - - session->stream = pw_stream_new(impl->core, - "rtp-source playback", props); - if (session->stream == NULL) { - res = -errno; - pw_log_error("can't create stream: %m"); - goto error; - } - - pw_stream_add_listener(session->stream, - &session->stream_listener, - &out_stream_events, session); - - n_params = 0; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - switch (info->info.media_type) { - case SPA_MEDIA_TYPE_audio: - paramsn_params++ = spa_format_audio_build(&b, - SPA_PARAM_EnumFormat, &info->info); - break; - case SPA_MEDIA_TYPE_application: - paramsn_params++ = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); - break; - default: - return -EINVAL; - } - - if ((res = pw_stream_connect(session->stream, - PW_DIRECTION_OUTPUT, - PW_ID_ANY, - PW_STREAM_FLAG_MAP_BUFFERS | - PW_STREAM_FLAG_AUTOCONNECT | - PW_STREAM_FLAG_RT_PROCESS, - params, n_params)) < 0) { - pw_log_error("can't connect stream: %s", spa_strerror(res)); - goto error; - } - - if (impl->always_process && - (res = session_start(impl, session)) < 0) - goto error; - - session_touch(session); - - session->impl = impl; - spa_list_append(&impl->sessions, &session->link); - impl->n_sessions++; - - return 0; -error: - session_free(session); - return res; -} - -static struct session *session_find(struct impl *impl, struct sdp_info *info) -{ - struct session *sess; - spa_list_for_each(sess, &impl->sessions, link) { - if (info->hash == sess->info.hash && - spa_streq(info->origin, sess->info.origin)) - return sess; - } - return NULL; -} - -static int parse_sdp_c(struct impl *impl, char *c, struct sdp_info *info) -{ - int res; - - cstrcspn(c, "/") = 0; - if (spa_strstartswith(c, "c=IN IP4 ")) { - struct sockaddr_in *sa = (struct sockaddr_in*) &info->sa; - - c += strlen("c=IN IP4 "); - if (inet_pton(AF_INET, c, &sa->sin_addr) <= 0) { - res = -errno; - pw_log_warn("inet_pton(%s) failed: %m", c); - goto error; - } - sa->sin_family = AF_INET; - info->salen = sizeof(struct sockaddr_in); - } - else if (spa_strstartswith(c, "c=IN IP6 ")) { - struct sockaddr_in6 *sa = (struct sockaddr_in6*) &info->sa; - - c += strlen("c=IN IP6 "); - if (inet_pton(AF_INET6, c, &sa->sin6_addr) <= 0) { - res = -errno; - pw_log_warn("inet_pton(%s) failed: %m", c); - goto error; - } - - sa->sin6_family = AF_INET6; - info->salen = sizeof(struct sockaddr_in6); - } else - return -EINVAL; - - - res= 0; -error: - return res; -} - -static int parse_sdp_m(struct impl *impl, char *c, struct sdp_info *info) -{ - int port, payload; - - if (!spa_strstartswith(c, "m=audio ")) - return -EINVAL; - - c += strlen("m=audio "); - if (sscanf(c, "%i RTP/AVP %i", &port, &payload) != 2) - return -EINVAL; - - if (port <= 0 || port > 0xFFFF) - return -EINVAL; - - if (payload < 0 || payload > 127) - return -EINVAL; - - info->port = (uint16_t) port; - info->payload = (uint8_t) payload; - - return 0; -} - -// some AES67 devices have channelmap encoded in i=* -// if `i` record is found, it matches the template -// and channel count matches, name the channels respectively -// `i=2 channels: 01, 08` is the format -static int parse_sdp_i(struct impl *impl, char *c, struct sdp_info *info) -{ - if (!strstr(c, " channels: ")) { - return 0; - } - - c += strlen("i="); - cstrcspn(c, " ") = '\0'; - - uint32_t channels; - if (sscanf(c, "%u", &channels) != 1 || channels <= 0 || channels > SPA_AUDIO_MAX_CHANNELS) - return 0; - - c += strcspn(c, "\0"); - c += strlen(" channels: "); - - strncpy(info->channelmap, c, sizeof(info->channelmap) - 1); - - return 0; -} - -static int parse_sdp_a_rtpmap(struct impl *impl, char *c, struct sdp_info *info) -{ - int payload, len, rate, channels; - - if (!spa_strstartswith(c, "a=rtpmap:")) - return 0; - - c += strlen("a=rtpmap:"); - - if (sscanf(c, "%i %n", &payload, &len) != 1) - return -EINVAL; - - if (payload < 0 || payload > 127) - return -EINVAL; - - if (payload != info->payload) - return 0; - - c += len; - cstrcspn(c, "/") = 0; - - info->format_info = find_format_info(c); - if (info->format_info == NULL) - return -EINVAL; - - info->stride = info->format_info->size; - - info->info.media_subtype = info->format_info->media_subtype; - - c += strlen(c) + 1; - - switch (info->info.media_subtype) { - case SPA_MEDIA_SUBTYPE_raw: - info->info.media_type = SPA_MEDIA_TYPE_audio; - info->info.info.raw.format = info->format_info->format; - if (sscanf(c, "%u/%u", &rate, &channels) == 2) { - info->info.info.raw.channels = channels; - } else if (sscanf(c, "%u", &rate) == 1) { - info->info.info.raw.channels = 1; - } else - return -EINVAL; - - info->info.info.raw.rate = rate; - - pw_log_debug("rate: %d, ch: %d", rate, channels); - - if (channels == 1) { - info->info.info.raw.position0 = SPA_AUDIO_CHANNEL_MONO; - } else if (channels == 2) { - info->info.info.raw.position0 = SPA_AUDIO_CHANNEL_FL; - info->info.info.raw.position1 = SPA_AUDIO_CHANNEL_FR; - } - info->stride *= channels; - info->rate = rate; - break; - case SPA_MEDIA_SUBTYPE_control: - info->info.media_type = SPA_MEDIA_TYPE_application; - if (sscanf(c, "%u", &rate) != 1) - return -EINVAL; - info->rate = rate; - break; - } - return 0; -} - -static int parse_sdp_a_mediaclk(struct impl *impl, char *c, struct sdp_info *info) -{ - if (!spa_strstartswith(c, "a=mediaclk:")) - return 0; - - c += strlen("a=mediaclk:"); - - if (spa_strstartswith(c, "direct=")) { - int offset; - c += strlen("direct="); - if (sscanf(c, "%i", &offset) != 1) - return -EINVAL; - info->ts_offset = offset; - } else if (spa_strstartswith(c, "sender")) { - info->ts_offset = 0; - } - return 0; -} - -static int parse_sdp_a_ts_refclk(struct impl *impl, char *c, struct sdp_info *info) -{ - if (!spa_strstartswith(c, "a=ts-refclk:")) - return 0; - - c += strlen("a=ts-refclk:"); - snprintf(info->refclk, sizeof(info->refclk), "%s", c); - return 0; -} - -static int parse_sdp(struct impl *impl, char *sdp, struct sdp_info *info) -{ - char *s = sdp; - int count = 0, res = 0; - size_t l; - - while (*s) { - if ((l = strcspn(s, "\r\n")) < 2) - goto too_short; - - sl = 0; - pw_log_debug("%d: %s", count, s); - - if (count++ == 0 && strcmp(s, "v=0") != 0) - goto invalid_version; - - if (spa_strstartswith(s, "o=")) - snprintf(info->origin, sizeof(info->origin), "%s", &s2); - else if (spa_strstartswith(s, "s=")) - snprintf(info->session, sizeof(info->session), "%s", &s2); - else if (spa_strstartswith(s, "c=")) - res = parse_sdp_c(impl, s, info); - else if (spa_strstartswith(s, "m=")) - res = parse_sdp_m(impl, s, info); - else if (spa_strstartswith(s, "a=rtpmap:")) - res = parse_sdp_a_rtpmap(impl, s, info); - else if (spa_strstartswith(s, "a=mediaclk:")) - res = parse_sdp_a_mediaclk(impl, s, info); - else if (spa_strstartswith(s, "a=ts-refclk:")) - res = parse_sdp_a_ts_refclk(impl, s, info); - else if (spa_strstartswith(s, "i=")) - res = parse_sdp_i(impl, s, info); - - if (res < 0) - goto error; - s += l + 1; - while (isspace(*s)) - s++; - } - if (((struct sockaddr*) &info->sa)->sa_family == AF_INET) - ((struct sockaddr_in*) &info->sa)->sin_port = htons(info->port); - else - ((struct sockaddr_in6*) &info->sa)->sin6_port = htons(info->port); - - return 0; -too_short: - pw_log_warn("SDP: line starting with `%.6s...' too short", s); - return -EINVAL; -invalid_version: - pw_log_warn("SDP: invalid first version line `%*s'", (int)l, s); - return -EINVAL; -error: - pw_log_warn("SDP: error: %s", spa_strerror(res)); - return res; + pw_loop_destroy_source(impl->data_loop, impl->source); + impl->source = NULL; } -static int parse_sap(struct impl *impl, void *data, size_t len) +static void stream_destroy(void *d) { - struct sap_header *header; - char *mime, *sdp; - struct sdp_info info; - struct session *sess; - int res; - size_t offs; - bool bye; - - if (len < 8) - return -EINVAL; - - header = (struct sap_header*) data; - if (header->v != 1) - return -EINVAL; - - if (header->e) - return -ENOTSUP; - if (header->c) - return -ENOTSUP; - - offs = header->a ? 12 : 8; - offs += header->auth_len * 4; - if (len <= offs) - return -EINVAL; - - mime = SPA_PTROFF(data, offs, char); - if (spa_strstartswith(mime, "v=0")) { - sdp = mime; - mime = SAP_MIME_TYPE; - } else if (spa_streq(mime, SAP_MIME_TYPE)) - sdp = SPA_PTROFF(mime, strlen(mime)+1, char); - else - return -EINVAL; - - pw_log_debug("got sap: %s %s", mime, sdp); - - spa_zero(info); - if ((res = parse_sdp(impl, sdp, &info)) < 0) - return res; - - bye = header->t; - - sess = session_find(impl, &info); - if (sess == NULL) { - if (!bye) - session_new(impl, &info); - } else { - if (bye) - session_free(sess); - else - session_touch(sess); - } - return res; + struct impl *impl = d; + impl->stream = NULL; } -static void -on_sap_io(void *data, int fd, uint32_t mask) +static void stream_state_changed(void *data, bool started, const char *error) { struct impl *impl = data; - if (mask & SPA_IO_IN) { - uint8_t buffer2048; - ssize_t len; - - if ((len = recv(fd, buffer, sizeof(buffer), 0)) < 0) { - pw_log_warn("recv error: %m"); - return; - } - if ((size_t)len >= sizeof(buffer)) - return; - - bufferlen = 0; - parse_sap(impl, buffer, len); + if (error) { + pw_log_error("stream error: %s", error); + pw_impl_module_schedule_destroy(impl->module); + } else if (started) { + if ((errno = -stream_start(impl)) < 0) + pw_log_error("failed to start RTP stream: %m"); + } else { + if (!impl->always_process) + stream_stop(impl); } } -static int start_sap_listener(struct impl *impl) -{ - struct sockaddr_in sa4; - struct sockaddr_in6 sa6; - struct sockaddr *sa; - socklen_t salen; - int fd, res; - - if (inet_pton(AF_INET, impl->sap_ip, &sa4.sin_addr) > 0) { - sa4.sin_family = AF_INET; - sa4.sin_port = htons(impl->sap_port); - sa = (struct sockaddr*) &sa4; - salen = sizeof(sa4); - } else if (inet_pton(AF_INET6, impl->sap_ip, &sa6.sin6_addr) > 0) { - sa6.sin6_family = AF_INET6; - sa6.sin6_port = htons(impl->sap_port); - sa = (struct sockaddr*) &sa6; - salen = sizeof(sa6); - } else - return -EINVAL; - - if ((fd = make_socket(sa, salen, impl->ifname)) < 0) - return fd; - - pw_log_info("starting SAP listener"); - impl->sap_source = pw_loop_add_io(impl->loop, fd, - SPA_IO_IN, true, on_sap_io, impl); - if (impl->sap_source == NULL) { - res = -errno; - goto error; - } - return 0; -error: - close(fd); - return res; - -} +static const struct rtp_stream_events stream_events = { + RTP_VERSION_STREAM_EVENTS, + .destroy = stream_destroy, + .state_changed = stream_state_changed, +}; static void on_timer_event(void *data, uint64_t expirations) { struct impl *impl = data; - struct timespec now; - struct session *sess, *tmp; - uint64_t timestamp, interval; - - clock_gettime(CLOCK_MONOTONIC, &now); - timestamp = SPA_TIMESPEC_TO_NSEC(&now); - interval = impl->cleanup_interval * SPA_NSEC_PER_SEC; - - spa_list_for_each_safe(sess, tmp, &impl->sessions, link) { - if (sess->timestamp + interval < timestamp) { - pw_log_debug("More than %lu elapsed from last advertisement at %lu", - interval, sess->timestamp); - if (!sess->receiving) { - pw_log_info("SAP timeout, closing inactive RTP source"); - session_free(sess); - } else { - pw_log_info("SAP timeout, keeping active RTP source"); - } - } - sess->receiving = false; + + if (!impl->receiving) { + pw_log_info("timeout, inactive RTP source"); + //pw_impl_module_schedule_destroy(impl->module); + } else { + pw_log_debug("timeout, keeping active RTP source"); } + impl->receiving = false; } static void core_destroy(void *d) @@ -1468,15 +367,14 @@ static void impl_destroy(struct impl *impl) { - struct session *sess; - spa_list_consume(sess, &impl->sessions, link) - session_free(sess); + if (impl->stream) + rtp_stream_destroy(impl->stream); + if (impl->source) + pw_loop_destroy_source(impl->data_loop, impl->source); if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); - if (impl->sap_source) - pw_loop_destroy_source(impl->loop, impl->sap_source); if (impl->timer) pw_loop_destroy_source(impl->loop, impl->timer); @@ -1484,7 +382,6 @@ pw_properties_free(impl->props); free(impl->ifname); - free(impl->sap_ip); free(impl); } @@ -1516,13 +413,24 @@ .error = on_core_error, }; +static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) +{ + const char *str; + if ((str = pw_properties_get(props, key)) != NULL) { + if (pw_properties_get(impl->stream_props, key) == NULL) + pw_properties_set(impl->stream_props, key, str); + } +} + SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); struct impl *impl; - const char *str; + const char *str, *sess_name; struct timespec value, interval; + struct pw_properties *props, *stream_props; + int64_t ts_offset; int res = 0; PW_LOG_TOPIC_INIT(mod_topic); @@ -1531,50 +439,86 @@ if (impl == NULL) return -errno; - spa_list_init(&impl->sessions); - if (args == NULL) args = ""; - impl->props = pw_properties_new_string(args); - impl->stream_props = pw_properties_new(NULL, NULL); - if (impl->props == NULL || impl->stream_props == NULL) { + props = impl->props = pw_properties_new_string(args); + stream_props = impl->stream_props = pw_properties_new(NULL, NULL); + if (props == NULL || stream_props == NULL) { res = -errno; pw_log_error( "can't create properties: %m"); goto out; } impl->module = module; - impl->module_context = context; + impl->context = context; impl->loop = pw_context_get_main_loop(context); impl->data_loop = pw_data_loop_get_loop(pw_context_get_data_loop(context)); - if (pw_properties_get(impl->stream_props, PW_KEY_NODE_VIRTUAL) == NULL) - pw_properties_set(impl->stream_props, PW_KEY_NODE_VIRTUAL, "true"); - if (pw_properties_get(impl->stream_props, PW_KEY_NODE_NETWORK) == NULL) - pw_properties_set(impl->stream_props, PW_KEY_NODE_NETWORK, "true"); + if ((sess_name = pw_properties_get(props, "sess.name")) == NULL) + sess_name = pw_get_host_name(); + + if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL) + pw_properties_setf(props, PW_KEY_NODE_NAME, "rtp_session.%s", sess_name); + if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) + pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, "%s", sess_name); + if (pw_properties_get(props, PW_KEY_MEDIA_NAME) == NULL) + pw_properties_setf(props, PW_KEY_MEDIA_NAME, "RTP Session with %s", + sess_name); + + if ((str = pw_properties_get(props, "stream.props")) != NULL) + pw_properties_update_string(stream_props, str, strlen(str)); + + copy_props(impl, props, PW_KEY_AUDIO_FORMAT); + copy_props(impl, props, PW_KEY_AUDIO_RATE); + copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_POSITION); + copy_props(impl, props, PW_KEY_NODE_NAME); + copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); + copy_props(impl, props, PW_KEY_NODE_GROUP); + copy_props(impl, props, PW_KEY_NODE_LATENCY); + copy_props(impl, props, PW_KEY_NODE_VIRTUAL); + copy_props(impl, props, PW_KEY_NODE_CHANNELNAMES); + copy_props(impl, props, PW_KEY_MEDIA_NAME); + copy_props(impl, props, PW_KEY_MEDIA_CLASS); + copy_props(impl, props, "net.mtu"); + copy_props(impl, props, "sess.media"); + copy_props(impl, props, "sess.name"); + copy_props(impl, props, "sess.min-ptime"); + copy_props(impl, props, "sess.max-ptime"); + copy_props(impl, props, "sess.latency.msec"); + copy_props(impl, props, "sess.ts-direct"); + + str = pw_properties_get(props, "local.ifname"); + impl->ifname = str ? strdup(str) : NULL; - if ((str = pw_properties_get(impl->props, "stream.props")) != NULL) - pw_properties_update_string(impl->stream_props, str, strlen(str)); + impl->src_port = pw_properties_get_uint32(props, "source.port", 0); + if (impl->src_port == 0) { + pw_log_error("invalid source.port"); + goto out; + } + if ((str = pw_properties_get(props, "source.ip")) == NULL) + str = DEFAULT_SOURCE_IP; + if ((res = parse_address(str, impl->src_port, &impl->src_addr, &impl->src_len)) < 0) { + pw_log_error("invalid source.ip %s: %s", str, spa_strerror(res)); + goto out; + } - str = pw_properties_get(impl->props, "local.ifname"); - impl->ifname = str ? strdup(str) : NULL; + ts_offset = pw_properties_get_int64(props, "sess.ts-offset", DEFAULT_TS_OFFSET); + if (ts_offset == -1) + ts_offset = pw_rand32(); + pw_properties_setf(stream_props, "rtp.receiver-ts-offset", "%u", (uint32_t)ts_offset); - impl->always_process = pw_properties_get_bool(impl->props, PW_KEY_NODE_ALWAYS_PROCESS, false); + impl->always_process = pw_properties_get_bool(stream_props, + PW_KEY_NODE_ALWAYS_PROCESS, true); - str = pw_properties_get(impl->props, "sap.ip"); - impl->sap_ip = strdup(str ? str : DEFAULT_SAP_IP); - impl->sap_port = pw_properties_get_uint32(impl->props, - "sap.port", DEFAULT_SAP_PORT); - impl->sess_latency_msec = pw_properties_get_uint32(impl->props, - "sess.latency.msec", DEFAULT_SESS_LATENCY); - impl->cleanup_interval = pw_properties_get_uint32(impl->props, - "sap.interval.sec", DEFAULT_CLEANUP_INTERVAL_SEC); + impl->cleanup_interval = pw_properties_get_uint32(props, + "cleanup.sec", DEFAULT_CLEANUP_SEC); - impl->core = pw_context_get_object(impl->module_context, PW_TYPE_INTERFACE_Core); + impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { - str = pw_properties_get(impl->props, PW_KEY_REMOTE_NAME); - impl->core = pw_context_connect(impl->module_context, + str = pw_properties_get(props, PW_KEY_REMOTE_NAME); + impl->core = pw_context_connect(impl->context, pw_properties_new( PW_KEY_REMOTE_NAME, str, NULL), @@ -1600,14 +544,20 @@ pw_log_error("can't create timer source: %m"); goto out; } - value.tv_sec = 0; - value.tv_nsec = 1; + value.tv_sec = impl->cleanup_interval; + value.tv_nsec = 0; interval.tv_sec = impl->cleanup_interval; interval.tv_nsec = 0; pw_loop_update_timer(impl->loop, impl->timer, &value, &interval, false); - if ((res = start_sap_listener(impl)) < 0) + impl->stream = rtp_stream_new(impl->core, + PW_DIRECTION_OUTPUT, pw_properties_copy(stream_props), + &stream_events, impl); + if (impl->stream == NULL) { + res = -errno; + pw_log_error("can't create stream: %m"); goto out; + } pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
View file
pipewire-0.3.68.tar.gz/src/modules/module-rtp/apple-midi.h
Added
@@ -0,0 +1,52 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans <wim.taymans@gmail.com> */ +/* SPDX-License-Identifier: MIT */ + +#ifndef PIPEWIRE_APPLE_MIDI_H +#define PIPEWIRE_APPLE_MIDI_H + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct rtp_apple_midi { + uint32_t cmd; + uint32_t protocol; + uint32_t initiator; + uint32_t ssrc; + char name0; +} __attribute__ ((packed)); + +struct rtp_apple_midi_ck { + uint32_t cmd; + uint32_t ssrc; + uint8_t count; + uint8_t padding3; + uint32_t ts1_h; + uint32_t ts1_l; + uint32_t ts2_h; + uint32_t ts2_l; + uint32_t ts3_h; + uint32_t ts3_l; +} __attribute__ ((packed)); + +struct rtp_apple_midi_rs { + uint32_t cmd; + uint32_t ssrc; + uint32_t seqnum; +} __attribute__ ((packed)); + +#define APPLE_MIDI_CMD_IN (0xffff0000 | 'I'<<8 | 'N') +#define APPLE_MIDI_CMD_NO (0xffff0000 | 'N'<<8 | 'O') +#define APPLE_MIDI_CMD_OK (0xffff0000 | 'O'<<8 | 'K') +#define APPLE_MIDI_CMD_CK (0xffff0000 | 'C'<<8 | 'K') +#define APPLE_MIDI_CMD_BY (0xffff0000 | 'B'<<8 | 'Y') +#define APPLE_MIDI_CMD_RS (0xffff0000 | 'R'<<8 | 'S') + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_APPLE_MIDI_H */
View file
pipewire-0.3.68.tar.gz/src/modules/module-rtp/audio.c
Added
@@ -0,0 +1,313 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans <wim.taymans@gmail.com> */ +/* SPDX-License-Identifier: MIT */ + +static void rtp_audio_process_playback(void *data) +{ + struct impl *impl = data; + struct pw_buffer *buf; + struct spa_data *d; + uint32_t wanted, timestamp, target_buffer, stride, maxsize; + int32_t avail; + + if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) { + pw_log_debug("Out of stream buffers: %m"); + return; + } + d = buf->buffer->datas; + + stride = impl->stride; + + maxsize = d0.maxsize / stride; + wanted = buf->requested ? SPA_MIN(buf->requested, maxsize) : maxsize; + + if (impl->io_position && impl->direct_timestamp) { + /* in direct mode, read directly from the timestamp index, + * because sender and receiver are in sync, this would keep + * target_buffer of samples available. */ + spa_ringbuffer_read_update(&impl->ring, + impl->io_position->clock.position); + } + avail = spa_ringbuffer_get_read_index(&impl->ring, ×tamp); + + target_buffer = impl->target_buffer; + + if (avail < (int32_t)wanted) { + enum spa_log_level level; + memset(d0.data, 0, wanted * stride); + if (impl->have_sync) { + impl->have_sync = false; + level = SPA_LOG_LEVEL_WARN; + } else { + level = SPA_LOG_LEVEL_DEBUG; + } + pw_log(level, "underrun %d/%u < %u", + avail, target_buffer, wanted); + } else { + float error, corr; + if (impl->first) { + if ((uint32_t)avail > target_buffer) { + uint32_t skip = avail - target_buffer; + pw_log_debug("first: avail:%d skip:%u target:%u", + avail, skip, target_buffer); + timestamp += skip; + avail = target_buffer; + } + impl->first = false; + } else if (avail > (int32_t)SPA_MIN(target_buffer * 8, BUFFER_SIZE / stride)) { + pw_log_warn("overrun %u > %u", avail, target_buffer * 8); + timestamp += avail - target_buffer; + avail = target_buffer; + } + if (!impl->direct_timestamp) { + /* when not using direct timestamp and clocks are not + * in sync, try to adjust our playback rate to keep the + * requested target_buffer bytes in the ringbuffer */ + error = (float)target_buffer - (float)avail; + error = SPA_CLAMP(error, -impl->max_error, impl->max_error); + + corr = spa_dll_update(&impl->dll, error); + + pw_log_debug("avail:%u target:%u error:%f corr:%f", avail, + target_buffer, error, corr); + + if (impl->io_rate_match) { + SPA_FLAG_SET(impl->io_rate_match->flags, + SPA_IO_RATE_MATCH_FLAG_ACTIVE); + impl->io_rate_match->rate = 1.0f / corr; + } + } + spa_ringbuffer_read_data(&impl->ring, + impl->buffer, + BUFFER_SIZE, + (timestamp * stride) & BUFFER_MASK, + d0.data, wanted * stride); + + timestamp += wanted; + spa_ringbuffer_read_update(&impl->ring, timestamp); + } + d0.chunk->size = wanted * stride; + d0.chunk->stride = stride; + d0.chunk->offset = 0; + buf->size = wanted; + + pw_stream_queue_buffer(impl->stream, buf); +} + +static int rtp_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len) +{ + struct rtp_header *hdr; + ssize_t hlen, plen; + uint16_t seq; + uint32_t timestamp, samples, write, expected_write; + uint32_t stride = impl->stride; + int32_t filled; + + if (len < 12) + goto short_packet; + + hdr = (struct rtp_header*)buffer; + if (hdr->v != 2) + goto invalid_version; + + hlen = 12 + hdr->cc * 4; + if (hlen > len) + goto invalid_len; + + if (impl->have_ssrc && impl->ssrc != hdr->ssrc) + goto unexpected_ssrc; + impl->ssrc = hdr->ssrc; + impl->have_ssrc = true; + + seq = ntohs(hdr->sequence_number); + if (impl->have_seq && impl->seq != seq) { + pw_log_info("unexpected seq (%d != %d) SSRC:%u", + seq, impl->seq, hdr->ssrc); + impl->have_sync = false; + } + impl->seq = seq + 1; + impl->have_seq = true; + + timestamp = ntohl(hdr->timestamp) - impl->ts_offset; + + impl->receiving = true; + + plen = len - hlen; + samples = plen / stride; + + filled = spa_ringbuffer_get_write_index(&impl->ring, &expected_write); + + /* we always write to timestamp + delay */ + write = timestamp + impl->target_buffer; + + if (!impl->have_sync) { + pw_log_info("sync to timestamp:%u seq:%u ts_offset:%u SSRC:%u target:%u direct:%u", + timestamp, seq, impl->ts_offset, impl->ssrc, + impl->target_buffer, impl->direct_timestamp); + + /* we read from timestamp, keeping target_buffer of data + * in the ringbuffer. */ + impl->ring.readindex = timestamp; + impl->ring.writeindex = write; + filled = impl->target_buffer; + + spa_dll_init(&impl->dll); + spa_dll_set_bw(&impl->dll, SPA_DLL_BW_MIN, 128, impl->rate); + memset(impl->buffer, 0, BUFFER_SIZE); + impl->have_sync = true; + } else if (expected_write != write) { + pw_log_debug("unexpected write (%u != %u)", + write, expected_write); + } + + if (filled + samples > BUFFER_SIZE / stride) { + pw_log_debug("capture overrun %u + %u > %u", filled, samples, + BUFFER_SIZE / stride); + impl->have_sync = false; + } else { + pw_log_debug("got samples:%u", samples); + spa_ringbuffer_write_data(&impl->ring, + impl->buffer, + BUFFER_SIZE, + (write * stride) & BUFFER_MASK, + &bufferhlen, (samples * stride)); + write += samples; + spa_ringbuffer_write_update(&impl->ring, write); + } + return 0; + +short_packet: + pw_log_warn("short packet received"); + return -EINVAL; +invalid_version: + pw_log_warn("invalid RTP version"); + spa_debug_mem(0, buffer, len); + return -EPROTO; +invalid_len: + pw_log_warn("invalid RTP length"); + return -EINVAL; +unexpected_ssrc: + pw_log_warn("unexpected SSRC (expected %u != %u)", + impl->ssrc, hdr->ssrc); + return -EINVAL; +} + +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 rtp_audio_flush_packets(struct impl *impl) +{ + int32_t avail, tosend; + uint32_t stride, timestamp; + struct iovec iov3; + struct rtp_header header; + + avail = spa_ringbuffer_get_read_index(&impl->ring, ×tamp); + tosend = impl->psamples; + + if (avail < tosend) + return; + + stride = impl->stride; + + spa_zero(header); + header.v = 2; + header.pt = impl->payload; + header.ssrc = htonl(impl->ssrc); + + iov0.iov_base = &header; + iov0.iov_len = sizeof(header); + + while (avail >= tosend) { + header.sequence_number = htons(impl->seq); + header.timestamp = htonl(impl->ts_offset + timestamp); + + set_iovec(&impl->ring, + impl->buffer, BUFFER_SIZE, + (timestamp * stride) & BUFFER_MASK, + &iov1, tosend * stride); + + pw_log_trace("sending %d timestamp:%d", tosend, timestamp); + + rtp_stream_emit_send_packet(impl, iov, 3); + + impl->seq++; + timestamp += tosend; + avail -= tosend; + } + spa_ringbuffer_read_update(&impl->ring, timestamp); +} + +static void rtp_audio_process_capture(void *data) +{ + struct impl *impl = data; + struct pw_buffer *buf; + struct spa_data *d; + uint32_t offs, size, timestamp, expected_timestamp, stride; + int32_t filled, wanted; + + if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) { + pw_log_debug("Out of stream buffers: %m"); + return; + } + d = buf->buffer->datas; + + offs = SPA_MIN(d0.chunk->offset, d0.maxsize); + size = SPA_MIN(d0.chunk->size, d0.maxsize - offs); + stride = impl->stride; + wanted = size / stride; + + filled = spa_ringbuffer_get_write_index(&impl->ring, &expected_timestamp); + + if (SPA_LIKELY(impl->io_position)) { + uint32_t rate = impl->io_position->clock.rate.denom; + timestamp = impl->io_position->clock.position * impl->rate / rate; + } else + timestamp = expected_timestamp; + + if (!impl->have_sync) { + pw_log_info("sync to timestamp:%u seq:%u ts_offset:%u SSRC:%u", + timestamp, impl->seq, impl->ts_offset, impl->ssrc); + impl->ring.readindex = impl->ring.writeindex = timestamp; + memset(impl->buffer, 0, BUFFER_SIZE); + impl->have_sync = true; + expected_timestamp = timestamp; + } else { + if (SPA_ABS((int32_t)expected_timestamp - (int32_t)timestamp) > 32) { + pw_log_warn("expected %u != timestamp %u", expected_timestamp, timestamp); + impl->have_sync = false; + } else if (filled + wanted > (int32_t)(BUFFER_SIZE / stride)) { + pw_log_warn("overrun %u + %u > %u", filled, wanted, BUFFER_SIZE / stride); + impl->have_sync = false; + } + } + + spa_ringbuffer_write_data(&impl->ring, + impl->buffer, + BUFFER_SIZE, + (expected_timestamp * stride) & BUFFER_MASK, + SPA_PTROFF(d0.data, offs, void), wanted * stride); + expected_timestamp += wanted; + spa_ringbuffer_write_update(&impl->ring, expected_timestamp); + + pw_stream_queue_buffer(impl->stream, buf); + + rtp_audio_flush_packets(impl); +} + +static int rtp_audio_init(struct impl *impl, enum spa_direction direction) +{ + if (direction == SPA_DIRECTION_INPUT) + impl->stream_events.process = rtp_audio_process_capture; + else + impl->stream_events.process = rtp_audio_process_playback; + impl->receive_rtp = rtp_audio_receive; + return 0; +}
View file
pipewire-0.3.68.tar.gz/src/modules/module-rtp/midi.c
Added
@@ -0,0 +1,498 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans <wim.taymans@gmail.com> */ +/* SPDX-License-Identifier: MIT */ + +static void rtp_midi_process_playback(void *data) +{ + struct impl *impl = data; + struct pw_buffer *buf; + struct spa_data *d; + uint32_t timestamp, duration, maxsize, read, rate; + struct spa_pod_builder b; + struct spa_pod_frame f1; + void *ptr; + struct spa_pod *pod; + struct spa_pod_control *c; + + if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) { + pw_log_debug("Out of stream buffers: %m"); + return; + } + d = buf->buffer->datas; + + maxsize = d0.maxsize; + + /* we always use the graph position to select events, the receiver side is + * responsible for smoothing out the RTP timestamps to graph time */ + if (impl->io_position) { + duration = impl->io_position->clock.duration; + timestamp = impl->io_position->clock.position; + rate = impl->io_position->clock.rate.denom; + } else { + duration = 8192; + timestamp = 0; + rate = impl->rate; + } + + /* we copy events into the buffer based on the rtp timestamp + delay. */ + spa_pod_builder_init(&b, d0.data, maxsize); + spa_pod_builder_push_sequence(&b, &f0, 0); + + while (true) { + int32_t avail = spa_ringbuffer_get_read_index(&impl->ring, &read); + if (avail <= 0) + break; + + ptr = SPA_PTROFF(impl->buffer, read & BUFFER_MASK2, void); + + if ((pod = spa_pod_from_data(ptr, avail, 0, avail)) == NULL) + goto done; + if (!spa_pod_is_sequence(pod)) + goto done; + + /* the ringbuffer contains series of sequences, one for each + * received packet */ + SPA_POD_SEQUENCE_FOREACH((struct spa_pod_sequence*)pod, c) { + /* try to render with given delay */ + uint32_t target = c->offset + impl->target_buffer; + target = (uint64_t)target * rate / impl->rate; + if (timestamp != 0) { + /* skip old packets */ + if (target < timestamp) + continue; + /* event for next cycle */ + if (target >= timestamp + duration) + goto complete; + } else { + timestamp = target; + } + spa_pod_builder_control(&b, target - timestamp, SPA_CONTROL_Midi); + spa_pod_builder_bytes(&b, + SPA_POD_BODY(&c->value), + SPA_POD_BODY_SIZE(&c->value)); + } + /* we completed a sequence (one RTP packet), advance ringbuffer + * and go to the next packet */ + read += SPA_PTRDIFF(c, ptr); + spa_ringbuffer_read_update(&impl->ring, read); + } +complete: + spa_pod_builder_pop(&b, &f0); + + if (b.state.offset > maxsize) { + pw_log_warn("overflow buffer %u %u", b.state.offset, maxsize); + b.state.offset = 0; + } + d0.chunk->size = b.state.offset; + d0.chunk->stride = 1; + d0.chunk->offset = 0; +done: + pw_stream_queue_buffer(impl->stream, buf); +} + +static int parse_varlen(uint8_t *p, uint32_t avail, uint32_t *result) +{ + uint32_t value = 0, offs = 0; + while (offs < avail) { + uint8_t b = poffs++; + value = (value << 7) | (b & 0x7f); + if ((b & 0x80) == 0) + break; + } + *result = value; + return offs; +} + +static int get_midi_size(uint8_t *p, uint32_t avail) +{ + int size; + uint32_t offs = 0, value; + + switch (poffs++) { + case 0xc0 ... 0xdf: + size = 2; + break; + case 0x80 ... 0xbf: + case 0xe0 ... 0xef: + size = 3; + break; + case 0xff: + case 0xf0: + case 0xf7: + size = parse_varlen(&poffs, avail - offs, &value); + size += value + 1; + break; + default: + return -EINVAL; + } + return size; +} +static int parse_journal(struct impl *impl, uint8_t *packet, uint16_t seq, uint32_t len) +{ + struct rtp_midi_journal *j = (struct rtp_midi_journal*)packet; + uint16_t seqnum = ntohs(j->checkpoint_seqnum); + rtp_stream_emit_send_feedback(impl, seqnum); + return 0; +} + +static double get_time(struct impl *impl) +{ + struct timespec ts; + struct spa_io_position *pos; + double t; + + clock_gettime(CLOCK_MONOTONIC, &ts); + if ((pos = impl->io_position) != NULL) { + t = pos->clock.position / (double) pos->clock.rate.denom; + t += (SPA_TIMESPEC_TO_NSEC(&ts) - pos->clock.nsec) / (double)SPA_NSEC_PER_SEC; + } else { + t = SPA_TIMESPEC_TO_NSEC(&ts); + } + return t; +} + +static int rtp_midi_receive_midi(struct impl *impl, uint8_t *packet, uint32_t timestamp, + uint16_t seq, uint32_t payload_offset, uint32_t plen) +{ + uint32_t write; + struct rtp_midi_header *hdr; + int32_t filled; + struct spa_pod_builder b; + struct spa_pod_frame f1; + void *ptr; + uint32_t offs = payload_offset, len, end; + bool first = true; + + if (impl->direct_timestamp) { + /* in direct timestamp we attach the RTP timestamp directly on the + * midi events and render them in the corresponding cycle */ + if (!impl->have_sync) { + pw_log_info("sync to timestamp:%u seq:%u ts_offset:%u SSRC:%u direct:%d", + timestamp, seq, impl->ts_offset, impl->ssrc, + impl->direct_timestamp); + impl->have_sync = true; + } + } else { + /* in non-direct timestamp mode, we relate the graph clock against + * the RTP timestamps */ + double ts = timestamp / (float) impl->rate; + double t = get_time(impl); + double elapsed, estimated, diff; + + /* the elapsed time between RTP timestamps */ + elapsed = ts - impl->last_timestamp; + /* for that elapsed time, our clock should have advanced + * by this amount since the last estimation */ + estimated = impl->last_time + elapsed * impl->corr; + /* calculate the diff between estimated and current clock time in + * samples */ + diff = (estimated - t) * impl->rate; + + /* no sync or we drifted too far, resync */ + if (!impl->have_sync || fabs(diff) > impl->target_buffer) { + impl->corr = 1.0; + spa_dll_set_bw(&impl->dll, SPA_DLL_BW_MIN, 256, impl->rate); + + pw_log_info("sync to timestamp:%u seq:%u ts_offset:%u SSRC:%u direct:%d", + timestamp, seq, impl->ts_offset, impl->ssrc, + impl->direct_timestamp); + impl->have_sync = true; + impl->ring.readindex = impl->ring.writeindex; + } else { + /* update our new rate correction */ + impl->corr = spa_dll_update(&impl->dll, diff); + /* our current time is now the estimated time */ + t = estimated; + } + pw_log_debug("%f %f %f %f", t, estimated, diff, impl->corr); + + timestamp = t * impl->rate; + + impl->last_timestamp = ts; + impl->last_time = t; + } + + filled = spa_ringbuffer_get_write_index(&impl->ring, &write); + if (filled > (int32_t)BUFFER_SIZE2) { + pw_log_warn("overflow"); + return -ENOSPC; + } + + hdr = (struct rtp_midi_header *)&packetoffs++; + len = hdr->len; + if (hdr->b) { + len = (len << 8) | hdr->len_b; + offs++; + } + end = len + offs; + if (end > plen) { + pw_log_warn("invalid packet %d > %d", end, plen); + return -EINVAL; + } + if (hdr->j) + parse_journal(impl, &packetend, seq, plen - end); + + ptr = SPA_PTROFF(impl->buffer, write & BUFFER_MASK2, void); + + /* each packet is written as a sequence of events. The offset is + * the RTP timestamp */ + spa_pod_builder_init(&b, ptr, BUFFER_SIZE2 - filled); + spa_pod_builder_push_sequence(&b, &f0, 0); + + while (offs < end) { + uint32_t delta; + int size; + + if (first && !hdr->z) + delta = 0; + else + offs += parse_varlen(&packetoffs, end - offs, &delta); + + timestamp += delta * impl->corr; + spa_pod_builder_control(&b, timestamp, SPA_CONTROL_Midi); + + size = get_midi_size(&packetoffs, end - offs); + + if (size <= 0 || offs + size > end) { + pw_log_warn("invalid size (%08x) %d (%u %u)", + packetoffs, size, offs, end); + break; + } + + spa_pod_builder_bytes(&b, &packetoffs, size); + + offs += size; + first = false; + } + spa_pod_builder_pop(&b, &f0); + + write += b.state.offset; + spa_ringbuffer_write_update(&impl->ring, write); + + return 0; +} + +static int rtp_midi_receive(struct impl *impl, uint8_t *buffer, ssize_t len) +{ + struct rtp_header *hdr; + ssize_t hlen; + uint16_t seq; + uint32_t timestamp; + + if (len < 12) + goto short_packet; + + hdr = (struct rtp_header*)buffer; + if (hdr->v != 2) + goto invalid_version; + + hlen = 12 + hdr->cc * 4; + if (hlen > len) + goto invalid_len; + + if (impl->have_ssrc && impl->ssrc != hdr->ssrc) + goto unexpected_ssrc; + impl->ssrc = hdr->ssrc; + impl->have_ssrc = true; + + seq = ntohs(hdr->sequence_number); + if (impl->have_seq && impl->seq != seq) { + pw_log_info("unexpected seq (%d != %d) SSRC:%u", + seq, impl->seq, hdr->ssrc); + impl->have_sync = false; + } + impl->seq = seq + 1; + impl->have_seq = true; + + timestamp = ntohl(hdr->timestamp) - impl->ts_offset; + + impl->receiving = true; + + return rtp_midi_receive_midi(impl, buffer, timestamp, seq, hlen, len); + +short_packet: + pw_log_warn("short packet received"); + return -EINVAL; +invalid_version: + pw_log_warn("invalid RTP version"); + spa_debug_mem(0, buffer, len); + return -EPROTO; +invalid_len: + pw_log_warn("invalid RTP length"); + return -EINVAL; +unexpected_ssrc: + pw_log_warn("unexpected SSRC (expected %u != %u)", + impl->ssrc, hdr->ssrc); + return -EINVAL; +} + +static int write_event(uint8_t *p, uint32_t value, void *ev, uint32_t size) +{ + uint64_t buffer; + uint8_t b; + int count = 0; + + buffer = value & 0x7f; + while ((value >>= 7)) { + buffer <<= 8; + buffer |= ((value & 0x7f) | 0x80); + } + do { + b = buffer & 0xff; + pcount++ = b; + buffer >>= 8; + } while (b & 0x80); + + memcpy(&pcount, ev, size); + return count + size; +} + +static void rtp_midi_flush_packets(struct impl *impl, + struct spa_pod_sequence *sequence, uint32_t timestamp, uint32_t rate) +{ + struct spa_pod_control *c; + struct rtp_header header; + struct rtp_midi_header midi_header; + struct iovec iov3; + uint32_t len, prev_offset, base; + + spa_zero(header); + header.v = 2; + header.pt = impl->payload; + header.ssrc = htonl(impl->ssrc); + + spa_zero(midi_header); + + iov0.iov_base = &header; + iov0.iov_len = sizeof(header); + iov1.iov_base = &midi_header; + iov1.iov_len = sizeof(midi_header); + iov2.iov_base = impl->buffer; + iov2.iov_len = 0; + + prev_offset = len = base = 0; + + SPA_POD_SEQUENCE_FOREACH(sequence, c) { + void *ev; + uint32_t size, delta, offset; + + if (c->type != SPA_CONTROL_Midi) + continue; + + ev = SPA_POD_BODY(&c->value), + size = SPA_POD_BODY_SIZE(&c->value); + + offset = c->offset * impl->rate / rate; + + if (len > 0 && (len + size > impl->mtu || + offset - base > impl->psamples)) { + /* flush packet when we have one and when it's either + * too large or has too much data. */ + if (len < 16) { + midi_header.b = 0; + midi_header.len = len; + iov1.iov_len = sizeof(midi_header) - 1; + } else { + midi_header.b = 1; + midi_header.len = (len >> 8) & 0xf; + midi_header.len_b = len & 0xff; + iov1.iov_len = sizeof(midi_header); + } + iov2.iov_len = len; + + pw_log_debug("sending %d timestamp:%d %u %u", + len, timestamp + base, + offset, impl->psamples); + rtp_stream_emit_send_packet(impl, iov, 3); + + impl->seq++; + len = 0; + } + if (len == 0) { + /* start new packet */ + base = prev_offset = offset; + header.sequence_number = htons(impl->seq); + header.timestamp = htonl(impl->ts_offset + timestamp + base); + + memcpy(&impl->bufferlen, ev, size); + len += size; + } else { + delta = offset - prev_offset; + prev_offset = offset; + len += write_event(&impl->bufferlen, delta, ev, size); + } + } + if (len > 0) { + /* flush last packet */ + if (len < 16) { + midi_header.b = 0; + midi_header.len = len; + iov1.iov_len = sizeof(midi_header) - 1; + } else { + midi_header.b = 1; + midi_header.len = (len >> 8) & 0xf; + midi_header.len_b = len & 0xff; + iov1.iov_len = sizeof(midi_header); + } + iov2.iov_len = len; + + pw_log_debug("sending %d timestamp:%d", len, base); + rtp_stream_emit_send_packet(impl, iov, 3); + impl->seq++; + } +} + +static void rtp_midi_process_capture(void *data) +{ + struct impl *impl = data; + struct pw_buffer *buf; + struct spa_data *d; + uint32_t offs, size, timestamp, rate; + struct spa_pod *pod; + void *ptr; + + if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) { + pw_log_debug("Out of stream buffers: %m"); + return; + } + d = buf->buffer->datas; + + offs = SPA_MIN(d0.chunk->offset, d0.maxsize); + size = SPA_MIN(d0.chunk->size, d0.maxsize - offs); + + if (SPA_LIKELY(impl->io_position)) { + rate = impl->io_position->clock.rate.denom; + timestamp = impl->io_position->clock.position * impl->rate / rate; + } else { + rate = 10000; + timestamp = 0; + } + + ptr = SPA_PTROFF(d0.data, offs, void); + + if ((pod = spa_pod_from_data(ptr, size, 0, size)) == NULL) + goto done; + if (!spa_pod_is_sequence(pod)) + goto done; + + if (!impl->have_sync) { + pw_log_info("sync to timestamp:%u seq:%u ts_offset:%u SSRC:%u", + timestamp, impl->seq, impl->ts_offset, impl->ssrc); + impl->have_sync = true; + } + + rtp_midi_flush_packets(impl, (struct spa_pod_sequence*)pod, timestamp, rate); + +done: + pw_stream_queue_buffer(impl->stream, buf); +} + +static int rtp_midi_init(struct impl *impl, enum spa_direction direction) +{ + if (direction == SPA_DIRECTION_INPUT) + impl->stream_events.process = rtp_midi_process_capture; + else + impl->stream_events.process = rtp_midi_process_playback; + impl->receive_rtp = rtp_midi_receive; + return 0; +}
View file
pipewire-0.3.68.tar.gz/src/modules/module-rtp/opus.c
Added
@@ -0,0 +1,373 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans <wim.taymans@gmail.com> */ +/* SPDX-License-Identifier: MIT */ + +#ifdef HAVE_OPUS + +#include <opus/opus.h> +#include <opus/opus_multistream.h> + +static void rtp_opus_process_playback(void *data) +{ + struct impl *impl = data; + struct pw_buffer *buf; + struct spa_data *d; + uint32_t wanted, timestamp, target_buffer, stride, maxsize; + int32_t avail; + + if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) { + pw_log_debug("Out of stream buffers: %m"); + return; + } + d = buf->buffer->datas; + + stride = impl->stride; + + maxsize = d0.maxsize / stride; + wanted = buf->requested ? SPA_MIN(buf->requested, maxsize) : maxsize; + + if (impl->io_position && impl->direct_timestamp) { + /* in direct mode, read directly from the timestamp index, + * because sender and receiver are in sync, this would keep + * target_buffer of samples available. */ + spa_ringbuffer_read_update(&impl->ring, + impl->io_position->clock.position); + } + avail = spa_ringbuffer_get_read_index(&impl->ring, ×tamp); + + target_buffer = impl->target_buffer; + + if (avail < (int32_t)wanted) { + enum spa_log_level level; + memset(d0.data, 0, wanted * stride); + if (impl->have_sync) { + impl->have_sync = false; + level = SPA_LOG_LEVEL_WARN; + } else { + level = SPA_LOG_LEVEL_DEBUG; + } + pw_log(level, "underrun %d/%u < %u", + avail, target_buffer, wanted); + } else { + float error, corr; + if (impl->first) { + if ((uint32_t)avail > target_buffer) { + uint32_t skip = avail - target_buffer; + pw_log_debug("first: avail:%d skip:%u target:%u", + avail, skip, target_buffer); + timestamp += skip; + avail = target_buffer; + } + impl->first = false; + } else if (avail > (int32_t)SPA_MIN(target_buffer * 8, BUFFER_SIZE2 / stride)) { + pw_log_warn("overrun %u > %u", avail, target_buffer * 8); + timestamp += avail - target_buffer; + avail = target_buffer; + } + if (!impl->direct_timestamp) { + /* when not using direct timestamp and clocks are not + * in sync, try to adjust our playback rate to keep the + * requested target_buffer bytes in the ringbuffer */ + error = (float)target_buffer - (float)avail; + error = SPA_CLAMP(error, -impl->max_error, impl->max_error); + + corr = spa_dll_update(&impl->dll, error); + + pw_log_debug("avail:%u target:%u error:%f corr:%f", avail, + target_buffer, error, corr); + + if (impl->io_rate_match) { + SPA_FLAG_SET(impl->io_rate_match->flags, + SPA_IO_RATE_MATCH_FLAG_ACTIVE); + impl->io_rate_match->rate = 1.0f / corr; + } + } + spa_ringbuffer_read_data(&impl->ring, + impl->buffer, + BUFFER_SIZE2, + (timestamp * stride) & BUFFER_MASK2, + d0.data, wanted * stride); + + timestamp += wanted; + spa_ringbuffer_read_update(&impl->ring, timestamp); + } + d0.chunk->size = wanted * stride; + d0.chunk->stride = stride; + d0.chunk->offset = 0; + buf->size = wanted; + + pw_stream_queue_buffer(impl->stream, buf); +} + +static int rtp_opus_receive(struct impl *impl, uint8_t *buffer, ssize_t len) +{ + struct rtp_header *hdr; + ssize_t hlen, plen; + uint16_t seq; + uint32_t timestamp, samples, write, expected_write; + uint32_t stride = impl->stride; + OpusMSDecoder *dec = impl->stream_data; + int32_t filled; + int res; + + if (len < 12) + goto short_packet; + + hdr = (struct rtp_header*)buffer; + if (hdr->v != 2) + goto invalid_version; + + hlen = 12 + hdr->cc * 4; + if (hlen > len) + goto invalid_len; + + if (impl->have_ssrc && impl->ssrc != hdr->ssrc) + goto unexpected_ssrc; + impl->ssrc = hdr->ssrc; + impl->have_ssrc = true; + + seq = ntohs(hdr->sequence_number); + if (impl->have_seq && impl->seq != seq) { + pw_log_info("unexpected seq (%d != %d) SSRC:%u", + seq, impl->seq, hdr->ssrc); + impl->have_sync = false; + } + impl->seq = seq + 1; + impl->have_seq = true; + + timestamp = ntohl(hdr->timestamp) - impl->ts_offset; + + impl->receiving = true; + + plen = len - hlen; + + filled = spa_ringbuffer_get_write_index(&impl->ring, &expected_write); + + /* we always write to timestamp + delay */ + write = timestamp + impl->target_buffer; + + if (!impl->have_sync) { + pw_log_info("sync to timestamp:%u seq:%u ts_offset:%u SSRC:%u target:%u direct:%u", + timestamp, seq, impl->ts_offset, impl->ssrc, + impl->target_buffer, impl->direct_timestamp); + + /* we read from timestamp, keeping target_buffer of data + * in the ringbuffer. */ + impl->ring.readindex = timestamp; + impl->ring.writeindex = write; + filled = impl->target_buffer; + + spa_dll_init(&impl->dll); + spa_dll_set_bw(&impl->dll, SPA_DLL_BW_MIN, 128, impl->rate); + memset(impl->buffer, 0, BUFFER_SIZE); + impl->have_sync = true; + } else if (expected_write != write) { + pw_log_debug("unexpected write (%u != %u)", + write, expected_write); + } + + if (filled + plen > BUFFER_SIZE2 / stride) { + pw_log_debug("capture overrun %u + %zd > %u", filled, plen, + BUFFER_SIZE2 / stride); + impl->have_sync = false; + } else { + uint32_t index = (write * stride) & BUFFER_MASK2, end; + + res = opus_multistream_decode_float(dec, + &bufferhlen, plen, + (float*)&impl->bufferindex, 2880, + 0); + + end = index + (res * stride); + /* fold to the lower part of the ringbuffer when overflow */ + if (end > BUFFER_SIZE2) + memmove(impl->buffer, &impl->bufferBUFFER_SIZE2, end - BUFFER_SIZE2); + + pw_log_debug("receiving %zd len:%d timestamp:%d %u", plen, res, timestamp, index); + samples = res; + + write += samples; + spa_ringbuffer_write_update(&impl->ring, write); + } + return 0; + +short_packet: + pw_log_warn("short packet received"); + return -EINVAL; +invalid_version: + pw_log_warn("invalid RTP version"); + spa_debug_mem(0, buffer, len); + return -EPROTO; +invalid_len: + pw_log_warn("invalid RTP length"); + return -EINVAL; +unexpected_ssrc: + pw_log_warn("unexpected SSRC (expected %u != %u)", + impl->ssrc, hdr->ssrc); + return -EINVAL; +} + +static void rtp_opus_flush_packets(struct impl *impl) +{ + int32_t avail, tosend; + uint32_t stride, timestamp, offset; + uint8_t out1280; + struct iovec iov2; + struct rtp_header header; + OpusMSEncoder *enc = impl->stream_data; + int res = 0; + + avail = spa_ringbuffer_get_read_index(&impl->ring, ×tamp); + tosend = impl->psamples; + + if (avail < tosend) + return; + + stride = impl->stride; + + spa_zero(header); + header.v = 2; + header.pt = impl->payload; + header.ssrc = htonl(impl->ssrc); + + iov0.iov_base = &header; + iov0.iov_len = sizeof(header); + iov1.iov_base = out; + iov1.iov_len = 0; + + offset = 0; + while (avail >= tosend) { + header.sequence_number = htons(impl->seq); + header.timestamp = htonl(impl->ts_offset + timestamp); + + res = opus_multistream_encode_float(enc, + (const float*)&impl->bufferoffset * stride, tosend, + out, sizeof(out)); + + pw_log_debug("sending %d len:%d timestamp:%d", tosend, res, timestamp); + iov1.iov_len = res; + + rtp_stream_emit_send_packet(impl, iov, 2); + + impl->seq++; + timestamp += tosend; + offset += tosend; + avail -= tosend; + } + + pw_log_debug("move %d offset:%d", avail, offset); + memmove(impl->buffer, &impl->bufferoffset * stride, avail * stride); + + spa_ringbuffer_read_update(&impl->ring, timestamp); +} + +static void rtp_opus_process_capture(void *data) +{ + struct impl *impl = data; + struct pw_buffer *buf; + struct spa_data *d; + uint32_t offs, size, timestamp, expected_timestamp, stride; + int32_t filled, wanted; + + if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) { + pw_log_debug("Out of stream buffers: %m"); + return; + } + d = buf->buffer->datas; + + offs = SPA_MIN(d0.chunk->offset, d0.maxsize); + size = SPA_MIN(d0.chunk->size, d0.maxsize - offs); + stride = impl->stride; + wanted = size / stride; + + filled = spa_ringbuffer_get_write_index(&impl->ring, &expected_timestamp); + + if (SPA_LIKELY(impl->io_position)) { + uint32_t rate = impl->io_position->clock.rate.denom; + timestamp = impl->io_position->clock.position * impl->rate / rate; + } else + timestamp = expected_timestamp; + + if (!impl->have_sync) { + pw_log_info("sync to timestamp:%u seq:%u ts_offset:%u SSRC:%u", + timestamp, impl->seq, impl->ts_offset, impl->ssrc); + impl->ring.readindex = impl->ring.writeindex = expected_timestamp = timestamp; + memset(impl->buffer, 0, BUFFER_SIZE); + impl->have_sync = true; + } else { + if (SPA_ABS((int32_t)expected_timestamp - (int32_t)timestamp) > 32) { + pw_log_warn("expected %u != timestamp %u", expected_timestamp, timestamp); + impl->have_sync = false; + } else if (filled + wanted > (int32_t)(BUFFER_SIZE / stride)) { + pw_log_warn("overrun %u + %u > %u", filled, wanted, BUFFER_SIZE / stride); + impl->have_sync = false; + } + } + + spa_ringbuffer_write_data(&impl->ring, + impl->buffer, + BUFFER_SIZE, + (filled * stride) & BUFFER_MASK, + SPA_PTROFF(d0.data, offs, void), wanted * stride); + expected_timestamp += wanted; + spa_ringbuffer_write_update(&impl->ring, expected_timestamp); + + pw_stream_queue_buffer(impl->stream, buf); + + rtp_opus_flush_packets(impl); +} + +static int rtp_opus_init(struct impl *impl, enum spa_direction direction) +{ + int err; + unsigned char mapping64; + uint32_t i; + + if (impl->psamples >= 2880) + impl->psamples = 2880; + else if (impl->psamples >= 1920) + impl->psamples = 1920; + else if (impl->psamples >= 960) + impl->psamples = 960; + else if (impl->psamples >= 480) + impl->psamples = 480; + else if (impl->psamples >= 240) + impl->psamples = 240; + else + impl->psamples = 120; + + for (i = 0; i < impl->info.info.opus.channels; i++) + mappingi = i; + + impl->receive_rtp = rtp_opus_receive; + if (direction == SPA_DIRECTION_INPUT) { + impl->stream_events.process = rtp_opus_process_capture; + + impl->stream_data = opus_multistream_encoder_create( + impl->info.info.opus.rate, + impl->info.info.opus.channels, + impl->info.info.opus.channels, 0, + mapping, + OPUS_APPLICATION_AUDIO, + &err); + } + else { + impl->stream_events.process = rtp_opus_process_playback; + + impl->stream_data = opus_multistream_decoder_create( + impl->info.info.opus.rate, + impl->info.info.opus.channels, + impl->info.info.opus.channels, 0, + mapping, + &err); + } + if (!impl->stream_data) + pw_log_error("opus error: %d", err); + return impl->stream_data ? 0 : err; +} +#else +static int rtp_opus_init(struct impl *impl, enum spa_direction direction) +{ + return -ENOTSUP; +} +#endif
View file
pipewire-0.3.67.tar.gz/src/modules/module-rtp/rtp.h -> pipewire-0.3.68.tar.gz/src/modules/module-rtp/rtp.h
Changed
@@ -58,17 +58,34 @@ unsigned z:1; unsigned j:1; unsigned b:1; - uint8_t len_b; #elif __BYTE_ORDER == __BIG_ENDIAN unsigned b:1; unsigned j:1; unsigned z:1; unsigned p:1; unsigned len:4; +#endif uint8_t len_b; +} __attribute__ ((packed)); + +struct rtp_midi_journal { +#if __BYTE_ORDER == __LITTLE_ENDIAN + unsigned totchan:4; + unsigned H:1; + unsigned A:1; + unsigned Y:1; + unsigned S:1; +#elif __BYTE_ORDER == __BIG_ENDIAN + unsigned S:1; + unsigned Y:1; + unsigned A:1; + unsigned H:1; + unsigned totchan:4; #endif + uint16_t checkpoint_seqnum; } __attribute__ ((packed)); + #ifdef __cplusplus } #endif
View file
pipewire-0.3.68.tar.gz/src/modules/module-rtp/stream.c
Added
@@ -0,0 +1,521 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans <wim.taymans@gmail.com> */ +/* SPDX-License-Identifier: MIT */ + +#include <sys/socket.h> +#include <arpa/inet.h> + +#include <spa/utils/result.h> +#include <spa/utils/json.h> +#include <spa/utils/ringbuffer.h> +#include <spa/utils/dll.h> +#include <spa/param/audio/format-utils.h> +#include <spa/control/control.h> +#include <spa/debug/types.h> +#include <spa/debug/mem.h> + +#include "config.h" + +#include <pipewire/pipewire.h> +#include <pipewire/impl.h> + +#include <module-rtp/rtp.h> +#include <module-rtp/stream.h> +#include <module-rtp/apple-midi.h> + +#define BUFFER_SIZE (1u<<22) +#define BUFFER_MASK (BUFFER_SIZE-1) +#define BUFFER_SIZE2 (BUFFER_SIZE>>1) +#define BUFFER_MASK2 (BUFFER_SIZE2-1) + +#define rtp_stream_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, \ + struct rtp_stream_events, m, v, ##__VA_ARGS__) +#define rtp_stream_emit_destroy(s) rtp_stream_emit(s, destroy, 0) +#define rtp_stream_emit_state_changed(s,n,e) rtp_stream_emit(s, state_changed,0,n,e) +#define rtp_stream_emit_send_packet(s,i,l) rtp_stream_emit(s, send_packet,0,i,l) +#define rtp_stream_emit_send_feedback(s,seq) rtp_stream_emit(s, send_feedback,0,seq) + +struct impl { + struct spa_audio_info info; + struct spa_audio_info stream_info; + + struct pw_stream *stream; + struct spa_hook stream_listener; + struct pw_stream_events stream_events; + + struct spa_hook_list listener_list; + struct spa_hook listener; + + const struct format_info *format_info; + + void *stream_data; + + uint32_t rate; + uint32_t stride; + uint8_t payload; + uint32_t ssrc; + uint16_t seq; + unsigned have_ssrc:1; + unsigned have_seq:1; + uint32_t ts_offset; + uint32_t psamples; + uint32_t mtu; + + struct spa_ringbuffer ring; + uint8_t bufferBUFFER_SIZE; + + struct spa_io_rate_match *io_rate_match; + struct spa_io_position *io_position; + struct spa_dll dll; + double corr; + uint32_t target_buffer; + float max_error; + + float last_timestamp; + float last_time; + + unsigned direct_timestamp:1; + unsigned always_process:1; + unsigned started:1; + unsigned have_sync:1; + unsigned receiving:1; + unsigned first:1; + + int (*receive_rtp)(struct impl *impl, uint8_t *buffer, ssize_t len); +}; + +#include "module-rtp/audio.c" +#include "module-rtp/midi.c" +#include "module-rtp/opus.c" + +struct format_info { + uint32_t media_subtype; + uint32_t format; + uint32_t size; + const char *mime; + const char *media_type; +}; + +static const struct format_info audio_format_info = { + { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_U8, 1, "L8", "audio" }, + { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_ALAW, 1, "PCMA", "audio" }, + { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_ULAW, 1, "PCMU", "audio" }, + { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_S16_BE, 2, "L16", "audio" }, + { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_S24_BE, 3, "L24", "audio" }, + { SPA_MEDIA_SUBTYPE_control, 0, 1, "rtp-midi", "audio" }, + { SPA_MEDIA_SUBTYPE_opus, 0, 4, "opus", "audio" }, +}; + +static void stream_io_changed(void *data, uint32_t id, void *area, uint32_t size) +{ + struct impl *impl = data; + switch (id) { + case SPA_IO_RateMatch: + impl->io_rate_match = area; + break; + case SPA_IO_Position: + impl->io_position = area; + break; + } +} + +static void stream_destroy(void *d) +{ + struct impl *impl = d; + spa_hook_remove(&impl->stream_listener); + impl->stream = NULL; +} + +static int stream_start(struct impl *impl) +{ + if (impl->started) + return 0; + + rtp_stream_emit_state_changed(impl, true, NULL); + + impl->started = true; + return 0; +} + +static int stream_stop(struct impl *impl) +{ + if (!impl->started) + return 0; + + rtp_stream_emit_state_changed(impl, false, NULL); + + impl->started = false; + return 0; +} + +static void on_stream_state_changed(void *d, enum pw_stream_state old, + enum pw_stream_state state, const char *error) +{ + struct impl *impl = d; + + switch (state) { + case PW_STREAM_STATE_UNCONNECTED: + pw_log_info("stream disconnected"); + break; + case PW_STREAM_STATE_ERROR: + pw_log_error("stream error: %s", error); + rtp_stream_emit_state_changed(impl, false, error); + break; + case PW_STREAM_STATE_STREAMING: + if ((errno = -stream_start(impl)) < 0) + pw_log_error("failed to start RTP stream: %m"); + break; + case PW_STREAM_STATE_PAUSED: + if (!impl->always_process) + stream_stop(impl); + impl->have_sync = false; + break; + default: + break; + } +} + +static const struct pw_stream_events stream_events = { + PW_VERSION_STREAM_EVENTS, + .destroy = stream_destroy, + .state_changed = on_stream_state_changed, + .io_changed = stream_io_changed, +}; + +static const struct format_info *find_audio_format_info(const struct spa_audio_info *info) +{ + SPA_FOR_EACH_ELEMENT_VAR(audio_format_info, f) + if (f->media_subtype == info->media_subtype && + (f->format == 0 || f->format == info->info.raw.format)) + return f; + return NULL; +} + +static inline uint32_t 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 uint32_t channel_from_name(const char *name) +{ + int i; + for (i = 0; spa_type_audio_channeli.name; i++) { + if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_channeli.name))) + return spa_type_audio_channeli.type; + } + return SPA_AUDIO_CHANNEL_UNKNOWN; +} + +static void parse_position(struct spa_audio_info_raw *info, const char *val, size_t len) +{ + struct spa_json it2; + char v256; + + spa_json_init(&it0, val, len); + if (spa_json_enter_array(&it0, &it1) <= 0) + spa_json_init(&it1, val, len); + + info->channels = 0; + while (spa_json_get_string(&it1, v, sizeof(v)) > 0 && + info->channels < SPA_AUDIO_MAX_CHANNELS) { + info->positioninfo->channels++ = channel_from_name(v); + } +} + +static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +{ + const char *str; + + spa_zero(*info); + if ((str = pw_properties_get(props, PW_KEY_AUDIO_FORMAT)) == NULL) + str = DEFAULT_FORMAT; + info->format = format_from_name(str, strlen(str)); + + info->rate = pw_properties_get_uint32(props, PW_KEY_AUDIO_RATE, info->rate); + if (info->rate == 0) + info->rate = DEFAULT_RATE; + + info->channels = pw_properties_get_uint32(props, PW_KEY_AUDIO_CHANNELS, info->channels); + info->channels = SPA_MIN(info->channels, SPA_AUDIO_MAX_CHANNELS); + if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) != NULL) + parse_position(info, str, strlen(str)); + if (info->channels == 0) + parse_position(info, DEFAULT_POSITION, strlen(DEFAULT_POSITION)); +} + +static uint32_t msec_to_samples(struct impl *impl, uint32_t msec) +{ + return msec * impl->rate / 1000; +} + +struct rtp_stream *rtp_stream_new(struct pw_core *core, + enum pw_direction direction, struct pw_properties *props, + const struct rtp_stream_events *events, void *data) +{ + struct impl *impl; + const char *str; + uint8_t buffer1024; + struct spa_pod_builder b; + uint32_t n_params, min_samples, max_samples; + float min_ptime, max_ptime; + const struct spa_pod *params1; + enum pw_stream_flags flags; + int latency_msec; + int res; + + impl = calloc(1, sizeof(*impl)); + if (impl == NULL) { + res = -errno; + goto out; + return NULL; + } + impl->first = true; + spa_hook_list_init(&impl->listener_list); + impl->stream_events = stream_events; + + if ((str = pw_properties_get(props, "sess.media")) == NULL) + str = "audio"; + + if (spa_streq(str, "audio")) { + impl->info.media_type = SPA_MEDIA_TYPE_audio; + impl->info.media_subtype = SPA_MEDIA_SUBTYPE_raw; + impl->payload = 127; + } + else if (spa_streq(str, "midi")) { + impl->info.media_type = SPA_MEDIA_TYPE_application; + impl->info.media_subtype = SPA_MEDIA_SUBTYPE_control; + impl->payload = 0x61; + } +#ifdef HAVE_OPUS + else if (spa_streq(str, "opus")) { + impl->info.media_type = SPA_MEDIA_TYPE_audio; + impl->info.media_subtype = SPA_MEDIA_SUBTYPE_opus; + impl->payload = 127; + } +#endif + else { + pw_log_error("unsupported media type:%s", str); + res = -EINVAL; + goto out; + } + + switch (impl->info.media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + parse_audio_info(props, &impl->info.info.raw); + impl->stream_info = impl->info; + impl->format_info = find_audio_format_info(&impl->info); + if (impl->format_info == NULL) { + pw_log_error("unsupported audio format:%d channels:%d", + impl->stream_info.info.raw.format, + impl->stream_info.info.raw.channels); + res = -EINVAL; + goto out; + } + impl->stride = impl->format_info->size * impl->stream_info.info.raw.channels; + impl->rate = impl->stream_info.info.raw.rate; + break; + case SPA_MEDIA_SUBTYPE_control: + impl->stream_info = impl->info; + impl->format_info = find_audio_format_info(&impl->info); + if (impl->format_info == NULL) { + res = -EINVAL; + goto out; + } + pw_properties_set(props, PW_KEY_FORMAT_DSP, "8 bit raw midi"); + impl->stride = impl->format_info->size; + impl->rate = pw_properties_get_uint32(props, "midi.rate", 10000); + if (impl->rate == 0) + impl->rate = 10000; + break; + case SPA_MEDIA_SUBTYPE_opus: + impl->stream_info.media_type = SPA_MEDIA_TYPE_audio; + impl->stream_info.media_subtype = SPA_MEDIA_SUBTYPE_raw; + parse_audio_info(props, &impl->stream_info.info.raw); + impl->stream_info.info.raw.format = SPA_AUDIO_FORMAT_F32; + impl->info.info.opus.rate = impl->stream_info.info.raw.rate; + impl->info.info.opus.channels = impl->stream_info.info.raw.channels; + + impl->format_info = find_audio_format_info(&impl->info); + if (impl->format_info == NULL) { + pw_log_error("unsupported audio format:%d channels:%d", + impl->stream_info.info.raw.format, + impl->stream_info.info.raw.channels); + res = -EINVAL; + goto out; + } + impl->stride = impl->format_info->size * impl->stream_info.info.raw.channels; + impl->rate = impl->stream_info.info.raw.rate; + break; + default: + spa_assert_not_reached(); + break; + } + + pw_properties_setf(props, "rtp.mime", "%s", impl->format_info->mime); + + if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) + pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); + if (pw_properties_get(props, PW_KEY_NODE_NETWORK) == NULL) + pw_properties_set(props, PW_KEY_NODE_NETWORK, "true"); + + impl->direct_timestamp = pw_properties_get_bool(props, "sess.ts-direct", false); + + if (direction == PW_DIRECTION_INPUT) { + impl->ssrc = pw_properties_get_uint32(props, "rtp.sender-ssrc", pw_rand32()); + impl->ts_offset = pw_properties_get_uint32(props, "rtp.sender-ts-offset", pw_rand32()); + } else { + impl->have_ssrc = pw_properties_fetch_uint32(props, "rtp.receiver-ssrc", &impl->ssrc); + if (pw_properties_fetch_uint32(props, "rtp.receiver-ts-offset", &impl->ts_offset) < 0) + impl->direct_timestamp = false; + } + + impl->payload = pw_properties_get_uint32(props, "rtp.payload", impl->payload); + impl->mtu = pw_properties_get_uint32(props, "net.mtu", DEFAULT_MTU); + + impl->seq = pw_rand32(); + + str = pw_properties_get(props, "sess.min-ptime"); + if (!spa_atof(str, &min_ptime)) + min_ptime = DEFAULT_MIN_PTIME; + str = pw_properties_get(props, "sess.max-ptime"); + if (!spa_atof(str, &max_ptime)) + max_ptime = DEFAULT_MAX_PTIME; + + min_samples = min_ptime * impl->rate / 1000; + max_samples = max_ptime * impl->rate / 1000; + + impl->psamples = impl->mtu / impl->stride; + impl->psamples = SPA_CLAMP(impl->psamples, min_samples, max_samples); + + latency_msec = pw_properties_get_uint32(props, + "sess.latency.msec", DEFAULT_SESS_LATENCY); + impl->target_buffer = msec_to_samples(impl, latency_msec); + impl->max_error = msec_to_samples(impl, ERROR_MSEC); + + pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%d", impl->rate); + if (direction == PW_DIRECTION_INPUT) { + pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%d/%d", + impl->psamples, impl->rate); + } else { + pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%d/%d", + impl->target_buffer / 2, impl->rate); + } + + pw_properties_setf(props, "net.mtu", "%u", impl->mtu); + pw_properties_setf(props, "rtp.ptime", "%u", + impl->psamples * 1000 / impl->rate); + pw_properties_setf(props, "rtp.media", "%s", impl->format_info->media_type); + pw_properties_setf(props, "rtp.mime", "%s", impl->format_info->mime); + pw_properties_setf(props, "rtp.payload", "%u", impl->payload); + pw_properties_setf(props, "rtp.rate", "%u", impl->rate); + if (impl->info.info.raw.channels > 0) + pw_properties_setf(props, "rtp.channels", "%u", impl->info.info.raw.channels); + if ((str = pw_properties_get(props, "sess.ts-refclk")) != NULL) { + pw_properties_setf(props, "rtp.ts-offset", "%u", impl->ts_offset); + pw_properties_set(props, "rtp.ts-refclk", str); + } + + spa_dll_init(&impl->dll); + spa_dll_set_bw(&impl->dll, SPA_DLL_BW_MIN, 128, impl->rate); + impl->corr = 1.0; + + impl->stream = pw_stream_new(core, "rtp-session", props); + props = NULL; + if (impl->stream == NULL) { + res = -errno; + pw_log_error("can't create stream: %m"); + goto out; + } + + n_params = 0; + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + flags = PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS; + + switch (impl->info.media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + paramsn_params++ = spa_format_audio_build(&b, + SPA_PARAM_EnumFormat, &impl->stream_info); + flags |= PW_STREAM_FLAG_AUTOCONNECT; + rtp_audio_init(impl, direction); + break; + case SPA_MEDIA_SUBTYPE_control: + paramsn_params++ = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); + rtp_midi_init(impl, direction); + break; + case SPA_MEDIA_SUBTYPE_opus: + paramsn_params++ = spa_format_audio_build(&b, + SPA_PARAM_EnumFormat, &impl->stream_info); + flags |= PW_STREAM_FLAG_AUTOCONNECT; + rtp_opus_init(impl, direction); + break; + default: + res = -EINVAL; + goto out; + } + + pw_stream_add_listener(impl->stream, + &impl->stream_listener, + &impl->stream_events, impl); + + if ((res = pw_stream_connect(impl->stream, + direction, + PW_ID_ANY, + flags, + params, n_params)) < 0) { + pw_log_error("can't connect stream: %s", spa_strerror(res)); + goto out; + } + + if (impl->always_process && + (res = stream_start(impl)) < 0) + goto out; + + spa_hook_list_append(&impl->listener_list, &impl->listener, events, data); + + return (struct rtp_stream*)impl; +out: + pw_properties_free(props); + errno = -res; + return NULL; +} + +void rtp_stream_destroy(struct rtp_stream *s) +{ + struct impl *impl = (struct impl*)s; + + rtp_stream_emit_destroy(impl); + + if (impl->stream) + pw_stream_destroy(impl->stream); + + spa_hook_list_clean(&impl->listener_list); + free(impl); +} + +int rtp_stream_receive_packet(struct rtp_stream *s, uint8_t *buffer, size_t len) +{ + struct impl *impl = (struct impl*)s; + return impl->receive_rtp(impl, buffer, len); +} + +uint64_t rtp_stream_get_time(struct rtp_stream *s, uint64_t *rate) +{ + struct impl *impl = (struct impl*)s; + struct spa_io_position *pos = impl->io_position; + + if (pos == NULL) + return -EIO; + + *rate = impl->rate; + return pos->clock.position * impl->rate * + pos->clock.rate.num / pos->clock.rate.denom; +}
View file
pipewire-0.3.68.tar.gz/src/modules/module-rtp/stream.h
Added
@@ -0,0 +1,54 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans <wim.taymans@gmail.com> */ +/* SPDX-License-Identifier: MIT */ + +#ifndef PIPEWIRE_RTP_STREAM_H +#define PIPEWIRE_RTP_STREAM_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct rtp_stream; + +#define DEFAULT_FORMAT "S16BE" +#define DEFAULT_RATE 48000 +#define DEFAULT_CHANNELS 2 +#define DEFAULT_POSITION " FL FR " + +#define ERROR_MSEC 2 +#define DEFAULT_SESS_LATENCY 100 + +#define DEFAULT_MTU 1280 +#define DEFAULT_MIN_PTIME 2 +#define DEFAULT_MAX_PTIME 20 + +struct rtp_stream_events { +#define RTP_VERSION_STREAM_EVENTS 0 + uint32_t version; + + void (*destroy) (void *data); + + void (*state_changed) (void *data, bool started, const char *error); + + void (*send_packet) (void *data, struct iovec *iov, size_t iovlen); + + void (*send_feedback) (void *data, uint32_t senum); +}; + +struct rtp_stream *rtp_stream_new(struct pw_core *core, + enum pw_direction direction, struct pw_properties *props, + const struct rtp_stream_events *events, void *data); + +void rtp_stream_destroy(struct rtp_stream *s); + +int rtp_stream_receive_packet(struct rtp_stream *s, uint8_t *buffer, size_t len); + +uint64_t rtp_stream_get_time(struct rtp_stream *s, uint64_t *rate); + + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_RTP_STREAM_H */
View file
pipewire-0.3.67.tar.gz/src/modules/module-x11-bell.c -> pipewire-0.3.68.tar.gz/src/modules/module-x11-bell.c
Changed
@@ -230,10 +230,10 @@ static const struct spa_dict_item module_x11_bell_info = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "X11 Bell interceptor" }, - { PW_KEY_MODULE_USAGE, "sink.name=<name for the sink> " - "sample.name=<the sample name> " - "x11.display=<the X11 display> " - "x11.xauthority=<the X11 XAuthority> " }, + { PW_KEY_MODULE_USAGE, "( sink.name=<name for the sink> ) " + "( sample.name=<the sample name> ) " + "( x11.display=<the X11 display> ) " + ".x11.xauthority=<the X11 XAuthority> )" }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; SPA_EXPORT
View file
pipewire-0.3.67.tar.gz/src/modules/module-zeroconf-discover.c -> pipewire-0.3.68.tar.gz/src/modules/module-zeroconf-discover.c
Changed
@@ -55,7 +55,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic -#define MODULE_USAGE "pulse.latency=<latency in msec> " +#define MODULE_USAGE "( pulse.latency=<latency in msec, default 200> ) " static const struct spa_dict_item module_props = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
View file
pipewire-0.3.67.tar.gz/src/pipewire/buffers.c -> pipewire-0.3.68.tar.gz/src/pipewire/buffers.c
Changed
@@ -292,6 +292,9 @@ align = SPA_MAX(align, qalign); types = qtypes; + if (SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_ASYNC)) + max_buffers = SPA_MAX(2u, max_buffers); + pw_log_debug("%p: %d %d %d %d %d %d -> %d %zd %zd %d %zd %d", result, qblocks, qminsize, qstride, qmax_buffers, qalign, qtypes, blocks, minsize, stride, max_buffers, align, types); @@ -300,6 +303,7 @@ minsize = 8192; max_buffers = 2; } + if (SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_SHARED_MEM)) { if (types != SPA_ID_INVALID) SPA_FLAG_CLEAR(types, 1<<SPA_DATA_MemPtr);
View file
pipewire-0.3.67.tar.gz/src/pipewire/buffers.h -> pipewire-0.3.68.tar.gz/src/pipewire/buffers.h
Changed
@@ -29,6 +29,7 @@ #define PW_BUFFERS_FLAG_DYNAMIC (1<<2) /**< buffers have dynamic data */ #define PW_BUFFERS_FLAG_SHARED_MEM (1<<3) /**< buffers need shared memory */ #define PW_BUFFERS_FLAG_IN_PRIORITY (1<<4) /**< input parameters have priority */ +#define PW_BUFFERS_FLAG_ASYNC (1<<5) /**< one of the nodes is async */ struct pw_buffers { struct pw_memblock *mem; /**< allocated buffer memory */
View file
pipewire-0.3.67.tar.gz/src/pipewire/context.c -> pipewire-0.3.68.tar.gz/src/pipewire/context.c
Changed
@@ -768,13 +768,89 @@ static int ensure_state(struct pw_impl_node *node, bool running) { enum pw_node_state state = node->info.state; - if (node->active && !SPA_FLAG_IS_SET(node->spa_flags, SPA_NODE_FLAG_NEED_CONFIGURE) && running) + if (node->active && node->runnable && + !SPA_FLAG_IS_SET(node->spa_flags, SPA_NODE_FLAG_NEED_CONFIGURE) && running) state = PW_NODE_STATE_RUNNING; else if (state > PW_NODE_STATE_IDLE) state = PW_NODE_STATE_IDLE; return pw_impl_node_set_state(node, state); } +/* From a node (that is runnable) follow all prepared links and groups to + * active nodes up to the driver and make them recursively runnable as well. + * + * We stop at driver nodes so that other paths linked to the driver will stay + * unrunnable when no other runnable path exists. + */ +static inline int run_nodes(struct pw_context *context, struct pw_impl_node *node, struct spa_list *nodes) +{ + struct pw_impl_node *t; + struct pw_impl_port *p; + struct pw_impl_link *l; + + if (!node->runnable) + return 0; + + pw_log_debug("node %p: '%s'", node, node->name); + + spa_list_for_each(p, &node->input_ports, link) { + spa_list_for_each(l, &p->links, input_link) { + t = l->output->node; + + if (!t->active || !l->prepared || t->runnable) + continue; + + pw_log_debug(" peer %p: '%s'", t, t->name); + t->runnable = true; + if (!t->driver) + run_nodes(context, t, nodes); + } + } + spa_list_for_each(p, &node->output_ports, link) { + spa_list_for_each(l, &p->links, output_link) { + t = l->input->node; + + if (!t->active || !l->prepared || t->runnable) + continue; + + pw_log_debug(" peer %p: '%s'", t, t->name); + t->runnable = true; + if (!t->driver) + run_nodes(context, t, nodes); + } + } + /* now go through all the nodes that have the same link group and + * that are not yet visited. Note how nodes with the same group + * don't get included here. They were added to the same driver but + * need to otherwise stay idle unless some non-passive link activates + * them. */ + if (node->link_group != NULL) { + spa_list_for_each(t, nodes, sort_link) { + if (t->exported || !t->active || t->runnable) + continue; + if (!spa_streq(t->link_group, node->link_group)) + continue; + + pw_log_debug(" group %p: '%s'", t, t->name); + t->runnable = true; + if (!t->driver) + run_nodes(context, t, nodes); + } + } + return 0; +} + +/* Follow all prepared links and groups from node, activate the links. + * If a non-passive link is found, we set the peer runnable flag. + * + * After this is done, we end up with a list of nodes in collect that are all + * linked to node. + * Some of the nodes have the runnable flag set. We then start from those nodes + * and make all linked nodes and groups runnable as well. (see run_nodes). + * + * This ensures that we only activate the paths from the runnable nodes to the + * driver nodes and leave the other nodes idle. + */ static int collect_nodes(struct pw_context *context, struct pw_impl_node *node, struct spa_list *collect) { struct spa_list queue; @@ -794,7 +870,6 @@ spa_list_consume(n, &queue, sort_link) { spa_list_remove(&n->sort_link); spa_list_append(collect, &n->sort_link); - n->runnable = n->always_process; pw_log_debug(" next node %p: '%s' runnable:%u", n, n->name, n->runnable); @@ -810,16 +885,13 @@ pw_impl_link_prepare(l); - if (!l->prepared) + if (!l->prepared || t->visited) continue; if (!l->passive) - n->runnable = true; - - if (!t->visited) { - t->visited = true; - spa_list_append(&queue, &t->sort_link); - } + t->runnable = true; + t->visited = true; + spa_list_append(&queue, &t->sort_link); } } spa_list_for_each(p, &n->output_ports, link) { @@ -831,33 +903,35 @@ pw_impl_link_prepare(l); - if (!l->prepared) + if (!l->prepared || t->visited) continue; if (!l->passive) - n->runnable = true; - - if (!t->visited) { - t->visited = true; - spa_list_append(&queue, &t->sort_link); - } + t->runnable = true; + t->visited = true; + spa_list_append(&queue, &t->sort_link); } } /* now go through all the nodes that have the same group and * that are not yet visited */ - if (n->group != NULL) { + if (n->group != NULL || n->link_group != NULL) { spa_list_for_each(t, &context->node_list, link) { if (t->exported || !t->active || t->visited) continue; - if (!spa_streq(t->group, n->group)) + if ((t->group == NULL || !spa_streq(t->group, n->group)) && + (t->link_group == NULL || !spa_streq(t->link_group, n->link_group))) continue; - pw_log_debug("%p: %s join group %s", - t, t->name, t->group); + pw_log_debug("%p: %s join group:%s link-group:%s", + t, t->name, n->group, n->link_group); t->visited = true; spa_list_append(&queue, &t->sort_link); } } + pw_log_debug(" next node %p: '%s' runnable:%u", n, n->name, n->runnable); } + spa_list_for_each(n, collect, sort_link) + run_nodes(context, n, collect); + return 0; } @@ -868,8 +942,7 @@ pw_log_debug("driver: %p %s runnable:%u", driver, driver->name, driver->runnable); spa_list_consume(n, nodes, sort_link) { spa_list_remove(&n->sort_link); - if (n->runnable) - driver->runnable = true; + pw_log_debug(" follower: %p %s runnable:%u driver-runnable:%u", n, n->name, n->runnable, driver->runnable); pw_impl_node_set_driver(n, driver); @@ -1104,6 +1177,12 @@ again: impl->recalc = true; + /* clean up the flags first */ + spa_list_for_each(n, &context->node_list, link) { + n->visited = false; + n->runnable = n->always_process; + } + get_quantums(context, &def_quantum, &min_quantum, &max_quantum, &lim_quantum, &rate_quantum); rates = get_rates(context, &def_rate, &n_rates, &global_force_rate); @@ -1181,6 +1260,7 @@ /* is any active and want a driver */ if (t->want_driver && t->active && t->runnable) { driver = target; + driver->runnable = true; break; } } @@ -1188,13 +1268,10 @@ /* driver needed for this group */ move_to_driver(context, &collect, driver); } else { - /* no driver, make sure the nodes stops */ + /* no driver, make sure the nodes stop */ remove_from_driver(context, &collect); } } - /* clean up the visited flag now */ - spa_list_for_each(n, &context->node_list, link) - n->visited = false; /* assign final quantum and set state for followers and drivers */ spa_list_for_each(n, &context->driver_list, driver_link) { @@ -1204,7 +1281,7 @@ struct spa_fraction rate = SPA_FRACTION(0, 0); uint32_t quantum, target_rate, current_rate; uint64_t quantum_stamp = 0, rate_stamp = 0; - bool force_rate, force_quantum; + bool force_rate, force_quantum, restore_rate = false; const uint32_t *node_rates; uint32_t node_n_rates, node_def_rate; uint32_t node_max_quantum, node_min_quantum, node_def_quantum, node_rate_quantum; @@ -1276,6 +1353,13 @@ s->moved = false; } + if (n->forced_rate && !force_rate && n->runnable) { + /* A node that was forced to a rate but is no longer being + * forced can restore its rate */ + pw_log_info("(%s-%u) restore rate", n->name, n->info.id); + restore_rate = true; + } + if (force_quantum) lock_quantum = false; if (force_rate) @@ -1284,11 +1368,12 @@ if (n->reconfigure) running = true; - current_rate = n->current_rate.denom; - if (lock_rate || n->reconfigure || !running || - (!force_rate && - (n->info.state > PW_NODE_STATE_IDLE))) - /* when someone wants us to lock the rate of this driver or + current_rate = n->target_rate.denom; + if (!restore_rate && + (lock_rate || n->reconfigure || !running || + (!force_rate && (n->info.state > PW_NODE_STATE_IDLE)))) + /* when we don't need to restore or rate and + * when someone wants us to lock the rate of this driver or * when we are in the process of reconfiguring the driver or * when we are not running any followers or * when the driver is busy and we don't need to force a rate, @@ -1307,26 +1392,27 @@ if (target_rate != current_rate) { bool do_reconfigure = false; /* we doing a rate switch */ - pw_log_info("(%s-%u) state:%s new rate:%u->%u", + pw_log_info("(%s-%u) state:%s new rate:%u/(%u)->%u", n->name, n->info.id, pw_node_state_as_string(n->info.state), - n->current_rate.denom, + n->target_rate.denom, current_rate, target_rate); if (force_rate) { if (settings->clock_rate_update_mode == CLOCK_RATE_UPDATE_MODE_HARD) - do_reconfigure = true; + do_reconfigure = !n->target_pending; } else { if (n->info.state >= PW_NODE_STATE_SUSPENDED) - do_reconfigure = true; + do_reconfigure = !n->target_pending; } if (do_reconfigure) reconfigure_driver(context, n); /* we're setting the pending rate. This will become the new * current rate in the next iteration of the graph. */ - n->current_rate = SPA_FRACTION(1, target_rate); - n->current_pending = true; + n->target_rate = SPA_FRACTION(1, target_rate); + n->target_pending = true; + n->forced_rate = force_rate; current_rate = target_rate; /* we might be suspended now and the links need to be prepared again */ if (do_reconfigure) @@ -1356,27 +1442,33 @@ if (settings->clock_power_of_two_quantum) quantum = flp2(quantum); - if (running && quantum != n->current_quantum && !lock_quantum) { + if (running && quantum != n->target_quantum && !lock_quantum) { pw_log_info("(%s-%u) new quantum:%"PRIu64"->%u", n->name, n->info.id, - n->current_quantum, + n->target_quantum, quantum); /* this is the new pending quantum */ - n->current_quantum = quantum; - n->current_pending = true; + n->target_quantum = quantum; + n->target_pending = true; } - if (n->info.state < PW_NODE_STATE_RUNNING && n->current_pending) { - /* the driver node is not actually running and we have a - * pending change. Apply the change to the position now so - * that we have the right values when we change the node - * states of the driver and followers to RUNNING below */ + if (n->target_pending) { + /* we have a pending change. We place the new values in the + * pending fields so that they are picked up by the driver in + * the next cycle */ pw_log_debug("%p: apply duration:%"PRIu64" rate:%u/%u", context, - n->current_quantum, n->current_rate.num, - n->current_rate.denom); - n->rt.position->clock.duration = n->current_quantum; - n->rt.position->clock.rate = n->current_rate; - n->current_pending = false; + n->target_quantum, n->target_rate.num, + n->target_rate.denom); + SEQ_WRITE(n->rt.position->clock.target_seq); + n->rt.position->clock.target_duration = n->target_quantum; + n->rt.position->clock.target_rate = n->target_rate; + SEQ_WRITE(n->rt.position->clock.target_seq); + + if (n->info.state < PW_NODE_STATE_RUNNING) { + n->rt.position->clock.duration = n->target_quantum; + n->rt.position->clock.rate = n->target_rate; + } + n->target_pending = false; } pw_log_debug("%p: driver %p running:%d runnable:%d quantum:%u '%s'",
View file
pipewire-0.3.67.tar.gz/src/pipewire/core.c -> pipewire-0.3.68.tar.gz/src/pipewire/core.c
Changed
@@ -69,9 +69,8 @@ struct pw_proxy *proxy; pw_log_debug("%p: proxy id %u bound %u", this, id, global_id); - if ((proxy = pw_map_lookup(&this->objects, id)) != NULL) { + if ((proxy = pw_map_lookup(&this->objects, id)) != NULL) pw_proxy_set_bound_id(proxy, global_id); - } } static void core_event_add_mem(void *data, uint32_t id, uint32_t type, int fd, uint32_t flags) @@ -90,6 +89,16 @@ } } +static void core_event_bound_props(void *data, uint32_t id, uint32_t global_id, const struct spa_dict *props) +{ + struct pw_core *this = data; + struct pw_proxy *proxy; + + pw_log_debug("%p: proxy id %u bound %u", this, id, global_id); + if ((proxy = pw_map_lookup(&this->objects, id)) != NULL) + pw_proxy_emit_bound_props(proxy, global_id, props); +} + static void core_event_remove_mem(void *data, uint32_t id) { struct pw_core *this = data; @@ -106,6 +115,7 @@ .bound_id = core_event_bound_id, .add_mem = core_event_add_mem, .remove_mem = core_event_remove_mem, + .bound_props = core_event_bound_props, }; SPA_EXPORT
View file
pipewire-0.3.67.tar.gz/src/pipewire/core.h -> pipewire-0.3.68.tar.gz/src/pipewire/core.h
Changed
@@ -34,7 +34,7 @@ #define PW_TYPE_INTERFACE_Core PW_TYPE_INFO_INTERFACE_BASE "Core" #define PW_TYPE_INTERFACE_Registry PW_TYPE_INFO_INTERFACE_BASE "Registry" -#define PW_VERSION_CORE 3 +#define PW_VERSION_CORE 4 struct pw_core; #define PW_VERSION_REGISTRY 3 struct pw_registry; @@ -80,21 +80,22 @@ /** Core */ -#define PW_CORE_EVENT_INFO 0 -#define PW_CORE_EVENT_DONE 1 -#define PW_CORE_EVENT_PING 2 -#define PW_CORE_EVENT_ERROR 3 -#define PW_CORE_EVENT_REMOVE_ID 4 -#define PW_CORE_EVENT_BOUND_ID 5 -#define PW_CORE_EVENT_ADD_MEM 6 +#define PW_CORE_EVENT_INFO 0 +#define PW_CORE_EVENT_DONE 1 +#define PW_CORE_EVENT_PING 2 +#define PW_CORE_EVENT_ERROR 3 +#define PW_CORE_EVENT_REMOVE_ID 4 +#define PW_CORE_EVENT_BOUND_ID 5 +#define PW_CORE_EVENT_ADD_MEM 6 #define PW_CORE_EVENT_REMOVE_MEM 7 -#define PW_CORE_EVENT_NUM 8 +#define PW_CORE_EVENT_BOUND_PROPS 8 +#define PW_CORE_EVENT_NUM 9 /** \struct pw_core_events * \brief Core events */ struct pw_core_events { -#define PW_VERSION_CORE_EVENTS 0 +#define PW_VERSION_CORE_EVENTS 1 uint32_t version; /** @@ -188,6 +189,8 @@ * \param id the memory id to remove */ void (*remove_mem) (void *data, uint32_t id); + + void (*bound_props) (void *data, uint32_t id, uint32_t global_id, const struct spa_dict *props); }; #define PW_CORE_METHOD_ADD_LISTENER 0
View file
pipewire-0.3.67.tar.gz/src/pipewire/filter.c -> pipewire-0.3.68.tar.gz/src/pipewire/filter.c
Changed
@@ -144,7 +144,6 @@ unsigned int disconnecting:1; unsigned int disconnect_core:1; - unsigned int subscribe:1; unsigned int draining:1; unsigned int allow_mlock:1; unsigned int warn_mlock:1; @@ -1133,10 +1132,12 @@ PW_FILTER_STATE_ERROR, message); } -static void proxy_bound(void *_data, uint32_t global_id) +static void proxy_bound_props(void *_data, uint32_t global_id, const struct spa_dict *props) { struct pw_filter *filter = _data; filter->node_id = global_id; + if (props) + pw_properties_update(filter->properties, props); filter_set_state(filter, PW_FILTER_STATE_PAUSED, NULL); } @@ -1145,7 +1146,7 @@ .removed = proxy_removed, .destroy = proxy_destroy, .error = proxy_error, - .bound = proxy_bound, + .bound_props = proxy_bound_props, }; static void on_core_error(void *_data, uint32_t id, int seq, int res, const char *message) @@ -1191,6 +1192,8 @@ struct match match; int res; + ensure_loop(context->main_loop, return NULL); + impl = calloc(1, sizeof(struct filter)); if (impl == NULL) { res = -errno; @@ -1355,21 +1358,58 @@ return "invalid-state"; } +static int filter_disconnect(struct filter *impl) +{ + struct pw_filter *filter = &impl->this; + pw_log_debug("%p: disconnect", impl); + + if (impl->disconnecting) + return -EBUSY; + + impl->disconnecting = true; + + if (filter->proxy) { + pw_proxy_destroy(filter->proxy); + filter->proxy = NULL; + } + if (impl->disconnect_core) { + impl->disconnect_core = false; + spa_hook_remove(&filter->core_listener); + spa_list_remove(&filter->link); + pw_core_disconnect(filter->core); + filter->core = NULL; + } + return 0; +} + +static void free_port(struct filter *impl, struct port *port) +{ + spa_list_remove(&port->link); + spa_node_emit_port_info(&impl->hooks, port->direction, port->id, NULL); + pw_map_remove(&impl->portsport->direction, port->id); + clear_buffers(port); + clear_params(impl, port, SPA_ID_INVALID); + pw_properties_free(port->props); + free(port); +} + SPA_EXPORT void pw_filter_destroy(struct pw_filter *filter) { struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this); struct port *p; + ensure_loop(impl->context->main_loop, return); + pw_log_debug("%p: destroy", filter); pw_filter_emit_destroy(filter); if (!impl->disconnecting) - pw_filter_disconnect(filter); + filter_disconnect(impl); spa_list_consume(p, &impl->port_list, link) - pw_filter_remove_port(p->user_data); + free_port(impl, p); if (filter->core) { spa_hook_remove(&filter->core_listener); @@ -1412,6 +1452,9 @@ void *data) { struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this); + + ensure_loop(impl->context->main_loop); + spa_hook_list_append(&filter->listener_list, listener, events, data); if (events->process && impl->rt_callbacks.funcs == NULL) { impl->rt_callbacks = SPA_CALLBACKS_INIT(events, data); @@ -1460,6 +1503,8 @@ struct port *port = SPA_CONTAINER_OF(port_data, struct port, user_data); int changed = 0; + ensure_loop(impl->context->main_loop, return -EIO); + if (port_data) { changed = pw_properties_update(port->props, dict); port->info.props = &port->props->dict; @@ -1497,6 +1542,11 @@ uint32_t i; struct spa_dict_item items1; + ensure_loop(impl->context->main_loop, return -EIO); + + if (filter->proxy != NULL || filter->state != PW_FILTER_STATE_UNCONNECTED) + return -EBUSY; + pw_log_debug("%p: connect", filter); impl->flags = flags; @@ -1532,6 +1582,8 @@ } impl->disconnecting = false; + impl->draining = false; + impl->driving = false; filter_set_state(filter, PW_FILTER_STATE_CONNECTING, NULL); if (flags & PW_FILTER_FLAG_DRIVER) @@ -1585,22 +1637,8 @@ int pw_filter_disconnect(struct pw_filter *filter) { struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this); - - pw_log_debug("%p: disconnect", filter); - impl->disconnecting = true; - - if (filter->proxy) { - pw_proxy_destroy(filter->proxy); - filter->proxy = NULL; - } - if (impl->disconnect_core) { - impl->disconnect_core = false; - spa_hook_remove(&filter->core_listener); - spa_list_remove(&filter->link); - pw_core_disconnect(filter->core); - filter->core = NULL; - } - return 0; + ensure_loop(impl->context->main_loop, return -EIO); + return filter_disconnect(impl); } static void add_port_params(struct filter *impl, struct port *port) @@ -1682,6 +1720,8 @@ struct port *p; const char *str; + ensure_loop(impl->context->main_loop, return NULL); + if (props == NULL) props = pw_properties_new(NULL, NULL); if (props == NULL) @@ -1738,22 +1778,14 @@ return NULL; } -static inline void free_port(struct filter *impl, struct port *port) -{ - spa_list_remove(&port->link); - spa_node_emit_port_info(&impl->hooks, port->direction, port->id, NULL); - pw_map_remove(&impl->portsport->direction, port->id); - clear_buffers(port); - clear_params(impl, port, SPA_ID_INVALID); - pw_properties_free(port->props); - free(port); -} - SPA_EXPORT int pw_filter_remove_port(void *port_data) { struct port *port = SPA_CONTAINER_OF(port_data, struct port, user_data); struct filter *impl = port->filter; + + ensure_loop(impl->context->main_loop, return -EIO); + free_port(impl, port); return 0; } @@ -1762,6 +1794,10 @@ int pw_filter_set_error(struct pw_filter *filter, int res, const char *error, ...) { + struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this); + + ensure_loop(impl->context->main_loop, return -EIO); + if (res < 0) { va_list args; char *value; @@ -1792,6 +1828,8 @@ struct port *port; int res; + ensure_loop(impl->context->main_loop, return -EIO); + pw_log_debug("%p: update params", filter); port = port_data ? SPA_CONTAINER_OF(port_data, struct port, user_data) : NULL; @@ -1811,6 +1849,10 @@ SPA_EXPORT int pw_filter_set_active(struct pw_filter *filter, bool active) { + struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this); + + ensure_loop(impl->context->main_loop, return -EIO); + pw_log_debug("%p: active:%d", filter, active); return 0; } @@ -1948,8 +1990,10 @@ return spa_node_call_ready(&impl->callbacks, res); } -static int trigger_request_process(struct filter *impl) +static int do_trigger_request_process(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) { + struct filter *impl = user_data; uint8_t buffer1024; struct spa_pod_builder b = { 0 }; @@ -1969,7 +2013,8 @@ pw_log_trace_fp("%p", impl); if (!impl->driving) { - res = trigger_request_process(impl); + res = pw_loop_invoke(impl->context->main_loop, + do_trigger_request_process, 1, NULL, 0, false, impl); } else { res = pw_loop_invoke(impl->context->data_loop, do_trigger_process, 1, NULL, 0, false, impl);
View file
pipewire-0.3.67.tar.gz/src/pipewire/impl-core.c -> pipewire-0.3.68.tar.gz/src/pipewire/impl-core.c
Changed
@@ -5,9 +5,6 @@ #include "config.h" #include <unistd.h> -#ifndef ENODATA -#define ENODATA 9919 -#endif #include <spa/debug/types.h> #include <spa/utils/string.h> @@ -175,6 +172,8 @@ pw_log_debug("%p: hello %d from resource %p", context, version, resource); pw_map_for_each(&client->objects, destroy_resource, client); + resource->version = version; + pw_mempool_clear(client->pool); this->info.change_mask = PW_CORE_CHANGE_MASK_ALL;
View file
pipewire-0.3.67.tar.gz/src/pipewire/impl-link.c -> pipewire-0.3.68.tar.gz/src/pipewire/impl-link.c
Changed
@@ -1203,7 +1203,10 @@ /* passive means that this link does not make the nodes active */ str = pw_properties_get(properties, PW_KEY_LINK_PASSIVE); - this->passive = str ? spa_atob(str) : output->passive | input->passive; + this->passive = str ? spa_atob(str) : + (output->passive && input_node->can_suspend) || + (input->passive && output_node->can_suspend) || + (input->passive && output->passive); if (this->passive && str == NULL) pw_properties_set(properties, PW_KEY_LINK_PASSIVE, "true"); @@ -1256,10 +1259,9 @@ output_node, output->port_id, this->rt.out_mix.port.port_id, input_node, input->port_id, this->rt.in_mix.port.port_id); - if (asprintf(&this->name, "%d.%d -> %d.%d", + this->name = spa_aprintf("%d.%d -> %d.%d", output_node->info.id, output->port_id, - input_node->info.id, input->port_id) < 0) - this->name = NULL; + input_node->info.id, input->port_id); pw_log_info("(%s) (%s) -> (%s)", this->name, output_node->name, input_node->name); pw_impl_port_emit_link_added(output, this);
View file
pipewire-0.3.67.tar.gz/src/pipewire/impl-node.c -> pipewire-0.3.68.tar.gz/src/pipewire/impl-node.c
Changed
@@ -688,8 +688,9 @@ pw_log_debug("%p: set position %p", node, &node->rt.activation->position); node->rt.position = &node->rt.activation->position; - node->current_rate = node->rt.position->clock.rate; - node->current_quantum = node->rt.position->clock.duration; + node->target_rate = node->rt.position->clock.target_rate; + node->target_quantum = node->rt.position->clock.target_duration; + node->target_pending = false; } else if (node->driver) { pw_log_warn("%p: can't set position on driver", node); } @@ -804,8 +805,8 @@ pw_log_trace("%p: set position %p", node, &driver->rt.activation->position); node->rt.position = &driver->rt.activation->position; - node->current_rate = node->rt.position->clock.rate; - node->current_quantum = node->rt.position->clock.duration; + node->target_rate = node->rt.position->clock.target_rate; + node->target_quantum = node->rt.position->clock.target_duration; if (node->source.loop != NULL) { remove_node(node); @@ -841,14 +842,16 @@ remove_segment_owner(old, node->info.id); if (old != node && old->driving && driver->info.state < PW_NODE_STATE_RUNNING) { - driver->current_rate = old->current_rate; - driver->current_quantum = old->current_quantum; - driver->current_pending = true; - pw_log_info("move quantum:%"PRIu64" rate:%d (%s-%d -> %s-%d)", - driver->current_quantum, - driver->current_rate.denom, + pw_log_info("move quantum:%"PRIu64"->%"PRIu64" rate:%d->%d (%s-%d -> %s-%d)", + driver->target_quantum, + old->target_quantum, + driver->target_rate.denom, + old->target_rate.denom, old->name, old->info.id, driver->name, driver->info.id); + driver->target_rate = old->target_rate; + driver->target_quantum = old->target_quantum; + driver->target_pending = true; } was_driving = node->driving; node->driving = node->driver && driver == node; @@ -892,7 +895,7 @@ const char *str, *recalc_reason = NULL; struct spa_fraction frac; uint32_t value; - bool driver; + bool driver, trigger; if ((str = pw_properties_get(node->properties, PW_KEY_PRIORITY_DRIVER))) { node->priority_driver = pw_properties_parse_int(str); @@ -925,8 +928,14 @@ } /* not scheduled automatically so we add an additional required trigger */ - if (pw_properties_get_bool(node->properties, PW_KEY_NODE_TRIGGER, false)) - node->rt.activation->state0.required++; + trigger = pw_properties_get_bool(node->properties, PW_KEY_NODE_TRIGGER, false); + if (trigger != node->trigger) { + node->trigger = trigger; + if (trigger) + node->rt.activation->state0.required++; + else + node->rt.activation->state0.required--; + } /* group defines what nodes are scheduled together */ str = pw_properties_get(node->properties, PW_KEY_NODE_GROUP); @@ -947,6 +956,12 @@ recalc_reason = "link group changed"; } + if ((str = pw_properties_get(node->properties, PW_KEY_MEDIA_CLASS)) != NULL && + (strstr(str, "/Sink") != NULL || strstr(str, "/Source") != NULL)) { + node->can_suspend = true; + } else { + node->can_suspend = false; + } if ((str = pw_properties_get(node->properties, PW_KEY_NODE_PASSIVE)) == NULL) str = "false"; if (spa_streq(str, "out")) @@ -1009,11 +1024,16 @@ node->lock_rate = pw_properties_get_bool(node->properties, PW_KEY_NODE_LOCK_RATE, false); if ((str = pw_properties_get(node->properties, PW_KEY_NODE_FORCE_RATE))) { - if (spa_atou32(str, &value, 0) && - node->force_rate != value) { - node->force_rate = value; - node->stamp = ++context->stamp; - recalc_reason = "force rate changed"; + if (spa_atou32(str, &value, 0)) { + if (value == 0) + value = node->rate.denom; + if (node->force_rate != value) { + pw_log_info("(%s-%u) force-rate:%u -> %u", node->name, + node->info.id, node->force_rate, value); + node->force_rate = value; + node->stamp = ++context->stamp; + recalc_reason = "force rate changed"; + } } } @@ -1218,11 +1238,11 @@ uint32_t quantum = s->clock_force_quantum == 0 ? s->clock_quantum : s->clock_force_quantum; uint32_t rate = s->clock_force_rate == 0 ? s->clock_rate : s->clock_force_rate; - this->current_rate = SPA_FRACTION(1, rate); - this->current_quantum = quantum; + this->target_rate = SPA_FRACTION(1, rate); + this->target_quantum = quantum; - pos->clock.rate = this->current_rate; - pos->clock.duration = this->current_quantum; + pos->clock.rate = pos->clock.target_rate = this->target_rate; + pos->clock.duration = pos->clock.target_duration = this->target_quantum; pos->video.flags = SPA_IO_VIDEO_SIZE_VALID; pos->video.size = s->video_size; pos->video.stride = pos->video.size.width * 16; @@ -1681,10 +1701,15 @@ node->rt.target.signal_func(node->rt.target.data); } - if (node->current_pending) { - node->rt.position->clock.duration = node->current_quantum; - node->rt.position->clock.rate = node->current_rate; - node->current_pending = false; + /* This update is done too late, the driver should do this + * before calling the ready callback so that it can use the new target + * duration and rate to schedule the next update. We do this here to + * help drivers that don't support this yet */ + if (node->rt.position->clock.duration != node->rt.position->clock.target_duration || + node->rt.position->clock.rate.denom != node->rt.position->clock.target_rate.denom) { + pw_log_warn("driver %s did not update duration/rate", node->name); + node->rt.position->clock.duration = node->rt.position->clock.target_duration; + node->rt.position->clock.rate = node->rt.position->clock.target_rate; } sync_type = check_updates(node, &reposition_owner);
View file
pipewire-0.3.67.tar.gz/src/pipewire/impl-port.c -> pipewire-0.3.68.tar.gz/src/pipewire/impl-port.c
Changed
@@ -1574,9 +1574,12 @@ /* try dynamic data */ alloc_flags = PW_BUFFERS_FLAG_DYNAMIC; + if (SPA_FLAG_IS_SET(node->spa_flags, SPA_NODE_FLAG_ASYNC)) + alloc_flags |= PW_BUFFERS_FLAG_ASYNC; - pw_log_debug("%p: %d.%d negotiate %d buffers on node: %p", - port, port->direction, port->port_id, n_buffers, node->node); + pw_log_debug("%p: %d.%d negotiate %d buffers on node: %p flags:%08x", + port, port->direction, port->port_id, n_buffers, node->node, + alloc_flags); if (port->added) { pw_loop_invoke(node->data_loop, do_remove_port, SPA_ID_INVALID, NULL, 0, true, port);
View file
pipewire-0.3.67.tar.gz/src/pipewire/keys.h -> pipewire-0.3.68.tar.gz/src/pipewire/keys.h
Changed
@@ -149,7 +149,8 @@ #define PW_KEY_NODE_LOCK_RATE "node.lock-rate" /**< don't change rate when this node * is active */ #define PW_KEY_NODE_FORCE_RATE "node.force-rate" /**< force a rate while the node is - * active */ + * active. A value of 0 takes the denominator + * of node.rate */ #define PW_KEY_NODE_DONT_RECONNECT "node.dont-reconnect" /**< don't reconnect this node. The node is * initially linked to target.object or the
View file
pipewire-0.3.67.tar.gz/src/pipewire/loop.c -> pipewire-0.3.68.tar.gz/src/pipewire/loop.c
Changed
@@ -9,6 +9,7 @@ #include <spa/utils/result.h> #include <pipewire/pipewire.h> +#include <pipewire/private.h> #include <pipewire/loop.h> #include <pipewire/log.h> #include <pipewire/type.h> @@ -23,6 +24,9 @@ struct spa_handle *system_handle; struct spa_handle *loop_handle; + + void *user_data; + const struct pw_loop_callbacks *cb; }; /** \endcond */ @@ -140,3 +144,24 @@ pw_unload_spa_handle(impl->system_handle); free(impl); } + +void +pw_loop_set_callbacks(struct pw_loop *loop, const struct pw_loop_callbacks *cb, void *data) +{ + struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this); + + impl->user_data = data; + impl->cb = cb; +} + +SPA_EXPORT +int pw_loop_check(struct pw_loop *loop) +{ + struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this); + int res; + if (impl->cb && impl->cb->check) + res = impl->cb->check(impl->user_data, loop); + else + res = spa_loop_control_check(loop->control); + return res; +}
View file
pipewire-0.3.67.tar.gz/src/pipewire/private.h -> pipewire-0.3.68.tar.gz/src/pipewire/private.h
Changed
@@ -346,6 +346,7 @@ #define pw_core_resource_bound_id(r,...) pw_core_resource(r,bound_id,0,__VA_ARGS__) #define pw_core_resource_add_mem(r,...) pw_core_resource(r,add_mem,0,__VA_ARGS__) #define pw_core_resource_remove_mem(r,...) pw_core_resource(r,remove_mem,0,__VA_ARGS__) +#define pw_core_resource_bound_props(r,...) pw_core_resource(r,bound_props,1,__VA_ARGS__) static inline SPA_PRINTF_FUNC(5,0) void pw_core_resource_errorv(struct pw_resource *resource, uint32_t id, int seq, @@ -373,6 +374,29 @@ va_end(args); } +struct pw_loop_callbacks { +#define PW_VERSION_LOOP_CALLBACKS 0 + uint32_t version; + + int (*check) (void *data, struct pw_loop *loop); +}; + +void +pw_loop_set_callbacks(struct pw_loop *loop, const struct pw_loop_callbacks *cb, void *data); + +int pw_loop_check(struct pw_loop *loop); + +#define ensure_loop(loop,...) ({ \ + int res = pw_loop_check(loop); \ + if (res != 1) { \ + pw_log_warn("%s called from wrong context, check thread and locking: %s", \ + __func__, spa_strerror(res)); \ + fprintf(stderr, "*** %s called from wrong context, check thread and locking: %s\n",\ + __func__, spa_strerror(res)); \ + /* __VA_ARGS__ */ \ + } \ +}) + #define pw_context_driver_emit(c,m,v,...) spa_hook_list_call_simple(&c->driver_listener_list, struct pw_context_driver_events, m, v, ##__VA_ARGS__) #define pw_context_driver_emit_start(c,n) pw_context_driver_emit(c, start, 0, n) #define pw_context_driver_emit_xrun(c,n) pw_context_driver_emit(c, xrun, 0, n) @@ -695,12 +719,16 @@ unsigned int lock_quantum:1; /**< don't change graph quantum */ unsigned int lock_rate:1; /**< don't change graph rate */ unsigned int transport_sync:1; /**< supports transport sync */ - unsigned int current_pending:1; /**< a quantum/rate update is pending */ + unsigned int target_pending:1; /**< a quantum/rate update is pending */ unsigned int moved:1; /**< the node was moved drivers */ unsigned int added:1; /**< the node was add to graph */ unsigned int pause_on_idle:1; /**< Pause processing when IDLE */ unsigned int suspend_on_idle:1; unsigned int reconfigure:1; + unsigned int forced_rate:1; + unsigned int trigger:1; /**< has the TRIGGER property and needs an extra + * trigger to start processing. */ + unsigned int can_suspend:1; uint32_t port_user_data_size; /**< extra size for port user data */ @@ -748,10 +776,10 @@ struct ratelimit rate_limit; } rt; - struct spa_fraction current_rate; - uint64_t current_quantum; + struct spa_fraction target_rate; + uint64_t target_quantum; - void *user_data; /**< extra user data */ + void *user_data; /**< extra user data */ }; struct pw_impl_port_mix { @@ -965,6 +993,7 @@ #define pw_proxy_emit_removed(p) pw_proxy_emit(p, removed, 0) #define pw_proxy_emit_done(p,s) pw_proxy_emit(p, done, 0, s) #define pw_proxy_emit_error(p,s,r,m) pw_proxy_emit(p, error, 0, s, r, m) +#define pw_proxy_emit_bound_props(p,g,r) pw_proxy_emit(p, bound_props, 1, g, r) struct pw_proxy { struct spa_interface impl; /**< object implementation */
View file
pipewire-0.3.67.tar.gz/src/pipewire/proxy.h -> pipewire-0.3.68.tar.gz/src/pipewire/proxy.h
Changed
@@ -89,7 +89,7 @@ /** Proxy events, use \ref pw_proxy_add_listener */ struct pw_proxy_events { -#define PW_VERSION_PROXY_EVENTS 0 +#define PW_VERSION_PROXY_EVENTS 1 uint32_t version; /** The proxy is destroyed */ @@ -107,6 +107,8 @@ /** an error occurred on the proxy */ void (*error) (void *data, int seq, int res, const char *message); + + void (*bound_props) (void *data, uint32_t global_id, const struct spa_dict *props); }; /* Make a new proxy object. The id can be used to bind to a remote object and
View file
pipewire-0.3.67.tar.gz/src/pipewire/resource.c -> pipewire-0.3.68.tar.gz/src/pipewire/resource.c
Changed
@@ -194,9 +194,19 @@ struct pw_impl_client *client = resource->client; resource->bound_id = global_id; + if (client->core_resource != NULL) { - pw_log_debug("%p: %u global_id:%u", resource, resource->id, global_id); - pw_core_resource_bound_id(client->core_resource, resource->id, global_id); + struct pw_global *global = pw_map_lookup(&resource->context->globals, global_id); + const struct spa_dict *dict = global ? &global->properties->dict : NULL; + + pw_log_debug("%p: %u global_id:%u %d", resource, resource->id, global_id, + client->core_resource->version); + + if (client->core_resource->version >= 4) + pw_core_resource_bound_props(client->core_resource, resource->id, global_id, + dict); + else + pw_core_resource_bound_id(client->core_resource, resource->id, global_id); } return 0; }
View file
pipewire-0.3.67.tar.gz/src/pipewire/stream.c -> pipewire-0.3.68.tar.gz/src/pipewire/stream.c
Changed
@@ -17,6 +17,7 @@ #include <spa/pod/filter.h> #include <spa/pod/dynamic.h> #include <spa/debug/types.h> +#include <spa/debug/dict.h> #define PW_ENABLE_DEPRECATED @@ -1044,7 +1045,6 @@ 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 @@ -1055,6 +1055,8 @@ ask_more = !impl->process_rt && impl->rate_match == NULL && queue_is_empty(impl, &impl->queued) && !queue_is_empty(impl, &impl->dequeued); + pw_log_trace_fp("%p: pop %d %p ask_more:%u %p", stream, b->id, io, + ask_more, impl->rate_match); } else if (impl->draining || impl->drained) { impl->draining = true; impl->drained = true; @@ -1137,10 +1139,12 @@ PW_STREAM_STATE_ERROR, message); } -static void proxy_bound(void *data, uint32_t global_id) +static void proxy_bound_props(void *data, uint32_t global_id, const struct spa_dict *props) { struct pw_stream *stream = data; stream->node_id = global_id; + if (props) + pw_properties_update(stream->properties, props); stream_set_state(stream, PW_STREAM_STATE_PAUSED, NULL); } @@ -1149,7 +1153,7 @@ .removed = proxy_removed, .destroy = proxy_destroy, .error = proxy_error, - .bound = proxy_bound, + .bound_props = proxy_bound_props, }; static struct control *find_control(struct pw_stream *stream, uint32_t id) @@ -1332,6 +1336,14 @@ return 0; } +static void node_event_destroy(void *data) +{ + struct pw_stream *stream = data; + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + spa_hook_remove(&stream->node_listener); + impl->node = NULL; +} + static void node_event_info(void *data, const struct pw_node_info *info) { struct pw_stream *stream = data; @@ -1359,6 +1371,7 @@ static const struct pw_impl_node_events node_events = { PW_VERSION_IMPL_NODE_EVENTS, + .destroy = node_event_destroy, .info_changed = node_event_info, }; @@ -1423,6 +1436,8 @@ struct match match; int res; + ensure_loop(context->main_loop, return NULL); + impl = calloc(1, sizeof(struct stream)); if (impl == NULL) { res = -errno; @@ -1596,18 +1611,52 @@ return "invalid-state"; } +static int stream_disconnect(struct stream *impl) +{ + struct pw_stream *stream = &impl->this; + + pw_log_debug("%p: disconnect", stream); + + if (impl->disconnecting) + return -EBUSY; + + impl->disconnecting = true; + + if (impl->node) + pw_impl_node_set_active(impl->node, false); + + if (stream->proxy) { + pw_proxy_destroy(stream->proxy); + stream->proxy = NULL; + } + + if (impl->node) + pw_impl_node_destroy(impl->node); + + if (impl->disconnect_core) { + impl->disconnect_core = false; + spa_hook_remove(&stream->core_listener); + spa_list_remove(&stream->link); + pw_core_disconnect(stream->core); + stream->core = NULL; + } + return 0; +} + SPA_EXPORT void pw_stream_destroy(struct pw_stream *stream) { struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); struct control *c; + ensure_loop(impl->context->main_loop, return); + pw_log_debug("%p: destroy", stream); pw_stream_emit_destroy(stream); if (!impl->disconnecting) - pw_stream_disconnect(stream); + stream_disconnect(impl); if (stream->core) { spa_hook_remove(&stream->core_listener); @@ -1656,6 +1705,9 @@ void *data) { struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + + ensure_loop(impl->context->main_loop); + spa_hook_list_append(&stream->listener_list, listener, events, data); if (events->process && impl->rt_callbacks.funcs == NULL) { @@ -1692,6 +1744,8 @@ int changed, res = 0; struct match match; + ensure_loop(impl->context->main_loop, return -EIO); + changed = pw_properties_update(stream->properties, dict); if (!changed) return 0; @@ -1786,8 +1840,7 @@ } } -SPA_EXPORT -int +SPA_EXPORT int pw_stream_connect(struct pw_stream *stream, enum pw_direction direction, uint32_t target_id, @@ -1802,7 +1855,13 @@ uint32_t i; int res; + ensure_loop(impl->context->main_loop, return -EIO); + pw_log_debug("%p: connect target:%d", stream, target_id); + + if (impl->node != NULL || stream->state != PW_STREAM_STATE_UNCONNECTED) + return -EBUSY; + impl->direction = direction == PW_DIRECTION_INPUT ? SPA_DIRECTION_INPUT : SPA_DIRECTION_OUTPUT; impl->flags = flags; @@ -1879,6 +1938,11 @@ return res; impl->disconnecting = false; + impl->drained = false; + impl->draining = false; + impl->driving = false; + impl->trigger = false; + impl->using_trigger = false; stream_set_state(stream, PW_STREAM_STATE_CONNECTING, NULL); if ((str = getenv("PIPEWIRE_NODE")) != NULL) @@ -2022,40 +2086,18 @@ int pw_stream_disconnect(struct pw_stream *stream) { struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); - - pw_log_debug("%p: disconnect", stream); - - if (impl->disconnecting) - return 0; - - impl->disconnecting = true; - - if (impl->node) - pw_impl_node_set_active(impl->node, false); - - if (stream->proxy) { - pw_proxy_destroy(stream->proxy); - stream->proxy = NULL; - } - - if (impl->node) { - pw_impl_node_destroy(impl->node); - impl->node = NULL; - } - if (impl->disconnect_core) { - impl->disconnect_core = false; - spa_hook_remove(&stream->core_listener); - spa_list_remove(&stream->link); - pw_core_disconnect(stream->core); - stream->core = NULL; - } - return 0; + ensure_loop(impl->context->main_loop, return -EIO); + return stream_disconnect(impl); } SPA_EXPORT int pw_stream_set_error(struct pw_stream *stream, int res, const char *error, ...) { + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + + ensure_loop(impl->context->main_loop, return -EIO); + if (res < 0) { va_list args; char *value; @@ -2084,6 +2126,8 @@ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); int res; + ensure_loop(impl->context->main_loop, return -EIO); + pw_log_debug("%p: update params", stream); if ((res = update_params(impl, SPA_ID_INVALID, params, n_params)) < 0) return res; @@ -2105,6 +2149,8 @@ struct spa_pod *pod; struct control *c; + ensure_loop(impl->context->main_loop, return -EIO); + if (impl->node == NULL) return -EIO; @@ -2115,8 +2161,9 @@ pw_log_debug("%p: set control %d %d %f", stream, id, n_values, values0); if ((c = find_control(stream, id))) { + uint32_t container = n_values > 0 ? c->container : SPA_TYPE_None; spa_pod_builder_prop(&b, id, 0); - switch (c->container) { + switch (container) { case SPA_TYPE_Float: spa_pod_builder_float(&b, values0); break; @@ -2172,9 +2219,15 @@ int pw_stream_set_active(struct pw_stream *stream, bool active) { struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + + ensure_loop(impl->context->main_loop, return -EIO); + pw_log_debug("%p: active:%d", stream, active); - if (impl->node) - pw_impl_node_set_active(impl->node, active); + + if (impl->node == NULL) + return -EIO; + + pw_impl_node_set_active(impl->node, active); if (!active || impl->drained) impl->drained = impl->draining = false; @@ -2329,9 +2382,14 @@ int pw_stream_flush(struct pw_stream *stream, bool drain) { struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + + if (impl->node == NULL) + return -EIO; + pw_loop_invoke(impl->context->data_loop, drain ? do_drain : do_flush, 1, NULL, 0, true, impl); - if (!drain && impl->node != NULL) + + if (!drain) spa_node_send_command(impl->node->node, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Flush)); return 0; @@ -2360,8 +2418,10 @@ return spa_node_call_ready(&impl->callbacks, res); } -static int trigger_request_process(struct stream *impl) +static int do_trigger_request_process(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) { + struct stream *impl = user_data; uint8_t buffer1024; struct spa_pod_builder b = { 0 }; @@ -2384,7 +2444,8 @@ impl->using_trigger = true; if (!impl->driving && !impl->trigger) { - res = trigger_request_process(impl); + res = pw_loop_invoke(impl->context->main_loop, + do_trigger_request_process, 1, NULL, 0, false, impl); } else { if (!impl->process_rt) call_process(impl);
View file
pipewire-0.3.67.tar.gz/src/pipewire/thread-loop.c -> pipewire-0.3.68.tar.gz/src/pipewire/thread-loop.c
Changed
@@ -9,6 +9,7 @@ #include <spa/support/thread.h> #include <spa/utils/result.h> +#include "private.h" #include "log.h" #include "thread.h" #include "thread-loop.h" @@ -32,6 +33,7 @@ pthread_cond_t accept_cond; pthread_t thread; + int recurse; struct spa_hook hook; @@ -44,22 +46,71 @@ }; /** \endcond */ -static void before(void *data) +static int do_lock(struct pw_thread_loop *this) +{ + int res; + if ((res = pthread_mutex_lock(&this->lock)) != 0) + pw_log_error("%p: thread:%lu: %s", this, pthread_self(), strerror(res)); + else + this->recurse++; + return -res; +} + +static int do_unlock(struct pw_thread_loop *this) +{ + int res; + spa_return_val_if_fail(this->recurse > 0, -EIO); + this->recurse--; + if ((res = pthread_mutex_unlock(&this->lock)) != 0) { + pw_log_error("%p: thread:%lu: %s", this, pthread_self(), strerror(res)); + this->recurse++; + } + return -res; +} + +static void impl_before(void *data) { struct pw_thread_loop *this = data; - pthread_mutex_unlock(&this->lock); + do_unlock(this); } -static void after(void *data) +static void impl_after(void *data) { struct pw_thread_loop *this = data; - pthread_mutex_lock(&this->lock); + do_lock(this); } static const struct spa_loop_control_hooks impl_hooks = { SPA_VERSION_LOOP_CONTROL_HOOKS, - before, - after, + .before = impl_before, + .after = impl_after, +}; + +static int impl_check(void *data, struct pw_loop *loop) +{ + struct pw_thread_loop *this = data; + int res; + + /* we are in the thread running the loop */ + if (spa_loop_control_check(this->loop->control) == 1) + return 1; + + /* if lock taken by something else, error */ + if ((res = pthread_mutex_trylock(&this->lock)) != 0) { + pw_log_debug("%p: thread:%lu: %s", this, pthread_self(), strerror(res)); + return -res; + } + /* we could take the lock, check if we actually locked it somewhere */ + res = this->recurse > 0 ? 1 : -EPERM; + if (res < 0) + pw_log_debug("%p: thread:%lu: recurse:%d", this, pthread_self(), this->recurse); + pthread_mutex_unlock(&this->lock); + return res; +} + +static const struct pw_loop_callbacks impl_callbacks = { + PW_VERSION_LOOP_CALLBACKS, + .check = impl_check, }; static void do_stop(void *data, uint64_t count) @@ -121,6 +172,7 @@ goto clean_acceptcond; } + pw_loop_set_callbacks(loop, &impl_callbacks, this); pw_loop_add_hook(loop, &this->hook, &impl_hooks, this); return this; @@ -225,7 +277,7 @@ struct pw_thread_loop *this = user_data; int res; - pthread_mutex_lock(&this->lock); + do_lock(this); pw_log_debug("%p: enter thread", this); pw_loop_enter(this->loop); @@ -239,7 +291,7 @@ } pw_log_debug("%p: leave thread", this); pw_loop_leave(this->loop); - pthread_mutex_unlock(&this->lock); + do_unlock(this); return NULL; } @@ -306,7 +358,7 @@ SPA_EXPORT void pw_thread_loop_lock(struct pw_thread_loop *loop) { - pthread_mutex_lock(&loop->lock); + do_lock(loop); pw_log_trace("%p", loop); } @@ -319,7 +371,7 @@ void pw_thread_loop_unlock(struct pw_thread_loop *loop) { pw_log_trace("%p", loop); - pthread_mutex_unlock(&loop->lock); + do_unlock(loop); } /** Signal the thread @@ -342,8 +394,11 @@ if (wait_for_accept) { loop->n_waiting_for_accept++; - while (loop->n_waiting_for_accept > 0) - pthread_cond_wait(&loop->accept_cond, &loop->lock); + while (loop->n_waiting_for_accept > 0) { + int res; + if ((res = pthread_cond_wait(&loop->accept_cond, &loop->lock)) != 0) + pw_log_error("%p: thread:%lu: %s", loop, pthread_self(), strerror(res)); + } } } @@ -355,9 +410,15 @@ SPA_EXPORT void pw_thread_loop_wait(struct pw_thread_loop *loop) { - pw_log_trace("%p, waiting %d", loop, loop->n_waiting); + int res; + + pw_log_trace("%p, waiting:%d recurse:%d", loop, loop->n_waiting, loop->recurse); + spa_return_if_fail(loop->recurse > 0); loop->n_waiting++; - pthread_cond_wait(&loop->cond, &loop->lock); + loop->recurse--; + if ((res = pthread_cond_wait(&loop->cond, &loop->lock)) != 0) + pw_log_error("%p: thread:%lu: %s", loop, pthread_self(), strerror(res)); + loop->recurse++; loop->n_waiting--; pw_log_trace("%p, waiting done %d", loop, loop->n_waiting); } @@ -418,8 +479,11 @@ int pw_thread_loop_timed_wait_full(struct pw_thread_loop *loop, const struct timespec *abstime) { int ret; + spa_return_val_if_fail(loop->recurse > 0, -EIO); loop->n_waiting++; + loop->recurse--; ret = pthread_cond_timedwait(&loop->cond, &loop->lock, abstime); + loop->recurse++; loop->n_waiting--; return -ret; } @@ -445,5 +509,5 @@ SPA_EXPORT bool pw_thread_loop_in_thread(struct pw_thread_loop *loop) { - return loop->running && pthread_self() == loop->thread; + return loop->running && pthread_equal(loop->thread, pthread_self()); }
View file
pipewire-0.3.67.tar.gz/src/pipewire/utils.h -> pipewire-0.3.68.tar.gz/src/pipewire/utils.h
Changed
@@ -16,6 +16,10 @@ # include <sys/mount.h> #endif +#ifndef ENODATA +#define ENODATA 9919 +#endif + #include <spa/utils/defs.h> #include <spa/pod/pod.h>
View file
pipewire-0.3.67.tar.gz/src/tests/test-interfaces.c -> pipewire-0.3.68.tar.gz/src/tests/test-interfaces.c
Changed
@@ -42,6 +42,7 @@ void (*bound_id) (void *data, uint32_t id, uint32_t global_id); void (*add_mem) (void *data, uint32_t id, uint32_t type, int fd, uint32_t flags); void (*remove_mem) (void *data, uint32_t id); + void (*bound_props) (void *data, uint32_t id, uint32_t global_id, const struct spa_dict *props); } events = { PW_VERSION_CORE_EVENTS, }; struct pw_core_events e; @@ -68,7 +69,8 @@ TEST_FUNC(e, events, bound_id); TEST_FUNC(e, events, add_mem); TEST_FUNC(e, events, remove_mem); - spa_assert_se(PW_VERSION_CORE_EVENTS == 0); + TEST_FUNC(e, events, bound_props); + spa_assert_se(PW_VERSION_CORE_EVENTS == 1); spa_assert_se(sizeof(e) == sizeof(events)); }
View file
pipewire-0.3.67.tar.gz/src/tools/meson.build -> pipewire-0.3.68.tar.gz/src/tools/meson.build
Changed
@@ -58,16 +58,26 @@ 'pw-encplay', - executable('pw-cat', + pw_cat = executable('pw-cat', pwcat_sources, install: true, dependencies : pwcat_deps, pipewire_dep, mathlib, ) foreach alias : pwcat_aliases - dst = pipewire_bindir / alias - cmd = 'ln -fs @0@ $DESTDIR@1@'.format('pw-cat', dst) - meson.add_install_script('sh', '-c', cmd) + custom_target( + alias, + build_by_default: true, + install: false, + command: ln, '-sf', meson.project_build_root() + '/@INPUT@', '@OUTPUT@', + input: pw_cat, + output: alias, + ) + install_symlink( + alias, + pointing_to: pw_cat.name(), + install_dir: pipewire_bindir, + ) endforeach elif not sndfile_dep.found() and get_option('pw-cat').enabled() error('pw-cat is enabled but required dependency `sndfile` was not found.')
View file
pipewire-0.3.67.tar.gz/src/tools/pw-cli.c -> pipewire-0.3.68.tar.gz/src/tools/pw-cli.c
Changed
@@ -1502,7 +1502,7 @@ return global_port_found; } -static void create_link_with_properties(struct data *data, struct pw_properties *props) +static void create_link_with_properties(struct data *data, const struct pw_properties *props) { struct remote_data *rd = data->current; uint32_t id; @@ -1534,6 +1534,7 @@ char *a5; int n; struct pw_properties *props = NULL; + bool res = false; n = pw_split_ip(args, WHITESPACE, 5, a); if (n < 4) { @@ -1562,12 +1563,12 @@ global_out = find_global(rd, a0); if (global_out == NULL) { *error = spa_aprintf("%s: unknown global '%s'", cmd, a0); - return false; + goto done; } global_in = find_global(rd, a2); if (global_in == NULL) { *error = spa_aprintf("%s: unknown global '%s'", cmd, a2); - return false; + goto done; } pd_out = pw_proxy_get_user_data(global_out->proxy); @@ -1578,7 +1579,7 @@ if (n_output_ports != n_input_ports) { *error = spa_aprintf("%s: Number of ports don't match (%u != %u)", cmd, n_output_ports, n_input_ports); - return false; + goto done; } for (uint32_t i = 0; i < n_output_ports; i++) { @@ -1601,9 +1602,12 @@ } else create_link_with_properties(data, props); + res = true; + +done: pw_properties_free(props); - return true; + return res; } static bool do_export_node(struct data *data, const char *cmd, char *args, char **error) @@ -2336,8 +2340,10 @@ fprintf(stderr, "Error: \"%s\"\n", error); free(error); } - data.current->prompt_pending = pw_core_sync(data.current->core, 0, 0); - while (!data.quit && data.current) { + if (data.current != NULL) + data.current->prompt_pending = pw_core_sync(data.current->core, 0, 0); + + while (!data.quit && data.current != NULL) { pw_main_loop_run(data.loop); if (!monitor) break;
View file
pipewire-0.3.67.tar.gz/src/tools/pw-top.c -> pipewire-0.3.68.tar.gz/src/tools/pw-top.c
Changed
@@ -171,7 +171,8 @@ { uint32_t media_type, media_subtype; - spa_format_parse(param, &media_type, &media_subtype); + if (spa_format_parse(param, &media_type, &media_subtype) < 0) + goto done; switch(media_type) { case SPA_MEDIA_TYPE_audio:
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
.