diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 61e276c4d35cbae9930cb5dfd68ec98ff78b416d..2ab6a08f301941238644453fa3febd236109252f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,7 +25,7 @@ include: .fedora: variables: # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2022-03-05.0' + FDO_DISTRIBUTION_TAG: '2022-11-07.0' FDO_DISTRIBUTION_VERSION: '35' FDO_DISTRIBUTION_PACKAGES: >- alsa-lib-devel @@ -52,6 +52,7 @@ include: libv4l-devel libva-devel libX11-devel + ModemManager-devel openssl-devel pulseaudio-libs-devel python3-docutils @@ -68,6 +69,7 @@ include: python3-pip pulseaudio-utils openal-soft + readline-devel FDO_DISTRIBUTION_EXEC: >- pip3 install meson @@ -105,7 +107,7 @@ include: .alpine: variables: # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2022-01-28.2' + FDO_DISTRIBUTION_TAG: '2022-09-07.0' FDO_DISTRIBUTION_VERSION: '3.15' FDO_DISTRIBUTION_PACKAGES: >- alsa-lib-dev @@ -128,6 +130,7 @@ include: libusb-dev libx11-dev meson + modemmanager-dev ncurses-dev pulseaudio-dev readline-dev @@ -176,7 +179,6 @@ include: - ninja $NINJA_ARGS -C "$BUILD_DIR" - ninja $NINJA_ARGS -C "$BUILD_DIR" test - ninja $NINJA_ARGS -C "$BUILD_DIR" install - - ./check_missing_headers.sh artifacts: name: pipewire-$CI_COMMIT_SHA when: always @@ -223,6 +225,8 @@ build_on_ubuntu: - .fdo.distribution-image@ubuntu - .build stage: build + variables: + MESON_OPTIONS: "-Dsession-managers=[]" .build_on_fedora: extends: @@ -248,6 +252,7 @@ build_on_fedora: -Dvulkan=enabled -Dsdl2=enabled -Dsndfile=enabled + -Dsession-managers=[] artifacts: name: pipewire-$CI_COMMIT_SHA when: always @@ -262,15 +267,24 @@ build_on_alpine: - .fdo.distribution-image@alpine - .build stage: build + variables: + MESON_OPTIONS: "-Dsession-managers=[]" # build with all auto() options enabled build_all: extends: - .build_on_fedora variables: - # Fedora doesn't have libfreeaptx, lc3plus, or roc + # Fedora doesn't have libfreeaptx, lc3plus, lc3, or roc # libcamera has no stable API, so let's not chase that target - MESON_OPTIONS: "-Dauto_features=enabled -Dbluez5-codec-aptx=disabled -Dbluez5-codec-lc3plus=disabled -Droc=disabled -Dlibcamera=disabled" + MESON_OPTIONS: >- + -Dauto_features=enabled + -Dbluez5-codec-aptx=disabled + -Dbluez5-codec-lc3plus=disabled + -Dbluez5-codec-lc3=disabled + -Droc=disabled + -Dlibcamera=disabled + -Dsession-managers=[] parallel: matrix: - CC: [gcc, clang] @@ -280,7 +294,7 @@ build_with_no_commandline_options: extends: - .build_on_fedora variables: - MESON_OPTIONS: "" + MESON_OPTIONS: "-Dsession-managers=[]" parallel: matrix: - CC: [gcc, clang] @@ -296,7 +310,7 @@ build_with_custom_options: 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" + - 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 @@ -307,7 +321,7 @@ build_release: extends: - .build_on_fedora variables: - MESON_OPTIONS: "-Dtest=enabled -Dbuildtype=release -Db_ndebug=true" + MESON_OPTIONS: "-Dtest=enabled -Dbuildtype=release -Db_ndebug=true -Dsession-managers=[]" parallel: matrix: - CC: [gcc, clang] @@ -363,6 +377,8 @@ valgrind: - echo "Building with meson options $MESON_OPTIONS" - meson "$BUILD_DIR" . --prefix="$PREFIX" $MESON_OPTIONS - meson test -C "$BUILD_DIR" --setup=valgrind + variables: + MESON_OPTIONS: "-Dsession-managers=[]" build_with_coverity: extends: @@ -383,6 +399,7 @@ build_with_coverity: -Dvulkan=enabled -Dsdl2=enabled -Dsndfile=enabled + -Dsession-managers=[] - cov-configure --config coverity_conf.xml --comptype gcc --compiler cc --template --xml-option=append_arg@C:--ppp_translator @@ -432,6 +449,18 @@ doccheck: git grep -q -e "\\\subpage $page" || (echo "\\page $page is missing \\subpage entry in doc/pipewire-modules.dox" && false) done +check_missing_headers: + extends: + - .fedora + - .not_coverity + - .fdo.distribution-image@fedora + stage: analysis + dependencies: + - build_on_fedora + script: + - export PREFIX=`find -name prefix-*` + - ./.gitlab/ci/check_missing_headers.sh + pages: extends: - .not_coverity diff --git a/check_missing_headers.sh b/.gitlab/ci/check_missing_headers.sh similarity index 100% rename from check_missing_headers.sh rename to .gitlab/ci/check_missing_headers.sh diff --git a/NEWS b/NEWS index 72d442f54fffd7b342c03ad877f571490e17d987..f2c768108514ee639799e07b6ae68111d1b2720b 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,292 @@ +# PipeWire 0.3.61 (2022-11-24) + +This is a bugfix release that is API and ABI compatible with previous +0.3.x releases. + +## Highlights + - Fix a bug in audioadapter that could cause crashes when switching + bluetooth profiles. + - Fix sound in QEMU, deadbeef and openal again. + - libcamera plugin fixes, dynamic add and remove should now work with + the next wireplumber version. + - Fix a regression in pw-midiplay where the first buffer would not + play and some events would be missing. + - The network module now doesn't export other network sources + anymore. + - pulse-server now detects clients that keep underrunning for a long time + and will pause them to save power. + - Many more bugfixes and improvements. + + +## PipeWire + - Optimize away some useless graph recalculations. + - Increase alternative sample rates from 16 to 32. + - FreeBSD and musl build fixes. + - Silence some module loading errors when the error can be ignored. + - Fix initial buffer requested size for pw-stream when operating in + async mode. This also indirectly fixes the first buffer in + pw-midiplay. (#2843) + +## Modules + - Set the network property on pulse-tunnel streams so that they are + not exported anymore. (#2384) + - Filter-chain has optimized mix functions now. + +## SPA + - Handle some errors in libcamera better. + - Fix libcamera remove events. Fix the id allocation for devices. + - Fix a bug in audioadapter where it would not renegotiate after + a port reconfiguration, leading to crashes, especially when + automatically switching profiles in bluetooth. (#2764) + - Do ALSA probing in 44100Hz again. Some devices seem to fail + otherwise for some unknown reason. (#2718) + - Force playback start when the ALSA buffer is full. This fixes sound + in QEMU. (#2830) + - Support Digital 5.1 AC3 for Asus Xonar SE. + - Improve format renegotiation in audioadapter. This makes the ALSA + plugin work again for deadbeef. (#2832) + - Fix latency reporting on adapter DSP ports. + +## pulse-server + - Fix a bug where openal based applications would hang. (#2821) + - Improve zeroconf publish. Only publish on the address of the first + running server. This avoids duplicate entries for IPv4 and IPv6. + Add support for republish entries when new servers are started. + - Add a pulse.idle.timeout option (default to 5 seconds) to pause + streams that have been underrunning for this amount of time. Badly + behaving clients will then not keep the graph and device busy so + that devices can be suspended to save battery. This should give + better default behaviour with speech-dispatcher. (#2839) + +## JACK + - Add an option to configure the filter character. + - Fix connect_callbacks. It was only called once for output ports. + (#2841) + - Add option to set node.passive on jack clients. Make some quirks + for qsynth to make it suspend and fade out better. + + +Older versions: + +# PipeWire 0.3.60 (2022-11-10) + +This is a bugfix release that is API and ABI compatible with previous +0.3.x releases. + +## Highlights + - The filter-chain now handles errors better and has fixes for many + crasher bugs. + - A new RTP module was added with a sender and receiver. It uses SAP + to announce and consume RTP streams and is compatible with the + PulseAudio RTP modules. + - Many small bluetooth improvements and fixes. + - The alsa plugin will now only start playback when there is data. This + results in better sync and lower latency between capture and playback. + - The v4l2 and libcamera plugins have seen a lot of improvements. They + support control properties now. Also pw-v4l2 has seen many improvements + and mostly passes the v4l2-compliance test now. + - Many more bugfixes and improvements. + + +## PipeWire + - Code cleanups, compiler warning fixes. + - Add some extra checks to avoid scheduling an inactive node. + - Rework the sequence of events to start and stop nodes. + - Improve param enumeration. + - An option was added to give priority to the Buffer params of the + consumer. This makes it possible to use the default values of the + consumer (instead of the producer) when capturing from a source. + - The graph rate selection was improved to pick a rate closest to the + requested one (instead of picking the default). + +## Modules + - Fix some crashes in filter-chain. (#2737) + - X11 Bell module will now be loaded by default when available. + - A new RTP module was added with a sender and receiver. It uses SAP + to announce and consume RTP streams and is compatible with the + PulseAudio RTP modules. + - Improve RAOP compatibility. + - The echo-cancel module now uses the resampler prefill option to align + input and output samples without buffering. Better latency control + when starting and stopping has been implemented. + - The pulse tunnel will now write aligned samples to pulseaudio even + when the ringbuffer wraps around. This fixes playback issues with + multichannel sinks. + - Add a delay option to module-loopback using a ringbuffer. + - Implement echo-cancel params. + - The filter-chain module has better error reporting. + - The LADSPA search path was extended with some more common paths. + - The echo-canceler input can now also be a monitor of a sink. This + improves compatibility with some proton games that expect a real + sink instead of a virtual one. + +## Tools + - Better error reporting in pw-link. + - pw-top now also shows IEC958 passthrough formats and JPEG/H264 video + formats. + - pw-top refreshes the screen faster. + - pw-top now prints the state of the node and shows less info for + inactive nodes. + - pw-dump now uses the new seq field in the spa_param_info to discard + old param updates and avoid duplicate params in the output. + +## Bluetooth + - Add ModemManager support in the native backend. + - Clean up GetManagedObjects handling. + - Handle QoS from the endpoints in the codec. + - Increase the socket buffer to have more control over the rate and QoS. + - Simplify the packet flushing code. + - Stop processing nodes before destroying them. + - Fix timers when a source switches drivers. + - Codecs can now share endpoints. This reduces the amount of endpoints and + avoids problems with devices that can't handle a large amount of + codec endpoints. + - Report batery status to UPower for HFP AG. + - Fix bitpool increase. + +## SPA + - The audioresampler now avoids clicks and pops between activating and + deactivating the adaptive resampler when used by the stream API. + - Use default locale to parse float parameters. + - The upmix functions now have SSE optimizations. + - Avoid recalculating the complete channelmix setup when only the + volume changes. + - The alsa plugin will now only start playback when there is data. This + results in better sync and lower latency between capture and playback. + - The ALSA MIDI sequencer will now pull data from the graph even when it + did not output anything. Fixes some graph stalls with the sequencer in + some cases. (#2775) + - v4l2 and libcamera sources now recycle buffers when nothing is consuming + them. This avoids stalling the graph. + - libcamera now suggests a more appropriate frame size than the smallest + poster frame. + - Improve state changes in audioconvert. (#2764) + - A new seq field was added to spa_param_info to keep track of pending + param updates. + - Support speaker output only on RealTek ALC4080. (#2744) + - The v4l2 source now supports setting controls. + - The libcamera plugin now supports enumerating and setting controls. + - A new unit test for 6.1 channel mapping was added. (#2809) More debug + info was added to audioconvert for the channel matrix. + - Audioconvert will now also upmix a rear-center channel when needed. + +## pulse-server + - Add support for the RTP send and recv modules with the new native + RTP module. + - Add option to set latency for pulse-tunnel streams and + module-zeroconf-discover. + - The socket will now be given the same permissions as what pulseaudio + did (0777). + - Implement module-loopback latency_msec correctly with the new delay + parameter. + - sysfs.path is now filled with the same data as pulseaudio. + - The manager now uses the new seq field in the spa_param_info. + - Fix a bug where in some cases the read pointer would get out of sync + and cause too large requests. (#2799) + +## ALSA + - The alsa plugin now reuses the stream in prepare which results in + better performance. + - Some deadlocks have been fixed in the ALSA plugin. + - The ALSA plugin reports more accurate timing information in some cases. + +## V4l2 + - The v4l2 compatibility layer has received a lot of updates. + - Improved node names and format enumeration. + - Support for multiple /dev/videoX devices, each mapped to a unique + PipeWire node. + - Passes the v4l2-compliance test now with both the v4l2 and libcamera + backend in PipeWire. + - Improved mmap support for inline buffer memory. This makes it possible to + consume PipeWire streams. + - Negotiation works more reliably now. + +## JACK + - Implement jack_acquire_real_time_scheduling() and + jack_drop_real_time_scheduling() by keeping the thread utils in a global + state. + - Fix jack_client_thread_id() to return NULL when the client is not active, + just like jack1 and jack2. + - An option was added to let the jack_set_buffer_size() function update the + global metadata. A quirk was added so that jack_bufsize uses this new feature + to make the buffer size settings persistent and global, just like jack. + - jack_port_register() and jack_port_unregister() can be called on an + active client so make this thread safe. (#2652) + +# PipeWire 0.3.59 (2022-09-30) + +This is a bugfix release that is API and ABI compatible with previous +0.3.x releases. + +## Highlights + - Fix possible wrong samplerate in loopback streams after suspend and + rate switch. + - module-filter-chain can now adapt to the graph samplerate. + - Fix some potential stuttering and crackling in pulse-server. + - Add Bluetooth LE support. This requires experimental kernel and bluez + support. + - The ALSA plugin has more options to control the buffer size. This can + be used to work around high latency in davinci resolve. + - Many bugfixes and improvements. + + +## PipeWire + - Add audio capture example with volume meter. + - Fix a case where a rate switch would not suspend all the nodes of the + driver first. This could cause wrong samplerates in streams. + - Fix a case where a node would be Paused while still added to the + graph, causing potential crashes. (#2701) + +## Modules + - module-filter-chain and module-loopback now use the resample.prefill + option to avoid buffering extra samples and causing unwanted latency + when resampling is activated. + - module-filter-chain can now adapt to the graph samplerate. + - Improve module-raop to support the ALAC codec as raw PCM. + - Improve RTSP parsing to improve compatibility. + +## Tools + - Fix 100% CPU in pw-cli monitor mode. (#2709) + - spa-acp-tool can now be exited with ctrl-D. + +## SPA + - Various libcamera fixes and improvements. + - Set stride on audioconvert output buffers. + - Make sure we always place the last requested size from the resampler + on the buffers in pw-stream. + - Add resample.prefill option in the resampler to fill the history with + 0 so that we don't have smaller buffers at the start. + - Make sure that when an overflow corrupts a POD, that it will always + stay corrupted. + - Rate limit some ALSA warnings and reduce some unwanted warnings. + - Don't recalculate the audioconverter state for each pause/play. (#2701) + - Fix some POD parsing inconsistencies and potential overflows. + - Add support for Asus Xonar SE. + - Fix Flush command handling. It should not stop playback. (#2726) + - Refactor the peaks function and add some unit tests and optimizations. + - The channelmix has an optimized nXm converter and new unit tests. + - Normalization in the channelmixer was fixed. + +## pulse-server + - The requested latency of record streams was reduced to fix some + stuttering in Teamspeak. (#2702) + - Tweak the max amount of bytes sent to a client. (#2711) (#2715) + - Improve maxlength calculations, this fixes some crackling noise with + high samplerate and channel counts in some players (audacious). + +## Bluetooth + - Merge Bluetooth LE support. + - Make sure we are backward compatible with WirePlumber. + - Fix some HFP and HSP AT command parsing. (#2463) + - Use HFP by default over HSP. + +## ALSA + - Increase max number of periods. + - The parameters handling was improved. There is now an option to set the + buffer-bytes of the ALSA plugin. + - PIPEWIRE_ALSA can now be used as an environment variable to restrict the + plugin formats and buffer size. + # PipeWire 0.3.58 (2022-09-15) This is a bugfix release that is API and ABI compatible with previous @@ -64,9 +353,6 @@ This is a bugfix release that is API and ABI compatible with previous wakeups. (#1697) -Older versions: - - # PipeWire 0.3.57 (2022-09-02) This is a bugfix release that is API and ABI compatible with previous diff --git a/doc/pipewire-modules.dox b/doc/pipewire-modules.dox index 0b2f1f6980add4cbb34cfd7ac1bdc9d12cf23919..dfccbbc3c05428c3d681ca00d35973cfa528320f 100644 --- a/doc/pipewire-modules.dox +++ b/doc/pipewire-modules.dox @@ -73,6 +73,8 @@ List of known modules: - \subpage page_module_raop_discover - \subpage page_module_roc_sink - \subpage page_module_roc_source +- \subpage page_module_rtp_sink +- \subpage page_module_rtp_source - \subpage page_module_rt - \subpage page_module_session_manager - \subpage page_module_x11_bell diff --git a/man/pipewire.1.rst.in b/man/pipewire.1.rst.in index fd07017059bba520c9c224caf2022547c8c0cb08..8b63839696510960f4128cae1d980027d33bfb95 100644 --- a/man/pipewire.1.rst.in +++ b/man/pipewire.1.rst.in @@ -47,6 +47,8 @@ PipeWire is available from @PACKAGE_URL@ SEE ALSO ======== +``pw-top(1)``, +``pw-dump(1)``, ``pw-mon(1)``, ``pw-cat(1)``, ``pw-cli(1)``, diff --git a/man/pw-profiler.1.rst.in b/man/pw-profiler.1.rst.in index 119d8ceea4ca8004881be87ebe296a1bd8a9d005..6fb57c8eb29856b6c56366592b43099f4290acf1 100644 --- a/man/pw-profiler.1.rst.in +++ b/man/pw-profiler.1.rst.in @@ -27,6 +27,8 @@ When this program is stopped, a set of **gnuplot** files and a script to generat SVG files from the .plot files is generated, along with a .html file to visualize the profiling results in a browser. +This function uses the same data used by *pw-top*. + OPTIONS ======= @@ -52,3 +54,4 @@ SEE ALSO ======== ``pipewire(1)``, +``pw-top(1)``, diff --git a/man/pw-top.1.rst.in b/man/pw-top.1.rst.in index afd3573475a8dba839761cbff1a1d395f508cd28..ab8569b1fff750bc9b50ccf5b9a5d5194e081f1b 100644 --- a/man/pw-top.1.rst.in +++ b/man/pw-top.1.rst.in @@ -27,13 +27,15 @@ a tree-like representation. The columns presented are as follows: S - Measurement status. - ! representing inactive - no connections - - Blank representing active + Node status. + E = ERROR + C = CREATING + S = SUSPENDED + I = IDLE + R = RUNNING ID - The ID of the pipewire node/device, as found in *pw-dump* + The ID of the pipewire node/device, as found in *pw-dump* and *pw-cli* QUANT The current quantum (for drivers) and the suggested quantum for follower @@ -135,6 +137,8 @@ FORMAT For raw audio formats, the layout is <sampleformat> <channels> <samplerate>. + For IEC958 passthrough audio formats, the layout is IEC958 <codec> <samplerate>. + For DSD formats, the layout is <dsd-rate> <channels>. For Video formats, the layout is <pixelformat> <width>x<height>. @@ -168,4 +172,7 @@ SEE ALSO ======== ``pipewire(1)``, +``pw-dump(1)``, +``pw-cli(1)``, +``pw-profiler(1)``, diff --git a/meson.build b/meson.build index e7a1d8590183df74c7dfe50b8749b8a530915783..5420c10edf1d7f5d4c523d09c4f3999ec1a201af 100644 --- a/meson.build +++ b/meson.build @@ -1,9 +1,9 @@ project('pipewire', ['c' ], - version : '0.3.58', + version : '0.3.61', license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ], meson_version : '>= 0.59.0', default_options : [ 'warning_level=3', - 'c_std=gnu99', + 'c_std=gnu11', 'cpp_std=c++17', 'b_pie=true', #'b_sanitize=address,undefined', @@ -67,6 +67,7 @@ cc = meson.get_compiler('c') common_flags = [ '-fvisibility=hidden', + '-fno-strict-aliasing', '-Werror=suggest-attribute=format', '-Wsign-compare', '-Wpointer-arith', @@ -97,7 +98,7 @@ have_cpp = add_languages('cpp', native: false, required : false) if have_cpp cxx = meson.get_compiler('cpp') - cxx_flags = common_flags + cxx_flags = common_flags + [ '-Wno-c99-designator' ] add_project_arguments(cxx.get_supported_arguments(cxx_flags), language: 'cpp') endif @@ -259,10 +260,10 @@ cdata.set('HAVE_DBUS', dbus_dep.found()) sdl_dep = dependency('sdl2', required : get_option('sdl2')) summary({'SDL2 (video examples)': sdl_dep.found()}, bool_yn: true, section: 'Misc dependencies') drm_dep = dependency('libdrm', required : false) -readline_dep = dependency('readline', required : false) +readline_dep = dependency('readline', required : get_option('readline')) if not readline_dep.found() - readline_dep = cc.find_library('readline', required: false) + readline_dep = cc.find_library('readline', required : get_option('readline')) endif summary({'readline (for pw-cli)': readline_dep.found()}, bool_yn: true, section: 'Misc dependencies') diff --git a/meson_options.txt b/meson_options.txt index e6a5623e02039d23cd0fa070f44939ada40fdf49..f306ecbf92449ae98b273e963467cb9cc4699330 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -92,6 +92,10 @@ option('bluez5-backend-hfp-native', description: 'Enable HFP in native backend in bluez5 spa plugin', type: 'feature', value: 'enabled') +option('bluez5-backend-native-mm', + description: 'Enable ModemManager in native backend in bluez5 spa plugin', + type: 'feature', + value: 'disabled') option('bluez5-backend-ofono', description: 'Enable oFono HFP backend in bluez5 spa plugin (no dependency on oFono)', type: 'feature', @@ -120,6 +124,10 @@ option('bluez5-codec-opus', description: 'Enable Opus open source codec implementation', type: 'feature', value: 'auto') +option('bluez5-codec-lc3', + description: 'Enable LC3 open source codec implementation', + type: 'feature', + value: 'disabled') option('control', description: 'Enable control spa plugin integration', type: 'feature', @@ -224,7 +232,7 @@ option('libusb', option('session-managers', description : 'Session managers to build (can be [] for none or an absolute path)', type : 'array', - value : ['media-session']) + value : ['wireplumber']) option('raop', description: 'Enable module for Remote Audio Output Protocol', type: 'feature', @@ -257,3 +265,7 @@ option('flatpak', description: 'Enable Flatpak support', type: 'feature', value: 'enabled') +option('readline', + description: 'Enable code that depends on libreadline', + type: 'feature', + value: 'auto') diff --git a/pipewire-alsa/alsa-plugins/pcm_pipewire.c b/pipewire-alsa/alsa-plugins/pcm_pipewire.c index e3c58ccef4f87d21e9c87173f76687634a1b68c0..37a907b24daccda8b90217c49c976207d4492e40 100644 --- a/pipewire-alsa/alsa-plugins/pcm_pipewire.c +++ b/pipewire-alsa/alsa-plugins/pcm_pipewire.c @@ -64,6 +64,26 @@ PW_LOG_TOPIC_STATIC(alsa_log_topic, "alsa.pcm"); #define MIN_PERIOD 64 +#define MIN_PERIOD_BYTES (128) +#define MAX_PERIOD_BYTES (2*1024*1024) + +#define MIN_BUFFER_BYTES (2*MIN_PERIOD_BYTES) +#define MAX_BUFFER_BYTES (2*MAX_PERIOD_BYTES) + +struct params { + const char *node_name; + const char *server_name; + const char *playback_node; + const char *capture_node; + const char *role; + snd_pcm_format_t format; + int rate; + int channels; + int period_bytes; + int buffer_bytes; + uint32_t flags; +}; + typedef struct { snd_pcm_ioplug_t io; @@ -103,7 +123,9 @@ typedef struct { struct spa_hook stream_listener; int64_t delay; - uint64_t now; + uint64_t transfered; + uint64_t buffered; + int64_t now; uintptr_t seq; struct spa_audio_info_raw format; @@ -143,21 +165,15 @@ static int check_active(snd_pcm_ioplug_t *io) static int update_active(snd_pcm_ioplug_t *io) { snd_pcm_pipewire_t *pw = io->private_data; - bool active; - - active = check_active(io); - - if (pw->active != active) { - uint64_t val; + pw->active = check_active(io); + uint64_t val; - pw->active = active; + if (pw->active || pw->error < 0) + spa_system_eventfd_write(pw->system, io->poll_fd, 1); + else + spa_system_eventfd_read(pw->system, io->poll_fd, &val); - if (active) - spa_system_eventfd_write(pw->system, io->poll_fd, 1); - else - spa_system_eventfd_read(pw->system, io->poll_fd, &val); - } - return active; + return pw->active; } static void snd_pcm_pipewire_free(snd_pcm_pipewire_t *pw) @@ -178,6 +194,7 @@ static void snd_pcm_pipewire_free(snd_pcm_pipewire_t *pw) pw_thread_loop_destroy(pw->main_loop); free(pw->node_name); free(pw->target); + free(pw->role); snd_output_close(pw->output); fclose(pw->log_file); free(pw); @@ -212,8 +229,10 @@ static int snd_pcm_pipewire_poll_revents(snd_pcm_ioplug_t *io, return pw->error; *revents = pfds[0].revents & ~(POLLIN | POLLOUT); - if (pfds[0].revents & POLLIN && check_active(io)) + if (pfds[0].revents & POLLIN && check_active(io)) { *revents |= (io->stream == SND_PCM_STREAM_PLAYBACK) ? POLLOUT : POLLIN; + update_active(io); + } return 0; } @@ -245,7 +264,7 @@ static int snd_pcm_pipewire_delay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delay do { seq1 = SEQ_READ(pw->seq); - delay = pw->delay; + delay = pw->delay + pw->transfered; now = pw->now; if (io->stream == SND_PCM_STREAM_PLAYBACK) avail = snd_pcm_ioplug_hw_avail(io, pw->hw_ptr, io->appl_ptr); @@ -429,9 +448,8 @@ static void on_stream_process(void *data) pw_stream_get_time_n(pw->stream, &pwt, sizeof(pwt)); delay = pwt.delay; - if (pwt.rate.num != 0) { + if (pwt.rate.num != 0) delay = delay * io->rate * pwt.rate.num / pwt.rate.denom; - } before = hw_avail = snd_pcm_ioplug_hw_avail(io, pw->hw_ptr, io->appl_ptr); @@ -446,12 +464,20 @@ static void on_stream_process(void *data) SEQ_WRITE(pw->seq); + if (pw->now != pwt.now) { + pw->transfered = pw->buffered; + pw->buffered = 0; + } + xfer = snd_pcm_pipewire_process(pw, b, &hw_avail, want); pw->delay = delay; /* the buffer is now queued in the stream and consumed */ if (io->stream == SND_PCM_STREAM_PLAYBACK) - pw->delay += xfer; + pw->transfered += xfer; + + /* more then requested data transfered, use them in next iteration */ + pw->buffered = (want == 0 || pw->transfered < want) ? 0 : (pw->transfered % want); pw->now = pwt.now; SEQ_WRITE(pw->seq); @@ -542,11 +568,6 @@ static int snd_pcm_pipewire_prepare(snd_pcm_ioplug_t *io) goto done; pw->hw_params_changed = false; - if (pw->stream != NULL) { - pw_stream_destroy(pw->stream); - pw->stream = NULL; - } - props = pw_properties_new(NULL, NULL); if (props == NULL) goto error; @@ -572,13 +593,20 @@ static int snd_pcm_pipewire_prepare(snd_pcm_ioplug_t *io) pw_properties_get(props, PW_KEY_MEDIA_ROLE) == NULL) pw_properties_setf(props, PW_KEY_MEDIA_ROLE, "%s", pw->role); + params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &pw->format); + + if (pw->stream != NULL) { + pw_stream_update_properties(pw->stream, &props->dict); + pw_stream_update_params(pw->stream, params, 1); + goto done; + } + pw->stream = pw_stream_new(pw->core, pw->node_name, props); if (pw->stream == NULL) goto error; pw_stream_add_listener(pw->stream, &pw->stream_listener, &stream_events, pw); - params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &pw->format); pw->error = 0; pw_stream_connect(pw->stream, @@ -846,10 +874,9 @@ static const struct chmap_info chmap_info[] = { static enum snd_pcm_chmap_position channel_to_chmap(enum spa_audio_channel channel) { - uint32_t i; - for (i = 0; i < SPA_N_ELEMENTS(chmap_info); i++) - if (chmap_info[i].channel == channel) - return chmap_info[i].pos; + SPA_FOR_EACH_ELEMENT_VAR(chmap_info, info) + if (info->channel == channel) + return info->pos; return SND_CHMAP_UNKNOWN; } @@ -942,8 +969,7 @@ static snd_pcm_ioplug_callback_t pipewire_pcm_callback = { .query_chmaps = snd_pcm_pipewire_query_chmaps, }; -static int pipewire_set_hw_constraint(snd_pcm_pipewire_t *pw, int rate, - snd_pcm_format_t format, int channels, int period_bytes) +static int pipewire_set_hw_constraint(snd_pcm_pipewire_t *pw, struct params *p) { unsigned int access_list[] = { SND_PCM_ACCESS_MMAP_INTERLEAVED, @@ -975,26 +1001,38 @@ static int pipewire_set_hw_constraint(snd_pcm_pipewire_t *pw, int rate, int max_channels; int min_period_bytes; int max_period_bytes; + int min_buffer_bytes; + int max_buffer_bytes; int err; - if (rate > 0) { - min_rate = max_rate = rate; + if (p->rate > 0) { + min_rate = max_rate = SPA_CLAMP(p->rate, 1, MAX_RATE); } else { min_rate = 1; max_rate = MAX_RATE; } - if (channels > 0) { - min_channels = max_channels = channels; + if (p->channels > 0) { + min_channels = max_channels = SPA_CLAMP(p->channels, 1, MAX_CHANNELS); } else { min_channels = 1; max_channels = MAX_CHANNELS; } - if (period_bytes > 0) { - min_period_bytes = max_period_bytes = period_bytes; + if (p->period_bytes > 0) { + min_period_bytes = max_period_bytes = SPA_CLAMP(p->period_bytes, + MIN_PERIOD_BYTES, MAX_PERIOD_BYTES); + } else { + min_period_bytes = MIN_PERIOD_BYTES; + max_period_bytes = MAX_PERIOD_BYTES; + } + if (p->buffer_bytes > 0) { + min_buffer_bytes = max_buffer_bytes = SPA_CLAMP(p->buffer_bytes, + MIN_BUFFER_BYTES, MAX_BUFFER_BYTES); } else { - min_period_bytes = 128; - max_period_bytes = 2*1024*1024; + min_buffer_bytes = MIN_BUFFER_BYTES; + max_buffer_bytes = MAX_BUFFER_BYTES; } + if (min_period_bytes * 2 > max_buffer_bytes) + min_period_bytes = max_period_bytes = max_buffer_bytes / 2; if ((err = snd_pcm_ioplug_set_param_list(&pw->io, SND_PCM_IOPLUG_HW_ACCESS, SPA_N_ELEMENTS(access_list), access_list)) < 0 || @@ -1003,22 +1041,22 @@ static int pipewire_set_hw_constraint(snd_pcm_pipewire_t *pw, int rate, (err = snd_pcm_ioplug_set_param_minmax(&pw->io, SND_PCM_IOPLUG_HW_RATE, min_rate, max_rate)) < 0 || (err = snd_pcm_ioplug_set_param_minmax(&pw->io, SND_PCM_IOPLUG_HW_BUFFER_BYTES, - MIN_BUFFERS*min_period_bytes, - MIN_BUFFERS*max_period_bytes)) < 0 || + min_buffer_bytes, + max_buffer_bytes)) < 0 || (err = snd_pcm_ioplug_set_param_minmax(&pw->io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, min_period_bytes, max_period_bytes)) < 0 || (err = snd_pcm_ioplug_set_param_minmax(&pw->io, SND_PCM_IOPLUG_HW_PERIODS, - MIN_BUFFERS, MAX_BUFFERS)) < 0) { + MIN_BUFFERS, 1024)) < 0) { pw_log_warn("Can't set param list: %s", snd_strerror(err)); return err; } - if (format != SND_PCM_FORMAT_UNKNOWN) { + if (p->format != SND_PCM_FORMAT_UNKNOWN) { err = snd_pcm_ioplug_set_param_list(&pw->io, SND_PCM_IOPLUG_HW_FORMAT, - 1, (unsigned int *)&format); + 1, (unsigned int *)&p->format); if (err < 0) { pw_log_warn("Can't set param list: %s", snd_strerror(err)); return err; @@ -1075,60 +1113,77 @@ static cookie_io_functions_t io_funcs = { .write = log_write, }; -static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, const char *name, - const char *node_name, - const char *server_name, - const char *playback_node, - const char *capture_node, - const char *role, - snd_pcm_stream_t stream, - int mode, - uint32_t flags, - int rate, - snd_pcm_format_t format, - int channels, - int period_bytes) +static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, + struct params *p, snd_pcm_stream_t stream, int mode) { snd_pcm_pipewire_t *pw; int err; const char *str; struct pw_properties *props = NULL; struct pw_loop *loop; + uint32_t val; assert(pcmp); pw = calloc(1, sizeof(*pw)); if (!pw) return -ENOMEM; + props = pw_properties_new(NULL, NULL); + if (props == NULL) { + err = -errno; + goto error; + } + + str = getenv("PIPEWIRE_ALSA"); + if (str != NULL) { + pw_properties_update_string(props, str, strlen(str)); + if ((str = pw_properties_get(props, "alsa.format"))) + p->format = snd_pcm_format_value(str); + if ((str = pw_properties_get(props, "alsa.rate")) && + spa_atou32(str, &val, 0)) + p->rate = val; + if ((str = pw_properties_get(props, "alsa.channels")) && + spa_atou32(str, &val, 0)) + p->channels = val; + if ((str = pw_properties_get(props, "alsa.period-bytes")) && + spa_atou32(str, &val, 0)) + p->period_bytes = val; + if ((str = pw_properties_get(props, "alsa.buffer-bytes")) && + spa_atou32(str, &val, 0)) + p->buffer_bytes = val; + } + str = getenv("PIPEWIRE_REMOTE"); if (str != NULL && str[0] != '\0') - server_name = str; + p->server_name = str; str = getenv("PIPEWIRE_NODE"); pw_log_debug("%p: open name:%s stream:%s mode:%d flags:%08x rate:%d format:%s " - "channels:%d period-bytes:%d target:'%s'", pw, name, - snd_pcm_stream_name(stream), mode, flags, rate, - snd_pcm_format_name(format), channels, period_bytes, str); + "channels:%d period-bytes:%d buffer-bytes:%d target:'%s'", pw, p->node_name, + snd_pcm_stream_name(stream), mode, p->flags, p->rate, + snd_pcm_format_name(p->format), p->channels, p->period_bytes, + p->buffer_bytes, str); pw->fd = -1; pw->io.poll_fd = -1; - pw->flags = flags; + pw->flags = p->flags; pw->log_file = fopencookie(pw, "w", io_funcs); if (pw->log_file == NULL) { pw_log_error("can't create log file: %m"); - return -errno; + err = -errno; + goto error; } if ((err = snd_output_stdio_attach(&pw->output, pw->log_file, 0)) < 0) { pw_log_error("can't attach log file: %s", snd_strerror(err)); - return err; + goto error; } - if (node_name == NULL) + if (p->node_name == NULL) pw->node_name = spa_aprintf("ALSA %s", stream == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture"); else - pw->node_name = strdup(node_name); + pw->node_name = strdup(p->node_name); if (pw->node_name == NULL) { err = -errno; @@ -1140,12 +1195,12 @@ static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, const char *name, pw->target = strdup(str); else { if (stream == SND_PCM_STREAM_PLAYBACK) - pw->target = playback_node ? strdup(playback_node) : NULL; + pw->target = p->playback_node ? strdup(p->playback_node) : NULL; else - pw->target = capture_node ? strdup(capture_node) : NULL; + pw->target = p->capture_node ? strdup(p->capture_node) : NULL; } - pw->role = (role && *role) ? strdup(role) : NULL; + pw->role = (p->role && *p->role) ? strdup(p->role) : NULL; pw->main_loop = pw_thread_loop_new("alsa-pipewire", NULL); if (pw->main_loop == NULL) { @@ -1163,13 +1218,11 @@ static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, const char *name, goto error; } - props = pw_properties_new(NULL, NULL); - pw_properties_setf(props, PW_KEY_APP_NAME, "PipeWire ALSA [%s]", pw_get_prgname()); - if (server_name) - pw_properties_set(props, PW_KEY_REMOTE_NAME, server_name); + if (p->server_name) + pw_properties_set(props, PW_KEY_REMOTE_NAME, p->server_name); if ((err = pw_thread_loop_start(pw->main_loop)) < 0) goto error; @@ -1201,15 +1254,13 @@ static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, const char *name, #endif pw->io.flags |= SND_PCM_IOPLUG_FLAG_MONOTONIC; - if ((err = snd_pcm_ioplug_create(&pw->io, name, stream, mode)) < 0) + if ((err = snd_pcm_ioplug_create(&pw->io, p->node_name, stream, mode)) < 0) goto error; - - if ((err = pipewire_set_hw_constraint(pw, rate, format, channels, - period_bytes)) < 0) + if ((err = pipewire_set_hw_constraint(pw, p)) < 0) goto error; - pw_log_debug("%p: opened name:%s stream:%s mode:%d", pw, name, + pw_log_debug("%p: opened name:%s stream:%s mode:%d", pw, p->node_name, snd_pcm_stream_name(pw->io.stream), mode); *pcmp = pw->io.pcm; @@ -1217,7 +1268,7 @@ static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, const char *name, return 0; error: - pw_log_debug("%p: failed to open %s :%s", pw, name, spa_strerror(err)); + pw_log_debug("%p: failed to open %s :%s", pw, p->node_name, spa_strerror(err)); pw_properties_free(props); snd_pcm_pipewire_free(pw); return err; @@ -1228,16 +1279,7 @@ SPA_EXPORT SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) { snd_config_iterator_t i, next; - const char *node_name = NULL; - const char *server_name = NULL; - const char *playback_node = NULL; - const char *capture_node = NULL; - const char *role = NULL; - snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN; - int rate = 0; - int channels = 0; - int period_bytes = 0; - uint32_t flags = 0; + struct params params; int err; pw_init(NULL, NULL); @@ -1245,6 +1287,8 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) return -ENOTSUP; PW_LOG_TOPIC_INIT(alsa_log_topic); + spa_zero(params); + params.format = SND_PCM_FORMAT_UNKNOWN; snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); @@ -1254,35 +1298,35 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) if (spa_streq(id, "comment") || spa_streq(id, "type") || spa_streq(id, "hint")) continue; if (spa_streq(id, "name")) { - snd_config_get_string(n, &node_name); + snd_config_get_string(n, ¶ms.node_name); continue; } if (spa_streq(id, "server")) { - snd_config_get_string(n, &server_name); + snd_config_get_string(n, ¶ms.server_name); continue; } if (spa_streq(id, "playback_node")) { - snd_config_get_string(n, &playback_node); + snd_config_get_string(n, ¶ms.playback_node); continue; } if (spa_streq(id, "capture_node")) { - snd_config_get_string(n, &capture_node); + snd_config_get_string(n, ¶ms.capture_node); continue; } if (spa_streq(id, "role")) { - snd_config_get_string(n, &role); + snd_config_get_string(n, ¶ms.role); continue; } if (spa_streq(id, "exclusive")) { if (snd_config_get_bool(n)) - flags |= PW_STREAM_FLAG_EXCLUSIVE; + params.flags |= PW_STREAM_FLAG_EXCLUSIVE; continue; } if (spa_streq(id, "rate")) { long val; if (snd_config_get_integer(n, &val) == 0) - rate = val; + params.rate = val; else SNDERR("%s: invalid type", id); continue; @@ -1291,8 +1335,8 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) const char *str; if (snd_config_get_string(n, &str) == 0) { - format = snd_pcm_format_value(str); - if (format == SND_PCM_FORMAT_UNKNOWN) + params.format = snd_pcm_format_value(str); + if (*str && params.format == SND_PCM_FORMAT_UNKNOWN) SNDERR("%s: invalid value %s", id, str); } else { SNDERR("%s: invalid type", id); @@ -1303,7 +1347,7 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) long val; if (snd_config_get_integer(n, &val) == 0) - channels = val; + params.channels = val; else SNDERR("%s: invalid type", id); continue; @@ -1312,7 +1356,16 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) long val; if (snd_config_get_integer(n, &val) == 0) - period_bytes = val; + params.period_bytes = val; + else + SNDERR("%s: invalid type", id); + continue; + } + if (spa_streq(id, "buffer_bytes")) { + long val; + + if (snd_config_get_integer(n, &val) == 0) + params.buffer_bytes = val; else SNDERR("%s: invalid type", id); continue; @@ -1321,9 +1374,7 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) return -EINVAL; } - err = snd_pcm_pipewire_open(pcmp, name, node_name, server_name, playback_node, - capture_node, role, stream, mode, flags, rate, format, - channels, period_bytes); + err = snd_pcm_pipewire_open(pcmp, ¶ms, stream, mode); return err; } diff --git a/pipewire-alsa/conf/50-pipewire.conf b/pipewire-alsa/conf/50-pipewire.conf index f7e58472bda1e0b003c5a1046a762068b1e2d1f2..a3a08a61c98a00e71f2cde5b2c54369c7dd42330 100644 --- a/pipewire-alsa/conf/50-pipewire.conf +++ b/pipewire-alsa/conf/50-pipewire.conf @@ -4,9 +4,14 @@ defaults.pipewire.server "pipewire-0" defaults.pipewire.node "-1" defaults.pipewire.exclusive false defaults.pipewire.role "" +defaults.pipewire.rate 0 +defaults.pipewire.format "" +defaults.pipewire.channels 0 +defaults.pipewire.period_bytes 0 +defaults.pipewire.buffer_bytes 0 pcm.pipewire { - @args [ SERVER NODE EXCLUSIVE ROLE ] + @args [ SERVER NODE EXCLUSIVE ROLE RATE FORMAT CHANNELS PERIOD_BYTES BUFFER_BYTES ] @args.SERVER { type string default { @@ -35,7 +40,41 @@ pcm.pipewire { name defaults.pipewire.role } } - + @args.RATE { + type integer + default { + @func refer + name defaults.pipewire.rate + } + } + @args.FORMAT { + type string + default { + @func refer + name defaults.pipewire.format + } + } + @args.CHANNELS { + type integer + default { + @func refer + name defaults.pipewire.channels + } + } + @args.PERIOD_BYTES { + type integer + default { + @func refer + name defaults.pipewire.period_bytes + } + } + @args.BUFFER_BYTES { + type integer + default { + @func refer + name defaults.pipewire.buffer_bytes + } + } type pipewire server $SERVER @@ -43,6 +82,11 @@ pcm.pipewire { capture_node $NODE exclusive $EXCLUSIVE role $ROLE + rate $RATE + format $FORMAT + channels $CHANNELS + period_bytes $PERIOD_BYTES + buffer_bytes $BUFFER_BYTES hint { show on description "PipeWire Sound Server" diff --git a/pipewire-jack/jack/uuid.h b/pipewire-jack/jack/uuid.h index faa354b56f4d7492063fdfdbd4c111bc5778f73b..406c1195a85fc23ef02c85b1e0ba1e4f6fd12172 100644 --- a/pipewire-jack/jack/uuid.h +++ b/pipewire-jack/jack/uuid.h @@ -1,18 +1,18 @@ /* Copyright (C) 2013 Paul Davis - + This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License - along with this program; if not, write to the Free Software + along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ @@ -30,7 +30,7 @@ extern "C" { #define JACK_UUID_STRING_SIZE (JACK_UUID_SIZE+1) /* includes trailing null */ #define JACK_UUID_EMPTY_INITIALIZER 0 -extern jack_uuid_t jack_client_uuid_generate (); +extern jack_uuid_t jack_client_uuid_generate (void); extern jack_uuid_t jack_port_uuid_generate (uint32_t port_id); extern uint32_t jack_uuid_to_index (jack_uuid_t); diff --git a/pipewire-jack/src/pipewire-jack-extensions.h b/pipewire-jack/src/pipewire-jack-extensions.h index f75f5d31e14bb4c0c971f47ec349eb820d80e5a7..8e38e9e6f001d411546aea9cd6c92f51a6140a8b 100644 --- a/pipewire-jack/src/pipewire-jack-extensions.h +++ b/pipewire-jack/src/pipewire-jack-extensions.h @@ -24,6 +24,7 @@ #ifndef PIPEWIRE_JACK_EXTENSIONS_H #define PIPEWIRE_JACK_EXTENSIONS_H +#include <stdint.h> #ifdef __cplusplus extern "C" { diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index 7100a41b088b5d0c84aca37d99503ac17120db1b..98eede7de469ad72c999a1f40ea5977f661f2a49 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -98,6 +98,7 @@ struct globals { pthread_mutex_t lock; struct pw_array descriptions; struct spa_list free_objects; + struct spa_thread_utils *thread_utils; }; static struct globals globals; @@ -311,6 +312,7 @@ struct client { struct spa_hook proxy_listener; struct metadata *metadata; + struct metadata *settings; uint32_t node_id; uint32_t serial; @@ -401,6 +403,8 @@ struct client { int self_connect_mode; int rt_max; unsigned int fix_midi_events:1; + unsigned int global_buffer_size:1; + char filter_char; jack_position_t jack_position; jack_transport_state_t jack_state; @@ -610,13 +614,9 @@ static void free_port(struct client *c, struct port *p) { struct mix *m; - if (!p->valid) - return; - spa_list_consume(m, &p->mix, port_link) free_mix(c, m); - p->valid = false; pw_map_remove(&c->ports[p->direction], p->port_id); free_object(c, p->object); pw_properties_free(p->props); @@ -1056,7 +1056,7 @@ static inline void *get_buffer_output(struct port *p, uint32_t frames, uint32_t struct buffer *b; struct spa_data *d; - if (frames == 0) + if (frames == 0 || !p->valid) return NULL; if (SPA_UNLIKELY((mix = p->global_mix) == NULL)) @@ -2484,7 +2484,7 @@ static int client_node_port_set_mix_info(void *data, else mix->peer_port = l->port_link.our_input; - pw_log_info("peer port %p %p %p", mix->peer_port, + pw_log_debug("peer port %p %p %p", mix->peer_port, l->port_link.our_output, l->port_link.our_input); if (!l->port_link.is_complete) { @@ -2576,11 +2576,18 @@ static int impl_acquire_rt(void *object, struct spa_thread *thread, int priority return spa_thread_utils_acquire_rt(c->context.old_thread_utils, thread, priority); } +static int impl_drop_rt(void *object, struct spa_thread *thread) +{ + struct client *c = (struct client *) object; + return spa_thread_utils_drop_rt(c->context.old_thread_utils, thread); +} + static struct spa_thread_utils_methods thread_utils_impl = { SPA_VERSION_THREAD_UTILS_METHODS, .create = impl_create, .join = impl_join, .acquire_rt = impl_acquire_rt, + .drop_rt = impl_drop_rt, }; static jack_port_type_id_t string_to_type(const char *port_type) @@ -2721,6 +2728,24 @@ static const struct pw_proxy_events metadata_proxy_events = { .destroy = metadata_proxy_destroy, }; +static void settings_proxy_removed(void *data) +{ + struct client *c = data; + pw_proxy_destroy((struct pw_proxy*)c->settings->proxy); +} + +static void settings_proxy_destroy(void *data) +{ + struct client *c = data; + spa_hook_remove(&c->settings->proxy_listener); + c->settings = NULL; +} + +static const struct pw_proxy_events settings_proxy_events = { + PW_VERSION_PROXY_EVENTS, + .removed = settings_proxy_removed, + .destroy = settings_proxy_destroy, +}; static void proxy_removed(void *data) { struct object *o = data; @@ -2769,12 +2794,12 @@ static const struct pw_port_events port_events = { #define FILTER_NAME " ()[].:*$" #define FILTER_PORT " ()[].*$" -static void filter_name(char *str, const char *filter) +static void filter_name(char *str, const char *filter, char filter_char) { char *p; for (p = str; *p; p++) { if (strchr(filter, *p) != NULL) - *p = ' '; + *p = filter_char; } } @@ -2839,7 +2864,7 @@ static void registry_event_global(void *data, uint32_t id, snprintf(tmp, sizeof(tmp), "%s", str); if (c->filter_name) - filter_name(tmp, FILTER_NAME); + filter_name(tmp, FILTER_NAME, c->filter_char); ot = find_node(c, tmp); if (ot != NULL && o->node.client_id != ot->node.client_id) { @@ -2956,7 +2981,7 @@ static void registry_event_global(void *data, uint32_t id, snprintf(tmp, sizeof(tmp), "%s:%s", ot->node.name, str); if (c->filter_name) - filter_name(tmp, FILTER_PORT); + filter_name(tmp, FILTER_PORT, c->filter_char); op = find_port_by_name(c, tmp); if (op != NULL) @@ -3033,24 +3058,34 @@ static void registry_event_global(void *data, uint32_t id, if (c->metadata != NULL) goto exit; - if ((str = spa_dict_lookup(props, PW_KEY_METADATA_NAME)) != NULL && - !spa_streq(str, "default")) + if ((str = spa_dict_lookup(props, PW_KEY_METADATA_NAME)) == NULL) goto exit; - proxy = pw_registry_bind(c->registry, - id, type, PW_VERSION_METADATA, sizeof(struct metadata)); - - c->metadata = pw_proxy_get_user_data(proxy); - c->metadata->proxy = (struct pw_metadata*)proxy; - c->metadata->default_audio_sink[0] = '\0'; - c->metadata->default_audio_source[0] = '\0'; - - pw_proxy_add_listener(proxy, - &c->metadata->proxy_listener, - &metadata_proxy_events, c); - pw_metadata_add_listener(proxy, - &c->metadata->listener, - &metadata_events, c); + if (spa_streq(str, "default")) { + proxy = pw_registry_bind(c->registry, + id, type, PW_VERSION_METADATA, sizeof(struct metadata)); + + c->metadata = pw_proxy_get_user_data(proxy); + c->metadata->proxy = (struct pw_metadata*)proxy; + c->metadata->default_audio_sink[0] = '\0'; + c->metadata->default_audio_source[0] = '\0'; + + pw_proxy_add_listener(proxy, + &c->metadata->proxy_listener, + &metadata_proxy_events, c); + pw_metadata_add_listener(proxy, + &c->metadata->listener, + &metadata_events, c); + } else if (spa_streq(str, "settings")) { + proxy = pw_registry_bind(c->registry, + id, type, PW_VERSION_METADATA, sizeof(struct metadata)); + + c->settings = pw_proxy_get_user_data(proxy); + c->settings->proxy = (struct pw_metadata*)proxy; + pw_proxy_add_listener(proxy, + &c->settings->proxy_listener, + &settings_proxy_events, c); + } goto exit; } else { @@ -3303,6 +3338,8 @@ jack_client_t * jack_client_open (const char *client_name, if (client->context.old_thread_utils == NULL) client->context.old_thread_utils = pw_thread_utils_get(); + globals.thread_utils = client->context.old_thread_utils; + client->context.thread_utils.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_ThreadUtils, SPA_VERSION_THREAD_UTILS, @@ -3352,6 +3389,8 @@ jack_client_t * jack_client_open (const char *client_name, pw_properties_set(client->props, PW_KEY_NODE_LATENCY, str); if ((str = getenv("PIPEWIRE_RATE")) != NULL) pw_properties_set(client->props, PW_KEY_NODE_RATE, str); + if ((str = getenv("PIPEWIRE_LINK_PASSIVE")) != NULL) + pw_properties_set(client->props, PW_KEY_NODE_PASSIVE, str); if ((str = pw_properties_get(client->props, PW_KEY_NODE_LATENCY)) != NULL) { uint32_t num, denom; @@ -3408,9 +3447,13 @@ jack_client_t * jack_client_open (const char *client_name, client->merge_monitor = pw_properties_get_bool(client->props, "jack.merge-monitor", false); client->short_name = pw_properties_get_bool(client->props, "jack.short-name", false); client->filter_name = pw_properties_get_bool(client->props, "jack.filter-name", false); + client->filter_char = ' '; + if ((str = pw_properties_get(client->props, "jack.filter-char")) != NULL && str[0] != '\0') + client->filter_char = str[0]; client->locked_process = pw_properties_get_bool(client->props, "jack.locked-process", true); client->default_as_system = pw_properties_get_bool(client->props, "jack.default-as-system", false); client->fix_midi_events = pw_properties_get_bool(client->props, "jack.fix-midi-events", true); + client->global_buffer_size = pw_properties_get_bool(client->props, "jack.global-buffer-size", false); client->self_connect_mode = SELF_CONNECT_ALLOW; if ((str = pw_properties_get(client->props, "jack.self-connect-mode")) != NULL) { @@ -3509,10 +3552,11 @@ int jack_client_close (jack_client_t *client) pw_proxy_destroy((struct pw_proxy*)c->registry); } if (c->metadata && c->metadata->proxy) { - spa_hook_remove(&c->metadata->listener); - spa_hook_remove(&c->metadata->proxy_listener); pw_proxy_destroy((struct pw_proxy*)c->metadata->proxy); } + if (c->settings && c->settings->proxy) { + pw_proxy_destroy((struct pw_proxy*)c->settings->proxy); + } if (c->core) { spa_hook_remove(&c->core_listener); @@ -3773,14 +3817,10 @@ SPA_EXPORT jack_native_thread_t jack_client_thread_id (jack_client_t *client) { struct client *c = (struct client *) client; - void *thr; spa_return_val_if_fail(c != NULL, (pthread_t){0}); - thr = pw_data_loop_get_thread(c->loop); - if (thr == NULL) - return pthread_self(); - return (pthread_t) thr; + return (jack_native_thread_t)pw_data_loop_get_thread(c->loop); } SPA_EXPORT @@ -4137,15 +4177,22 @@ int jack_set_buffer_size (jack_client_t *client, jack_nframes_t nframes) pw_log_info("%p: buffer-size %u", client, nframes); pw_thread_loop_lock(c->context.loop); - pw_properties_setf(c->props, PW_KEY_NODE_FORCE_QUANTUM, "%u", nframes); + if (c->global_buffer_size && c->settings && c->settings->proxy) { + char val[256]; + snprintf(val, sizeof(val), "%u", nframes == 1 ? 0: nframes); + pw_metadata_set_property(c->settings->proxy, 0, + "clock.force-quantum", "", val); + } else { + pw_properties_setf(c->props, PW_KEY_NODE_FORCE_QUANTUM, "%u", nframes); - c->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; - c->info.props = &c->props->dict; + c->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; + c->info.props = &c->props->dict; - pw_client_node_update(c->node, - PW_CLIENT_NODE_UPDATE_INFO, - 0, NULL, &c->info); - c->info.change_mask = 0; + pw_client_node_update(c->node, + PW_CLIENT_NODE_UPDATE_INFO, + 0, NULL, &c->info); + c->info.change_mask = 0; + } pw_thread_loop_unlock(c->context.loop); return 0; @@ -4369,6 +4416,15 @@ jack_port_t * jack_port_register (jack_client_t *client, return (jack_port_t *) o; } +static int +do_invalidate_port(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct port *p = user_data; + p->valid = false; + return 0; +} + SPA_EXPORT int jack_port_unregister (jack_client_t *client, jack_port_t *port) { @@ -4389,6 +4445,9 @@ int jack_port_unregister (jack_client_t *client, jack_port_t *port) res = -EINVAL; goto done; } + pw_data_loop_invoke(c->loop, + do_invalidate_port, 1, NULL, 0, !c->data_locked, p); + pw_log_info("%p: port %p unregister \"%s\"", client, port, o->port.name); pw_client_node_port_update(c->node, @@ -4566,6 +4625,8 @@ void * jack_port_get_buffer (jack_port_t *port, jack_nframes_t frames) return SPA_PTROFF(d->data, offset, void); } + if (!p->valid) + return NULL; ptr = p->get_buffer(p, frames); pw_log_trace_fp("%p: port %p buffer %p empty:%u", p->client, p, ptr, p->empty_out); @@ -5113,7 +5174,7 @@ int jack_connect (jack_client_t *client, items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_INPUT_NODE, val[2]); items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_INPUT_PORT, val[3]); items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_OBJECT_LINGER, "true"); - if ((str = getenv("PIPEWIRE_LINK_PASSIVE")) != NULL && + if ((str = pw_properties_get(c->props, PW_KEY_NODE_PASSIVE)) != NULL && pw_properties_parse_bool(str)) items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_PASSIVE, "true"); @@ -5715,7 +5776,7 @@ jack_nframes_t jack_time_to_frames(const jack_client_t *client, jack_time_t usec } SPA_EXPORT -jack_time_t jack_get_time() +jack_time_t jack_get_time(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); @@ -6115,15 +6176,21 @@ int jack_client_max_real_time_priority (jack_client_t *client) SPA_EXPORT int jack_acquire_real_time_scheduling (jack_native_thread_t thread, int priority) { - pw_log_info("acquire"); - return pw_thread_utils_acquire_rt((struct spa_thread*)thread, priority); + struct spa_thread *t = (struct spa_thread*)thread; + pw_log_info("acquire %p", t); + spa_return_val_if_fail(globals.thread_utils != NULL, -1); + spa_return_val_if_fail(t != NULL, -1); + return spa_thread_utils_acquire_rt(globals.thread_utils, t, priority); } SPA_EXPORT int jack_drop_real_time_scheduling (jack_native_thread_t thread) { - pw_log_info("drop"); - return pw_thread_utils_drop_rt((struct spa_thread*)thread); + struct spa_thread *t = (struct spa_thread*)thread; + pw_log_info("drop %p", t); + spa_return_val_if_fail(globals.thread_utils != NULL, -1); + spa_return_val_if_fail(t != NULL, -1); + return spa_thread_utils_drop_rt(globals.thread_utils, t); } /** diff --git a/pipewire-jack/src/uuid.c b/pipewire-jack/src/uuid.c index 88d8bd881b919910745f0e48d26d41c54327e9b7..8584e11e2c3d91ce3ebbc8399ed7beec885a8926 100644 --- a/pipewire-jack/src/uuid.c +++ b/pipewire-jack/src/uuid.c @@ -33,7 +33,7 @@ #include <pipewire/pipewire.h> SPA_EXPORT -jack_uuid_t jack_client_uuid_generate () +jack_uuid_t jack_client_uuid_generate (void) { static uint32_t uuid_cnt = 0; jack_uuid_t uuid = 0x2; /* JackUUIDClient */; diff --git a/pipewire-v4l2/src/pipewire-v4l2.c b/pipewire-v4l2/src/pipewire-v4l2.c index d2e13dbf741e56163abc6dff0803b85c1b5f1827..59640d2c17c0285e233063ec6a9a272cc03fb67c 100644 --- a/pipewire-v4l2/src/pipewire-v4l2.c +++ b/pipewire-v4l2/src/pipewire-v4l2.c @@ -57,6 +57,7 @@ PW_LOG_TOPIC_STATIC(v4l2_log_topic, "v4l2"); #define DEFAULT_CARD "PipeWire Camera" #define DEFAULT_BUS_INFO "PipeWire" +#define MAX_DEV 32 struct file_map { void *addr; struct file *file; @@ -64,6 +65,8 @@ struct file_map { struct fd_map { int fd; +#define FD_MAP_DUP (1<<0) + uint32_t flags; struct file *file; }; @@ -73,6 +76,7 @@ struct globals { pthread_mutex_t lock; struct pw_array fd_maps; struct pw_array file_maps; + uint32_t dev_map[MAX_DEV]; }; static struct globals globals; @@ -93,6 +97,9 @@ struct buffer { struct file { int ref; + uint32_t dev_id; + uint32_t serial; + struct pw_properties *props; struct pw_thread_loop *loop; struct pw_loop *l; @@ -114,23 +121,27 @@ struct file { struct pw_stream *stream; struct spa_hook stream_listener; + enum v4l2_priority priority; + struct v4l2_format v4l2_format; uint32_t reqbufs; + int reqbufs_fd; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; uint32_t size; + uint32_t sequence; + struct pw_array buffer_maps; uint32_t last_fourcc; unsigned int running:1; + unsigned int closed:1; int fd; }; -#define MAX_PARAMS 32 - struct global_info { const char *type; uint32_t version; @@ -156,8 +167,8 @@ struct global { int changed; void *info; + struct spa_list pending_list; struct spa_list param_list; - int param_seq[MAX_PARAMS]; union { struct { @@ -173,6 +184,7 @@ struct global { struct param { struct spa_list link; uint32_t id; + int32_t seq; struct spa_pod *param; }; @@ -192,7 +204,7 @@ static uint32_t clear_params(struct spa_list *param_list, uint32_t id) } static struct param *add_param(struct spa_list *params, - int seq, int *param_seq, uint32_t id, const struct spa_pod *param) + int seq, uint32_t id, const struct spa_pod *param) { struct param *p; @@ -204,24 +216,12 @@ static struct param *add_param(struct spa_list *params, id = SPA_POD_OBJECT_ID(param); } - if (id >= MAX_PARAMS) { - pw_log_error("too big param id %d", id); - errno = EINVAL; - return NULL; - } - - if (seq != param_seq[id]) { - pw_log_debug("ignoring param %d, seq:%d != current_seq:%d", - id, seq, param_seq[id]); - errno = EBUSY; - return NULL; - } - p = malloc(sizeof(*p) + (param != NULL ? SPA_POD_SIZE(param) : 0)); if (p == NULL) return NULL; p->id = id; + p->seq = seq; if (param != NULL) { p->param = SPA_PTROFF(p, sizeof(*p), struct spa_pod); memcpy(p->param, param, SPA_POD_SIZE(param)); @@ -234,6 +234,39 @@ static struct param *add_param(struct spa_list *params, return p; } +static void update_params(struct file *file) +{ + struct param *p, *t; + struct global *node; + struct pw_node_info *info; + uint32_t i; + + if ((node = file->node) == NULL) + return; + if ((info = node->info) == NULL) + return; + + for (i = 0; i < info->n_params; i++) { + spa_list_for_each_safe(p, t, &node->pending_list, link) { + if (p->id == info->params[i].id && + p->seq != info->params[i].seq && + p->param != NULL) { + spa_list_remove(&p->link); + free(p); + } + } + } + + spa_list_consume(p, &node->pending_list, link) { + spa_list_remove(&p->link); + if (p->param == NULL) { + clear_params(&node->param_list, p->id); + free(p); + } else { + spa_list_append(&node->param_list, &p->link); + } + } +} #define ATOMIC_DEC(s) __atomic_sub_fetch(&(s), 1, __ATOMIC_SEQ_CST) #define ATOMIC_INC(s) __atomic_add_fetch(&(s), 1, __ATOMIC_SEQ_CST) @@ -247,6 +280,8 @@ static struct file *make_file(void) file->ref = 1; file->fd = -1; + file->reqbufs_fd = -1; + file->priority = V4L2_PRIORITY_DEFAULT; spa_list_init(&file->globals); pw_array_init(&file->buffer_maps, sizeof(struct buffer_map) * MAX_BUFFERS); return file; @@ -254,6 +289,8 @@ static struct file *make_file(void) static void free_file(struct file *file) { + pw_log_info("file:%d", file->fd); + if (file->loop) pw_thread_loop_stop(file->loop); @@ -282,49 +319,99 @@ static void free_file(struct file *file) static void unref_file(struct file *file) { + pw_log_debug("file:%d ref:%d", file->fd, file->ref); if (ATOMIC_DEC(file->ref) <= 0) free_file(file); } -static int add_fd_map(int fd, struct file *file) +static int add_fd_map(int fd, struct file *file, uint32_t flags) { struct fd_map *map; pthread_mutex_lock(&globals.lock); map = pw_array_add(&globals.fd_maps, sizeof(*map)); if (map != NULL) { map->fd = fd; + map->flags = flags; map->file = file; ATOMIC_INC(file->ref); + pw_log_debug("fd:%d -> file:%d ref:%d", fd, file->fd, file->ref); } pthread_mutex_unlock(&globals.lock); return 0; } +static uint32_t find_dev_for_serial(uint32_t serial) +{ + uint32_t i, res = SPA_ID_INVALID; + pthread_mutex_lock(&globals.lock); + for (i = 0; i < SPA_N_ELEMENTS(globals.dev_map); i++) { + if (globals.dev_map[i] == serial) { + res = i; + break; + } + } + pthread_mutex_unlock(&globals.lock); + return res; +} + +static bool add_dev_for_serial(uint32_t dev, uint32_t serial) +{ + pthread_mutex_lock(&globals.lock); + globals.dev_map[dev] = serial; + pthread_mutex_unlock(&globals.lock); + return true; +} + /* must be called with `globals.lock` held */ static struct fd_map *find_fd_map_unlocked(int fd) { struct fd_map *map; - pw_array_for_each(map, &globals.fd_maps) { if (map->fd == fd) { ATOMIC_INC(map->file->ref); + pw_log_debug("fd:%d find:%d ref:%d", map->fd, fd, map->file->ref); return map; } } - return NULL; } -static struct file *find_file(int fd) +static struct file *find_file(int fd, uint32_t *flags) { pthread_mutex_lock(&globals.lock); struct fd_map *map = find_fd_map_unlocked(fd); struct file *file = NULL; - if (map != NULL) + if (map != NULL) { file = map->file; + *flags = map->flags; + } + + pthread_mutex_unlock(&globals.lock); + + return file; +} + +static struct file *find_file_by_dev(uint32_t dev) +{ + struct fd_map *map = NULL, *tmp; + struct file *file = NULL; + pthread_mutex_lock(&globals.lock); + pw_array_for_each(tmp, &globals.fd_maps) { + if (tmp->file->dev_id == dev) { + if (tmp->file->closed) + tmp->file->fd = tmp->fd; + ATOMIC_INC(tmp->file->ref); + map = tmp; + pw_log_debug("dev:%d find:%d ref:%d", + tmp->file->dev_id, dev, tmp->file->ref); + break; + } + } + if (map != NULL) + file = map->file; pthread_mutex_unlock(&globals.lock); return file; @@ -339,6 +426,7 @@ static struct file *remove_fd_map(int fd) if (map != NULL) { file = map->file; + pw_log_debug("fd:%d find:%d", map->fd, fd); pw_array_remove(&globals.fd_maps, map); } @@ -350,7 +438,7 @@ static struct file *remove_fd_map(int fd) return file; } -static int add_file_map(void *addr, struct file *file) +static int add_file_map(struct file *file, void *addr) { struct file_map *map; pthread_mutex_lock(&globals.lock); @@ -448,15 +536,17 @@ static void on_sync_reply(void *data, uint32_t id, int seq) return; file->last_seq = seq; - if (file->pending_seq == seq) + if (file->pending_seq == seq) { + update_params(file); pw_thread_loop_signal(file->loop, false); + } } static void on_error(void *data, uint32_t id, int seq, int res, const char *message) { struct file *file = data; - pw_log_warn("%p: error id:%u seq:%d res:%d (%s): %s", file, + pw_log_warn("file:%d: error id:%u seq:%d res:%d (%s): %s", file->fd, id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE) { @@ -485,6 +575,8 @@ static void node_event_info(void *object, const struct pw_node_info *info) uint32_t i; info = g->info = pw_node_info_merge(g->info, info, g->changed == 0); + if (info == NULL) + return; pw_log_debug("update %d %"PRIu64, g->id, info->change_mask); @@ -512,21 +604,14 @@ static void node_event_info(void *object, const struct pw_node_info *info) continue; info->params[i].user = 0; - if (id >= MAX_PARAMS) { - pw_log_error("too big param id %d", id); - continue; - } - - if (id != SPA_PARAM_EnumFormat) - continue; - + add_param(&g->pending_list, info->params[i].seq, id, NULL); if (!(info->params[i].flags & SPA_PARAM_INFO_READ)) continue; res = pw_node_enum_params((struct pw_node*)g->proxy, - ++g->param_seq[id], id, 0, -1, NULL); + ++info->params[i].seq, id, 0, -1, NULL); if (SPA_RESULT_IS_ASYNC(res)) - g->param_seq[id] = res; + info->params[i].seq = res; } } do_resync(file); @@ -538,8 +623,8 @@ static void node_event_param(void *object, int seq, { struct global *g = object; - pw_log_debug("update param %d %d %d %d", g->id, id, seq, g->param_seq[id]); - add_param(&g->param_list, seq, g->param_seq, id, param); + pw_log_debug("update param %d %d %d", g->id, id, seq); + add_param(&g->pending_list, seq, id, param); } static const struct pw_node_events node_events = { @@ -566,6 +651,10 @@ static void proxy_destroy(void *data) struct global *g = data; spa_list_remove(&g->link); g->proxy = NULL; + if (g->file) + g->file->node = NULL; + clear_params(&g->param_list, SPA_ID_INVALID); + clear_params(&g->pending_list, SPA_ID_INVALID); } static const struct pw_proxy_events proxy_events = { @@ -582,20 +671,31 @@ static void registry_event_global(void *data, uint32_t id, const struct global_info *info = NULL; struct pw_proxy *proxy; const char *str; - - pw_log_debug("got %d %s", id, type); + uint32_t serial = SPA_ID_INVALID, dev; if (spa_streq(type, PW_TYPE_INTERFACE_Node)) { + if (file->node != NULL) return; - if (props == NULL || - ((str = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)) == NULL) || + pw_log_info("got %d %s", id, type); + + if (props == NULL) + return; + if (((str = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)) == NULL) || ((!spa_streq(str, "Video/Sink")) && (!spa_streq(str, "Video/Source")))) return; - pw_log_debug("found node %d type:%s", id, str); + if (((str = spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL)) == NULL) || + !spa_atou32(str, &serial, 10)) + return; + + dev = find_dev_for_serial(serial); + if (dev != SPA_ID_INVALID && dev != file->dev_id) + return; + + pw_log_info("found node:%d serial:%d type:%s", id, serial, str); info = &node_info; } if (info) { @@ -612,6 +712,7 @@ static void registry_event_global(void *data, uint32_t id, g->permissions = permissions; g->props = props ? pw_properties_new_dict(props) : NULL; g->proxy = proxy; + spa_list_init(&g->pending_list); spa_list_init(&g->param_list); spa_list_append(&file->globals, &g->link); @@ -627,6 +728,7 @@ static void registry_event_global(void *data, uint32_t id, if (info->init) info->init(g); + file->serial = serial; file->node = g; do_resync(file); @@ -660,17 +762,58 @@ static const struct pw_registry_events registry_events = { .global_remove = registry_event_global_remove, }; -static int v4l2_openat(int dirfd, const char *path, int oflag, mode_t mode) +static int do_dup(int oldfd, uint32_t flags) { int res; struct file *file; + uint32_t fl; - if (!spa_strstartswith(path, "/dev/video0")) + res = globals.old_fops.dup(oldfd); + if (res < 0) + return res; + + if ((file = find_file(oldfd, &fl)) != NULL) { + add_fd_map(res, file, flags | fl); + unref_file(file); + pw_log_info("fd:%d %08x -> %d (%s)", oldfd, flags, + res, strerror(res < 0 ? errno : 0)); + } + return res; +} + +static int v4l2_dup(int oldfd) +{ + return do_dup(oldfd, FD_MAP_DUP); +} + +static int v4l2_openat(int dirfd, const char *path, int oflag, mode_t mode) +{ + int res, flags; + struct file *file; + bool passthrough = true; + uint32_t dev_id = SPA_ID_INVALID; + + if (spa_strstartswith(path, "/dev/video")) { + if (spa_atou32(path+10, &dev_id, 10) && dev_id < MAX_DEV) + passthrough = false; + } + if (passthrough) return globals.old_fops.openat(dirfd, path, oflag, mode); + pw_log_info("path:%s oflag:%d mode:%d", path, oflag, mode); + + 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); + return res; + } + if ((file = make_file()) == NULL) goto error; + file->dev_id = dev_id; file->props = pw_properties_new( PW_KEY_CLIENT_API, "v4l2", NULL); @@ -711,47 +854,41 @@ static int v4l2_openat(int dirfd, const char *path, int oflag, mode_t mode) goto error_unlock; } if (file->node == NULL) { - errno = -ENOENT; + errno = ENOENT; goto error_unlock; } pw_thread_loop_unlock(file->loop); - res = file->fd = spa_system_eventfd_create(file->l->system, - SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); + flags = SPA_FD_CLOEXEC; + if (oflag & O_NONBLOCK) + flags |= SPA_FD_NONBLOCK; + + res = spa_system_eventfd_create(file->l->system, flags); if (res < 0) goto error; + file->fd = res; + pw_log_info("path:%s oflag:%d mode:%d -> %d (%s)", path, oflag, mode, res, strerror(res < 0 ? errno : 0)); - add_fd_map(res, file); + add_fd_map(res, file, 0); + add_dev_for_serial(file->dev_id, file->serial); + unref_file(file); return res; error_unlock: pw_thread_loop_unlock(file->loop); error: + res = -errno; if (file) free_file(file); - return -1; -} - -static int v4l2_dup(int oldfd) -{ - int res; - struct file *file; - res = globals.old_fops.dup(oldfd); - if (res < 0) - return res; - - if ((file = find_file(oldfd)) != NULL) { - add_fd_map(res, file); - unref_file(file); - pw_log_info("fd:%d -> %d (%s)", oldfd, - res, strerror(res < 0 ? errno : 0)); - } - return res; + pw_log_info("path:%s oflag:%d mode:%d -> %d (%s)", path, oflag, mode, + -1, spa_strerror(res)); + errno = -res; + return -1; } static int v4l2_close(int fd) @@ -761,9 +898,12 @@ static int v4l2_close(int fd) if ((file = remove_fd_map(fd)) == NULL) return globals.old_fops.close(fd); + pw_log_info("fd:%d file:%d", fd, file->fd); + if (fd != file->fd) spa_system_close(file->l->system, fd); + file->closed = true; unref_file(file); return 0; @@ -774,10 +914,24 @@ static int v4l2_close(int fd) static int vidioc_querycap(struct file *file, struct v4l2_capability *arg) { int res = 0; + const char *str = NULL; + struct pw_node_info *info; + + if (file->node == NULL) + return -EIO; + + info = file->node->info; + + if (info != NULL && info->props != NULL) { + str = spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION); + } + if (str == NULL) + str = DEFAULT_CARD; spa_scnprintf((char*)arg->driver, sizeof(arg->driver), "%s", DEFAULT_DRIVER); - spa_scnprintf((char*)arg->card, sizeof(arg->card), "%s", DEFAULT_CARD); - spa_scnprintf((char*)arg->bus_info, sizeof(arg->bus_info), "%s:%d", DEFAULT_BUS_INFO, 1); + spa_scnprintf((char*)arg->card, sizeof(arg->card), "%s", str); + spa_scnprintf((char*)arg->bus_info, sizeof(arg->bus_info), "platform:%s-%d", + DEFAULT_BUS_INFO, file->node->id); arg->version = KERNEL_VERSION(5, 2, 0); arg->device_caps = V4L2_CAP_VIDEO_CAPTURE @@ -786,7 +940,7 @@ static int vidioc_querycap(struct file *file, struct v4l2_capability *arg) arg->capabilities = arg->device_caps | V4L2_CAP_DEVICE_CAPS; memset(arg->reserved, 0, sizeof(arg->reserved)); - pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + pw_log_info("file:%d -> %d (%s)", file->fd, res, spa_strerror(res)); return res; } @@ -799,17 +953,17 @@ struct format_info { const char *desc; }; -#define MAKE_FORMAT(fcc,mt,mst,bpp,fmt) \ - { V4L2_PIX_FMT_ ## fcc, SPA_MEDIA_TYPE_ ## mt, SPA_MEDIA_SUBTYPE_ ## mst, SPA_VIDEO_FORMAT_ ## fmt, bpp, #fcc } +#define MAKE_FORMAT(fcc,mt,mst,bpp,fmt,...) \ + { V4L2_PIX_FMT_ ## fcc, SPA_MEDIA_TYPE_ ## mt, SPA_MEDIA_SUBTYPE_ ## mst, SPA_VIDEO_FORMAT_ ## fmt, bpp, __VA_ARGS__ } static const struct format_info format_info[] = { /* RGB formats */ - MAKE_FORMAT(RGB332, video, raw, 4, UNKNOWN), + MAKE_FORMAT(RGB332, video, raw, 4, UNKNOWN, "8-bit RGB 3-3-2"), MAKE_FORMAT(ARGB555, video, raw, 4, UNKNOWN), - MAKE_FORMAT(XRGB555, video, raw, 4, RGB15), + MAKE_FORMAT(XRGB555, video, raw, 4, RGB15, "16-bit XRGB 1-5-5-5"), MAKE_FORMAT(ARGB555X, video, raw, 4, UNKNOWN), - MAKE_FORMAT(XRGB555X, video, raw, 4, BGR15), - MAKE_FORMAT(RGB565, video, raw, 4, RGB16), + MAKE_FORMAT(XRGB555X, video, raw, 4, BGR15, "16-bit XRGB 1-5-5-5 BE"), + MAKE_FORMAT(RGB565, video, raw, 4, RGB16, "16-bit RGB 5-6-5"), MAKE_FORMAT(RGB565X, video, raw, 4, UNKNOWN), MAKE_FORMAT(BGR666, video, raw, 4, UNKNOWN), MAKE_FORMAT(BGR24, video, raw, 4, BGR), @@ -843,13 +997,13 @@ static const struct format_info format_info[] = { MAKE_FORMAT(UV8, video, raw, 2, UNKNOWN), /* Luminance+Chrominance formats */ - MAKE_FORMAT(YVU410, video, raw, 1, YVU9), - MAKE_FORMAT(YVU420, video, raw, 1, YV12), + MAKE_FORMAT(YVU410, video, raw, 1, YVU9, "Planar YVU 4:1:0"), + MAKE_FORMAT(YVU420, video, raw, 1, YV12, "Planar YVU 4:2:0"), MAKE_FORMAT(YVU420M, video, raw, 1, UNKNOWN), - MAKE_FORMAT(YUYV, video, raw, 2, YUY2), + MAKE_FORMAT(YUYV, video, raw, 2, YUY2, "YUYV 4:2:2"), MAKE_FORMAT(YYUV, video, raw, 2, UNKNOWN), - MAKE_FORMAT(YVYU, video, raw, 2, YVYU), - MAKE_FORMAT(UYVY, video, raw, 2, UYVY), + MAKE_FORMAT(YVYU, video, raw, 2, YVYU, "YVYU 4:2:2"), + MAKE_FORMAT(UYVY, video, raw, 2, UYVY, "UYVY 4:2:2"), MAKE_FORMAT(VYUY, video, raw, 2, UNKNOWN), MAKE_FORMAT(YUV422P, video, raw, 1, Y42B), MAKE_FORMAT(YUV411P, video, raw, 1, Y41B), @@ -859,24 +1013,24 @@ static const struct format_info format_info[] = { MAKE_FORMAT(YUV565, video, raw, 1, UNKNOWN), MAKE_FORMAT(YUV32, video, raw, 1, UNKNOWN), MAKE_FORMAT(YUV410, video, raw, 1, YUV9), - MAKE_FORMAT(YUV420, video, raw, 1, I420), - MAKE_FORMAT(YUV420M, video, raw, 1, I420), + MAKE_FORMAT(YUV420, video, raw, 1, I420, "Planar YUV 4:2:0"), + MAKE_FORMAT(YUV420M, video, raw, 1, I420, "Planar YUV 4:2:0 (N-C)"), MAKE_FORMAT(HI240, video, raw, 1, UNKNOWN), MAKE_FORMAT(HM12, video, raw, 1, UNKNOWN), MAKE_FORMAT(M420, video, raw, 1, UNKNOWN), /* two planes -- one Y, one Cr + Cb interleaved */ - MAKE_FORMAT(NV12, video, raw, 1, NV12), - MAKE_FORMAT(NV12M, video, raw, 1, NV12), - MAKE_FORMAT(NV12MT, video, raw, 1, NV12_64Z32), + MAKE_FORMAT(NV12, video, raw, 1, NV12, "Y/CbCr 4:2:0"), + MAKE_FORMAT(NV12M, video, raw, 1, NV12, "Y/CbCr 4:2:0 (N-C)"), + MAKE_FORMAT(NV12MT, video, raw, 1, NV12_64Z32, "Y/CbCr 4:2:0 (64x32 MB, N-C)"), MAKE_FORMAT(NV12MT_16X16, video, raw, 1, UNKNOWN), - MAKE_FORMAT(NV21, video, raw, 1, NV21), - MAKE_FORMAT(NV21M, video, raw, 1, NV21), - MAKE_FORMAT(NV16, video, raw, 1, NV16), - MAKE_FORMAT(NV16M, video, raw, 1, NV16), - MAKE_FORMAT(NV61, video, raw, 1, NV61), - MAKE_FORMAT(NV61M, video, raw, 1, NV61), - MAKE_FORMAT(NV24, video, raw, 1, NV24), + MAKE_FORMAT(NV21, video, raw, 1, NV21, "Y/CrCb 4:2:0"), + MAKE_FORMAT(NV21M, video, raw, 1, NV21, "Y/CrCb 4:2:0 (N-C)"), + MAKE_FORMAT(NV16, video, raw, 1, NV16, "Y/CbCr 4:2:2"), + MAKE_FORMAT(NV16M, video, raw, 1, NV16, "Y/CbCr 4:2:2 (N-C)"), + MAKE_FORMAT(NV61, video, raw, 1, NV61, "Y/CrCb 4:2:2"), + MAKE_FORMAT(NV61M, video, raw, 1, NV61, "Y/CrCb 4:2:2 (N-C)"), + MAKE_FORMAT(NV24, video, raw, 1, NV24, "Y/CbCr 4:4:4"), MAKE_FORMAT(NV42, video, raw, 1, UNKNOWN), /* Bayer formats - see http://www.siliconimaging.com/RGB%20Bayer.htm */ @@ -886,14 +1040,14 @@ static const struct format_info format_info[] = { MAKE_FORMAT(SRGGB8, video, bayer, 1, UNKNOWN), /* compressed formats */ - MAKE_FORMAT(MJPEG, video, mjpg, 1, ENCODED), - MAKE_FORMAT(JPEG, video, mjpg, 1, ENCODED), - MAKE_FORMAT(PJPG, video, mjpg, 1, ENCODED), + MAKE_FORMAT(MJPEG, video, mjpg, 1, ENCODED, "Motion-JPEG"), + MAKE_FORMAT(JPEG, video, mjpg, 1, ENCODED, "JFIF JPEG"), + MAKE_FORMAT(PJPG, video, mjpg, 1, ENCODED, "GSPCA PJPG"), MAKE_FORMAT(DV, video, dv, 1, ENCODED), MAKE_FORMAT(MPEG, video, mpegts, 1, ENCODED), - MAKE_FORMAT(H264, video, h264, 1, ENCODED), - MAKE_FORMAT(H264_NO_SC, video, h264, 1, ENCODED), - MAKE_FORMAT(H264_MVC, video, h264, 1, ENCODED), + MAKE_FORMAT(H264, video, h264, 1, ENCODED, "H.264"), + MAKE_FORMAT(H264_NO_SC, video, h264, 1, ENCODED, "H.264 (No Start Codes)"), + MAKE_FORMAT(H264_MVC, video, h264, 1, ENCODED, "H.264 MVC"), MAKE_FORMAT(H263, video, h263, 1, ENCODED), MAKE_FORMAT(MPEG1, video, mpeg1, 1, ENCODED), MAKE_FORMAT(MPEG2, video, mpeg2, 1, ENCODED), @@ -913,23 +1067,19 @@ static const struct format_info format_info[] = { static const struct format_info *format_info_from_media_type(uint32_t type, uint32_t subtype, uint32_t format) { - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) { - if ((format_info[i].media_type == type) && - (format_info[i].media_subtype == subtype) && - (format == 0 || format_info[i].format == format)) - return &format_info[i]; - } + SPA_FOR_EACH_ELEMENT_VAR(format_info, i) + if ((i->media_type == type) && + (i->media_subtype == subtype) && + (format == 0 || i->format == format)) + return i; return NULL; } static const struct format_info *format_info_from_fourcc(uint32_t fourcc) { - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) { - if (format_info[i].fourcc == fourcc) - return &format_info[i]; - } + SPA_FOR_EACH_ELEMENT_VAR(format_info, i) + if (i->fourcc == fourcc) + return i; return NULL; } @@ -940,7 +1090,7 @@ static int format_to_info(const struct v4l2_format *arg, struct spa_video_info * pw_log_info("type: %u", arg->type); pw_log_info("width: %u", arg->fmt.pix.width); pw_log_info("height: %u", arg->fmt.pix.height); - pw_log_info("fmt: %u", arg->fmt.pix.pixelformat); + pw_log_info("fmt: %.4s", (char*)&arg->fmt.pix.pixelformat); if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; @@ -1064,22 +1214,30 @@ static int info_to_fmt(const struct spa_video_info *info, struct v4l2_format *fm case SPA_MEDIA_SUBTYPE_raw: fmt->fmt.pix.width = info->info.raw.size.width; fmt->fmt.pix.height = info->info.raw.size.height; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; break; case SPA_MEDIA_SUBTYPE_mjpg: case SPA_MEDIA_SUBTYPE_jpeg: fmt->fmt.pix.width = info->info.mjpg.size.width; fmt->fmt.pix.height = info->info.mjpg.size.height; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_JPEG; break; case SPA_MEDIA_SUBTYPE_h264: fmt->fmt.pix.width = info->info.h264.size.width; fmt->fmt.pix.height = info->info.h264.size.height; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; break; default: return -EINVAL; } + if (fmt->fmt.pix.width == 0 || + fmt->fmt.pix.height == 0) + return -EINVAL; + fmt->fmt.pix.bytesperline = SPA_ROUND_UP_N(fmt->fmt.pix.width, 4) * fi->bpp; fmt->fmt.pix.sizeimage = fmt->fmt.pix.bytesperline * SPA_ROUND_UP_N(fmt->fmt.pix.height, 2); + return 0; } @@ -1108,7 +1266,7 @@ static void on_stream_param_changed(void *data, uint32_t id, const struct spa_po uint32_t n_params = 0; uint8_t buffer[4096]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - uint32_t buffers, size; + uint32_t buffers, size, stride; struct v4l2_format fmt; if (param == NULL || id != SPA_PARAM_Format) @@ -1119,18 +1277,20 @@ static void on_stream_param_changed(void *data, uint32_t id, const struct spa_po file->v4l2_format = fmt; - buffers = SPA_CLAMP(file->reqbufs, 2u, MAX_BUFFERS); - size = 0; + buffers = SPA_CLAMP(file->reqbufs, 1u, MAX_BUFFERS); + size = fmt.fmt.pix.sizeimage; + stride = fmt.fmt.pix.bytesperline; params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(buffers, - 2, MAX_BUFFERS), + 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(size, 0, INT_MAX), - SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(0, 0, INT_MAX), + SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(stride, 0, INT_MAX), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<<SPA_DATA_MemFd))); + pw_stream_update_params(file->stream, params, n_params); } @@ -1140,7 +1300,7 @@ static void on_stream_state_changed(void *data, enum pw_stream_state old, { struct file *file = data; - pw_log_info("%p: state %s", file, pw_stream_state_as_string(state)); + pw_log_info("file:%d: state %s", file->fd, pw_stream_state_as_string(state)); switch (state) { case PW_STREAM_STATE_ERROR: @@ -1165,7 +1325,8 @@ static void on_stream_add_buffer(void *data, struct pw_buffer *b) file->size = d->maxsize; - pw_log_info("%p: id:%d fd:%"PRIi64" size:%u", file, id, d->fd, file->size); + pw_log_info("file:%d: id:%d fd:%"PRIi64" size:%u offset:%u", file->fd, + id, d->fd, file->size, id * file->size); spa_zero(vb); vb.index = id; @@ -1192,7 +1353,8 @@ static void on_stream_remove_buffer(void *data, struct pw_buffer *b) static void on_stream_process(void *data) { struct file *file = data; - spa_system_eventfd_write(file->l->system, file->fd, 1); + pw_log_debug("file:%d", file->fd); + spa_system_eventfd_write(file->l->system, file->fd, 1); } static const struct pw_stream_events stream_events = { @@ -1251,7 +1413,7 @@ static int vidioc_enum_framesizes(struct file *file, struct v4l2_frmsizeenum *ar arg->discrete.width = size.width; arg->discrete.height = size.height; - pw_log_debug("count:%d %d %dx%d", count, fi->fourcc, + pw_log_debug("count:%d %.4s %dx%d", count, (char*)&fi->fourcc, size.width, size.height); if (count == arg->index) { found = true; @@ -1327,10 +1489,13 @@ static int vidioc_enum_fmt(struct file *file, struct v4l2_fmtdesc *arg) if (fi->fourcc == last_fourcc) continue; - pw_log_info("count:%d %d %d", count, fi->fourcc, last_fourcc); + pw_log_info("count:%d fourcc:%.4s last:%.4s", count, + (char*)&fi->fourcc, (char*)&last_fourcc); arg->flags = fi->format == SPA_VIDEO_FORMAT_ENCODED ? V4L2_FMT_FLAG_COMPRESSED : 0; arg->pixelformat = fi->fourcc; + snprintf((char*)arg->description, sizeof(arg->description), "%s", + fi->desc ? fi->desc : "Unknown"); last_fourcc = fi->fourcc; if (count == arg->index) { found = true; @@ -1343,7 +1508,7 @@ static int vidioc_enum_fmt(struct file *file, struct v4l2_fmtdesc *arg) if (!found) return -EINVAL; - pw_log_info("format: %u", arg->pixelformat); + pw_log_info("format: %.4s", (char*)&arg->pixelformat); pw_log_info("flags: %u", arg->type); memset(arg->reserved, 0, sizeof(arg->reserved)); @@ -1352,21 +1517,50 @@ static int vidioc_enum_fmt(struct file *file, struct v4l2_fmtdesc *arg) static int vidioc_g_fmt(struct file *file, struct v4l2_format *arg) { + struct param *p; + struct global *g = file->node; + int res; + if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; - *arg = file->v4l2_format; - return 0; + pw_thread_loop_lock(file->loop); + if (file->v4l2_format.fmt.pix.pixelformat != 0) { + *arg = file->v4l2_format; + } else { + struct v4l2_format tmp; + bool found = false; + + spa_list_for_each(p, &g->param_list, link) { + if (p->id != SPA_PARAM_EnumFormat || p->param == NULL) + continue; + + if (param_to_fmt(p->param, &tmp) < 0) + continue; + + found = true; + break; + } + if (!found) { + res = -EINVAL; + goto exit_unlock; + } + *arg = file->v4l2_format = tmp; + } + res = 0; +exit_unlock: + pw_thread_loop_unlock(file->loop); + return res; } static int score_diff(struct v4l2_format *fmt, struct v4l2_format *tmp) { - int score = 0; + int score = 0, w, h; if (fmt->fmt.pix.pixelformat != tmp->fmt.pix.pixelformat) score += 20000; - score += SPA_ABS((int)fmt->fmt.pix.width - (int)tmp->fmt.pix.width); - score += SPA_ABS((int)fmt->fmt.pix.height - (int)tmp->fmt.pix.height); - return score; + w = SPA_ABS((int)fmt->fmt.pix.width - (int)tmp->fmt.pix.width); + h = SPA_ABS((int)fmt->fmt.pix.height - (int)tmp->fmt.pix.height); + return score + (w*w) + (h*h); } static int try_format(struct file *file, struct v4l2_format *fmt) @@ -1377,6 +1571,9 @@ static int try_format(struct file *file, struct v4l2_format *fmt) int best = -1; pw_log_info("in: type: %u", fmt->type); + if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + pw_log_info("in: format: %.4s", (char*)&fmt->fmt.pix.pixelformat); pw_log_info("in: width: %u", fmt->fmt.pix.width); pw_log_info("in: height: %u", fmt->fmt.pix.height); @@ -1385,7 +1582,10 @@ static int try_format(struct file *file, struct v4l2_format *fmt) struct v4l2_format tmp; int score; - if (p->id != SPA_PARAM_EnumFormat || p->param == NULL) + if (p->param == NULL) + continue; + + if (p->id != SPA_PARAM_EnumFormat && p->id != SPA_PARAM_Format) continue; if (param_to_fmt(p->param, &tmp) < 0) @@ -1398,6 +1598,10 @@ static int try_format(struct file *file, struct v4l2_format *fmt) pw_log_debug("check: height: %u", tmp.fmt.pix.height); pw_log_debug("check: score: %d best:%d", score, best); + if (p->id == SPA_PARAM_Format) { + best_fmt = tmp; + break; + } if (best == -1 || score < best) { best = score; best_fmt = tmp; @@ -1415,6 +1619,7 @@ static int try_format(struct file *file, struct v4l2_format *fmt) static int disconnect_stream(struct file *file) { if (file->stream != NULL) { + pw_log_info("file:%d disconnect", file->fd); pw_stream_destroy(file->stream); file->stream = NULL; file->n_buffers = 0; @@ -1530,34 +1735,60 @@ static int vidioc_try_fmt(struct file *file, struct v4l2_format *arg) static int vidioc_enuminput(struct file *file, struct v4l2_input *arg) { - if (arg->index != 0) - return -EINVAL; - + uint32_t index = arg->index; spa_zero(*arg); - spa_scnprintf((char*)arg->name, sizeof(arg->name), "%s", DEFAULT_CARD); - arg->type = V4L2_INPUT_TYPE_CAMERA; - - return 0; + arg->index = index; + switch (arg->index) { + case 0: + spa_scnprintf((char*)arg->name, sizeof(arg->name), "%s", DEFAULT_CARD); + arg->type = V4L2_INPUT_TYPE_CAMERA; + break; + default: + return -EINVAL; + } + return 0; } static int vidioc_g_input(struct file *file, int *arg) { *arg = 0; - return 0; + return 0; } static int vidioc_s_input(struct file *file, int *arg) { if (*arg != 0) return -EINVAL; - return 0; + return 0; +} + +static int vidioc_g_priority(struct file *file, enum v4l2_priority *arg) +{ + *arg = file->priority; + pw_log_info("file:%d prio:%d", file->fd, *arg); + return 0; +} +static int vidioc_s_priority(struct file *file, int fd, enum v4l2_priority *arg) +{ + if (*arg > V4L2_PRIORITY_RECORD) + return -EINVAL; + + if (file->fd != fd && file->priority > *arg) + return -EINVAL; + + pw_log_info("file:%d (%d) prio:%d", file->fd, fd, *arg); + file->priority = *arg; + return 0; } -static int vidioc_reqbufs(struct file *file, struct v4l2_requestbuffers *arg) +static int vidioc_reqbufs(struct file *file, int fd, struct v4l2_requestbuffers *arg) { int res; pw_log_info("count: %u", arg->count); pw_log_info("type: %u", arg->type); pw_log_info("memory: %u", arg->memory); +#ifdef V4L2_MEMORY_FLAG_NON_COHERENT + pw_log_info("flags: %08x", arg->flags); +#endif if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; @@ -1566,17 +1797,25 @@ static int vidioc_reqbufs(struct file *file, struct v4l2_requestbuffers *arg) pw_thread_loop_lock(file->loop); + if (file->n_buffers > 0 && file->reqbufs_fd != fd) { + pw_log_info("%u fd:%d != %d", file->n_buffers, file->reqbufs_fd, fd); + res = -EBUSY; + goto exit_unlock; + } if (arg->count == 0) { if (pw_array_get_len(&file->buffer_maps, struct buffer_map) != 0) { + pw_log_info("fd:%d have maps", fd); res = -EBUSY; goto exit_unlock; } if (file->running) { + pw_log_info("fd:%d running", fd); res = -EBUSY; goto exit_unlock; } - file->reqbufs = 0; res = disconnect_stream(file); + file->reqbufs = 0; + file->reqbufs_fd = -1; } else { file->reqbufs = arg->count; @@ -1584,7 +1823,11 @@ static int vidioc_reqbufs(struct file *file, struct v4l2_requestbuffers *arg) goto exit_unlock; arg->count = file->n_buffers; + file->reqbufs_fd = fd; } +#ifdef V4L2_MEMORY_FLAG_NON_COHERENT + arg->flags = 0; +#endif #ifdef V4L2_BUF_CAP_SUPPORTS_MMAP arg->capabilities = V4L2_BUF_CAP_SUPPORTS_MMAP; #endif @@ -1593,6 +1836,8 @@ static int vidioc_reqbufs(struct file *file, struct v4l2_requestbuffers *arg) pw_log_info("result count: %u", arg->count); exit_unlock: + if (res < 0) + pw_log_info("error : %s", spa_strerror(res)); pw_thread_loop_unlock(file->loop); return res; } @@ -1645,42 +1890,52 @@ static int vidioc_qbuf(struct file *file, struct v4l2_buffer *arg) arg->flags = buf->v4l2.flags; pw_stream_queue_buffer(file->stream, buf->buf); - pw_log_debug("file:%p %d -> %d (%s)", file, arg->index, res, spa_strerror(res)); - exit: + pw_log_debug("file:%d %d -> %d (%s)", file->fd, arg->index, res, spa_strerror(res)); pw_thread_loop_unlock(file->loop); return res; } -static int vidioc_dqbuf(struct file *file, struct v4l2_buffer *arg) +static int vidioc_dqbuf(struct file *file, int fd, struct v4l2_buffer *arg) { int res = 0; struct pw_buffer *b; struct buffer *buf; uint64_t val; struct spa_data *d; + struct timespec ts; if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; if (arg->memory != V4L2_MEMORY_MMAP) return -EINVAL; + pw_log_debug("file:%d (%d) %d", file->fd, fd, + arg->index); + pw_thread_loop_lock(file->loop); if (arg->index >= file->n_buffers) { res = -EINVAL; goto exit_unlock; } - if (!file->running) { - res = -EINVAL; - goto exit_unlock; - } - b = pw_stream_dequeue_buffer(file->stream); - if (b == NULL) { - res = -EAGAIN; - goto exit_unlock; + while (true) { + if (!file->running) { + res = -EINVAL; + goto exit_unlock; + } + + b = pw_stream_dequeue_buffer(file->stream); + if (b != NULL) + break; + + pw_thread_loop_unlock(file->loop); + res = spa_system_eventfd_read(file->l->system, fd, &val); + pw_thread_loop_lock(file->loop); + if (res < 0) + goto exit_unlock; } - spa_system_eventfd_read(file->l->system, file->fd, &val); + buf = b->user_data; d = &buf->buf->buffer->datas[0]; @@ -1689,13 +1944,20 @@ static int vidioc_dqbuf(struct file *file, struct v4l2_buffer *arg) SPA_FLAG_UPDATE(buf->v4l2.flags, V4L2_BUF_FLAG_ERROR, SPA_FLAG_IS_SET(d->chunk->flags, SPA_CHUNK_FLAG_CORRUPTED)); + SPA_FLAG_SET(buf->v4l2.flags, V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC); + clock_gettime(CLOCK_MONOTONIC, &ts); + buf->v4l2.timestamp.tv_sec = ts.tv_sec; + buf->v4l2.timestamp.tv_usec = ts.tv_nsec / 1000; + + buf->v4l2.field = V4L2_FIELD_NONE; buf->v4l2.bytesused = d->chunk->size; + buf->v4l2.sequence = file->sequence++; *arg = buf->v4l2; exit_unlock: + pw_log_debug("file:%d (%d) %d -> %d (%s)", file->fd, fd, + arg->index, res, spa_strerror(res)); pw_thread_loop_unlock(file->loop); - - pw_log_debug("file:%p %d -> %d (%s)", file, arg->index, res, spa_strerror(res)); return res; } @@ -1703,8 +1965,6 @@ static int vidioc_streamon(struct file *file, int *arg) { int res; - pw_log_info("file:%p -> %d", file, *arg); - if (*arg != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; @@ -1723,28 +1983,34 @@ static int vidioc_streamon(struct file *file, int *arg) exit_unlock: pw_thread_loop_unlock(file->loop); - pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + pw_log_info("file:%d -> %d (%s)", file->fd, res, spa_strerror(res)); return res; } static int vidioc_streamoff(struct file *file, int *arg) { int res; + uint32_t i; if (*arg != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; pw_thread_loop_lock(file->loop); + for (i = 0; i < file->n_buffers; i++) { + struct buffer *buf = &file->buffers[i]; + SPA_FLAG_CLEAR(buf->v4l2.flags, V4L2_BUF_FLAG_QUEUED); + } if (!file->running) { res = 0; goto exit_unlock; } res = pw_stream_set_active(file->stream, false); file->running = false; + file->sequence = 0; exit_unlock: pw_thread_loop_unlock(file->loop); - pw_log_info("file:%p -> %d (%s)", file, res, spa_strerror(res)); + pw_log_info("file:%d -> %d (%s)", file->fd, res, spa_strerror(res)); return res; } @@ -1752,8 +2018,9 @@ static int v4l2_ioctl(int fd, unsigned long int request, void *arg) { int res; struct file *file; + uint32_t flags; - if ((file = find_file(fd)) == NULL) + if ((file = find_file(fd, &flags)) == NULL) return globals.old_fops.ioctl(fd, request, arg); #if defined(__FreeBSD__) || defined(__MidnightBSD__) @@ -1765,6 +2032,9 @@ static int v4l2_ioctl(int fd, unsigned long int request, void *arg) goto done; } + if (flags & FD_MAP_DUP) + fd = file->fd; + switch (request & 0xffffffff) { case VIDIOC_QUERYCAP: res = vidioc_querycap(file, (struct v4l2_capability *)arg); @@ -1793,8 +2063,14 @@ static int v4l2_ioctl(int fd, unsigned long int request, void *arg) case VIDIOC_S_INPUT: res = vidioc_s_input(file, (int *)arg); break; + case VIDIOC_G_PRIORITY: + res = vidioc_g_priority(file, (enum v4l2_priority *)arg); + break; + case VIDIOC_S_PRIORITY: + res = vidioc_s_priority(file, fd, (enum v4l2_priority *)arg); + break; case VIDIOC_REQBUFS: - res = vidioc_reqbufs(file, (struct v4l2_requestbuffers *)arg); + res = vidioc_reqbufs(file, fd, (struct v4l2_requestbuffers *)arg); break; case VIDIOC_QUERYBUF: res = vidioc_querybuf(file, (struct v4l2_buffer *)arg); @@ -1803,7 +2079,7 @@ static int v4l2_ioctl(int fd, unsigned long int request, void *arg) res = vidioc_qbuf(file, (struct v4l2_buffer *)arg); break; case VIDIOC_DQBUF: - res = vidioc_dqbuf(file, (struct v4l2_buffer *)arg); + res = vidioc_dqbuf(file, fd, (struct v4l2_buffer *)arg); break; case VIDIOC_STREAMON: res = vidioc_streamon(file, (int *)arg); @@ -1820,8 +2096,8 @@ done: errno = -res; res = -1; } - pw_log_debug("fd:%d request:%lx nr:%d arg:%p -> %d (%s)", - fd, request, (int)_IOC_NR(request), arg, + pw_log_debug("file:%d fd:%d request:%lx nr:%d arg:%p -> %d (%s)", + file->fd, fd, request, (int)_IOC_NR(request), arg, res, strerror(res < 0 ? errno : 0)); unref_file(file); @@ -1838,8 +2114,9 @@ static void *v4l2_mmap(void *addr, size_t length, int prot, struct pw_map_range range; struct buffer *buf; struct spa_data *data; + uint32_t fl; - if ((file = find_file(fd)) == NULL) + if ((file = find_file(fd, &fl)) == NULL) return globals.old_fops.mmap(addr, length, prot, flags, fd, offset); pw_thread_loop_lock(file->loop); @@ -1864,14 +2141,19 @@ static void *v4l2_mmap(void *addr, size_t length, int prot, if (!SPA_FLAG_IS_SET(data->flags, SPA_DATA_FLAG_WRITABLE)) prot &= ~PROT_WRITE; - res = globals.old_fops.mmap(addr, range.size, prot, flags, data->fd, range.offset); + if (data->data == NULL) + res = globals.old_fops.mmap(addr, range.size, prot, flags, data->fd, range.offset); + else + res = data->data; - add_file_map(file, addr); - add_buffer_map(file, addr, id); + add_file_map(file, res); + add_buffer_map(file, res, id); SPA_FLAG_SET(buf->v4l2.flags, V4L2_BUF_FLAG_MAPPED); - pw_log_info("addr:%p length:%u prot:%d flags:%d fd:%"PRIi64" offset:%u -> %p (%s)" , - addr, range.size, prot, flags, data->fd, range.offset, + pw_log_info("file:%d addr:%p length:%zu prot:%d flags:%d fd:%"PRIi64 + " offset:%"PRIi64" (%u - %u) -> %p (%s)" , + file->fd, addr, length, prot, flags, data->fd, offset, + range.offset, range.size, res, strerror(res == MAP_FAILED ? errno : 0)); error_unlock: @@ -1884,7 +2166,9 @@ static int v4l2_munmap(void *addr, size_t length) { int res; struct buffer_map *bmap; + struct buffer *buf; struct file *file; + struct spa_data *data; if ((file = remove_file_map(addr)) == NULL) return globals.old_fops.munmap(addr, length); @@ -1896,12 +2180,18 @@ static int v4l2_munmap(void *addr, size_t length) res = -EINVAL; goto exit_unlock; } - res = globals.old_fops.munmap(addr, length); + buf = &file->buffers[bmap->id]; + data = &buf->buf->buffer->datas[0]; + + if (data->data == NULL) + res = globals.old_fops.munmap(addr, length); + else + res = 0; pw_log_info("addr:%p length:%zu -> %d (%s)", addr, length, res, strerror(res < 0 ? errno : 0)); - file->buffers[bmap->id].v4l2.flags &= ~V4L2_BUF_FLAG_MAPPED; + buf->v4l2.flags &= ~V4L2_BUF_FLAG_MAPPED; remove_buffer_map(file, bmap); exit_unlock: diff --git a/pipewire-v4l2/src/pw-v4l2.in b/pipewire-v4l2/src/pw-v4l2.in index a383ba2305e400bd609cbdb8730d97cab5bdd59f..248d9ee60086984252ed9e4e180e7bee02b37c20 100755 --- a/pipewire-v4l2/src/pw-v4l2.in +++ b/pipewire-v4l2/src/pw-v4l2.in @@ -54,10 +54,16 @@ done shift $(( OPTIND - 1 )) +if [ "$PW_UNINSTALLED" = 1 ] ; then + PW_V4L2_LD_PRELOAD="$PW_BUILDDIR"'/pipewire-v4l2/src/libpw-v4l2.so' +else + PW_V4L2_LD_PRELOAD='@LIBV4L2_PATH@/libpw-v4l2.so' +fi + if [ "$LD_PRELOAD" = "" ] ; then - LD_PRELOAD='@LIBV4L2_PATH@/libpw-v4l2.so' + LD_PRELOAD="$PW_V4L2_LD_PRELOAD" else - LD_PRELOAD="$LD_PRELOAD "'@LIBV4L2_PATH@/libpw-v4l2.so' + LD_PRELOAD="$LD_PRELOAD $PW_V4L2_LD_PRELOAD" fi export LD_PRELOAD diff --git a/po/cs.po b/po/cs.po index 1020942eee6c36b441f27b99836ef744427bb998..b148320a896f9298dbced45dd1848b6210715101 100644 --- a/po/cs.po +++ b/po/cs.po @@ -8,10 +8,10 @@ msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx\n" -"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: 2021-10-12 14:18+0200\n" +"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" +"issues\n" +"POT-Creation-Date: 2022-09-15 15:26+0000\n" +"PO-Revision-Date: 2022-10-21 16:44+0200\n" "Last-Translator: Daniel Rusek <mail@asciiwolf.com>\n" "Language-Team: ÄeÅ¡tina <gnome-cs-list@gnome.org>\n" "Language: cs\n" @@ -19,9 +19,9 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" -"X-Generator: Poedit 3.0\n" +"X-Generator: Poedit 3.1.1\n" -#: src/daemon/pipewire.c:43 +#: src/daemon/pipewire.c:46 #, c-format msgid "" "%s [options]\n" @@ -42,36 +42,52 @@ msgstr "Multimediálnà systém PipeWire" msgid "Start the PipeWire Media System" msgstr "Spustit multimediálnà systém PipeWire" -#: src/examples/media-session/alsa-monitor.c:526 -#: spa/plugins/alsa/acp/compat.c:187 -msgid "Built-in Audio" -msgstr "VnitÅ™nà zvukový systém" +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:180 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:180 +#, c-format +msgid "Tunnel to %s/%s" +msgstr "Tunel do %s/%s" -#: src/examples/media-session/alsa-monitor.c:530 -#: spa/plugins/alsa/acp/compat.c:192 -msgid "Modem" -msgstr "Modem" +#: src/modules/module-fallback-sink.c:51 +#| msgid "Game Output" +msgid "Dummy Output" +msgstr "PÅ™edstÃraný výstup" -#: src/examples/media-session/alsa-monitor.c:539 +#: src/modules/module-pulse-tunnel.c:662 +#, c-format +msgid "Tunnel for %s@%s" +msgstr "Tunel pro %s@%s" + +#: src/modules/module-zeroconf-discover.c:332 msgid "Unknown device" msgstr "Neznámé zaÅ™ÃzenÃ" -#: src/tools/pw-cat.c:991 +#: src/modules/module-zeroconf-discover.c:344 +#, c-format +msgid "%s on %s@%s" +msgstr "%s na %s@%s" + +#: src/modules/module-zeroconf-discover.c:348 +#, c-format +msgid "%s on %s" +msgstr "%s na %s" + +#: src/tools/pw-cat.c:784 #, 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 [volby] <soubor>\n" +"%s [volby] [<soubor>|-]\n" " -h, --help Zobrazit tuto nápovÄ›du\n" " --version Zobrazit verzi\n" " -v, --verbose Povolit podrobné operace\n" "\n" -#: src/tools/pw-cat.c:998 +#: src/tools/pw-cat.c:791 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -85,7 +101,7 @@ msgid "" " 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ázev vzdáleného démonu\n" @@ -102,10 +118,10 @@ msgstr "" " nebo pÅ™Ãmé vzorky (256)\n" " frekvence je stejná jako u " "zdrojového souboru\n" -" --list-targets Zobrazit dostupné cÃle pro --target\n" +" -P --properties Nastavit vlastnosti uzlu\n" "\n" -#: src/tools/pw-cat.c:1016 +#: src/tools/pw-cat.c:809 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -141,19 +157,21 @@ msgstr "" "je %d)\n" "\n" -#: src/tools/pw-cat.c:1033 +#: src/tools/pw-cat.c:826 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" +" -d, --dsd DSD mode\n" "\n" msgstr "" " -p, --playback Playback mód\n" " -r, --record Recording mód\n" " -m, --midi Midi mód\n" +" -d, --dsd DSD mód\n" "\n" -#: src/tools/pw-cli.c:2932 +#: src/tools/pw-cli.c:2255 #, c-format msgid "" "%s [options] [command]\n" @@ -171,199 +189,195 @@ msgstr "" " -r, --remote Název vzdáleného démonu\n" "\n" -#: spa/plugins/alsa/acp/acp.c:290 +#: spa/plugins/alsa/acp/acp.c:321 msgid "Pro Audio" 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 +#: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 +#: spa/plugins/bluez5/bluez5-device.c:1236 msgid "Off" msgstr "Vypnuto" -#: spa/plugins/alsa/acp/channelmap.h:466 -msgid "(invalid)" -msgstr "(neplatné)" - -#: spa/plugins/alsa/acp/alsa-mixer.c:2709 +#: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Vstup" -#: spa/plugins/alsa/acp/alsa-mixer.c:2710 +#: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Vstup dokovacà stanice" -#: spa/plugins/alsa/acp/alsa-mixer.c:2711 +#: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Mikrofon dokovacà stanice" -#: spa/plugins/alsa/acp/alsa-mixer.c:2712 +#: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Linkový vstup dokovacà stanice" -#: 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 "Linkový vstup" -#: spa/plugins/alsa/acp/alsa-mixer.c:2714 -#: spa/plugins/alsa/acp/alsa-mixer.c:2798 -#: spa/plugins/bluez5/bluez5-device.c:1145 +#: spa/plugins/alsa/acp/alsa-mixer.c:2657 +#: spa/plugins/alsa/acp/alsa-mixer.c:2741 +#: spa/plugins/bluez5/bluez5-device.c:1454 msgid "Microphone" msgstr "Mikrofon" -#: 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 "PÅ™ednà mikrofon" -#: 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 "Zadnà mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2717 +#: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Externà mikrofon" -#: 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 "Internà mikrofon" -#: 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 "Rádio" -#: 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 "Obraz" -#: spa/plugins/alsa/acp/alsa-mixer.c:2721 +#: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Automatické Å™Ãzenà zesÃlenÃ" -#: spa/plugins/alsa/acp/alsa-mixer.c:2722 +#: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Bez automatického Å™Ãzenà zesÃlenÃ" -#: spa/plugins/alsa/acp/alsa-mixer.c:2723 +#: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "ZdůraznÄ›nÃ" -#: spa/plugins/alsa/acp/alsa-mixer.c:2724 +#: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Bez zdůraznÄ›nÃ" -#: spa/plugins/alsa/acp/alsa-mixer.c:2725 +#: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "ZesilovaÄ" -#: spa/plugins/alsa/acp/alsa-mixer.c:2726 +#: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Bez zesilovaÄe" -#: spa/plugins/alsa/acp/alsa-mixer.c:2727 +#: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "ZdůraznÄ›nà basů" -#: spa/plugins/alsa/acp/alsa-mixer.c:2728 +#: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Bez zdůraznÄ›nà basů" -#: spa/plugins/alsa/acp/alsa-mixer.c:2729 -#: spa/plugins/bluez5/bluez5-device.c:1150 +#: spa/plugins/alsa/acp/alsa-mixer.c:2672 +#: spa/plugins/bluez5/bluez5-device.c:1460 msgid "Speaker" msgstr "Reproduktor" -#: 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 "Sluchátka" -#: spa/plugins/alsa/acp/alsa-mixer.c:2797 +#: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Analogový vstup" -#: spa/plugins/alsa/acp/alsa-mixer.c:2801 +#: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Dokovacà mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2803 +#: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Mikrofon náhlavnà soupravy" -#: spa/plugins/alsa/acp/alsa-mixer.c:2807 +#: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Analogový výstup" -#: spa/plugins/alsa/acp/alsa-mixer.c:2809 +#: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Sluchátka 2" -#: spa/plugins/alsa/acp/alsa-mixer.c:2810 +#: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Sluchátkový výstup mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:2811 +#: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Linkový výstup" -#: spa/plugins/alsa/acp/alsa-mixer.c:2812 +#: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Analogový výstup mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:2813 +#: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Reproduktory" -#: 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 "Digitálnà výstup (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2816 +#: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Digitálnà vstup (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2817 +#: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "VÃcekanálový vstup" -#: spa/plugins/alsa/acp/alsa-mixer.c:2818 +#: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "VÃcekanálový výstup" -#: spa/plugins/alsa/acp/alsa-mixer.c:2819 +#: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Hernà výstup" -#: 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 "KomunikaÄnà výstup" -#: spa/plugins/alsa/acp/alsa-mixer.c:2822 +#: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "KomunikaÄnà vstup" -#: spa/plugins/alsa/acp/alsa-mixer.c:2823 +#: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Virtuálnà surround 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4527 +#: spa/plugins/alsa/acp/alsa-mixer.c:4471 msgid "Analog Mono" msgstr "Analogové mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4528 +#: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Analog Mono (Left)" msgstr "Analogové mono (levé)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4529 +#: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Mono (Right)" msgstr "Analogové mono (pravé)" @@ -372,147 +386,147 @@ msgstr "Analogové mono (pravé)" #. * 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 "Analogové stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4531 +#: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Mono" msgstr "Mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4532 +#: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Stereo" msgstr "Stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4540 -#: spa/plugins/alsa/acp/alsa-mixer.c:4698 -#: spa/plugins/bluez5/bluez5-device.c:1135 +#: spa/plugins/alsa/acp/alsa-mixer.c:4484 +#: spa/plugins/alsa/acp/alsa-mixer.c:4642 +#: spa/plugins/bluez5/bluez5-device.c:1442 msgid "Headset" msgstr "Náhlavnà souprava" -#: 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 "Hlasitý odposlech" -#: 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 "VÃce kanálů" -#: spa/plugins/alsa/acp/alsa-mixer.c:4544 +#: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Analog Surround 2.1" msgstr "Analogový Surround 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4545 +#: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Analog Surround 3.0" msgstr "Analogový Surround 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4546 +#: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Analog Surround 3.1" msgstr "Analogový Surround 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4547 +#: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Analog Surround 4.0" msgstr "Analogový Surround 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4548 +#: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Analog Surround 4.1" msgstr "Analogový Surround 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4549 +#: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Analog Surround 5.0" msgstr "Analogový Surround 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4550 +#: spa/plugins/alsa/acp/alsa-mixer.c:4494 msgid "Analog Surround 5.1" msgstr "Analogový Surround 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4551 +#: spa/plugins/alsa/acp/alsa-mixer.c:4495 msgid "Analog Surround 6.0" msgstr "Analogový Surround 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4552 +#: spa/plugins/alsa/acp/alsa-mixer.c:4496 msgid "Analog Surround 6.1" msgstr "Analogový Surround 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4553 +#: spa/plugins/alsa/acp/alsa-mixer.c:4497 msgid "Analog Surround 7.0" msgstr "Analogový Surround 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4554 +#: spa/plugins/alsa/acp/alsa-mixer.c:4498 msgid "Analog Surround 7.1" msgstr "Analogový Surround 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4555 +#: spa/plugins/alsa/acp/alsa-mixer.c:4499 msgid "Digital Stereo (IEC958)" msgstr "Digitálnà stereo (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 "Digitálnà Surround 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 "Digitálnà Surround 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 "Digitálnà Surround 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 "Digitálnà stereo (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4560 +#: spa/plugins/alsa/acp/alsa-mixer.c:4504 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digitálnà Surround 5.1 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4561 +#: spa/plugins/alsa/acp/alsa-mixer.c:4505 msgid "Chat" msgstr "Chat" -#: spa/plugins/alsa/acp/alsa-mixer.c:4562 +#: spa/plugins/alsa/acp/alsa-mixer.c:4506 msgid "Game" msgstr "Hra" -#: spa/plugins/alsa/acp/alsa-mixer.c:4696 +#: spa/plugins/alsa/acp/alsa-mixer.c:4640 msgid "Analog Mono Duplex" msgstr "Analogové duplexnà mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4697 +#: spa/plugins/alsa/acp/alsa-mixer.c:4641 msgid "Analog Stereo Duplex" msgstr "Analogové duplexnà stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4700 +#: spa/plugins/alsa/acp/alsa-mixer.c:4644 msgid "Digital Stereo Duplex (IEC958)" msgstr "Digitálnà duplexnà stereo (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4701 +#: spa/plugins/alsa/acp/alsa-mixer.c:4645 msgid "Multichannel Duplex" msgstr "VÃcekanálový duplex" -#: spa/plugins/alsa/acp/alsa-mixer.c:4702 +#: spa/plugins/alsa/acp/alsa-mixer.c:4646 msgid "Stereo Duplex" msgstr "Duplexnà stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4703 +#: spa/plugins/alsa/acp/alsa-mixer.c:4647 msgid "Mono Chat + 7.1 Surround" msgstr "Mono Chat + 7.1 Surround" -#: spa/plugins/alsa/acp/alsa-mixer.c:4806 +#: spa/plugins/alsa/acp/alsa-mixer.c:4754 #, c-format msgid "%s Output" msgstr "Výstup %s" -#: spa/plugins/alsa/acp/alsa-mixer.c:4813 +#: spa/plugins/alsa/acp/alsa-mixer.c:4761 #, c-format msgid "%s Input" msgstr "Vstup %s" -#: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 +#: spa/plugins/alsa/acp/alsa-util.c:1187 spa/plugins/alsa/acp/alsa-util.c:1281 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -540,16 +554,16 @@ msgstr[2] "" "S nejvÄ›tšà pravdÄ›podobnostà se jedná o chybu v ovladaÄi ALSA „%s“. Nahlaste " "prosÃm tento problém vývojářům ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1241 +#: spa/plugins/alsa/acp/alsa-util.c:1253 #, 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." msgstr[0] "" @@ -568,7 +582,7 @@ msgstr[2] "" "S nejvÄ›tšà pravdÄ›podobnostà se jedná o chybu v ovladaÄi ALSA „%s“. Nahlaste " "prosÃm tento problém vývojářům ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1288 +#: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -581,7 +595,7 @@ msgstr "" "S nejvÄ›tšà pravdÄ›podobnostà se jedná o chybu v ovladaÄi ALSA „%s“. Nahlaste " "prosÃm tento problém vývojářům ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1331 +#: spa/plugins/alsa/acp/alsa-util.c:1343 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -609,61 +623,109 @@ msgstr[2] "" "S nejvÄ›tšà pravdÄ›podobnostà se jedná o chybu v ovladaÄi ALSA „%s“. Nahlaste " "prosÃm tento problém vývojářům ALSA." -#: spa/plugins/bluez5/bluez5-device.c:1010 +#: spa/plugins/alsa/acp/channelmap.h:457 +msgid "(invalid)" +msgstr "(neplatné)" + +#: spa/plugins/alsa/acp/compat.c:189 +msgid "Built-in Audio" +msgstr "VnitÅ™nà zvukový systém" + +#: spa/plugins/alsa/acp/compat.c:194 +msgid "Modem" +msgstr "Modem" + +#: spa/plugins/bluez5/bluez5-device.c:1247 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Zvuková brána (A2DP Source & HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1033 +#: spa/plugins/bluez5/bluez5-device.c:1272 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "High Fidelity Playback (A2DP Sink, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1035 +#: spa/plugins/bluez5/bluez5-device.c:1275 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "High Fidelity Duplex (A2DP Source/Sink, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1041 +#: spa/plugins/bluez5/bluez5-device.c:1283 msgid "High Fidelity Playback (A2DP Sink)" msgstr "High Fidelity Playback (A2DP Sink)" -#: spa/plugins/bluez5/bluez5-device.c:1043 +#: spa/plugins/bluez5/bluez5-device.c:1285 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "High Fidelity Duplex (A2DP Source/Sink)" -#: spa/plugins/bluez5/bluez5-device.c:1070 +#: spa/plugins/bluez5/bluez5-device.c:1322 +#, c-format +#| msgid "High Fidelity Playback (A2DP Sink, codec %s)" +msgid "High Fidelity Playback (BAP Sink, codec %s)" +msgstr "High Fidelity Playback (BAP Sink, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1326 +#, c-format +#| msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" +msgid "High Fidelity Input (BAP Source, codec %s)" +msgstr "High Fidelity Input (BAP Source, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1330 +#, c-format +#| msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" +msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" +msgstr "High Fidelity Duplex (BAP Source/Sink, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1359 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Jednotka náhlavnà soupravy (HSP/HFP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1074 +#: spa/plugins/bluez5/bluez5-device.c:1364 msgid "Headset Head Unit (HSP/HFP)" msgstr "Jednotka náhlavnà soupravy (HSP/HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1140 +#: spa/plugins/bluez5/bluez5-device.c:1443 +#: spa/plugins/bluez5/bluez5-device.c:1448 +#: spa/plugins/bluez5/bluez5-device.c:1455 +#: spa/plugins/bluez5/bluez5-device.c:1461 +#: spa/plugins/bluez5/bluez5-device.c:1467 +#: spa/plugins/bluez5/bluez5-device.c:1473 +#: spa/plugins/bluez5/bluez5-device.c:1479 +#: spa/plugins/bluez5/bluez5-device.c:1485 +#: spa/plugins/bluez5/bluez5-device.c:1491 msgid "Handsfree" msgstr "Handsfree" -#: spa/plugins/bluez5/bluez5-device.c:1155 +#: spa/plugins/bluez5/bluez5-device.c:1449 +#| msgid "Handsfree" +msgid "Handsfree (HFP)" +msgstr "Handsfree (HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:1466 msgid "Headphone" msgstr "Sluchátko" -#: spa/plugins/bluez5/bluez5-device.c:1160 +#: spa/plugins/bluez5/bluez5-device.c:1472 msgid "Portable" msgstr "PÅ™enosné zaÅ™ÃzenÃ" -#: spa/plugins/bluez5/bluez5-device.c:1165 +#: spa/plugins/bluez5/bluez5-device.c:1478 msgid "Car" msgstr "Auto" -#: spa/plugins/bluez5/bluez5-device.c:1170 +#: spa/plugins/bluez5/bluez5-device.c:1484 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:1175 +#: spa/plugins/bluez5/bluez5-device.c:1490 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:1181 +#: spa/plugins/bluez5/bluez5-device.c:1497 msgid "Bluetooth" msgstr "Bluetooth" + +#: spa/plugins/bluez5/bluez5-device.c:1498 +#| msgid "Bluetooth" +msgid "Bluetooth (HFP)" +msgstr "Bluetooth (HFP)" diff --git a/po/hr.po b/po/hr.po index e8c8c02119c0e65560f6d0f8ee121c4080d50b75..dbde6091c85380224e7bf18779582bb816f1ebb7 100644 --- a/po/hr.po +++ b/po/hr.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-06-30 12:50+0200\n" -"PO-Revision-Date: 2022-06-30 13:14+0200\n" +"POT-Creation-Date: 2022-10-01 14:01+0200\n" +"PO-Revision-Date: 2022-10-01 14:12+0200\n" "Last-Translator: gogo <trebelnik2@gmail.com>\n" "Language-Team: Croatian <https://translate.fedoraproject.org/projects/" "pipewire/pipewire/hr/>\n" @@ -16,9 +16,9 @@ msgstr "" "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 2.3\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.1\n" "X-Launchpad-Export-Date: 2017-04-20 21:04+0000\n" #: src/daemon/pipewire.c:46 @@ -44,7 +44,7 @@ msgstr "Tunel do %s/%s" msgid "Dummy Output" msgstr "Lažni izlaz" -#: src/modules/module-pulse-tunnel.c:648 +#: src/modules/module-pulse-tunnel.c:662 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunel za %s@%s" @@ -164,7 +164,7 @@ msgstr "" " -d, --dsd DSD naÄin\n" "\n" -#: src/tools/pw-cli.c:3165 +#: src/tools/pw-cli.c:2250 #, c-format msgid "" "%s [options] [command]\n" @@ -187,8 +187,8 @@ msgstr "" msgid "Pro Audio" msgstr "Pro Audio" -#: spa/plugins/alsa/acp/acp.c:446 spa/plugins/alsa/acp/alsa-mixer.c:4648 -#: spa/plugins/bluez5/bluez5-device.c:1161 +#: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 +#: spa/plugins/bluez5/bluez5-device.c:1236 msgid "Off" msgstr "IskljuÄeno" @@ -215,7 +215,7 @@ msgstr "Ulaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:1330 +#: spa/plugins/bluez5/bluez5-device.c:1454 msgid "Microphone" msgstr "Mikrofon" @@ -281,7 +281,7 @@ msgid "No Bass Boost" msgstr "Bez pojaÄanja basa" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:1335 +#: spa/plugins/bluez5/bluez5-device.c:1460 msgid "Speaker" msgstr "ZvuÄnik" @@ -396,7 +396,7 @@ msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 -#: spa/plugins/bluez5/bluez5-device.c:1320 +#: spa/plugins/bluez5/bluez5-device.c:1442 msgid "Headset" msgstr "SluÅ¡alice s mikrofonom" @@ -520,7 +520,8 @@ msgstr "%s izlaz" msgid "%s Input" msgstr "%s ulaz" -#: spa/plugins/alsa/acp/alsa-util.c:1173 spa/plugins/alsa/acp/alsa-util.c:1267 +#: spa/plugins/alsa/acp/alsa-util.c:1187 +#: spa/plugins/alsa/acp/alsa-util.c:1281 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -548,16 +549,16 @@ msgstr[2] "" "Najvjerojatnije je ovo greÅ¡ka ALSA upravljaÄkog programa '%s'. Prijavite " "problem ALSA razvijateljima." -#: spa/plugins/alsa/acp/alsa-util.c:1239 +#: spa/plugins/alsa/acp/alsa-util.c:1253 #, 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." msgstr[0] "" @@ -566,17 +567,17 @@ msgstr[0] "" "Najvjerojatnije je ovo greÅ¡ka ALSA upravljaÄkog programa '%s'. Prijavite " "problem ALSA razvijateljima." msgstr[1] "" -"snd_pcm_delay() je vratio vrijednost koja je iznimno velika: %li bajta (%s" -"%lu ms).\n" +"snd_pcm_delay() je vratio vrijednost koja je iznimno velika: %li bajta " +"(%s%lu ms).\n" "Najvjerojatnije je ovo greÅ¡ka ALSA upravljaÄkog programa '%s'. Prijavite " "problem ALSA razvijateljima." msgstr[2] "" -"snd_pcm_delay() je vratio vrijednost koja je iznimno velika: %li bajta (%s" -"%lu ms).\n" +"snd_pcm_delay() je vratio vrijednost koja je iznimno velika: %li bajta " +"(%s%lu ms).\n" "Najvjerojatnije je ovo greÅ¡ka ALSA upravljaÄkog programa '%s'. Prijavite " "problem ALSA razvijateljima." -#: spa/plugins/alsa/acp/alsa-util.c:1286 +#: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -589,7 +590,7 @@ msgstr "" "Najvjerojatnije je ovo greÅ¡ka ALSA upravljaÄkog programa '%s'. Prijavite " "problem ALSA razvijateljima." -#: spa/plugins/alsa/acp/alsa-util.c:1329 +#: spa/plugins/alsa/acp/alsa-util.c:1343 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -629,65 +630,96 @@ msgstr "UgraÄ‘eni zvuk" msgid "Modem" msgstr "Modem" -#: spa/plugins/bluez5/bluez5-device.c:1172 +#: spa/plugins/bluez5/bluez5-device.c:1247 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "ZvuÄni pristupnik (A2DP izvor i HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1197 +#: spa/plugins/bluez5/bluez5-device.c:1272 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Reprodukcija visoke autentiÄnosti (A2DP slivnik, kôdek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1200 +#: spa/plugins/bluez5/bluez5-device.c:1275 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Telefonija visoke autentiÄnosti (A2DP slivnik, kôdek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1208 +#: spa/plugins/bluez5/bluez5-device.c:1283 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Reprodukcija visoke autentiÄnosti (A2DP slivnik)" -#: spa/plugins/bluez5/bluez5-device.c:1210 +#: spa/plugins/bluez5/bluez5-device.c:1285 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Telefonija visoke autentiÄnosti (A2DP izvor/slivnik)" -#: spa/plugins/bluez5/bluez5-device.c:1238 +#: spa/plugins/bluez5/bluez5-device.c:1322 +#, c-format +msgid "High Fidelity Playback (BAP Sink, codec %s)" +msgstr "Reprodukcija visoke autentiÄnosti (BAP slivnik, kôdek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1326 +#, c-format +msgid "High Fidelity Input (BAP Source, codec %s)" +msgstr "Ulaz visoke autentiÄnosti (BAP izvor, kôdek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1330 +#, c-format +msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" +msgstr "Telefonija visoke autentiÄnosti (BAP izvor/slivnik, kôdek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1359 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Jedinica sluÅ¡alice s mikrofonom (HSP/HFP, kôdek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1243 +#: spa/plugins/bluez5/bluez5-device.c:1364 msgid "Headset Head Unit (HSP/HFP)" msgstr "Jedinica sluÅ¡alice s mikrofonom (HSP/HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1325 +#: spa/plugins/bluez5/bluez5-device.c:1443 +#: spa/plugins/bluez5/bluez5-device.c:1448 +#: spa/plugins/bluez5/bluez5-device.c:1455 +#: spa/plugins/bluez5/bluez5-device.c:1461 +#: spa/plugins/bluez5/bluez5-device.c:1467 +#: spa/plugins/bluez5/bluez5-device.c:1473 +#: spa/plugins/bluez5/bluez5-device.c:1479 +#: spa/plugins/bluez5/bluez5-device.c:1485 +#: spa/plugins/bluez5/bluez5-device.c:1491 msgid "Handsfree" msgstr "Bez-ruku" -#: spa/plugins/bluez5/bluez5-device.c:1340 +#: spa/plugins/bluez5/bluez5-device.c:1449 +msgid "Handsfree (HFP)" +msgstr "Bez-ruku (HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:1466 msgid "Headphone" -msgstr "SluÅ¡alice" +msgstr "SluÅ¡alica" -#: spa/plugins/bluez5/bluez5-device.c:1345 +#: spa/plugins/bluez5/bluez5-device.c:1472 msgid "Portable" msgstr "Prijenosnik" -#: spa/plugins/bluez5/bluez5-device.c:1350 +#: spa/plugins/bluez5/bluez5-device.c:1478 msgid "Car" msgstr "Automobil" -#: spa/plugins/bluez5/bluez5-device.c:1355 +#: spa/plugins/bluez5/bluez5-device.c:1484 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:1360 +#: spa/plugins/bluez5/bluez5-device.c:1490 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:1366 +#: spa/plugins/bluez5/bluez5-device.c:1497 msgid "Bluetooth" msgstr "Bluetooth" +#: spa/plugins/bluez5/bluez5-device.c:1498 +msgid "Bluetooth (HFP)" +msgstr "Bluetooth (HFP)" + #~ msgid "PipeWire Media System" #~ msgstr "PipeWire medijski sustav" diff --git a/po/hu.po b/po/hu.po index dcb2521679eab68102fdee5cde77befa6b23837e..7fb3b3d74944cb07fa3b6c878aa6eafd519272b9 100644 --- a/po/hu.po +++ b/po/hu.po @@ -1,18 +1,18 @@ -# Hungarian translation of PipeWire -# Copyright (C) 2012, 2016. Free Software Foundation, Inc. +# Hungarian translation for PipeWire. +# Copyright (C) 2012, 2016, 2022. Free Software Foundation, Inc. # This file is distributed under the same license as the PipeWire package. # -# KAMI <kami911@gmail.com>, 2012. +# KAMI <kami911 at gmail dot com>, 2012. # Gabor Kelemen <kelemeng at ubuntu dot com>, 2016. -# Balázs Úr <urbalazs at gmail dot com>, 2016. +# Balázs Úr <ur.balazs at fsf dot hu>, 2016, 2022. msgid "" msgstr "" "Project-Id-Version: PipeWire master\n" -"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: 2020-07-21 15:29+0000\n" -"Last-Translator: Balázs Meskó <meskobalazs@mailbox.org>\n" +"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" +"issues\n" +"POT-Creation-Date: 2022-09-15 15:26+0000\n" +"PO-Revision-Date: 2022-09-21 22:35+0200\n" +"Last-Translator: Balázs Úr <ur.balazs at fsf dot hu>\n" "Language-Team: Hungarian <https://translate.fedoraproject.org/projects/" "pipewire/pipewire/hu/>\n" "Language: hu\n" @@ -20,12 +20,9 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.1.1\n" -"X-Poedit-Language: Hungarian\n" -"X-Poedit-Country: HUNGARY\n" -"X-Poedit-SourceCharset: utf-8\n" +"X-Generator: Lokalize 19.12.3\n" -#: src/daemon/pipewire.c:43 +#: src/daemon/pipewire.c:46 #, c-format msgid "" "%s [options]\n" @@ -33,40 +30,64 @@ msgid "" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" +"%s [kapcsolók]\n" +" -h, --help Ezen súgó megjelenÃtése\n" +" --version Verzió megjelenÃtése\n" +" -c, --config BeállÃtás betöltése (alapérték: %s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" -msgstr "" +msgstr "PipeWire médiarendszer" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" -msgstr "" +msgstr "A PipeWire médiarendszer indÃtása" -#: src/examples/media-session/alsa-monitor.c:526 -#: spa/plugins/alsa/acp/compat.c:187 -msgid "Built-in Audio" -msgstr "BelsÅ‘ hangforrás" +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:180 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:180 +#, c-format +msgid "Tunnel to %s/%s" +msgstr "Alagút ide: %s/%s" -#: src/examples/media-session/alsa-monitor.c:530 -#: spa/plugins/alsa/acp/compat.c:192 -msgid "Modem" -msgstr "Modem" +#: src/modules/module-fallback-sink.c:51 +msgid "Dummy Output" +msgstr "Üres kimenet" + +#: src/modules/module-pulse-tunnel.c:662 +#, c-format +msgid "Tunnel for %s@%s" +msgstr "Alagút ehhez: %s@%s" -#: src/examples/media-session/alsa-monitor.c:539 +#: src/modules/module-zeroconf-discover.c:332 msgid "Unknown device" -msgstr "" +msgstr "Ismeretlen eszköz" + +#: src/modules/module-zeroconf-discover.c:344 +#, c-format +msgid "%s on %s@%s" +msgstr "%s ezen: %s@%s" -#: src/tools/pw-cat.c:991 +#: src/modules/module-zeroconf-discover.c:348 +#, c-format +msgid "%s on %s" +msgstr "%s ezen: %s" + +#: src/tools/pw-cat.c:784 #, 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 [kapcsolók] [<fájl>|-]\n" +" -h, --help Ezen súgó megjelenÃtése\n" +" --version Verzió megjelenÃtése\n" +" -v, --verbose Részletes műveletek engedélyezése\n" +"\n" -#: src/tools/pw-cat.c:998 +#: src/tools/pw-cat.c:791 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -80,11 +101,30 @@ msgid "" " 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 Távoli démon neve\n" +" --media-type MédiatÃpus beállÃtása (alapérték: " +"%s)\n" +" --media-category Médiakategória beállÃtása\n" +" (alapérték: %s)\n" +" --media-role Médiaszerep beállÃtása (alapérték: " +"%s)\n" +" --target Csomópont céljának beállÃtása\n" +" (alapérték: %s), a 0 azt jelenti,\n" +" hogy ne linkeljen\n" +" --latency Csomópont késleltetésének " +"beállÃtása\n" +" (alapérték: %s)\n" +" Xegység (egység = s, ms, us, ns)\n" +" vagy közvetlen minták (256)\n" +" a gyakoriság a forrásfájl egyike\n" +" -P --properties Csomópont tulajdonságainak " +"beállÃtása\n" +"\n" -#: src/tools/pw-cat.c:1016 +#: src/tools/pw-cat.c:809 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -103,16 +143,39 @@ msgid "" "%d)\n" "\n" msgstr "" +" --rate Mintavételi gyakoriság (kötelezÅ‘ a\n" +" rögzÃtéshez) (alapérték: %u)\n" +" --channels Csatornák száma (kötelezÅ‘ a\n" +" rögzÃtéshez) (alapérték: %u)\n" +" --channel-map Csatornaleképezés\n" +" ezek egyike: „stereoâ€, " +"„surround-51â€\n" +" stb. vagy csatornanevek vesszÅ‘vel\n" +" tagolt listája, például: „FL,FRâ€\n" +" --format Mintavételi formátum: %s (kötelezÅ‘ " +"a\n" +" rögzÃtéshez) (alapérték: %s)\n" +" --volume Adatfolyam hangereje 0-1.0\n" +" (alapérték: %.3f)\n" +" -q --quality Újramintavételezési minÅ‘ség (0-15)\n" +" (alapérték: %d)\n" +"\n" -#: src/tools/pw-cat.c:1033 +#: src/tools/pw-cat.c:826 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" +" -d, --dsd DSD mode\n" "\n" msgstr "" +" -p, --playback Lejátszási mód\n" +" -r, --record RögzÃtési mód\n" +" -m, --midi Midi mód\n" +" -d, --dsd DSD mód\n" +"\n" -#: src/tools/pw-cli.c:2932 +#: src/tools/pw-cli.c:2255 #, c-format msgid "" "%s [options] [command]\n" @@ -122,360 +185,353 @@ msgid "" " -r, --remote Remote daemon name\n" "\n" msgstr "" +"%s [kapcsolók] [parancs]\n" +" -h, --help Ezen súgó megjelenÃtése\n" +" --version Verzió megjelenÃtése\n" +" -d, --daemon IndÃtás démonként (alapérték: " +"hamis)\n" +" -r, --remote Távoli démon neve\n" +"\n" -#: spa/plugins/alsa/acp/acp.c:290 +#: spa/plugins/alsa/acp/acp.c:321 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 +#: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 +#: spa/plugins/bluez5/bluez5-device.c:1236 msgid "Off" -msgstr "Kikapcsolva" +msgstr "Ki" -#: spa/plugins/alsa/acp/channelmap.h:466 -msgid "(invalid)" -msgstr "(Érvénytelen)" - -#: spa/plugins/alsa/acp/alsa-mixer.c:2709 +#: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Bemenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2710 +#: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Dokkolóállomás bemenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2711 +#: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Dokkolóállomás mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2712 +#: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Dokkolóállomás vonalbemenet" -#: 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 "Vonalbemenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2714 -#: spa/plugins/alsa/acp/alsa-mixer.c:2798 -#: spa/plugins/bluez5/bluez5-device.c:1145 +#: spa/plugins/alsa/acp/alsa-mixer.c:2657 +#: spa/plugins/alsa/acp/alsa-mixer.c:2741 +#: spa/plugins/bluez5/bluez5-device.c:1454 msgid "Microphone" msgstr "Mikrofon" -#: 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 "ElsÅ‘ mikrofon" +msgstr "ElülsÅ‘ mikrofon" -#: 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 "Hátsó mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2717 +#: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "KülsÅ‘ mikrofon" -#: 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 "BelsÅ‘ mikrofon" -#: 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 "Rádió" -#: 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 "Videó" -#: spa/plugins/alsa/acp/alsa-mixer.c:2721 +#: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Automatikus erÅ‘sÃtésszabályzás" -#: spa/plugins/alsa/acp/alsa-mixer.c:2722 +#: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Nincs automatikus erÅ‘sÃtésszabályzás" -#: spa/plugins/alsa/acp/alsa-mixer.c:2723 +#: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "ErÅ‘sÃtés" -#: spa/plugins/alsa/acp/alsa-mixer.c:2724 +#: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Nincs erÅ‘sÃtés" -#: spa/plugins/alsa/acp/alsa-mixer.c:2725 +#: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "ErÅ‘sÃtÅ‘" -#: spa/plugins/alsa/acp/alsa-mixer.c:2726 +#: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Nincs erÅ‘sÃtÅ‘" -#: spa/plugins/alsa/acp/alsa-mixer.c:2727 +#: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Basszuskiemelés" -#: spa/plugins/alsa/acp/alsa-mixer.c:2728 +#: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Nincs basszuskiemelés" -#: spa/plugins/alsa/acp/alsa-mixer.c:2729 -#: spa/plugins/bluez5/bluez5-device.c:1150 +#: spa/plugins/alsa/acp/alsa-mixer.c:2672 +#: spa/plugins/bluez5/bluez5-device.c:1460 msgid "Speaker" msgstr "Hangszóró" -#: 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 "Analóg fejhallgató" +msgstr "Fejhallgató" -#: spa/plugins/alsa/acp/alsa-mixer.c:2797 +#: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Analóg bemenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2801 +#: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Dokkolóállomás mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2803 +#: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Fejhallgató mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2807 +#: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Analóg kimenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2809 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" -msgstr "Analóg fejhallgató" +msgstr "2. fejhallgató" -#: spa/plugins/alsa/acp/alsa-mixer.c:2810 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" -msgstr "Analóg mono kimenet" +msgstr "Fejhallató monó kimenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2811 +#: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Vonalkimenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2812 +#: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" -msgstr "Analóg mono kimenet" +msgstr "Analóg monó kimenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2813 +#: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Hangszórók" -#: 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 "Digitális kimenet (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2816 +#: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Digitális bemenet (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2817 +#: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Többcsatornás bemenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2818 +#: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Többcsatornás kimenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2819 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" -msgstr "%s kimenet" +msgstr "Játék kimenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2820 -#: spa/plugins/alsa/acp/alsa-mixer.c:2821 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:2763 +#: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" -msgstr "%s kimenet" +msgstr "Csevegés kimenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2822 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" -msgstr "%s bemenet" +msgstr "Csevegés bemenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2823 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" -msgstr "Virtuális térhatású nyelÅ‘" +msgstr "Virtuális térhatás 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4527 +#: spa/plugins/alsa/acp/alsa-mixer.c:4471 msgid "Analog Mono" -msgstr "Analóg mono" +msgstr "Analóg monó" -#: spa/plugins/alsa/acp/alsa-mixer.c:4528 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Analog Mono (Left)" -msgstr "Analóg mono" +msgstr "Analóg monó (bal)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4529 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Mono (Right)" -msgstr "Analóg mono" +msgstr "Analóg monó (jobb)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * 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 "Analóg sztereó" -#: spa/plugins/alsa/acp/alsa-mixer.c:4531 +#: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Mono" -msgstr "Mono" +msgstr "Monó" -#: spa/plugins/alsa/acp/alsa-mixer.c:4532 +#: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Stereo" msgstr "Sztereó" -#: spa/plugins/alsa/acp/alsa-mixer.c:4540 -#: spa/plugins/alsa/acp/alsa-mixer.c:4698 -#: spa/plugins/bluez5/bluez5-device.c:1135 +#: spa/plugins/alsa/acp/alsa-mixer.c:4484 +#: spa/plugins/alsa/acp/alsa-mixer.c:4642 +#: spa/plugins/bluez5/bluez5-device.c:1442 msgid "Headset" msgstr "Fejhallgató" -#: spa/plugins/alsa/acp/alsa-mixer.c:4541 -#: spa/plugins/alsa/acp/alsa-mixer.c:4699 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:4485 +#: spa/plugins/alsa/acp/alsa-mixer.c:4643 msgid "Speakerphone" -msgstr "Hangszóró" +msgstr "Mikrofonos fejhallgató" -#: 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 "Többcsatornás" -#: spa/plugins/alsa/acp/alsa-mixer.c:4544 +#: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Analog Surround 2.1" msgstr "Analóg térhatású 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4545 +#: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Analog Surround 3.0" msgstr "Analóg térhatású 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4546 +#: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Analog Surround 3.1" msgstr "Analóg térhatású 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4547 +#: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Analog Surround 4.0" msgstr "Analóg térhatású 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4548 +#: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Analog Surround 4.1" msgstr "Analóg térhatású 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4549 +#: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Analog Surround 5.0" msgstr "Analóg térhatású 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4550 +#: spa/plugins/alsa/acp/alsa-mixer.c:4494 msgid "Analog Surround 5.1" msgstr "Analóg térhatású 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4551 +#: spa/plugins/alsa/acp/alsa-mixer.c:4495 msgid "Analog Surround 6.0" msgstr "Analóg térhatású 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4552 +#: spa/plugins/alsa/acp/alsa-mixer.c:4496 msgid "Analog Surround 6.1" msgstr "Analóg térhatású 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4553 +#: spa/plugins/alsa/acp/alsa-mixer.c:4497 msgid "Analog Surround 7.0" msgstr "Analóg térhatású 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4554 +#: spa/plugins/alsa/acp/alsa-mixer.c:4498 msgid "Analog Surround 7.1" msgstr "Analóg térhatású 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4555 +#: spa/plugins/alsa/acp/alsa-mixer.c:4499 msgid "Digital Stereo (IEC958)" msgstr "Digitális sztereó (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 "Digitális térhatású 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 "Digitális térhatású 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 "Digitális térhatású 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 "Digitális térhatású (HDMI)" +msgstr "Digitális sztereó (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4560 +#: spa/plugins/alsa/acp/alsa-mixer.c:4504 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digitális térhatású 5.1 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4561 +#: spa/plugins/alsa/acp/alsa-mixer.c:4505 msgid "Chat" -msgstr "" +msgstr "Csevegés" -#: spa/plugins/alsa/acp/alsa-mixer.c:4562 +#: spa/plugins/alsa/acp/alsa-mixer.c:4506 msgid "Game" -msgstr "" +msgstr "Játék" -#: spa/plugins/alsa/acp/alsa-mixer.c:4696 +#: spa/plugins/alsa/acp/alsa-mixer.c:4640 msgid "Analog Mono Duplex" -msgstr "Analóg mono duplex" +msgstr "Analóg monó kétirányú" -#: spa/plugins/alsa/acp/alsa-mixer.c:4697 +#: spa/plugins/alsa/acp/alsa-mixer.c:4641 msgid "Analog Stereo Duplex" -msgstr "Analóg sztereó duplex" +msgstr "Analóg sztereó kétirányú" -#: spa/plugins/alsa/acp/alsa-mixer.c:4700 +#: spa/plugins/alsa/acp/alsa-mixer.c:4644 msgid "Digital Stereo Duplex (IEC958)" -msgstr "Analóg sztereó duplex (IEC958)" +msgstr "Digitális sztereó kétirányú (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4701 +#: spa/plugins/alsa/acp/alsa-mixer.c:4645 msgid "Multichannel Duplex" -msgstr "Többcsatornás duplex" +msgstr "Többcsatornás kétirányú" -#: spa/plugins/alsa/acp/alsa-mixer.c:4702 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:4646 msgid "Stereo Duplex" -msgstr "Analóg sztereó duplex" +msgstr "Sztereó kétirányú" -#: spa/plugins/alsa/acp/alsa-mixer.c:4703 +#: spa/plugins/alsa/acp/alsa-mixer.c:4647 msgid "Mono Chat + 7.1 Surround" -msgstr "" +msgstr "Monó csevegés + 7.1 térhatású" -#: spa/plugins/alsa/acp/alsa-mixer.c:4806 +#: spa/plugins/alsa/acp/alsa-mixer.c:4754 #, c-format msgid "%s Output" msgstr "%s kimenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:4813 +#: spa/plugins/alsa/acp/alsa-mixer.c:4761 #, c-format msgid "%s Input" msgstr "%s bemenet" -#: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 -#, fuzzy, c-format +#: spa/plugins/alsa/acp/alsa-util.c:1187 spa/plugins/alsa/acp/alsa-util.c:1281 +#, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" @@ -487,18 +543,18 @@ msgid_plural "" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" -"A „snd_pcm_avail()†függvény visszatérési értéke váratlanul nagy értékű: %lu " +"Az „snd_pcm_avail()†függvény különlegesen nagy értéket adott vissza: %lu " "bájt (%lu ms).\n" "Ez valószÃnűleg egy hiba eredménye az ALSA „%s†illesztÅ‘programban. Jelentse " "ezt a problémát az ALSA fejlesztÅ‘i felé." msgstr[1] "" -"A „snd_pcm_avail()†függvény visszatérési értéke váratlanul nagy értékű: %lu " +"Az „snd_pcm_avail()†függvény különlegesen nagy értéket adott vissza: %lu " "bájt (%lu ms).\n" "Ez valószÃnűleg egy hiba eredménye az ALSA „%s†illesztÅ‘programban. Jelentse " "ezt a problémát az ALSA fejlesztÅ‘i felé." -#: spa/plugins/alsa/acp/alsa-util.c:1241 -#, fuzzy, c-format +#: spa/plugins/alsa/acp/alsa-util.c:1253 +#, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" @@ -510,17 +566,17 @@ msgid_plural "" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" -"A „snd_pcm_delay()†függvény visszatérési értéke váratlanul nagy értékű: %li " +"Az „snd_pcm_delay()†függvény különlegesen nagy értéket adott vissza: %li " "bájt (%s%lu ms).\n" "Ez valószÃnűleg egy hiba eredménye az ALSA „%s†illesztÅ‘programban. Jelentse " "ezt a problémát az ALSA fejlesztÅ‘i felé." msgstr[1] "" -"A „snd_pcm_delay()†függvény visszatérési értéke váratlanul nagy értékű: %li " +"Az „snd_pcm_delay()†függvény különlegesen nagy értéket adott vissza: %li " "bájt (%s%lu ms).\n" "Ez valószÃnűleg egy hiba eredménye az ALSA „%s†illesztÅ‘programban. Jelentse " "ezt a problémát az ALSA fejlesztÅ‘i felé." -#: spa/plugins/alsa/acp/alsa-util.c:1288 +#: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -528,13 +584,13 @@ msgid "" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" -"A „snd_pcm_avail_delay()†függvény furcsa értékeket adott vissza: a " +"Az „snd_pcm_avail_delay()†függvény furcsa értékeket adott vissza: a " "késleltetés (%lu) kisebb, mint az elérhetÅ‘ %lu.\n" "Ez valószÃnűleg egy hiba eredménye az ALSA „%s†illesztÅ‘programban. Jelentse " "ezt a problémát az ALSA fejlesztÅ‘i felé." -#: spa/plugins/alsa/acp/alsa-util.c:1331 -#, fuzzy, c-format +#: spa/plugins/alsa/acp/alsa-util.c:1343 +#, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" @@ -546,73 +602,114 @@ msgid_plural "" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" -"A „snd_pcm_mmap_begin()†függvény visszatérési értéke kivételesen nagy: %lu " -"bájt (%lu ms).\n" +"Az „snd_pcm_mmap_begin()†függvény különlegesen nagy értéket adott vissza: " +"%lu bájt (%lu ms).\n" "Ez valószÃnűleg egy hiba eredménye az ALSA „%s†illesztÅ‘programban. Jelentse " "ezt a problémát az ALSA fejlesztÅ‘i felé." msgstr[1] "" -"A „snd_pcm_mmap_begin()†függvény visszatérési értéke kivételesen nagy: %lu " -"bájt (%lu ms).\n" +"Az „snd_pcm_mmap_begin()†függvény különlegesen nagy értéket adott vissza: " +"%lu bájt (%lu ms).\n" "Ez valószÃnűleg egy hiba eredménye az ALSA „%s†illesztÅ‘programban. Jelentse " "ezt a problémát az ALSA fejlesztÅ‘i felé." -#: spa/plugins/bluez5/bluez5-device.c:1010 +#: spa/plugins/alsa/acp/channelmap.h:457 +msgid "(invalid)" +msgstr "(érvénytelen)" + +#: spa/plugins/alsa/acp/compat.c:189 +msgid "Built-in Audio" +msgstr "BeépÃtett hangforrás" + +#: spa/plugins/alsa/acp/compat.c:194 +msgid "Modem" +msgstr "Modem" + +#: spa/plugins/bluez5/bluez5-device.c:1247 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" -msgstr "" +msgstr "Hang átjáró (A2DP forrás és HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1033 +#: spa/plugins/bluez5/bluez5-device.c:1272 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" -msgstr "" +msgstr "Magas hűségű lejátszás (A2DP fogadó, %s kodek)" -#: spa/plugins/bluez5/bluez5-device.c:1035 +#: spa/plugins/bluez5/bluez5-device.c:1275 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" -msgstr "" +msgstr "Magas hűségű kétirányú (A2DP forrás/fogadó, %s kodek)" -#: spa/plugins/bluez5/bluez5-device.c:1041 +#: spa/plugins/bluez5/bluez5-device.c:1283 msgid "High Fidelity Playback (A2DP Sink)" -msgstr "" +msgstr "Magas hűségű lejátszás (A2DP fogadó)" -#: spa/plugins/bluez5/bluez5-device.c:1043 +#: spa/plugins/bluez5/bluez5-device.c:1285 msgid "High Fidelity Duplex (A2DP Source/Sink)" -msgstr "" +msgstr "Magas hűségű kétirányú (A2DP forrás/fogadó)" + +#: spa/plugins/bluez5/bluez5-device.c:1322 +#, c-format +msgid "High Fidelity Playback (BAP Sink, codec %s)" +msgstr "Magas hűségű lejátszás (BAP fogadó, %s kodek)" + +#: spa/plugins/bluez5/bluez5-device.c:1326 +#, c-format +msgid "High Fidelity Input (BAP Source, codec %s)" +msgstr "Magas hűségű bemenet (BAP forrás, %s kodek)" + +#: spa/plugins/bluez5/bluez5-device.c:1330 +#, c-format +msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" +msgstr "Magas hűségű kétirányú (BAP forrás/fogadó, %s kodek)" -#: spa/plugins/bluez5/bluez5-device.c:1070 +#: spa/plugins/bluez5/bluez5-device.c:1359 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" -msgstr "" +msgstr "Fejhallgató fejegység (HSP/HFP, %s kodek)" -#: spa/plugins/bluez5/bluez5-device.c:1074 +#: spa/plugins/bluez5/bluez5-device.c:1364 msgid "Headset Head Unit (HSP/HFP)" -msgstr "" - -# FIXME: utánanézni -#: spa/plugins/bluez5/bluez5-device.c:1140 +msgstr "Fejhallgató fejegység (HSP/HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:1443 +#: spa/plugins/bluez5/bluez5-device.c:1448 +#: spa/plugins/bluez5/bluez5-device.c:1455 +#: spa/plugins/bluez5/bluez5-device.c:1461 +#: spa/plugins/bluez5/bluez5-device.c:1467 +#: spa/plugins/bluez5/bluez5-device.c:1473 +#: spa/plugins/bluez5/bluez5-device.c:1479 +#: spa/plugins/bluez5/bluez5-device.c:1485 +#: spa/plugins/bluez5/bluez5-device.c:1491 msgid "Handsfree" msgstr "KihangosÃtó" -#: spa/plugins/bluez5/bluez5-device.c:1155 +#: spa/plugins/bluez5/bluez5-device.c:1449 +msgid "Handsfree (HFP)" +msgstr "KihangosÃtó (HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:1466 msgid "Headphone" -msgstr "Fülhallgató" +msgstr "Fejhallgató" -#: spa/plugins/bluez5/bluez5-device.c:1160 +#: spa/plugins/bluez5/bluez5-device.c:1472 msgid "Portable" msgstr "Hordozható" -#: spa/plugins/bluez5/bluez5-device.c:1165 +#: spa/plugins/bluez5/bluez5-device.c:1478 msgid "Car" msgstr "Autó" -#: spa/plugins/bluez5/bluez5-device.c:1170 +#: spa/plugins/bluez5/bluez5-device.c:1484 msgid "HiFi" msgstr "Hi-Fi" -#: spa/plugins/bluez5/bluez5-device.c:1175 +#: spa/plugins/bluez5/bluez5-device.c:1490 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:1181 -#, fuzzy +#: spa/plugins/bluez5/bluez5-device.c:1497 msgid "Bluetooth" -msgstr "Bluetooth bemenet" +msgstr "Bluetooth" + +#: spa/plugins/bluez5/bluez5-device.c:1498 +msgid "Bluetooth (HFP)" +msgstr "Bluetooth (HFP)" diff --git a/po/it.po b/po/it.po index f4a1c112a505d7d0073bb78826c3a2af0cb9ec29..bdd47ba8d7d530e92d9248f3f477272e200f882d 100644 --- a/po/it.po +++ b/po/it.po @@ -34,11 +34,11 @@ msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" -msgstr "Sistema Multimediale PipeWire" +msgstr "Sistema multimediale PipeWire" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" -msgstr "Avvia il Sistema Multimediale PipeWire" +msgstr "Avvia il sistema multimediale PipeWire" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 @@ -144,16 +144,16 @@ msgstr "Ingresso docking station" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" -msgstr "Microfono docking station" +msgstr "Microfono della docking station" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" -msgstr "Linea in docking station" +msgstr "Linea di ingresso nella docking station" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" -msgstr "Line-In" +msgstr "Linea di ingresso" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 @@ -258,7 +258,7 @@ msgstr "Uscita mono cuffie" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" -msgstr "Line-Out" +msgstr "Linea di uscita" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" @@ -350,7 +350,7 @@ msgstr "Vivavoce" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" -msgstr "Multi canale" +msgstr "Multicanale" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" @@ -442,7 +442,7 @@ msgstr "Duplex stereo digitale (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" -msgstr "Duplex multi canale" +msgstr "Duplex multicanale" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 msgid "Stereo Duplex" @@ -477,11 +477,11 @@ msgid_plural "" msgstr[0] "" "snd_pcm_avail() ha restituito un valore molto grande: %lu byte (%lu ms).\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " -"questo problema agli sviluppatori ALSA." +"questo problema ai suoi sviluppatori." msgstr[1] "" "snd_pcm_avail() ha restituito un valore molto grande: %lu byte (%lu ms).\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " -"questo problema agli sviluppatori ALSA." +"questo problema ai suoi sviluppatori." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, c-format @@ -498,11 +498,11 @@ msgid_plural "" msgstr[0] "" "snd_pcm_delay() ha restituito un valore molto grande: %li byte (%s%lu ms).\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " -"questo problema agli sviluppatori ALSA." +"questo problema ai suoi sviluppatori." msgstr[1] "" "snd_pcm_delay() ha restituito un valore molto grande: %li byte (%s%lu ms).\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " -"questo problema agli sviluppatori ALSA." +"questo problema ai suoi sviluppatori." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format @@ -515,7 +515,7 @@ msgstr "" "snd_pcm_avail() ha restituito dei valori strani: delay %lu è minore di avail " "%lu.\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " -"questo problema agli sviluppatori ALSA." +"questo problema ai suoi sviluppatori." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, c-format @@ -533,12 +533,12 @@ msgstr[0] "" "snd_pcm_mmap_begin() ha restituito un valore molto grande: %lu byte (%lu " "ms).\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " -"questo problema agli sviluppatori ALSA." +"questo problema ai suoi sviluppatori." msgstr[1] "" "snd_pcm_mmap_begin() ha restituito un valore molto grande: %lu byte (%lu " "ms).\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " -"questo problema agli sviluppatori ALSA." +"questo problema ai suoi sviluppatori." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" @@ -547,20 +547,20 @@ msgstr "Gateway Audio (Sorgente A2DP & HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" -msgstr "Riproduzione ad Alta Fedeltà (A2DP Sink, codec %s)" +msgstr "Riproduzione ad alta fedeltà (A2DP Sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" -msgstr "Duplex ad Alta Fedeltà (Sorgente/Sink, A2DP codec %s)" +msgstr "Duplex ad alta fedeltà (Sorgente/Sink, A2DP codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" -msgstr "Riproduzione ad Alta Fedeltà (A2DP Sink)" +msgstr "Riproduzione ad alta fedeltà (A2DP Sink)" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" -msgstr "Duplex ad Alta Fedeltà (A2DP Source/Sink)" +msgstr "Duplex ad alta fedeltà (A2DP Source/Sink)" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format @@ -573,7 +573,7 @@ msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" -msgstr "Sistema mani-libere" +msgstr "Vivavoce" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" diff --git a/po/ka.po b/po/ka.po index 1e78ffdfea21d246f9cd1d99e0391df811962d14..47f80678e3a6d34f79d8b645992be2ae01c37ddc 100644 --- a/po/ka.po +++ b/po/ka.po @@ -1,7 +1,7 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# Georgian translation for pipewire. +# Copyright (C) 2022 pipewire'S authors # This file is distributed under the same license as the pipewire package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# Temuri Doghonadze <temuri.doghonadze@gmail.com>, 2022. # msgid "" msgstr "" @@ -9,15 +9,15 @@ msgstr "" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2022-06-30 12:50+0200\n" -"PO-Revision-Date: 2022-07-25 13:11+0200\n" +"PO-Revision-Date: 2022-11-20 11:50+0100\n" "Last-Translator: Temuri Doghonadze <temuri.doghonadze@gmail.com>\n" -"Language-Team: Georgian <(nothing)>\n" +"Language-Team: \n" "Language: ka\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 3.1.1\n" +"X-Generator: Poedit 3.2\n" #: src/daemon/pipewire.c:46 #, c-format @@ -42,7 +42,7 @@ msgstr "გვირáƒáƒ‘ი %s/%s -მდე" msgid "Dummy Output" msgstr "ნულáƒáƒ•áƒáƒœáƒ˜ გáƒáƒ›áƒáƒ§áƒ•áƒáƒœáƒ" -#: src/modules/module-pulse-tunnel.c:648 +#: src/modules/module-pulse-tunnel.c:662 #, c-format msgid "Tunnel for %s@%s" msgstr "გვირáƒáƒ‘ი %s@%s-სთვის" @@ -153,7 +153,7 @@ msgstr "" " -d, --dsd DSD რეჟიმი\n" "\n" -#: src/tools/pw-cli.c:3165 +#: src/tools/pw-cli.c:2250 #, c-format msgid "" "%s [options] [command]\n" @@ -174,8 +174,8 @@ msgstr "" msgid "Pro Audio" msgstr "Pro Audio" -#: spa/plugins/alsa/acp/acp.c:446 spa/plugins/alsa/acp/alsa-mixer.c:4648 -#: spa/plugins/bluez5/bluez5-device.c:1161 +#: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 +#: spa/plugins/bluez5/bluez5-device.c:1236 msgid "Off" msgstr "გáƒáƒ›áƒáƒ თული" @@ -202,7 +202,7 @@ msgstr "Line In" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:1330 +#: spa/plugins/bluez5/bluez5-device.c:1454 msgid "Microphone" msgstr "მიკრáƒáƒ¤áƒáƒœáƒ˜" @@ -268,7 +268,7 @@ msgid "No Bass Boost" msgstr "Bass-ის გáƒáƒ«áƒšáƒ˜áƒ”რების გáƒáƒ ეშე" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:1335 +#: spa/plugins/bluez5/bluez5-device.c:1460 msgid "Speaker" msgstr "დინáƒáƒ›áƒ˜áƒ™áƒ˜" @@ -383,7 +383,7 @@ msgstr "სტერეáƒ" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 -#: spa/plugins/bluez5/bluez5-device.c:1320 +#: spa/plugins/bluez5/bluez5-device.c:1442 msgid "Headset" msgstr "ყურსáƒáƒªáƒ•áƒáƒ›áƒ”ბი & მიკრáƒáƒ¤áƒáƒœáƒ˜" @@ -507,7 +507,7 @@ msgstr "%s გáƒáƒ›áƒáƒ§áƒ•áƒáƒœáƒ" msgid "%s Input" msgstr "%s შეყვáƒáƒœáƒ" -#: spa/plugins/alsa/acp/alsa-util.c:1173 spa/plugins/alsa/acp/alsa-util.c:1267 +#: spa/plugins/alsa/acp/alsa-util.c:1187 spa/plugins/alsa/acp/alsa-util.c:1281 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -530,7 +530,7 @@ msgstr[1] "" "ყველáƒáƒ–ე ხშირáƒáƒ“ ეს ALSA-ს დრáƒáƒ˜áƒ•ერის (%s) შეცდáƒáƒ›áƒ˜áƒ¡ გáƒáƒ›áƒ ხდებáƒ. დáƒáƒ£áƒ™áƒáƒ•შირდით " "ALSA-ის პრáƒáƒ’რáƒáƒ›áƒ˜áƒ¡áƒ¢áƒ”ბს." -#: spa/plugins/alsa/acp/alsa-util.c:1239 +#: spa/plugins/alsa/acp/alsa-util.c:1253 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" @@ -553,7 +553,7 @@ msgstr[1] "" "ყველáƒáƒ–ე ხშირáƒáƒ“ ეს ALSA-ს დრáƒáƒ˜áƒ•ერის (%s) შეცდáƒáƒ›áƒ˜áƒ¡ გáƒáƒ›áƒ ხდებáƒ. დáƒáƒ£áƒ™áƒáƒ•შირდით " "ALSA-ის პრáƒáƒ’რáƒáƒ›áƒ˜áƒ¡áƒ¢áƒ”ბს." -#: spa/plugins/alsa/acp/alsa-util.c:1286 +#: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -566,7 +566,7 @@ msgstr "" "ყველáƒáƒ–ე ხშირáƒáƒ“ ეს ALSA-ს დრáƒáƒ˜áƒ•ერის (%s) შეცდáƒáƒ›áƒ˜áƒ¡ გáƒáƒ›áƒ ხდებáƒ. დáƒáƒ£áƒ™áƒáƒ•შირდით " "ALSA-ის პრáƒáƒ’რáƒáƒ›áƒ˜áƒ¡áƒ¢áƒ”ბს." -#: spa/plugins/alsa/acp/alsa-util.c:1329 +#: spa/plugins/alsa/acp/alsa-util.c:1343 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -601,61 +601,92 @@ msgstr "ჩáƒáƒ¨áƒ”ნებული áƒáƒ£áƒ“იáƒ" msgid "Modem" msgstr "მáƒáƒ“ემი" -#: spa/plugins/bluez5/bluez5-device.c:1172 +#: spa/plugins/bluez5/bluez5-device.c:1247 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Audio Gateway (A2DP წყáƒáƒ რ& HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1197 +#: spa/plugins/bluez5/bluez5-device.c:1272 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "მáƒáƒ¦áƒáƒšáƒ˜ ხáƒáƒ ისხის ხმრ(A2DP Sink, კáƒáƒ“ეკი %s)" -#: spa/plugins/bluez5/bluez5-device.c:1200 +#: spa/plugins/bluez5/bluez5-device.c:1275 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "მáƒáƒ¦áƒáƒšáƒ˜ ხáƒáƒ ისხის დუპლექსი (A2DP წყáƒáƒ áƒ/Sink, კáƒáƒ“ეკი %s)" -#: spa/plugins/bluez5/bluez5-device.c:1208 +#: spa/plugins/bluez5/bluez5-device.c:1283 msgid "High Fidelity Playback (A2DP Sink)" msgstr "მáƒáƒ¦áƒáƒšáƒ˜ ხáƒáƒ ისხის ხმრ(A2DP Sink)" -#: spa/plugins/bluez5/bluez5-device.c:1210 +#: spa/plugins/bluez5/bluez5-device.c:1285 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "მáƒáƒ¦áƒáƒšáƒ˜ ხáƒáƒ ისხის დუპლექსი(A2DP წყáƒáƒ áƒ/Sink)" -#: spa/plugins/bluez5/bluez5-device.c:1238 +#: spa/plugins/bluez5/bluez5-device.c:1322 +#, c-format +msgid "High Fidelity Playback (BAP Sink, codec %s)" +msgstr "მáƒáƒ¦áƒáƒšáƒ˜ ხáƒáƒ ისხის დáƒáƒ™áƒ•რრ(BAP Sink, კáƒáƒ“ეკი %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1326 +#, c-format +msgid "High Fidelity Input (BAP Source, codec %s)" +msgstr "მáƒáƒ¦áƒáƒšáƒ˜ ხáƒáƒ ისხის შეყვáƒáƒœáƒ (BAP წყáƒáƒ áƒ, კáƒáƒ“ეკი %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1330 +#, c-format +msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" +msgstr "მáƒáƒ¦áƒáƒšáƒ˜ ხáƒáƒ ისხის დუპლექსი (BAP წყáƒáƒ áƒ/Sink, კáƒáƒ“ეკი %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1359 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Headset Head Unit (HSP/HFP, კáƒáƒ“ეკი %s)" -#: spa/plugins/bluez5/bluez5-device.c:1243 +#: spa/plugins/bluez5/bluez5-device.c:1364 msgid "Headset Head Unit (HSP/HFP)" msgstr "Headset Head Unit (HSP/HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1325 +#: spa/plugins/bluez5/bluez5-device.c:1443 +#: spa/plugins/bluez5/bluez5-device.c:1448 +#: spa/plugins/bluez5/bluez5-device.c:1455 +#: spa/plugins/bluez5/bluez5-device.c:1461 +#: spa/plugins/bluez5/bluez5-device.c:1467 +#: spa/plugins/bluez5/bluez5-device.c:1473 +#: spa/plugins/bluez5/bluez5-device.c:1479 +#: spa/plugins/bluez5/bluez5-device.c:1485 +#: spa/plugins/bluez5/bluez5-device.c:1491 msgid "Handsfree" msgstr "ხელის გáƒáƒ ეშე სáƒáƒ›áƒáƒ თáƒáƒ•ი" -#: spa/plugins/bluez5/bluez5-device.c:1340 +#: spa/plugins/bluez5/bluez5-device.c:1449 +msgid "Handsfree (HFP)" +msgstr "ხელის გáƒáƒ ეშე სáƒáƒ›áƒáƒ თáƒáƒ•ი (HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:1466 msgid "Headphone" msgstr "ყურსáƒáƒªáƒ•áƒáƒ›áƒ˜" -#: spa/plugins/bluez5/bluez5-device.c:1345 +#: spa/plugins/bluez5/bluez5-device.c:1472 msgid "Portable" msgstr "გáƒáƒ“áƒáƒ¢áƒáƒœáƒáƒ“ი" -#: spa/plugins/bluez5/bluez5-device.c:1350 +#: spa/plugins/bluez5/bluez5-device.c:1478 msgid "Car" msgstr "მáƒáƒœáƒ¥áƒáƒœáƒ" -#: spa/plugins/bluez5/bluez5-device.c:1355 +#: spa/plugins/bluez5/bluez5-device.c:1484 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:1360 +#: spa/plugins/bluez5/bluez5-device.c:1490 msgid "Phone" msgstr "ტელეფáƒáƒœáƒ˜" -#: spa/plugins/bluez5/bluez5-device.c:1366 +#: spa/plugins/bluez5/bluez5-device.c:1497 msgid "Bluetooth" msgstr "Bluetooth" + +#: spa/plugins/bluez5/bluez5-device.c:1498 +msgid "Bluetooth (HFP)" +msgstr "Bluetooth (HFP)" diff --git a/po/pipewire.pot b/po/pipewire.pot index 26d49f3d46b9729260ed2b821e0c36f70f0d5037..af26ba80b02380f3a18658a41989c57ae33004ad 100644 --- a/po/pipewire.pot +++ b/po/pipewire.pot @@ -37,7 +37,7 @@ msgstr "" msgid "Dummy Output" msgstr "" -#: src/modules/module-pulse-tunnel.c:648 +#: src/modules/module-pulse-tunnel.c:662 #, c-format msgid "Tunnel for %s@%s" msgstr "" @@ -113,7 +113,7 @@ msgid "" "\n" msgstr "" -#: src/tools/pw-cli.c:3165 +#: src/tools/pw-cli.c:2250 #, c-format msgid "" "%s [options] [command]\n" @@ -128,8 +128,8 @@ msgstr "" msgid "Pro Audio" msgstr "" -#: spa/plugins/alsa/acp/acp.c:446 spa/plugins/alsa/acp/alsa-mixer.c:4648 -#: spa/plugins/bluez5/bluez5-device.c:1161 +#: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 +#: spa/plugins/bluez5/bluez5-device.c:1236 msgid "Off" msgstr "" @@ -156,7 +156,7 @@ msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:1330 +#: spa/plugins/bluez5/bluez5-device.c:1454 msgid "Microphone" msgstr "" @@ -222,7 +222,7 @@ msgid "No Bass Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:1335 +#: spa/plugins/bluez5/bluez5-device.c:1460 msgid "Speaker" msgstr "" @@ -337,7 +337,7 @@ msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 -#: spa/plugins/bluez5/bluez5-device.c:1320 +#: spa/plugins/bluez5/bluez5-device.c:1442 msgid "Headset" msgstr "" @@ -461,8 +461,8 @@ msgstr "" msgid "%s Input" msgstr "" -#: spa/plugins/alsa/acp/alsa-util.c:1173 -#: spa/plugins/alsa/acp/alsa-util.c:1267 +#: spa/plugins/alsa/acp/alsa-util.c:1187 +#: spa/plugins/alsa/acp/alsa-util.c:1281 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -477,22 +477,22 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: spa/plugins/alsa/acp/alsa-util.c:1239 +#: spa/plugins/alsa/acp/alsa-util.c:1253 #, 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." msgstr[0] "" msgstr[1] "" -#: spa/plugins/alsa/acp/alsa-util.c:1286 +#: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -501,7 +501,7 @@ msgid "" "to the ALSA developers." msgstr "" -#: spa/plugins/alsa/acp/alsa-util.c:1329 +#: spa/plugins/alsa/acp/alsa-util.c:1343 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -528,61 +528,92 @@ msgstr "" msgid "Modem" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1172 +#: spa/plugins/bluez5/bluez5-device.c:1247 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1197 +#: spa/plugins/bluez5/bluez5-device.c:1272 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1200 +#: spa/plugins/bluez5/bluez5-device.c:1275 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1208 +#: spa/plugins/bluez5/bluez5-device.c:1283 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1210 +#: spa/plugins/bluez5/bluez5-device.c:1285 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1238 +#: spa/plugins/bluez5/bluez5-device.c:1322 +#, c-format +msgid "High Fidelity Playback (BAP Sink, codec %s)" +msgstr "" + +#: spa/plugins/bluez5/bluez5-device.c:1326 +#, c-format +msgid "High Fidelity Input (BAP Source, codec %s)" +msgstr "" + +#: spa/plugins/bluez5/bluez5-device.c:1330 +#, c-format +msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" +msgstr "" + +#: spa/plugins/bluez5/bluez5-device.c:1359 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1243 +#: spa/plugins/bluez5/bluez5-device.c:1364 msgid "Headset Head Unit (HSP/HFP)" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1325 +#: spa/plugins/bluez5/bluez5-device.c:1443 +#: spa/plugins/bluez5/bluez5-device.c:1448 +#: spa/plugins/bluez5/bluez5-device.c:1455 +#: spa/plugins/bluez5/bluez5-device.c:1461 +#: spa/plugins/bluez5/bluez5-device.c:1467 +#: spa/plugins/bluez5/bluez5-device.c:1473 +#: spa/plugins/bluez5/bluez5-device.c:1479 +#: spa/plugins/bluez5/bluez5-device.c:1485 +#: spa/plugins/bluez5/bluez5-device.c:1491 msgid "Handsfree" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1340 +#: spa/plugins/bluez5/bluez5-device.c:1449 +msgid "Handsfree (HFP)" +msgstr "" + +#: spa/plugins/bluez5/bluez5-device.c:1466 msgid "Headphone" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1345 +#: spa/plugins/bluez5/bluez5-device.c:1472 msgid "Portable" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1350 +#: spa/plugins/bluez5/bluez5-device.c:1478 msgid "Car" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1355 +#: spa/plugins/bluez5/bluez5-device.c:1484 msgid "HiFi" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1360 +#: spa/plugins/bluez5/bluez5-device.c:1490 msgid "Phone" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1366 +#: spa/plugins/bluez5/bluez5-device.c:1497 msgid "Bluetooth" msgstr "" + +#: spa/plugins/bluez5/bluez5-device.c:1498 +msgid "Bluetooth (HFP)" +msgstr "" diff --git a/po/pl.po b/po/pl.po index b84b59b9c0538a3ac7d5699944418ebadcb68a19..032979a9724d28bac04ab9b758f2c79e598b4d5b 100644 --- a/po/pl.po +++ b/po/pl.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" -"POT-Creation-Date: 2022-08-27 13:57+0000\n" -"PO-Revision-Date: 2022-08-27 16:00+0200\n" +"POT-Creation-Date: 2022-09-15 15:26+0000\n" +"PO-Revision-Date: 2022-09-25 15:20+0200\n" "Last-Translator: Piotr DrÄ…g <piotrdrag@gmail.com>\n" "Language-Team: Polish <community-poland@mozilla.org>\n" "Language: pl\n" @@ -51,7 +51,7 @@ msgstr "Tunel do %s/%s" msgid "Dummy Output" msgstr "GÅ‚uche wyjÅ›cie" -#: src/modules/module-pulse-tunnel.c:648 +#: src/modules/module-pulse-tunnel.c:662 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunel dla %s@%s" @@ -195,7 +195,7 @@ msgid "Pro Audio" msgstr "DźwiÄ™k w zastosowaniach profesjonalnych" #: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 -#: spa/plugins/bluez5/bluez5-device.c:1188 +#: spa/plugins/bluez5/bluez5-device.c:1236 msgid "Off" msgstr "Wyłączone" @@ -222,7 +222,7 @@ msgstr "WejÅ›cie liniowe" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:1360 +#: spa/plugins/bluez5/bluez5-device.c:1454 msgid "Microphone" msgstr "Mikrofon" @@ -288,7 +288,7 @@ msgid "No Bass Boost" msgstr "Brak podbicia basów" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:1366 +#: spa/plugins/bluez5/bluez5-device.c:1460 msgid "Speaker" msgstr "GÅ‚oÅ›nik" @@ -403,7 +403,7 @@ msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 -#: spa/plugins/bluez5/bluez5-device.c:1348 +#: spa/plugins/bluez5/bluez5-device.c:1442 msgid "Headset" msgstr "SÅ‚uchawki z mikrofonem" @@ -627,77 +627,92 @@ msgstr "Wbudowany dźwiÄ™k" msgid "Modem" msgstr "Modem" -#: spa/plugins/bluez5/bluez5-device.c:1199 +#: spa/plugins/bluez5/bluez5-device.c:1247 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Bramka dźwiÄ™ku (źródÅ‚o A2DP i AG HSP/HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1224 +#: spa/plugins/bluez5/bluez5-device.c:1272 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Odtwarzanie o wysokiej dokÅ‚adnoÅ›ci (odpÅ‚yw A2DP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1227 +#: spa/plugins/bluez5/bluez5-device.c:1275 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Dupleks o wysokiej dokÅ‚adnoÅ›ci (źródÅ‚o/odpÅ‚yw A2DP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1235 +#: spa/plugins/bluez5/bluez5-device.c:1283 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Odtwarzanie o wysokiej dokÅ‚adnoÅ›ci (odpÅ‚yw A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1237 +#: spa/plugins/bluez5/bluez5-device.c:1285 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Dupleks o wysokiej dokÅ‚adnoÅ›ci (źródÅ‚o/odpÅ‚yw A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1265 +#: spa/plugins/bluez5/bluez5-device.c:1322 +#, c-format +msgid "High Fidelity Playback (BAP Sink, codec %s)" +msgstr "Odtwarzanie o wysokiej dokÅ‚adnoÅ›ci (odpÅ‚yw BAP, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1326 +#, c-format +msgid "High Fidelity Input (BAP Source, codec %s)" +msgstr "WejÅ›cie o wysokiej dokÅ‚adnoÅ›ci (źródÅ‚o BAP, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1330 +#, c-format +msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" +msgstr "Dupleks o wysokiej dokÅ‚adnoÅ›ci (źródÅ‚o/odpÅ‚yw BAP, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1359 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Jednostka główna sÅ‚uchawek z mikrofonem (HSP/HFP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1270 +#: spa/plugins/bluez5/bluez5-device.c:1364 msgid "Headset Head Unit (HSP/HFP)" msgstr "Jednostka główna sÅ‚uchawek z mikrofonem (HSP/HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1349 -#: spa/plugins/bluez5/bluez5-device.c:1354 -#: spa/plugins/bluez5/bluez5-device.c:1361 -#: spa/plugins/bluez5/bluez5-device.c:1367 -#: spa/plugins/bluez5/bluez5-device.c:1373 -#: spa/plugins/bluez5/bluez5-device.c:1379 -#: spa/plugins/bluez5/bluez5-device.c:1385 -#: spa/plugins/bluez5/bluez5-device.c:1391 -#: spa/plugins/bluez5/bluez5-device.c:1397 +#: spa/plugins/bluez5/bluez5-device.c:1443 +#: spa/plugins/bluez5/bluez5-device.c:1448 +#: spa/plugins/bluez5/bluez5-device.c:1455 +#: spa/plugins/bluez5/bluez5-device.c:1461 +#: spa/plugins/bluez5/bluez5-device.c:1467 +#: spa/plugins/bluez5/bluez5-device.c:1473 +#: spa/plugins/bluez5/bluez5-device.c:1479 +#: spa/plugins/bluez5/bluez5-device.c:1485 +#: spa/plugins/bluez5/bluez5-device.c:1491 msgid "Handsfree" msgstr "Zestaw gÅ‚oÅ›nomówiÄ…cy" -#: spa/plugins/bluez5/bluez5-device.c:1355 +#: spa/plugins/bluez5/bluez5-device.c:1449 msgid "Handsfree (HFP)" msgstr "Zestaw gÅ‚oÅ›nomówiÄ…cy (HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1372 +#: spa/plugins/bluez5/bluez5-device.c:1466 msgid "Headphone" msgstr "SÅ‚uchawki" -#: spa/plugins/bluez5/bluez5-device.c:1378 +#: spa/plugins/bluez5/bluez5-device.c:1472 msgid "Portable" msgstr "PrzenoÅ›ne" -#: spa/plugins/bluez5/bluez5-device.c:1384 +#: spa/plugins/bluez5/bluez5-device.c:1478 msgid "Car" msgstr "Samochód" -#: spa/plugins/bluez5/bluez5-device.c:1390 +#: spa/plugins/bluez5/bluez5-device.c:1484 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:1396 +#: spa/plugins/bluez5/bluez5-device.c:1490 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:1403 +#: spa/plugins/bluez5/bluez5-device.c:1497 msgid "Bluetooth" msgstr "Bluetooth" -#: spa/plugins/bluez5/bluez5-device.c:1404 +#: spa/plugins/bluez5/bluez5-device.c:1498 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" diff --git a/po/pt_BR.po b/po/pt_BR.po index e695bef5e0aab4862072b8adbbace8c563a9745a..9b70d6f0b104fbe95307fb3ff72e065a29a2ded6 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -1,18 +1,19 @@ # Brazilian Portuguese translation for pipewire -# Copyright (C) 2021 Rafael Fontenelle <rafaelff@gnome.org> +# Copyright (C) 2022 Rafael Fontenelle <rafaelff@gnome.org> # This file is distributed under the same license as the pipewire package. # Fabian Affolter <fab@fedoraproject.org>, 2008. # Igor Pires Soares <igor@projetofedora.org>, 2009, 2012. # Rafael Fontenelle <rafaelff@gnome.org>, 2013-2021. +# Matheus Barbosa <mdpb.matheus@gmail.com>, 2022. # msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" -"POT-Creation-Date: 2021-08-01 15:31+0000\n" -"PO-Revision-Date: 2021-08-01 17:02-0300\n" -"Last-Translator: Rafael Fontenelle <rafaelff@gnome.org>\n" +"POT-Creation-Date: 2022-09-30 03:27+0000\n" +"PO-Revision-Date: 2022-01-25 19:49-0300\n" +"Last-Translator: Matheus Barbosa <mdpb.matheus@gmail.com>\n" "Language-Team: Brazilian Portuguese <gnome-pt_br-list@gnome.org>\n" "Language: pt_BR\n" "MIME-Version: 1.0\n" @@ -21,7 +22,7 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n > 1)\n" "X-Generator: Gtranslator 40.0\n" -#: src/daemon/pipewire.c:45 +#: src/daemon/pipewire.c:46 #, c-format msgid "" "%s [options]\n" @@ -43,58 +44,51 @@ msgstr "Sistema de MÃdia PipeWire" msgid "Start the PipeWire Media System" msgstr "Inicia o Sistema de MÃdia PipeWire" -#: src/examples/media-session/alsa-monitor.c:586 -#: spa/plugins/alsa/acp/compat.c:189 -msgid "Built-in Audio" -msgstr "Ãudio interno" - -#: src/examples/media-session/alsa-monitor.c:590 -#: spa/plugins/alsa/acp/compat.c:194 -msgid "Modem" -msgstr "Modem" - -#: src/examples/media-session/alsa-monitor.c:599 -#: src/modules/module-zeroconf-discover.c:296 -msgid "Unknown device" -msgstr "Dispositivo desconhecido" - -#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:173 -#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:173 +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:180 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:180 #, c-format msgid "Tunnel to %s/%s" msgstr "Túnel para %s/%s" -#: src/modules/module-pulse-tunnel.c:534 +#: src/modules/module-fallback-sink.c:51 +msgid "Dummy Output" +msgstr "SaÃda de falsa" + +#: src/modules/module-pulse-tunnel.c:662 #, c-format msgid "Tunnel for %s@%s" msgstr "Túnel para %s@%s" -#: src/modules/module-zeroconf-discover.c:308 +#: src/modules/module-zeroconf-discover.c:332 +msgid "Unknown device" +msgstr "Dispositivo desconhecido" + +#: src/modules/module-zeroconf-discover.c:344 #, c-format msgid "%s on %s@%s" msgstr "%s em %s@%s" -#: src/modules/module-zeroconf-discover.c:312 +#: src/modules/module-zeroconf-discover.c:348 #, c-format msgid "%s on %s" msgstr "%s em %s" -#: src/tools/pw-cat.c:1000 +#: src/tools/pw-cat.c:784 #, 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 [opções] <arquivo>\n" +"%s [opções] [<arquivo>|-]\n" " -h, --help Mostra esta ajuda\n" " --version Mostra a versão\n" " -v, --verbose Habilita operações verbosas\n" "\n" -#: src/tools/pw-cat.c:1007 +#: src/tools/pw-cat.c:791 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -108,7 +102,7 @@ msgid "" " 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 Nome do daemon remoto\n" @@ -124,11 +118,10 @@ msgstr "" " Xunit (unidade = s, ms, us, ns)\n" " ou amostras diretas (256)\n" " a taxa é um dos arquivos fontes\n" -" --list-targets Lista alvos disponÃveis para --" -"target\n" +" --properties Define as propriedades do nó\n" "\n" -#: src/tools/pw-cat.c:1025 +#: src/tools/pw-cat.c:809 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -165,19 +158,21 @@ msgstr "" "(padrão: %d)\n" "\n" -#: src/tools/pw-cat.c:1042 +#: src/tools/pw-cat.c:826 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" +" -d, --dsd DSD mode\n" "\n" msgstr "" " -p, --playback Modo de reprodução\n" " -r, --record Modo de gravação\n" -" -m, --midi Modo midi\n" +" -m, --midi Modo Midi\n" +" -d, --dsd Modo DSD\n" "\n" -#: src/tools/pw-cli.c:2954 +#: src/tools/pw-cli.c:2255 #, c-format msgid "" "%s [options] [command]\n" @@ -194,12 +189,12 @@ msgstr "" " -r, --remote Nome do daemon remoto\n" "\n" -#: spa/plugins/alsa/acp/acp.c:306 +#: spa/plugins/alsa/acp/acp.c:321 msgid "Pro Audio" msgstr "Pro Audio" -#: spa/plugins/alsa/acp/acp.c:429 spa/plugins/alsa/acp/alsa-mixer.c:4648 -#: spa/plugins/bluez5/bluez5-device.c:1043 +#: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 +#: spa/plugins/bluez5/bluez5-device.c:1236 msgid "Off" msgstr "Desligado" @@ -226,7 +221,7 @@ msgstr "Entrada de linha" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:1198 +#: spa/plugins/bluez5/bluez5-device.c:1454 msgid "Microphone" msgstr "Microfone" @@ -298,7 +293,7 @@ msgid "No Bass Boost" msgstr "Sem reforço de graves" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:1203 +#: spa/plugins/bluez5/bluez5-device.c:1460 msgid "Speaker" msgstr "Auto-falante" @@ -414,7 +409,7 @@ msgstr "Estéreo" # Fone de ouvido não se encaixa como tradução aqui, pois há ou pode haver microfone junto. #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 -#: spa/plugins/bluez5/bluez5-device.c:1188 +#: spa/plugins/bluez5/bluez5-device.c:1442 msgid "Headset" msgstr "Headset" @@ -528,17 +523,17 @@ msgstr "Duplex estéreo" msgid "Mono Chat + 7.1 Surround" msgstr "Bate-papo monofônico + surround 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4750 +#: spa/plugins/alsa/acp/alsa-mixer.c:4754 #, c-format msgid "%s Output" msgstr "SaÃda %s" -#: spa/plugins/alsa/acp/alsa-mixer.c:4757 +#: spa/plugins/alsa/acp/alsa-mixer.c:4761 #, c-format msgid "%s Input" msgstr "Entrada %s" -#: spa/plugins/alsa/acp/alsa-util.c:1173 spa/plugins/alsa/acp/alsa-util.c:1267 +#: spa/plugins/alsa/acp/alsa-util.c:1187 spa/plugins/alsa/acp/alsa-util.c:1281 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -561,7 +556,7 @@ msgstr[1] "" "É mais provável que isso seja um erro no driver “%s†do ALSA. Por favor, " "relate esse problema aos desenvolvedores do ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1239 +#: spa/plugins/alsa/acp/alsa-util.c:1253 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" @@ -584,7 +579,7 @@ msgstr[1] "" "É mais provável que isso seja um erro no driver “%s†do ALSA. Por favor, " "relate esse problema aos desenvolvedores do ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1286 +#: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -597,7 +592,7 @@ msgstr "" "É mais provável que isso seja um erro no driver “%s†do ALSA. Por favor, " "relate esse problema aos desenvolvedores do ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1329 +#: spa/plugins/alsa/acp/alsa-util.c:1343 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -620,66 +615,106 @@ msgstr[1] "" "É mais provável que isso seja um erro no driver “%s†do ALSA. Por favor, " "relate esse problema aos desenvolvedores do ALSA." -#: spa/plugins/alsa/acp/channelmap.h:466 +#: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(inválido)" -#: spa/plugins/bluez5/bluez5-device.c:1053 +#: spa/plugins/alsa/acp/compat.c:189 +msgid "Built-in Audio" +msgstr "Ãudio interno" + +#: spa/plugins/alsa/acp/compat.c:194 +msgid "Modem" +msgstr "Modem" + +#: spa/plugins/bluez5/bluez5-device.c:1247 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Gateway de áudio (fonte A2DP & HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1076 +#: spa/plugins/bluez5/bluez5-device.c:1272 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Reprodução de alta-fidelidade (destino A2DP, codec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1078 +#: spa/plugins/bluez5/bluez5-device.c:1275 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Duplex de alta-fidelidade (fonte/destino A2DP, codec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1084 +#: spa/plugins/bluez5/bluez5-device.c:1283 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Reprodução de alta-fidelidade (destino A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1086 +#: spa/plugins/bluez5/bluez5-device.c:1285 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Duplex de alta-fidelidade (fonte/destino A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1113 +#: spa/plugins/bluez5/bluez5-device.c:1322 +#, c-format +msgid "High Fidelity Playback (BAP Sink, codec %s)" +msgstr "Reprodução de alta-fidelidade (destino BAP, codec %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1326 +#, c-format +msgid "High Fidelity Input (BAP Source, codec %s)" +msgstr "Entrada de alta-fidelidade (fonte BAP, codec %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1330 +#, c-format +msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" +msgstr "Duplex de alta-fidelidade (fonte/destino BAP, codec %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1359 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Unidade de headset (HSP/HFP, codec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1117 +#: spa/plugins/bluez5/bluez5-device.c:1364 msgid "Headset Head Unit (HSP/HFP)" msgstr "Unidade de headset (HSP/HFP)" -# Desconheço tradução comum para esta palavra. -#: spa/plugins/bluez5/bluez5-device.c:1193 +# Supostamente relacionado a HFP, hands-free profile, mas não encontrei tradução comum +#: spa/plugins/bluez5/bluez5-device.c:1443 +#: spa/plugins/bluez5/bluez5-device.c:1448 +#: spa/plugins/bluez5/bluez5-device.c:1455 +#: spa/plugins/bluez5/bluez5-device.c:1461 +#: spa/plugins/bluez5/bluez5-device.c:1467 +#: spa/plugins/bluez5/bluez5-device.c:1473 +#: spa/plugins/bluez5/bluez5-device.c:1479 +#: spa/plugins/bluez5/bluez5-device.c:1485 +#: spa/plugins/bluez5/bluez5-device.c:1491 msgid "Handsfree" msgstr "Handsfree" -#: spa/plugins/bluez5/bluez5-device.c:1208 +# Supostamente relacionado a HFP, hands-free profile, mas não encontrei tradução comum +#: spa/plugins/bluez5/bluez5-device.c:1449 +msgid "Handsfree (HFP)" +msgstr "Handsfree (HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:1466 msgid "Headphone" msgstr "Fones de ouvido" -#: spa/plugins/bluez5/bluez5-device.c:1213 +#: spa/plugins/bluez5/bluez5-device.c:1472 msgid "Portable" msgstr "Portátil" -#: spa/plugins/bluez5/bluez5-device.c:1218 +#: spa/plugins/bluez5/bluez5-device.c:1478 msgid "Car" msgstr "Carro" -#: spa/plugins/bluez5/bluez5-device.c:1223 +#: spa/plugins/bluez5/bluez5-device.c:1484 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:1228 +#: spa/plugins/bluez5/bluez5-device.c:1490 msgid "Phone" msgstr "Telefone" -#: spa/plugins/bluez5/bluez5-device.c:1234 +#: spa/plugins/bluez5/bluez5-device.c:1497 msgid "Bluetooth" msgstr "Bluetooth" + +#: spa/plugins/bluez5/bluez5-device.c:1498 +msgid "Bluetooth (HFP)" +msgstr "Bluetooth (HFP)" diff --git a/po/sv.po b/po/sv.po index f9a57802245ab89f1446ec91f7aa8c2e28e66010..86bf70163ae396fa41d838117b37747f708f0076 100644 --- a/po/sv.po +++ b/po/sv.po @@ -19,8 +19,8 @@ msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" -"POT-Creation-Date: 2022-07-19 15:27+0000\n" -"PO-Revision-Date: 2022-07-10 10:22+0200\n" +"POT-Creation-Date: 2022-10-20 15:27+0000\n" +"PO-Revision-Date: 2022-09-16 12:58+0200\n" "Last-Translator: Anders Jonsson <anders.jonsson@norsjovallen.se>\n" "Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n" "Language: sv\n" @@ -28,7 +28,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Poedit 3.1\n" +"X-Generator: Poedit 3.1.1\n" #: src/daemon/pipewire.c:46 #, c-format @@ -61,26 +61,26 @@ msgstr "Tunnel till %s/%s" msgid "Dummy Output" msgstr "AttrapputgÃ¥ng" -#: src/modules/module-pulse-tunnel.c:648 +#: src/modules/module-pulse-tunnel.c:681 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunnel för %s@%s" -#: src/modules/module-zeroconf-discover.c:332 +#: src/modules/module-zeroconf-discover.c:335 msgid "Unknown device" msgstr "Okänd enhet" -#: src/modules/module-zeroconf-discover.c:344 +#: src/modules/module-zeroconf-discover.c:347 #, c-format msgid "%s on %s@%s" msgstr "%s pÃ¥ %s@%s" -#: src/modules/module-zeroconf-discover.c:348 +#: src/modules/module-zeroconf-discover.c:351 #, c-format msgid "%s on %s" msgstr "%s pÃ¥ %s" -#: src/tools/pw-cat.c:784 +#: src/tools/pw-cat.c:782 #, c-format msgid "" "%s [options] [<file>|-]\n" @@ -95,7 +95,7 @@ msgstr "" " -v, --verbose Aktivera utförliga operationer\n" "\n" -#: src/tools/pw-cat.c:791 +#: src/tools/pw-cat.c:789 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -125,7 +125,7 @@ msgstr "" " -P --properties Sätt nodegenskaper\n" "\n" -#: src/tools/pw-cat.c:809 +#: src/tools/pw-cat.c:807 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -160,7 +160,7 @@ msgstr "" "%d)\n" "\n" -#: src/tools/pw-cat.c:826 +#: src/tools/pw-cat.c:824 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" @@ -174,7 +174,7 @@ msgstr "" " -d, --dsd DSD-läge\n" "\n" -#: src/tools/pw-cli.c:3165 +#: src/tools/pw-cli.c:2250 #, c-format msgid "" "%s [options] [command]\n" @@ -195,8 +195,8 @@ msgstr "" msgid "Pro Audio" msgstr "Professionellt ljud" -#: spa/plugins/alsa/acp/acp.c:446 spa/plugins/alsa/acp/alsa-mixer.c:4648 -#: spa/plugins/bluez5/bluez5-device.c:1188 +#: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 +#: spa/plugins/bluez5/bluez5-device.c:1237 msgid "Off" msgstr "Av" @@ -223,7 +223,7 @@ msgstr "Linje in" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:1360 +#: spa/plugins/bluez5/bluez5-device.c:1455 msgid "Microphone" msgstr "Mikrofon" @@ -289,7 +289,7 @@ msgid "No Bass Boost" msgstr "Ingen basökning" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:1366 +#: spa/plugins/bluez5/bluez5-device.c:1461 msgid "Speaker" msgstr "Högtalare" @@ -404,7 +404,7 @@ msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 -#: spa/plugins/bluez5/bluez5-device.c:1348 +#: spa/plugins/bluez5/bluez5-device.c:1443 msgid "Headset" msgstr "Headset" @@ -528,7 +528,7 @@ msgstr "%s-utgÃ¥ng" msgid "%s Input" msgstr "%s-ingÃ¥ng" -#: spa/plugins/alsa/acp/alsa-util.c:1173 spa/plugins/alsa/acp/alsa-util.c:1267 +#: spa/plugins/alsa/acp/alsa-util.c:1187 spa/plugins/alsa/acp/alsa-util.c:1281 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -551,7 +551,7 @@ msgstr[1] "" "Förmodligen är detta ett fel i ALSA-drivrutinen â€%sâ€. Vänligen rapportera " "problemet till ALSA-utvecklarna." -#: spa/plugins/alsa/acp/alsa-util.c:1239 +#: spa/plugins/alsa/acp/alsa-util.c:1253 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" @@ -574,7 +574,7 @@ msgstr[1] "" "Förmodligen är detta ett fel i ALSA-drivrutinen â€%sâ€. Vänligen rapportera " "problemet till ALSA-utvecklarna." -#: spa/plugins/alsa/acp/alsa-util.c:1286 +#: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -587,7 +587,7 @@ msgstr "" "Förmodligen är detta ett fel i ALSA-drivrutinen â€%sâ€. Vänligen rapportera " "problemet till ALSA-utvecklarna." -#: spa/plugins/alsa/acp/alsa-util.c:1329 +#: spa/plugins/alsa/acp/alsa-util.c:1343 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -622,77 +622,92 @@ msgstr "Inbyggt ljud" msgid "Modem" msgstr "Modem" -#: spa/plugins/bluez5/bluez5-device.c:1199 +#: spa/plugins/bluez5/bluez5-device.c:1248 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Audio gateway (A2DP-källa & HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1224 +#: spa/plugins/bluez5/bluez5-device.c:1273 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "High fidelity-uppspelning (A2DP-utgÃ¥ng, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1227 +#: spa/plugins/bluez5/bluez5-device.c:1276 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "High fidelity duplex (A2DP-källa/utgÃ¥ng, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1235 +#: spa/plugins/bluez5/bluez5-device.c:1284 msgid "High Fidelity Playback (A2DP Sink)" msgstr "High fidelity-uppspelning (A2DP-utgÃ¥ng)" -#: spa/plugins/bluez5/bluez5-device.c:1237 +#: spa/plugins/bluez5/bluez5-device.c:1286 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "High fidelity duplex (A2DP-källa/utgÃ¥ng)" -#: spa/plugins/bluez5/bluez5-device.c:1265 +#: spa/plugins/bluez5/bluez5-device.c:1323 +#, c-format +msgid "High Fidelity Playback (BAP Sink, codec %s)" +msgstr "High fidelity-uppspelning (BAP-utgÃ¥ng, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1327 +#, c-format +msgid "High Fidelity Input (BAP Source, codec %s)" +msgstr "High fidelity-ingÃ¥ng (BAP-källa, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1331 +#, c-format +msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" +msgstr "High fidelity duplex (BAP-källa/utgÃ¥ng, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1360 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Headset-huvudenhet (HSP/HFP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1270 +#: spa/plugins/bluez5/bluez5-device.c:1365 msgid "Headset Head Unit (HSP/HFP)" msgstr "Headset-huvudenhet (HSP/HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1349 -#: spa/plugins/bluez5/bluez5-device.c:1354 -#: spa/plugins/bluez5/bluez5-device.c:1361 -#: spa/plugins/bluez5/bluez5-device.c:1367 -#: spa/plugins/bluez5/bluez5-device.c:1373 -#: spa/plugins/bluez5/bluez5-device.c:1379 -#: spa/plugins/bluez5/bluez5-device.c:1385 -#: spa/plugins/bluez5/bluez5-device.c:1391 -#: spa/plugins/bluez5/bluez5-device.c:1397 +#: spa/plugins/bluez5/bluez5-device.c:1444 +#: spa/plugins/bluez5/bluez5-device.c:1449 +#: spa/plugins/bluez5/bluez5-device.c:1456 +#: spa/plugins/bluez5/bluez5-device.c:1462 +#: spa/plugins/bluez5/bluez5-device.c:1468 +#: spa/plugins/bluez5/bluez5-device.c:1474 +#: spa/plugins/bluez5/bluez5-device.c:1480 +#: spa/plugins/bluez5/bluez5-device.c:1486 +#: spa/plugins/bluez5/bluez5-device.c:1492 msgid "Handsfree" msgstr "Handsfree" -#: spa/plugins/bluez5/bluez5-device.c:1355 +#: spa/plugins/bluez5/bluez5-device.c:1450 msgid "Handsfree (HFP)" msgstr "Handsfree (HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1372 +#: spa/plugins/bluez5/bluez5-device.c:1467 msgid "Headphone" msgstr "Hörlurar" -#: spa/plugins/bluez5/bluez5-device.c:1378 +#: spa/plugins/bluez5/bluez5-device.c:1473 msgid "Portable" msgstr "Bärbar" -#: spa/plugins/bluez5/bluez5-device.c:1384 +#: spa/plugins/bluez5/bluez5-device.c:1479 msgid "Car" msgstr "Bil" -#: spa/plugins/bluez5/bluez5-device.c:1390 +#: spa/plugins/bluez5/bluez5-device.c:1485 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:1396 +#: spa/plugins/bluez5/bluez5-device.c:1491 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:1403 +#: spa/plugins/bluez5/bluez5-device.c:1498 msgid "Bluetooth" msgstr "Bluetooth" -#: spa/plugins/bluez5/bluez5-device.c:1404 +#: spa/plugins/bluez5/bluez5-device.c:1499 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" diff --git a/po/tr.po b/po/tr.po index a1017944907316438d19b8a59c31565ae3969618..766f48f48bb909805a8157bbe9ab3017f8484b70 100644 --- a/po/tr.po +++ b/po/tr.po @@ -11,8 +11,8 @@ msgstr "" "Project-Id-Version: PipeWire master\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" -"POT-Creation-Date: 2022-04-03 12:56+0200\n" -"PO-Revision-Date: 2022-05-14 18:35+0300\n" +"POT-Creation-Date: 2022-06-30 12:50+0200\n" +"PO-Revision-Date: 2022-10-23 10:40+0300\n" "Last-Translator: OÄŸuz Ersen <oguz@ersen.moe>\n" "Language-Team: Turkish <tr>\n" "Language: tr\n" @@ -35,8 +35,8 @@ msgstr "" " --version Sürümü göster\n" " -c, --config Yapılandırmayı yükle (Öntanımlı %s)\n" -#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:190 -#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:190 +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:180 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:180 #, c-format msgid "Tunnel to %s/%s" msgstr "%s/%s tüneli" @@ -45,41 +45,41 @@ msgstr "%s/%s tüneli" msgid "Dummy Output" msgstr "Temsili Çıkış" -#: src/modules/module-pulse-tunnel.c:545 +#: src/modules/module-pulse-tunnel.c:662 #, c-format msgid "Tunnel for %s@%s" msgstr "%s@%s için tünel" -#: src/modules/module-zeroconf-discover.c:313 +#: src/modules/module-zeroconf-discover.c:332 msgid "Unknown device" msgstr "Bilinmeyen aygıt" -#: src/modules/module-zeroconf-discover.c:325 +#: src/modules/module-zeroconf-discover.c:344 #, c-format msgid "%s on %s@%s" msgstr "%s, %s@%s" -#: src/modules/module-zeroconf-discover.c:329 +#: src/modules/module-zeroconf-discover.c:348 #, c-format msgid "%s on %s" msgstr "%s, %s" -#: src/tools/pw-cat.c:1087 +#: src/tools/pw-cat.c:784 #, 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 [seçenekler] <dosya>\n" +"%s [seçenekler] [<dosya>|-]\n" " -h, --help Bu yardımı göster\n" " --version Sürümü göster\n" " -v, --verbose Ayrıntılı iÅŸlemleri etkinleÅŸtir\n" "\n" -#: src/tools/pw-cat.c:1094 +#: src/tools/pw-cat.c:791 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -93,7 +93,7 @@ msgid "" " 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 Uzak arka plan programı adı\n" @@ -109,11 +109,10 @@ msgstr "" " Xbirim (birim = s, ms, us, ns)\n" " veya doÄŸrudan örneklemeler (256)\n" " oran kaynak dosyadan biridir\n" -" --list-targets --target için kullanılabilir " -"hedefleri listele\n" +" -P --properties Düğüm özelliklerini ayarla\n" "\n" -#: src/tools/pw-cat.c:1112 +#: src/tools/pw-cat.c:809 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -149,7 +148,7 @@ msgstr "" "15) (öntanımlı %d)\n" "\n" -#: src/tools/pw-cat.c:1129 +#: src/tools/pw-cat.c:826 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" @@ -163,7 +162,7 @@ msgstr "" " -d, --dsd DSD modu\n" "\n" -#: src/tools/pw-cli.c:3051 +#: src/tools/pw-cli.c:2250 #, c-format msgid "" "%s [options] [command]\n" @@ -186,7 +185,7 @@ msgid "Pro Audio" msgstr "Profesyonel Ses" #: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 -#: spa/plugins/bluez5/bluez5-device.c:1159 +#: spa/plugins/bluez5/bluez5-device.c:1236 msgid "Off" msgstr "Kapalı" @@ -213,7 +212,7 @@ msgstr "Hat GiriÅŸi" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:1328 +#: spa/plugins/bluez5/bluez5-device.c:1454 msgid "Microphone" msgstr "Mikrofon" @@ -279,7 +278,7 @@ msgid "No Bass Boost" msgstr "Bas Artırma Yok" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:1333 +#: spa/plugins/bluez5/bluez5-device.c:1460 msgid "Speaker" msgstr "Hoparlör" @@ -394,7 +393,7 @@ msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 -#: spa/plugins/bluez5/bluez5-device.c:1318 +#: spa/plugins/bluez5/bluez5-device.c:1442 msgid "Headset" msgstr "Kulaklık" @@ -518,7 +517,7 @@ msgstr "%s Çıkışı" msgid "%s Input" msgstr "%s GiriÅŸi" -#: spa/plugins/alsa/acp/alsa-util.c:1173 spa/plugins/alsa/acp/alsa-util.c:1267 +#: spa/plugins/alsa/acp/alsa-util.c:1187 spa/plugins/alsa/acp/alsa-util.c:1281 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -535,7 +534,7 @@ msgstr[0] "" "Büyük ihtimalle bu bir ALSA sürücüsü '%s' hatasıdır. Lütfen bu sorunu ALSA " "geliÅŸtiricilerine bildirin." -#: spa/plugins/alsa/acp/alsa-util.c:1239 +#: spa/plugins/alsa/acp/alsa-util.c:1253 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " @@ -552,7 +551,7 @@ msgstr[0] "" "Büyük ihtimalle bu bir ALSA sürücüsü '%s' hatasıdır. Lütfen bu sorunu ALSA " "geliÅŸtiricilerine bildirin." -#: spa/plugins/alsa/acp/alsa-util.c:1286 +#: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -565,7 +564,7 @@ msgstr "" "Büyük ihtimalle bu bir ALSA sürücüsü '%s' hatasıdır. Lütfen bu sorunu ALSA " "geliÅŸtiricilerine bildirin." -#: spa/plugins/alsa/acp/alsa-util.c:1329 +#: spa/plugins/alsa/acp/alsa-util.c:1343 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -583,7 +582,7 @@ msgstr[0] "" "Büyük ihtimalle bu bir ALSA sürücüsü '%s' hatasıdır. Lütfen bu sorunu ALSA " "geliÅŸtiricilerine bildirin." -#: spa/plugins/alsa/acp/channelmap.h:464 +#: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(geçersiz)" @@ -595,65 +594,96 @@ msgstr "Dahili Ses" msgid "Modem" msgstr "Modem" -#: spa/plugins/bluez5/bluez5-device.c:1170 +#: spa/plugins/bluez5/bluez5-device.c:1247 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Ses Geçidi (A2DP Kaynak & HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1195 +#: spa/plugins/bluez5/bluez5-device.c:1272 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Yüksek Kaliteli Çalma (A2DP Alıcı, çözücü %s)" -#: spa/plugins/bluez5/bluez5-device.c:1198 +#: spa/plugins/bluez5/bluez5-device.c:1275 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Yüksek Kaliteli İkili (A2DP Kaynak/Alıcı, çözücü %s)" -#: spa/plugins/bluez5/bluez5-device.c:1206 +#: spa/plugins/bluez5/bluez5-device.c:1283 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Yüksek Kaliteli Çalma (A2DP Alıcı)" -#: spa/plugins/bluez5/bluez5-device.c:1208 +#: spa/plugins/bluez5/bluez5-device.c:1285 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Yüksek Kaliteli İkili (A2DP Kaynak/Alıcı)" -#: spa/plugins/bluez5/bluez5-device.c:1236 +#: spa/plugins/bluez5/bluez5-device.c:1322 +#, c-format +msgid "High Fidelity Playback (BAP Sink, codec %s)" +msgstr "Yüksek Kaliteli Çalma (BAP Alıcı, çözücü %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1326 +#, c-format +msgid "High Fidelity Input (BAP Source, codec %s)" +msgstr "Yüksek Kaliteli GiriÅŸ (BAP Kaynak, çözücü %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1330 +#, c-format +msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" +msgstr "Yüksek Kaliteli İkili (BAP Kaynak/Alıcı, çözücü %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1359 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Kulaklık Ana Birimi (HSP/HFP, çözücü %s)" -#: spa/plugins/bluez5/bluez5-device.c:1241 +#: spa/plugins/bluez5/bluez5-device.c:1364 msgid "Headset Head Unit (HSP/HFP)" msgstr "Kulaklık Ana Birimi (HSP/HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1323 +#: spa/plugins/bluez5/bluez5-device.c:1443 +#: spa/plugins/bluez5/bluez5-device.c:1448 +#: spa/plugins/bluez5/bluez5-device.c:1455 +#: spa/plugins/bluez5/bluez5-device.c:1461 +#: spa/plugins/bluez5/bluez5-device.c:1467 +#: spa/plugins/bluez5/bluez5-device.c:1473 +#: spa/plugins/bluez5/bluez5-device.c:1479 +#: spa/plugins/bluez5/bluez5-device.c:1485 +#: spa/plugins/bluez5/bluez5-device.c:1491 msgid "Handsfree" msgstr "Ahizesiz" -#: spa/plugins/bluez5/bluez5-device.c:1338 +#: spa/plugins/bluez5/bluez5-device.c:1449 +msgid "Handsfree (HFP)" +msgstr "Ahizesiz (HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:1466 msgid "Headphone" msgstr "Kulaklık" -#: spa/plugins/bluez5/bluez5-device.c:1343 +#: spa/plugins/bluez5/bluez5-device.c:1472 msgid "Portable" msgstr "Taşınabilir" -#: spa/plugins/bluez5/bluez5-device.c:1348 +#: spa/plugins/bluez5/bluez5-device.c:1478 msgid "Car" msgstr "Araba" -#: spa/plugins/bluez5/bluez5-device.c:1353 +#: spa/plugins/bluez5/bluez5-device.c:1484 msgid "HiFi" msgstr "Yüksek Kalite" -#: spa/plugins/bluez5/bluez5-device.c:1358 +#: spa/plugins/bluez5/bluez5-device.c:1490 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:1364 +#: spa/plugins/bluez5/bluez5-device.c:1497 msgid "Bluetooth" msgstr "Bluetooth" +#: spa/plugins/bluez5/bluez5-device.c:1498 +msgid "Bluetooth (HFP)" +msgstr "Bluetooth (HFP)" + #~ msgid "PipeWire Media System" #~ msgstr "PipeWire Ortam Sistemi" diff --git a/pw-uninstalled.sh b/pw-uninstalled.sh index 7bc948ddde7c71f75e1638bc4c67d3d768bec128..1bb6c55c23b9a45f100497091db501870d15f358 100755 --- a/pw-uninstalled.sh +++ b/pw-uninstalled.sh @@ -41,7 +41,7 @@ export SPA_PLUGIN_DIR="${BUILDDIR}/spa/plugins" export SPA_DATA_DIR="${SCRIPT_DIR}/spa/plugins" # the directory with pipewire modules export PIPEWIRE_MODULE_DIR="${BUILDDIR}/src/modules" -export PATH="${BUILDDIR}/src/daemon:${BUILDDIR}/src/tools:${BUILDDIR}/src/media-session:${BUILDDIR}/src/examples:${PATH}" +export PATH="${BUILDDIR}/src/daemon:${BUILDDIR}/src/tools:${BUILDDIR}/src/media-session:${BUILDDIR}/src/examples:${BUILDDIR}/pipewire-v4l2/src:${PATH}" export LD_LIBRARY_PATH="${BUILDDIR}/src/pipewire/:${BUILDDIR}/pipewire-jack/src/${LD_LIBRARY_PATH+":$LD_LIBRARY_PATH"}" export GST_PLUGIN_PATH="${BUILDDIR}/src/gst/${GST_PLUGIN_PATH+":${GST_PLUGIN_PATH}"}" # the directory with card profiles and paths @@ -50,6 +50,7 @@ export ACP_PROFILES_DIR="${SCRIPT_DIR}/spa/plugins/alsa/mixer/profile-sets" # ALSA plugin directory export ALSA_PLUGIN_DIR="${BUILDDIR}/pipewire-alsa/alsa-plugins" +export PW_BUILDDIR=$BUILDDIR export PW_UNINSTALLED=1 export PKG_CONFIG_PATH="${BUILDDIR}/meson-uninstalled/:${PKG_CONFIG_PATH}" diff --git a/spa/include/spa/buffer/alloc.h b/spa/include/spa/buffer/alloc.h index 6417073838d71ed03c9fede73078ed7113a677e2..ec855415323dc09305bb6464a2bf7b829270eb35 100644 --- a/spa/include/spa/buffer/alloc.h +++ b/spa/include/spa/buffer/alloc.h @@ -161,8 +161,9 @@ static inline int spa_buffer_alloc_fill_info(struct spa_buffer_alloc_info *info, *target += info->chunk_size; for (i = 0, size = 0; i < n_datas; i++) { + int64_t align = data_aligns[i]; info->max_align = SPA_MAX(info->max_align, data_aligns[i]); - size = SPA_ROUND_UP_N(size, data_aligns[i]); + size = SPA_ROUND_UP_N(size, align); size += datas[i].maxsize; } info->data_size = size; diff --git a/spa/include/spa/buffer/meta.h b/spa/include/spa/buffer/meta.h index ae72ef970e5dc13a3b967f57e7398163fbd6c4ce..e270c56cacbaf13d9d9a93b5f1cc2ae127b067f4 100644 --- a/spa/include/spa/buffer/meta.h +++ b/spa/include/spa/buffer/meta.h @@ -64,9 +64,15 @@ struct spa_meta { void *data; /**< pointer to metadata */ }; -#define spa_meta_first(m) ((m)->data) -#define spa_meta_end(m) SPA_PTROFF((m)->data,(m)->size,void) -#define spa_meta_check(p,m) (SPA_PTROFF(p,sizeof(*p),void) <= spa_meta_end(m)) +static inline void *spa_meta_first(const struct spa_meta *m) { + return m->data; +} +#define spa_meta_first spa_meta_first +static inline void *spa_meta_end(const struct spa_meta *m) { + return SPA_PTROFF(m->data,m->size,void); +} +#define spa_meta_end spa_meta_end +#define spa_meta_check(p,m) (SPA_PTROFF(p,sizeof(*(p)),void) <= spa_meta_end(m)) /** * Describes essential buffer header metadata such as flags and @@ -92,11 +98,14 @@ struct spa_meta_region { struct spa_region region; }; -#define spa_meta_region_is_valid(m) ((m)->region.size.width != 0 && (m)->region.size.height != 0) +static inline bool spa_meta_region_is_valid(const struct spa_meta_region *m) { + return m->region.size.width != 0 && m->region.size.height != 0; +} +#define spa_meta_region_is_valid spa_meta_region_is_valid /** iterate all the items in a metadata */ #define spa_meta_for_each(pos,meta) \ - for (pos = (__typeof(pos))spa_meta_first(meta); \ + for ((pos) = (__typeof(pos))spa_meta_first(meta); \ spa_meta_check(pos, meta); \ (pos)++) diff --git a/spa/include/spa/debug/log.h b/spa/include/spa/debug/log.h index 8311e95460ce0221cb60b5fe82c6c70a4fea6cd6..bfe2d5ff84dbd061b1086dd4d21183f5aef615bd 100644 --- a/spa/include/spa/debug/log.h +++ b/spa/include/spa/debug/log.h @@ -36,10 +36,10 @@ extern "C" { */ #ifndef spa_debug -#define spa_debug(fmt,...) ({ printf(fmt"\n", ## __VA_ARGS__); }) +#define spa_debug(fmt,...) ({ printf((fmt"\n"), ## __VA_ARGS__); }) #endif #ifndef spa_debugn -#define spa_debugn(fmt,...) ({ printf(fmt, ## __VA_ARGS__); }) +#define spa_debugn(fmt,...) ({ printf((fmt), ## __VA_ARGS__); }) #endif /** diff --git a/spa/include/spa/graph/graph.h b/spa/include/spa/graph/graph.h index ba402481c96a7ca62f5d6e443e7a5f15b35448c2..0e887cd3c6d0ad436cb7eb40c709c262351faa2b 100644 --- a/spa/include/spa/graph/graph.h +++ b/spa/include/spa/graph/graph.h @@ -121,12 +121,12 @@ struct spa_graph_node { int __res = 0; \ spa_callbacks_call_res(&(n)->callbacks, \ struct spa_graph_node_callbacks, __res, \ - method, version, ##__VA_ARGS__); \ + method, (version), ##__VA_ARGS__); \ __res; \ }) -#define spa_graph_node_process(n) spa_graph_node_call(n, process, 0, n) -#define spa_graph_node_reuse_buffer(n,p,i) spa_graph_node_call(n, reuse_buffer, 0, n, p, i) +#define spa_graph_node_process(n) spa_graph_node_call((n), process, 0, (n)) +#define spa_graph_node_reuse_buffer(n,p,i) spa_graph_node_call((n), reuse_buffer, 0, (n), (p), (i)) struct spa_graph_port { struct spa_list link; /**< link in node port list */ diff --git a/spa/include/spa/interfaces/audio/aec.h b/spa/include/spa/interfaces/audio/aec.h index 9fb109df21b11a5b59373370017a20ca18edaf49..c5dcb68aedf5ae900df87b5d9db77aa1bd10cf4f 100644 --- a/spa/include/spa/interfaces/audio/aec.h +++ b/spa/include/spa/interfaces/audio/aec.h @@ -23,6 +23,7 @@ */ +#include <spa/pod/builder.h> #include <spa/utils/dict.h> #include <spa/utils/hook.h> #include <spa/param/audio/raw.h> @@ -60,7 +61,7 @@ struct spa_audio_aec_events { }; struct spa_audio_aec_methods { -#define SPA_VERSION_AUDIO_AEC_METHODS 1 +#define SPA_VERSION_AUDIO_AEC_METHODS 2 uint32_t version; int (*add_listener) (void *object, @@ -75,15 +76,20 @@ struct spa_audio_aec_methods { int (*activate) (void *object); /* since 0.3.58, version 1:1 */ int (*deactivate) (void *object); + + /* version 1:2 */ + int (*enum_props) (void* object, int index, struct spa_pod_builder* builder); + int (*get_params) (void* object, struct spa_pod_builder* builder); + int (*set_params) (void *object, const struct spa_pod *args); }; #define spa_audio_aec_method(o,method,version,...) \ ({ \ int _res = -ENOTSUP; \ - struct spa_audio_aec *_o = o; \ + struct spa_audio_aec *_o = (o); \ spa_interface_call_res(&_o->iface, \ struct spa_audio_aec_methods, _res, \ - method, version, ##__VA_ARGS__); \ + method, (version), ##__VA_ARGS__); \ _res; \ }) @@ -93,6 +99,9 @@ struct spa_audio_aec_methods { #define spa_audio_aec_set_props(o,...) spa_audio_aec_method(o, set_props, 0, __VA_ARGS__) #define spa_audio_aec_activate(o) spa_audio_aec_method(o, activate, 1) #define spa_audio_aec_deactivate(o) spa_audio_aec_method(o, deactivate, 1) +#define spa_audio_aec_enum_props(o,...) spa_audio_aec_method(o, enum_props, 2, __VA_ARGS__) +#define spa_audio_aec_get_params(o,...) spa_audio_aec_method(o, get_params, 2, __VA_ARGS__) +#define spa_audio_aec_set_params(o,...) spa_audio_aec_method(o, set_params, 2, __VA_ARGS__) #ifdef __cplusplus } /* extern "C" */ diff --git a/spa/include/spa/monitor/device.h b/spa/include/spa/monitor/device.h index 481874d184130342c7ecc16b8fb434fa56b71bd8..59fea51484c03ddba5d1ac03ac67fd82ebae7116 100644 --- a/spa/include/spa/monitor/device.h +++ b/spa/include/spa/monitor/device.h @@ -71,7 +71,7 @@ struct spa_device_info { uint32_t n_params; /**< number of elements in params */ }; -#define SPA_DEVICE_INFO_INIT() (struct spa_device_info){ SPA_VERSION_DEVICE_INFO, } +#define SPA_DEVICE_INFO_INIT() ((struct spa_device_info){ SPA_VERSION_DEVICE_INFO, }) /** * Information about a device object @@ -92,7 +92,7 @@ struct spa_device_object_info { const struct spa_dict *props; /**< extra object properties */ }; -#define SPA_DEVICE_OBJECT_INFO_INIT() (struct spa_device_object_info){ SPA_VERSION_DEVICE_OBJECT_INFO, } +#define SPA_DEVICE_OBJECT_INFO_INIT() ((struct spa_device_object_info){ SPA_VERSION_DEVICE_OBJECT_INFO, }) /** the result of spa_device_enum_params() */ #define SPA_RESULT_TYPE_DEVICE_PARAMS 1 @@ -243,10 +243,10 @@ struct spa_device_methods { #define spa_device_method(o,method,version,...) \ ({ \ int _res = -ENOTSUP; \ - struct spa_device *_o = o; \ + struct spa_device *_o = (o); \ spa_interface_call_res(&_o->iface, \ struct spa_device_methods, _res, \ - method, version, ##__VA_ARGS__); \ + method, (version), ##__VA_ARGS__); \ _res; \ }) diff --git a/spa/include/spa/node/io.h b/spa/include/spa/node/io.h index 74635c79a5c8ee0972ed92d1db0c64795492ed01..9211f65977e45f9b788d517ac4adf687eec2dafa 100644 --- a/spa/include/spa/node/io.h +++ b/spa/include/spa/node/io.h @@ -100,7 +100,7 @@ struct spa_io_buffers { uint32_t buffer_id; /**< a buffer id */ }; -#define SPA_IO_BUFFERS_INIT (struct spa_io_buffers) { SPA_STATUS_OK, SPA_ID_INVALID, } +#define SPA_IO_BUFFERS_INIT ((struct spa_io_buffers) { SPA_STATUS_OK, SPA_ID_INVALID, }) /** * IO area to exchange a memory region @@ -110,7 +110,7 @@ struct spa_io_memory { uint32_t size; /**< the size of \a data */ void *data; /**< a memory pointer */ }; -#define SPA_IO_MEMORY_INIT (struct spa_io_memory) { SPA_STATUS_OK, 0, NULL, } +#define SPA_IO_MEMORY_INIT ((struct spa_io_memory) { SPA_STATUS_OK, 0, NULL, }) /** A range, suitable for input ports that can suggest a range to output ports */ struct spa_io_range { diff --git a/spa/include/spa/node/node.h b/spa/include/spa/node/node.h index 17be05d67f371088ab86dfa258d973acb7179c4f..6efa6d01fd75ab3139ed163167c74fc902af8e9b 100644 --- a/spa/include/spa/node/node.h +++ b/spa/include/spa/node/node.h @@ -85,7 +85,7 @@ struct spa_node_info { uint32_t n_params; /**< number of items in \a params */ }; -#define SPA_NODE_INFO_INIT() (struct spa_node_info) { 0, } +#define SPA_NODE_INFO_INIT() ((struct spa_node_info) { 0, }) /** * Port information structure @@ -124,7 +124,7 @@ struct spa_port_info { uint32_t n_params; /**< number of items in \a params */ }; -#define SPA_PORT_INFO_INIT() (struct spa_port_info) { 0, } +#define SPA_PORT_INFO_INIT() ((struct spa_port_info) { 0, }) #define SPA_RESULT_TYPE_NODE_ERROR 1 #define SPA_RESULT_TYPE_NODE_PARAMS 2 diff --git a/spa/include/spa/param/audio/dsd.h b/spa/include/spa/param/audio/dsd.h index 3228f2565df89c1c4ea8351dae04d9c511f0b8ee..3b317f2ed44e989d804441697e780ce1e295894a 100644 --- a/spa/include/spa/param/audio/dsd.h +++ b/spa/include/spa/param/audio/dsd.h @@ -68,7 +68,7 @@ struct spa_audio_info_dsd { uint32_t position[SPA_AUDIO_MAX_CHANNELS]; /*< channel position from enum spa_audio_channel */ }; -#define SPA_AUDIO_INFO_DSD_INIT(...) (struct spa_audio_info_dsd) { __VA_ARGS__ } +#define SPA_AUDIO_INFO_DSD_INIT(...) ((struct spa_audio_info_dsd) { __VA_ARGS__ }) /** * \} diff --git a/spa/include/spa/param/audio/format-utils.h b/spa/include/spa/param/audio/format-utils.h index 6ee8f933590f34e1b8eebb34182b1bd13a0b35c2..55ccec3297d7c52581dafb2598d59eecb2f926b2 100644 --- a/spa/include/spa/param/audio/format-utils.h +++ b/spa/include/spa/param/audio/format-utils.h @@ -47,9 +47,9 @@ spa_format_audio_raw_parse(const struct spa_pod *format, struct spa_audio_info_r info->flags = 0; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, - SPA_FORMAT_AUDIO_format, SPA_POD_Id(&info->format), - SPA_FORMAT_AUDIO_rate, SPA_POD_Int(&info->rate), - SPA_FORMAT_AUDIO_channels, SPA_POD_Int(&info->channels), + SPA_FORMAT_AUDIO_format, SPA_POD_OPT_Id(&info->format), + SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), + SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position)); if (position == NULL || !spa_pod_copy_array(position, SPA_TYPE_Id, info->position, SPA_AUDIO_MAX_CHANNELS)) @@ -64,7 +64,7 @@ spa_format_audio_dsp_parse(const struct spa_pod *format, struct spa_audio_info_d int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, - SPA_FORMAT_AUDIO_format, SPA_POD_Id(&info->format)); + SPA_FORMAT_AUDIO_format, SPA_POD_OPT_Id(&info->format)); return res; } @@ -74,8 +74,8 @@ spa_format_audio_iec958_parse(const struct spa_pod *format, struct spa_audio_inf int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, - SPA_FORMAT_AUDIO_iec958Codec, SPA_POD_Id(&info->codec), - SPA_FORMAT_AUDIO_rate, SPA_POD_Int(&info->rate)); + SPA_FORMAT_AUDIO_iec958Codec, SPA_POD_OPT_Id(&info->codec), + SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate)); return res; } @@ -87,10 +87,10 @@ spa_format_audio_dsd_parse(const struct spa_pod *format, struct spa_audio_info_d info->flags = 0; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, - SPA_FORMAT_AUDIO_bitorder, SPA_POD_Id(&info->bitorder), - SPA_FORMAT_AUDIO_interleave, SPA_POD_Int(&info->interleave), - SPA_FORMAT_AUDIO_rate, SPA_POD_Int(&info->rate), - SPA_FORMAT_AUDIO_channels, SPA_POD_Int(&info->channels), + SPA_FORMAT_AUDIO_bitorder, SPA_POD_OPT_Id(&info->bitorder), + SPA_FORMAT_AUDIO_interleave, SPA_POD_OPT_Int(&info->interleave), + SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), + SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position)); if (position == NULL || !spa_pod_copy_array(position, SPA_TYPE_Id, info->position, SPA_AUDIO_MAX_CHANNELS)) diff --git a/spa/include/spa/param/audio/iec958.h b/spa/include/spa/param/audio/iec958.h index fb46c561e1be4db3266de6782a9ee7c52aa5ec75..94451387fce5d6944764b94e0a6e654f4ae6fa6d 100644 --- a/spa/include/spa/param/audio/iec958.h +++ b/spa/include/spa/param/audio/iec958.h @@ -56,7 +56,7 @@ struct spa_audio_info_iec958 { uint32_t rate; /*< sample rate */ }; -#define SPA_AUDIO_INFO_IEC958_INIT(...) (struct spa_audio_info_iec958) { __VA_ARGS__ } +#define SPA_AUDIO_INFO_IEC958_INIT(...) ((struct spa_audio_info_iec958) { __VA_ARGS__ }) /** * \} diff --git a/spa/include/spa/param/audio/raw.h b/spa/include/spa/param/audio/raw.h index a34915c422b32e47af5ec10ef785d8100c7518a5..5690db1db0cce703d2d5895f938b46c09eacdd39 100644 --- a/spa/include/spa/param/audio/raw.h +++ b/spa/include/spa/param/audio/raw.h @@ -294,7 +294,7 @@ struct spa_audio_info_raw { uint32_t position[SPA_AUDIO_MAX_CHANNELS]; /*< channel position from enum spa_audio_channel */ }; -#define SPA_AUDIO_INFO_RAW_INIT(...) (struct spa_audio_info_raw) { __VA_ARGS__ } +#define SPA_AUDIO_INFO_RAW_INIT(...) ((struct spa_audio_info_raw) { __VA_ARGS__ }) #define SPA_KEY_AUDIO_FORMAT "audio.format" /**< an audio format as string, * Ex. "S16LE" */ @@ -311,7 +311,7 @@ struct spa_audio_info_dsp { enum spa_audio_format format; /*< format, one of the DSP formats in enum spa_audio_format_dsp */ }; -#define SPA_AUDIO_INFO_DSP_INIT(...) (struct spa_audio_info_dsp) { __VA_ARGS__ } +#define SPA_AUDIO_INFO_DSP_INIT(...) ((struct spa_audio_info_dsp) { __VA_ARGS__ }) /** * \} diff --git a/spa/include/spa/param/bluetooth/audio.h b/spa/include/spa/param/bluetooth/audio.h index f1037657c5df72c91222a0a8bdc9221afff77878..27f3197178501ce03a2469cf4b35db05f6da235d 100644 --- a/spa/include/spa/param/bluetooth/audio.h +++ b/spa/include/spa/param/bluetooth/audio.h @@ -58,6 +58,9 @@ enum spa_bluetooth_audio_codec { /* HFP */ SPA_BLUETOOTH_AUDIO_CODEC_CVSD = 0x100, SPA_BLUETOOTH_AUDIO_CODEC_MSBC, + + /* BAP */ + SPA_BLUETOOTH_AUDIO_CODEC_LC3 = 0x200, }; /** diff --git a/spa/include/spa/param/bluetooth/type-info.h b/spa/include/spa/param/bluetooth/type-info.h index 8286b970ad2576556093b756d16fd46358e6c447..729bd58cefb9870337904d2084933577c61b5ec3 100644 --- a/spa/include/spa/param/bluetooth/type-info.h +++ b/spa/include/spa/param/bluetooth/type-info.h @@ -62,6 +62,8 @@ static const struct spa_type_info spa_type_bluetooth_audio_codec[] = { { SPA_BLUETOOTH_AUDIO_CODEC_CVSD, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "cvsd", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_MSBC, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "msbc", NULL }, + { SPA_BLUETOOTH_AUDIO_CODEC_LC3, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "lc3", NULL }, + { 0, 0, NULL, NULL }, }; diff --git a/spa/include/spa/param/latency-utils.h b/spa/include/spa/param/latency-utils.h index 57a6828813ef285a308ac249fc6687afa6f396e3..e072574c3c72caf9ee942283f7ddf79b6a153932 100644 --- a/spa/include/spa/param/latency-utils.h +++ b/spa/include/spa/param/latency-utils.h @@ -50,7 +50,7 @@ struct spa_latency_info { uint64_t max_ns; }; -#define SPA_LATENCY_INFO(dir,...) (struct spa_latency_info) { .direction = (dir), ## __VA_ARGS__ } +#define SPA_LATENCY_INFO(dir,...) ((struct spa_latency_info) { .direction = (dir), ## __VA_ARGS__ }) static inline int spa_latency_info_compare(const struct spa_latency_info *a, struct spa_latency_info *b) @@ -146,7 +146,7 @@ struct spa_process_latency_info { uint64_t ns; }; -#define SPA_PROCESS_LATENCY_INFO_INIT(...) (struct spa_process_latency_info) { __VA_ARGS__ } +#define SPA_PROCESS_LATENCY_INFO_INIT(...) ((struct spa_process_latency_info) { __VA_ARGS__ }) static inline int spa_process_latency_parse(const struct spa_pod *latency, struct spa_process_latency_info *info) diff --git a/spa/include/spa/param/param.h b/spa/include/spa/param/param.h index 6059ee3618b49e263e67c2535fa71f1f79f4ebf2..90db8440366b00518913c71467a3c44ec5987d13 100644 --- a/spa/include/spa/param/param.h +++ b/spa/include/spa/param/param.h @@ -72,10 +72,12 @@ struct spa_param_info { uint32_t flags; uint32_t user; /**< private user field. You can use this to keep * state. */ - uint32_t padding[5]; + int32_t seq; /**< private seq field. You can use this to keep + * state of a pending update. */ + uint32_t padding[4]; }; -#define SPA_PARAM_INFO(id,flags) (struct spa_param_info){ (id), (flags) } +#define SPA_PARAM_INFO(id,flags) ((struct spa_param_info){ (id), (flags) }) /** properties for SPA_TYPE_OBJECT_ParamBuffers */ enum spa_param_buffers { diff --git a/spa/include/spa/param/video/format-utils.h b/spa/include/spa/param/video/format-utils.h index 9abf67021d032bd39ee675cb2ca5cc39ee375b99..339e7e523e68143553cc38da83aed73caf2a8ab3 100644 --- a/spa/include/spa/param/video/format-utils.h +++ b/spa/include/spa/param/video/format-utils.h @@ -44,10 +44,10 @@ spa_format_video_raw_parse(const struct spa_pod *format, { return spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, - SPA_FORMAT_VIDEO_format, SPA_POD_Id(&info->format), + SPA_FORMAT_VIDEO_format, SPA_POD_OPT_Id(&info->format), SPA_FORMAT_VIDEO_modifier, SPA_POD_OPT_Long(&info->modifier), - SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&info->size), - SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&info->framerate), + SPA_FORMAT_VIDEO_size, SPA_POD_OPT_Rectangle(&info->size), + SPA_FORMAT_VIDEO_framerate, SPA_POD_OPT_Fraction(&info->framerate), SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_OPT_Fraction(&info->max_framerate), SPA_FORMAT_VIDEO_views, SPA_POD_OPT_Int(&info->views), SPA_FORMAT_VIDEO_interlaceMode, SPA_POD_OPT_Id(&info->interlace_mode), @@ -67,7 +67,7 @@ spa_format_video_dsp_parse(const struct spa_pod *format, { return spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, - SPA_FORMAT_VIDEO_format, SPA_POD_Id(&info->format), + SPA_FORMAT_VIDEO_format, SPA_POD_OPT_Id(&info->format), SPA_FORMAT_VIDEO_modifier, SPA_POD_OPT_Long(&info->modifier)); } diff --git a/spa/include/spa/param/video/raw.h b/spa/include/spa/param/video/raw.h index dae4e738eb6ce11aecf289d7077376fc8ad60fc0..47fbd19b3c14f6131b6c7090924f992e4dce169d 100644 --- a/spa/include/spa/param/video/raw.h +++ b/spa/include/spa/param/video/raw.h @@ -206,14 +206,14 @@ struct spa_video_info_raw { enum spa_video_color_primaries color_primaries; /**< color primaries. used to convert between R'G'B' and CIE XYZ */ }; -#define SPA_VIDEO_INFO_RAW_INIT(...) (struct spa_video_info_raw) { __VA_ARGS__ } +#define SPA_VIDEO_INFO_RAW_INIT(...) ((struct spa_video_info_raw) { __VA_ARGS__ }) struct spa_video_info_dsp { enum spa_video_format format; int64_t modifier; }; -#define SPA_VIDEO_INFO_DSP_INIT(...) (struct spa_video_info_dsp) { __VA_ARGS__ } +#define SPA_VIDEO_INFO_DSP_INIT(...) ((struct spa_video_info_dsp) { __VA_ARGS__ }) /** * \} diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index 624b543c401df2e7d50bd9c2df20cee332b19278..860673a7cef6d0e8f45b4f17071d8bc8acd71bfc 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -69,7 +69,7 @@ struct spa_pod_builder { struct spa_callbacks callbacks; }; -#define SPA_POD_BUILDER_INIT(buffer,size) (struct spa_pod_builder){ buffer, size, 0, {}, {} } +#define SPA_POD_BUILDER_INIT(buffer,size) ((struct spa_pod_builder){ (buffer), (size), 0, {}, {} }) static inline void spa_pod_builder_get_state(struct spa_pod_builder *builder, struct spa_pod_builder_state *state) @@ -143,8 +143,10 @@ static inline int spa_pod_builder_raw(struct spa_pod_builder *builder, const voi if (offset + size > builder->size) { res = -ENOSPC; - spa_callbacks_call_res(&builder->callbacks, struct spa_pod_builder_callbacks, res, - overflow, 0, offset + size); + if (offset <= builder->size) + spa_callbacks_call_res(&builder->callbacks, + struct spa_pod_builder_callbacks, res, + overflow, 0, offset + size); } if (res == 0 && data) memcpy(SPA_PTROFF(builder->data, offset, void), data, size); @@ -212,7 +214,7 @@ spa_pod_builder_primitive(struct spa_pod_builder *builder, const struct spa_pod return res; } -#define SPA_POD_INIT(size,type) (struct spa_pod) { size, type } +#define SPA_POD_INIT(size,type) ((struct spa_pod) { (size), (type) }) #define SPA_POD_INIT_None() SPA_POD_INIT(0, SPA_TYPE_None) @@ -229,7 +231,7 @@ static inline int spa_pod_builder_child(struct spa_pod_builder *builder, uint32_ return spa_pod_builder_raw(builder, &p, sizeof(p)); } -#define SPA_POD_INIT_Bool(val) (struct spa_pod_bool){ { sizeof(uint32_t), SPA_TYPE_Bool }, val ? 1 : 0, 0 } +#define SPA_POD_INIT_Bool(val) ((struct spa_pod_bool){ { sizeof(uint32_t), SPA_TYPE_Bool }, (val) ? 1 : 0, 0 }) static inline int spa_pod_builder_bool(struct spa_pod_builder *builder, bool val) { @@ -237,7 +239,7 @@ static inline int spa_pod_builder_bool(struct spa_pod_builder *builder, bool val return spa_pod_builder_primitive(builder, &p.pod); } -#define SPA_POD_INIT_Id(val) (struct spa_pod_id){ { sizeof(uint32_t), SPA_TYPE_Id }, (uint32_t)val, 0 } +#define SPA_POD_INIT_Id(val) ((struct spa_pod_id){ { sizeof(uint32_t), SPA_TYPE_Id }, (val), 0 }) static inline int spa_pod_builder_id(struct spa_pod_builder *builder, uint32_t val) { @@ -245,7 +247,7 @@ static inline int spa_pod_builder_id(struct spa_pod_builder *builder, uint32_t v return spa_pod_builder_primitive(builder, &p.pod); } -#define SPA_POD_INIT_Int(val) (struct spa_pod_int){ { sizeof(int32_t), SPA_TYPE_Int }, (int32_t)val, 0 } +#define SPA_POD_INIT_Int(val) ((struct spa_pod_int){ { sizeof(int32_t), SPA_TYPE_Int }, (val), 0 }) static inline int spa_pod_builder_int(struct spa_pod_builder *builder, int32_t val) { @@ -253,7 +255,7 @@ static inline int spa_pod_builder_int(struct spa_pod_builder *builder, int32_t v return spa_pod_builder_primitive(builder, &p.pod); } -#define SPA_POD_INIT_Long(val) (struct spa_pod_long){ { sizeof(int64_t), SPA_TYPE_Long }, (int64_t)val } +#define SPA_POD_INIT_Long(val) ((struct spa_pod_long){ { sizeof(int64_t), SPA_TYPE_Long }, (val) }) static inline int spa_pod_builder_long(struct spa_pod_builder *builder, int64_t val) { @@ -261,7 +263,7 @@ static inline int spa_pod_builder_long(struct spa_pod_builder *builder, int64_t return spa_pod_builder_primitive(builder, &p.pod); } -#define SPA_POD_INIT_Float(val) (struct spa_pod_float){ { sizeof(float), SPA_TYPE_Float }, val, 0 } +#define SPA_POD_INIT_Float(val) ((struct spa_pod_float){ { sizeof(float), SPA_TYPE_Float }, (val), 0 }) static inline int spa_pod_builder_float(struct spa_pod_builder *builder, float val) { @@ -269,7 +271,7 @@ static inline int spa_pod_builder_float(struct spa_pod_builder *builder, float v return spa_pod_builder_primitive(builder, &p.pod); } -#define SPA_POD_INIT_Double(val) (struct spa_pod_double){ { sizeof(double), SPA_TYPE_Double }, val } +#define SPA_POD_INIT_Double(val) ((struct spa_pod_double){ { sizeof(double), SPA_TYPE_Double }, (val) }) static inline int spa_pod_builder_double(struct spa_pod_builder *builder, double val) { @@ -277,7 +279,7 @@ static inline int spa_pod_builder_double(struct spa_pod_builder *builder, double return spa_pod_builder_primitive(builder, &p.pod); } -#define SPA_POD_INIT_String(len) (struct spa_pod_string){ { len, SPA_TYPE_String } } +#define SPA_POD_INIT_String(len) ((struct spa_pod_string){ { (len), SPA_TYPE_String } }) static inline int spa_pod_builder_write_string(struct spa_pod_builder *builder, const char *str, uint32_t len) @@ -307,7 +309,7 @@ static inline int spa_pod_builder_string(struct spa_pod_builder *builder, const return spa_pod_builder_string_len(builder, str ? str : "", len); } -#define SPA_POD_INIT_Bytes(len) (struct spa_pod_bytes){ { len, SPA_TYPE_Bytes } } +#define SPA_POD_INIT_Bytes(len) ((struct spa_pod_bytes){ { (len), SPA_TYPE_Bytes } }) static inline int spa_pod_builder_bytes(struct spa_pod_builder *builder, const void *bytes, uint32_t len) @@ -327,7 +329,7 @@ spa_pod_builder_reserve_bytes(struct spa_pod_builder *builder, uint32_t len) return SPA_POD_BODY(spa_pod_builder_deref(builder, offset)); } -#define SPA_POD_INIT_Pointer(type,value) (struct spa_pod_pointer){ { sizeof(struct spa_pod_pointer_body), SPA_TYPE_Pointer }, { type, 0, value } } +#define SPA_POD_INIT_Pointer(type,value) ((struct spa_pod_pointer){ { sizeof(struct spa_pod_pointer_body), SPA_TYPE_Pointer }, { (type), 0, (value) } }) static inline int spa_pod_builder_pointer(struct spa_pod_builder *builder, uint32_t type, const void *val) @@ -336,7 +338,7 @@ spa_pod_builder_pointer(struct spa_pod_builder *builder, uint32_t type, const vo return spa_pod_builder_primitive(builder, &p.pod); } -#define SPA_POD_INIT_Fd(fd) (struct spa_pod_fd){ { sizeof(int64_t), SPA_TYPE_Fd }, fd } +#define SPA_POD_INIT_Fd(fd) ((struct spa_pod_fd){ { sizeof(int64_t), SPA_TYPE_Fd }, (fd) }) static inline int spa_pod_builder_fd(struct spa_pod_builder *builder, int64_t fd) { @@ -344,7 +346,7 @@ static inline int spa_pod_builder_fd(struct spa_pod_builder *builder, int64_t fd return spa_pod_builder_primitive(builder, &p.pod); } -#define SPA_POD_INIT_Rectangle(val) (struct spa_pod_rectangle){ { sizeof(struct spa_rectangle), SPA_TYPE_Rectangle }, val } +#define SPA_POD_INIT_Rectangle(val) ((struct spa_pod_rectangle){ { sizeof(struct spa_rectangle), SPA_TYPE_Rectangle }, (val) }) static inline int spa_pod_builder_rectangle(struct spa_pod_builder *builder, uint32_t width, uint32_t height) @@ -353,7 +355,7 @@ spa_pod_builder_rectangle(struct spa_pod_builder *builder, uint32_t width, uint3 return spa_pod_builder_primitive(builder, &p.pod); } -#define SPA_POD_INIT_Fraction(val) (struct spa_pod_fraction){ { sizeof(struct spa_fraction), SPA_TYPE_Fraction }, val } +#define SPA_POD_INIT_Fraction(val) ((struct spa_pod_fraction){ { sizeof(struct spa_fraction), SPA_TYPE_Fraction }, (val) }) static inline int spa_pod_builder_fraction(struct spa_pod_builder *builder, uint32_t num, uint32_t denom) @@ -389,12 +391,12 @@ spa_pod_builder_array(struct spa_pod_builder *builder, } #define SPA_POD_INIT_CHOICE_BODY(type, flags, child_size, child_type) \ - (struct spa_pod_choice_body) { type, flags, { child_size, child_type }} + ((struct spa_pod_choice_body) { (type), (flags), { (child_size), (child_type) }}) #define SPA_POD_INIT_Choice(type, ctype, child_type, n_vals, ...) \ - (struct { struct spa_pod_choice choice; ctype vals[n_vals];}) \ - { { { n_vals * sizeof(ctype) + sizeof(struct spa_pod_choice_body), SPA_TYPE_Choice }, \ - { type, 0, { sizeof(ctype), child_type } } }, { __VA_ARGS__ } } + ((struct { struct spa_pod_choice choice; ctype vals[(n_vals)];}) \ + { { { (n_vals) * sizeof(ctype) + sizeof(struct spa_pod_choice_body), SPA_TYPE_Choice }, \ + { (type), 0, { sizeof(ctype), (child_type) } } }, { __VA_ARGS__ } }) static inline int spa_pod_builder_push_choice(struct spa_pod_builder *builder, struct spa_pod_frame *frame, @@ -409,7 +411,7 @@ spa_pod_builder_push_choice(struct spa_pod_builder *builder, struct spa_pod_fram return res; } -#define SPA_POD_INIT_Struct(size) (struct spa_pod_struct){ { size, SPA_TYPE_Struct } } +#define SPA_POD_INIT_Struct(size) ((struct spa_pod_struct){ { (size), SPA_TYPE_Struct } }) static inline int spa_pod_builder_push_struct(struct spa_pod_builder *builder, struct spa_pod_frame *frame) @@ -421,7 +423,7 @@ spa_pod_builder_push_struct(struct spa_pod_builder *builder, struct spa_pod_fram return res; } -#define SPA_POD_INIT_Object(size,type,id,...) (struct spa_pod_object){ { size, SPA_TYPE_Object }, { type, id }, ##__VA_ARGS__ } +#define SPA_POD_INIT_Object(size,type,id,...) ((struct spa_pod_object){ { (size), SPA_TYPE_Object }, { (type), (id) }, ##__VA_ARGS__ }) static inline int spa_pod_builder_push_object(struct spa_pod_builder *builder, struct spa_pod_frame *frame, @@ -436,7 +438,7 @@ spa_pod_builder_push_object(struct spa_pod_builder *builder, struct spa_pod_fram } #define SPA_POD_INIT_Prop(key,flags,size,type) \ - (struct spa_pod_prop){ key, flags, { size, type } } + ((struct spa_pod_prop){ (key), (flags), { (size), (type) } }) static inline int spa_pod_builder_prop(struct spa_pod_builder *builder, uint32_t key, uint32_t flags) @@ -446,7 +448,7 @@ spa_pod_builder_prop(struct spa_pod_builder *builder, uint32_t key, uint32_t fla } #define SPA_POD_INIT_Sequence(size,unit) \ - (struct spa_pod_sequence){ { size, SPA_TYPE_Sequence}, {unit, 0 } } + ((struct spa_pod_sequence){ { (size), SPA_TYPE_Sequence}, {(unit), 0 } }) static inline int spa_pod_builder_push_sequence(struct spa_pod_builder *builder, struct spa_pod_frame *frame, uint32_t unit) @@ -650,26 +652,29 @@ static inline int spa_pod_builder_add(struct spa_pod_builder *builder, ...) #define spa_pod_builder_add_object(b,type,id,...) \ ({ \ + struct spa_pod_builder *_b = (b); \ struct spa_pod_frame _f; \ - spa_pod_builder_push_object(b, &_f, type, id); \ - spa_pod_builder_add(b, ##__VA_ARGS__, 0); \ - spa_pod_builder_pop(b, &_f); \ + spa_pod_builder_push_object(_b, &_f, type, id); \ + spa_pod_builder_add(_b, ##__VA_ARGS__, 0); \ + spa_pod_builder_pop(_b, &_f); \ }) #define spa_pod_builder_add_struct(b,...) \ ({ \ + struct spa_pod_builder *_b = (b); \ struct spa_pod_frame _f; \ - spa_pod_builder_push_struct(b, &_f); \ - spa_pod_builder_add(b, ##__VA_ARGS__, NULL); \ - spa_pod_builder_pop(b, &_f); \ + spa_pod_builder_push_struct(_b, &_f); \ + spa_pod_builder_add(_b, ##__VA_ARGS__, NULL); \ + spa_pod_builder_pop(_b, &_f); \ }) #define spa_pod_builder_add_sequence(b,unit,...) \ ({ \ + struct spa_pod_builder *_b = (b); \ struct spa_pod_frame _f; \ - spa_pod_builder_push_sequence(b, &_f, unit); \ - spa_pod_builder_add(b, ##__VA_ARGS__, 0, 0); \ - spa_pod_builder_pop(b, &_f); \ + spa_pod_builder_push_sequence(_b, &_f, unit); \ + spa_pod_builder_add(_b, ##__VA_ARGS__, 0, 0); \ + spa_pod_builder_pop(_b, &_f); \ }) /** Copy a pod structure */ diff --git a/spa/include/spa/pod/command.h b/spa/include/spa/pod/command.h index 0c292eeae5e2aa3fab0e9fe174b1028e6b41c8c1..3cd30a0e4e23c5e8e7cab37c14c90005027b168f 100644 --- a/spa/include/spa/pod/command.h +++ b/spa/include/spa/pod/command.h @@ -47,12 +47,12 @@ struct spa_command { }; #define SPA_COMMAND_TYPE(cmd) ((cmd)->body.body.type) -#define SPA_COMMAND_ID(cmd,type) (SPA_COMMAND_TYPE(cmd) == type ? \ +#define SPA_COMMAND_ID(cmd,type) (SPA_COMMAND_TYPE(cmd) == (type) ? \ (cmd)->body.body.id : SPA_ID_INVALID) -#define SPA_COMMAND_INIT_FULL(t,size,type,id,...) (t) \ - { { size, SPA_TYPE_Object }, \ - { { type, id }, ##__VA_ARGS__ } } \ +#define SPA_COMMAND_INIT_FULL(t,size,type,id,...) ((t) \ + { { (size), SPA_TYPE_Object }, \ + { { (type), (id) }, ##__VA_ARGS__ } }) #define SPA_COMMAND_INIT(type,id) \ SPA_COMMAND_INIT_FULL(struct spa_command, \ diff --git a/spa/include/spa/pod/event.h b/spa/include/spa/pod/event.h index 2ecfb0eabb643816ef1d5596c079073929967c61..2328af778b9f0a3433a497a7177d48ec339952a6 100644 --- a/spa/include/spa/pod/event.h +++ b/spa/include/spa/pod/event.h @@ -46,12 +46,12 @@ struct spa_event { }; #define SPA_EVENT_TYPE(ev) ((ev)->body.body.type) -#define SPA_EVENT_ID(ev,type) (SPA_EVENT_TYPE(ev) == type ? \ +#define SPA_EVENT_ID(ev,type) (SPA_EVENT_TYPE(ev) == (type) ? \ (ev)->body.body.id : SPA_ID_INVALID) -#define SPA_EVENT_INIT_FULL(t,size,type,id,...) (t) \ - { { size, SPA_TYPE_OBJECT }, \ - { { type, id }, ##__VA_ARGS__ } } \ +#define SPA_EVENT_INIT_FULL(t,size,type,id,...) ((t) \ + { { (size), SPA_TYPE_OBJECT }, \ + { { (type), (id) }, ##__VA_ARGS__ } }) \ #define SPA_EVENT_INIT(type,id) \ SPA_EVENT_INIT_FULL(struct spa_event, \ diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index c0a3099da64fd5f6a3f1afc4d2b36252187e3501..e0546751f455bf2a1e5fc297aa487a87b7d77e6f 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -53,7 +53,7 @@ struct spa_pod_parser { struct spa_pod_parser_state state; }; -#define SPA_POD_PARSER_INIT(buffer,size) (struct spa_pod_parser){ buffer, size, 0, {} } +#define SPA_POD_PARSER_INIT(buffer,size) ((struct spa_pod_parser){ (buffer), (size), 0, {} }) static inline void spa_pod_parser_init(struct spa_pod_parser *parser, const void *data, uint32_t size) @@ -82,12 +82,20 @@ spa_pod_parser_reset(struct spa_pod_parser *parser, struct spa_pod_parser_state static inline struct spa_pod * spa_pod_parser_deref(struct spa_pod_parser *parser, uint32_t offset, uint32_t size) { - if (offset + 8 <= size) { - struct spa_pod *pod = SPA_PTROFF(parser->data, offset, struct spa_pod); - if (offset + SPA_POD_SIZE(pod) <= size) - return pod; + /* Cast to uint64_t to avoid wraparound. Add 8 for the pod itself. */ + const uint64_t long_offset = (uint64_t)offset + 8; + if (long_offset <= size && (offset & 7) == 0) { + /* Use void* because creating a misaligned pointer is undefined. */ + void *pod = SPA_PTROFF(parser->data, offset, void); + /* + * Check that the pointer is aligned and that the size (rounded + * to the next multiple of 8) is in bounds. + */ + if (SPA_IS_ALIGNED(pod, __alignof__(struct spa_pod)) && + long_offset + SPA_ROUND_UP_N((uint64_t)SPA_POD_BODY_SIZE(pod), 8) <= size) + return (struct spa_pod *)pod; } - return NULL; + return NULL; } static inline struct spa_pod *spa_pod_parser_frame(struct spa_pod_parser *parser, struct spa_pod_frame *frame) @@ -285,10 +293,15 @@ static inline bool spa_pod_parser_can_collect(const struct spa_pod *pod, char ty if (pod == NULL) return false; - if (spa_pod_is_choice(pod) && - SPA_POD_CHOICE_TYPE(pod) == SPA_CHOICE_None && - spa_pod_parser_can_collect(SPA_POD_CHOICE_CHILD(pod), type)) - return true; + if (SPA_POD_TYPE(pod) == SPA_TYPE_Choice) { + if (!spa_pod_is_choice(pod)) + return false; + if (type == 'V') + return true; + if (SPA_POD_CHOICE_TYPE(pod) != SPA_CHOICE_None) + return false; + pod = SPA_POD_CHOICE_CHILD(pod); + } switch (type) { case 'P': @@ -328,7 +341,6 @@ static inline bool spa_pod_parser_can_collect(const struct spa_pod *pod, char ty case 'O': return spa_pod_is_object(pod) || spa_pod_is_none(pod); case 'V': - return spa_pod_is_choice(pod); default: return false; } @@ -355,7 +367,7 @@ do { \ break; \ case 's': \ *va_arg(args, char**) = \ - (pod == NULL || (SPA_POD_TYPE(pod) == SPA_TYPE_None) \ + ((pod) == NULL || (SPA_POD_TYPE(pod) == SPA_TYPE_None) \ ? NULL \ : (char *)SPA_POD_CONTENTS(struct spa_pod_string, pod)); \ break; \ @@ -407,8 +419,8 @@ do { \ { \ const struct spa_pod **d = va_arg(args, const struct spa_pod**); \ if (d) \ - *d = (pod == NULL || (SPA_POD_TYPE(pod) == SPA_TYPE_None) \ - ? NULL : pod); \ + *d = ((pod) == NULL || (SPA_POD_TYPE(pod) == SPA_TYPE_None) \ + ? NULL : (pod)); \ break; \ } \ default: \ @@ -493,8 +505,7 @@ static inline int spa_pod_parser_getv(struct spa_pod_parser *parser, va_list arg } SPA_POD_PARSER_SKIP(*format, args); } else { - if (pod->type == SPA_TYPE_Choice && *format != 'V' && - SPA_POD_CHOICE_TYPE(pod) == SPA_CHOICE_None) + if (pod->type == SPA_TYPE_Choice && *format != 'V') pod = SPA_POD_CHOICE_CHILD(pod); SPA_POD_PARSER_COLLECT(pod, *format, args); diff --git a/spa/include/spa/pod/pod.h b/spa/include/spa/pod/pod.h index 2d2eaad6a18b4df69657844fdeb91f53c68566eb..1864b19b9f4c50bd2ba584ff427b9f0468d2fdc9 100644 --- a/spa/include/spa/pod/pod.h +++ b/spa/include/spa/pod/pod.h @@ -39,7 +39,7 @@ extern "C" { #define SPA_POD_BODY_SIZE(pod) (((struct spa_pod*)(pod))->size) #define SPA_POD_TYPE(pod) (((struct spa_pod*)(pod))->type) -#define SPA_POD_SIZE(pod) (sizeof(struct spa_pod) + SPA_POD_BODY_SIZE(pod)) +#define SPA_POD_SIZE(pod) ((uint64_t)sizeof(struct spa_pod) + SPA_POD_BODY_SIZE(pod)) #define SPA_POD_CONTENTS_SIZE(type,pod) (SPA_POD_SIZE(pod)-sizeof(type)) #define SPA_POD_CONTENTS(type,pod) SPA_PTROFF((pod),sizeof(type),void) @@ -52,7 +52,7 @@ struct spa_pod { uint32_t type; /* a basic id of enum spa_type */ }; -#define SPA_POD_VALUE(type,pod) (((type*)pod)->value) +#define SPA_POD_VALUE(type,pod) (((type*)(pod))->value) struct spa_pod_bool { struct spa_pod pod; diff --git a/spa/include/spa/support/log-impl.h b/spa/include/spa/support/log-impl.h index 124153d073dfb39a722c87e626ee77f48705d893..e1ee21dd86f317bfea7a40babe71c5d39c9f16cc 100644 --- a/spa/include/spa/support/log-impl.h +++ b/spa/include/spa/support/log-impl.h @@ -121,7 +121,7 @@ struct { \ #define SPA_LOG_IMPL_INIT(name) \ { { { SPA_TYPE_INTERFACE_Log, SPA_VERSION_LOG, \ - SPA_CALLBACKS_INIT(&name.methods, &name) }, \ + SPA_CALLBACKS_INIT(&(name).methods, &(name)) }, \ SPA_LOG_LEVEL_INFO, }, \ { SPA_VERSION_LOG_METHODS, \ spa_log_impl_log, \ diff --git a/spa/include/spa/support/log.h b/spa/include/spa/support/log.h index 1475eedf2416bb71f06b52a608503b7470eb6bad..008fb34175fea0e49ff273181cb48d3a7c09b220 100644 --- a/spa/include/spa/support/log.h +++ b/spa/include/spa/support/log.h @@ -212,7 +212,7 @@ struct spa_log_methods { #define SPA_LOG_TOPIC(v, t) \ - (struct spa_log_topic){ .version = v, .topic = (t)} + (struct spa_log_topic){ .version = (v), .topic = (t)} #define spa_log_topic_init(l, topic) \ do { \ @@ -231,10 +231,10 @@ do { \ ({ \ struct spa_log *_log = l; \ enum spa_log_level _lev = _log ? _log->level : SPA_LOG_LEVEL_NONE; \ - struct spa_log_topic *_t = (struct spa_log_topic *)topic; \ + struct spa_log_topic *_t = (struct spa_log_topic *)(topic); \ if (_t && _t->has_custom_level) \ _lev = _t->level; \ - _lev >= lev; \ + _lev >= (lev); \ }) /* Transparently calls to version 0 log if v1 is not supported */ @@ -269,26 +269,32 @@ do { \ } \ }) +#define spa_logt_lev(l,lev,t,...) \ + spa_log_logt(l,lev,t,__FILE__,__LINE__,__func__,__VA_ARGS__) + +#define spa_log_lev(l,lev,...) \ + spa_logt_lev(l,lev,SPA_LOG_TOPIC_DEFAULT,__VA_ARGS__) + #define spa_log_log(l,lev,...) \ spa_log_logt(l,lev,SPA_LOG_TOPIC_DEFAULT,__VA_ARGS__) #define spa_log_logv(l,lev,...) \ spa_log_logtv(l,lev,SPA_LOG_TOPIC_DEFAULT,__VA_ARGS__) -#define spa_log_error(l,...) spa_log_log(l,SPA_LOG_LEVEL_ERROR,__FILE__,__LINE__,__func__,__VA_ARGS__) -#define spa_log_warn(l,...) spa_log_log(l,SPA_LOG_LEVEL_WARN,__FILE__,__LINE__,__func__,__VA_ARGS__) -#define spa_log_info(l,...) spa_log_log(l,SPA_LOG_LEVEL_INFO,__FILE__,__LINE__,__func__,__VA_ARGS__) -#define spa_log_debug(l,...) spa_log_log(l,SPA_LOG_LEVEL_DEBUG,__FILE__,__LINE__,__func__,__VA_ARGS__) -#define spa_log_trace(l,...) spa_log_log(l,SPA_LOG_LEVEL_TRACE,__FILE__,__LINE__,__func__,__VA_ARGS__) +#define spa_log_error(l,...) spa_log_lev(l,SPA_LOG_LEVEL_ERROR,__VA_ARGS__) +#define spa_log_warn(l,...) spa_log_lev(l,SPA_LOG_LEVEL_WARN,__VA_ARGS__) +#define spa_log_info(l,...) spa_log_lev(l,SPA_LOG_LEVEL_INFO,__VA_ARGS__) +#define spa_log_debug(l,...) spa_log_lev(l,SPA_LOG_LEVEL_DEBUG,__VA_ARGS__) +#define spa_log_trace(l,...) spa_log_lev(l,SPA_LOG_LEVEL_TRACE,__VA_ARGS__) -#define spa_logt_error(l,t,...) spa_log_logt(l,SPA_LOG_LEVEL_ERROR,t,__FILE__,__LINE__,__func__,__VA_ARGS__) -#define spa_logt_warn(l,t,...) spa_log_logt(l,SPA_LOG_LEVEL_WARN,t,__FILE__,__LINE__,__func__,__VA_ARGS__) -#define spa_logt_info(l,t,...) spa_log_logt(l,SPA_LOG_LEVEL_INFO,t,__FILE__,__LINE__,__func__,__VA_ARGS__) -#define spa_logt_debug(l,t,...) spa_log_logt(l,SPA_LOG_LEVEL_DEBUG,t,__FILE__,__LINE__,__func__,__VA_ARGS__) -#define spa_logt_trace(l,t,...) spa_log_logt(l,SPA_LOG_LEVEL_TRACE,t,__FILE__,__LINE__,__func__,__VA_ARGS__) +#define spa_logt_error(l,t,...) spa_logt_lev(l,SPA_LOG_LEVEL_ERROR,t,__VA_ARGS__) +#define spa_logt_warn(l,t,...) spa_logt_lev(l,SPA_LOG_LEVEL_WARN,t,__VA_ARGS__) +#define spa_logt_info(l,t,...) spa_logt_lev(l,SPA_LOG_LEVEL_INFO,t,__VA_ARGS__) +#define spa_logt_debug(l,t,...) spa_logt_lev(l,SPA_LOG_LEVEL_DEBUG,t,__VA_ARGS__) +#define spa_logt_trace(l,t,...) spa_logt_lev(l,SPA_LOG_LEVEL_TRACE,t,__VA_ARGS__) #ifndef FASTPATH -#define spa_log_trace_fp(l,...) spa_log_log(l,SPA_LOG_LEVEL_TRACE,__FILE__,__LINE__,__func__,__VA_ARGS__) +#define spa_log_trace_fp(l,...) spa_log_lev(l,SPA_LOG_LEVEL_TRACE,__VA_ARGS__) #else #define spa_log_trace_fp(l,...) #endif @@ -296,18 +302,16 @@ do { \ #define spa_log_hexdump(l,lev,indent,data,len) \ ({ \ char str[512]; \ - uint8_t *buf = (uint8_t *)data; \ - size_t i; \ + uint8_t *buf = (uint8_t *)(data); \ + size_t i, j = (len); \ int pos = 0; \ \ - for (i = 0; i < len; i++) { \ + for (i = 0; i < j; i++) { \ if (i % 16 == 0) \ pos = 0; \ pos += sprintf(str + pos, "%02x ", buf[i]); \ - if (i % 16 == 15 || i == len - 1) { \ - spa_log_log(l,lev,__FILE__,__LINE__,__func__, \ - "%*s" "%s",indent,"", str); \ - } \ + if (i % 16 == 15 || i == j - 1) \ + spa_log_lev(l,lev, "%*s" "%s",indent,"", str); \ } \ }) diff --git a/spa/include/spa/support/plugin.h b/spa/include/spa/support/plugin.h index e66bdc99760809c00cf2f5c499aac77f89d02a4d..80cadda61ee49de149f15994d61b83b34cb12594 100644 --- a/spa/include/spa/support/plugin.h +++ b/spa/include/spa/support/plugin.h @@ -105,7 +105,7 @@ static inline void *spa_support_find(const struct spa_support *support, return NULL; } -#define SPA_SUPPORT_INIT(type,data) (struct spa_support) { (type), (data) } +#define SPA_SUPPORT_INIT(type,data) ((struct spa_support) { (type), (data) }) struct spa_handle_factory { /** The version of this structure */ diff --git a/spa/include/spa/support/system.h b/spa/include/spa/support/system.h index 8076ceb4bbc71da6d46917750be6d0363595efdf..79127d9062cc9d089914aef0ce9b9d9664dd970c 100644 --- a/spa/include/spa/support/system.h +++ b/spa/include/spa/support/system.h @@ -119,7 +119,7 @@ struct spa_system_methods { #define spa_system_method_r(o,method,version,...) \ ({ \ - int _res = -ENOTSUP; \ + volatile int _res = -ENOTSUP; \ struct spa_system *_o = o; \ spa_interface_call_res(&_o->iface, \ struct spa_system_methods, _res, \ diff --git a/spa/include/spa/utils/defs.h b/spa/include/spa/utils/defs.h index cf15cc2446cd2fd7d7850aaa0960c7a48799afb1..c602c9348827805dbafc63bf2732e0d5df833158 100644 --- a/spa/include/spa/utils/defs.h +++ b/spa/include/spa/utils/defs.h @@ -27,8 +27,18 @@ #ifdef __cplusplus extern "C" { +# if __cplusplus >= 201103L +# define SPA_STATIC_ASSERT static_assert +# endif #else -#include <stdbool.h> +# include <stdbool.h> +# if __STDC_VERSION__ >= 201112L +# define SPA_STATIC_ASSERT _Static_assert +# endif +#endif +#ifndef SPA_STATIC_ASSERT +#define SPA_STATIC_ASSERT(a, b) \ + ((void)sizeof(struct { int spa_static_assertion_failed : 2 * !!(a) - 1; })) #endif #include <inttypes.h> #include <signal.h> @@ -72,10 +82,19 @@ extern "C" { #endif #define SPA_FLAG_MASK(field,mask,flag) (((field) & (mask)) == (flag)) -#define SPA_FLAG_IS_SET(field,flag) SPA_FLAG_MASK(field,flag,flag) +#define SPA_FLAG_IS_SET(field,flag) SPA_FLAG_MASK(field, flag, flag) + #define SPA_FLAG_SET(field,flag) ((field) |= (flag)) -#define SPA_FLAG_CLEAR(field,flag) ((field) &= ~(flag)) -#define SPA_FLAG_UPDATE(field,flag,val) ((val) ? SPA_FLAG_SET(field,flag) : SPA_FLAG_CLEAR(field,flag)) +#define SPA_FLAG_CLEAR(field, flag) \ +({ \ + SPA_STATIC_ASSERT(__builtin_constant_p(flag) ? \ + (__typeof__(flag))(__typeof__(field))(__typeof__(flag))(flag) == (flag) : \ + sizeof(field) >= sizeof(flag), \ + "truncation problem when masking " #field \ + " with ~" #flag); \ + ((field) &= ~(__typeof__(field))(flag)); \ +}) +#define SPA_FLAG_UPDATE(field,flag,val) ((val) ? SPA_FLAG_SET((field),(flag)) : SPA_FLAG_CLEAR((field),(flag))) enum spa_direction { SPA_DIRECTION_INPUT = 0, @@ -84,25 +103,25 @@ enum spa_direction { #define SPA_DIRECTION_REVERSE(d) ((d) ^ 1) -#define SPA_RECTANGLE(width,height) (struct spa_rectangle){ width, height } +#define SPA_RECTANGLE(width,height) ((struct spa_rectangle){ (width), (height) }) struct spa_rectangle { uint32_t width; uint32_t height; }; -#define SPA_POINT(x,y) (struct spa_point){ x, y } +#define SPA_POINT(x,y) ((struct spa_point){ (x), (y) }) struct spa_point { int32_t x; int32_t y; }; -#define SPA_REGION(x,y,width,height) (struct spa_region){ SPA_POINT(x,y), SPA_RECTANGLE(width,height) } +#define SPA_REGION(x,y,width,height) ((struct spa_region){ SPA_POINT(x,y), SPA_RECTANGLE(width,height) }) struct spa_region { struct spa_point position; struct spa_rectangle size; }; -#define SPA_FRACTION(num,denom) (struct spa_fraction){ num, denom } +#define SPA_FRACTION(num,denom) ((struct spa_fraction){ (num), (denom) }) struct spa_fraction { uint32_t num; uint32_t denom; @@ -120,23 +139,26 @@ struct spa_fraction { * ``` */ #define SPA_FOR_EACH_ELEMENT(arr, ptr) \ - for (ptr = arr; (void*)ptr < SPA_PTROFF(arr, sizeof(arr), void); ptr++) + for ((ptr) = arr; (void*)(ptr) < SPA_PTROFF(arr, sizeof(arr), void); (ptr)++) + +#define SPA_FOR_EACH_ELEMENT_VAR(arr, var) \ + for (__typeof__((arr)[0])* (var) = arr; (void*)(var) < SPA_PTROFF(arr, sizeof(arr), void); (var)++) #define SPA_ABS(a) \ ({ \ __typeof__(a) _a = (a); \ SPA_LIKELY(_a >= 0) ? _a : -_a; \ }) -#define SPA_MIN(a,b) \ -({ \ - __typeof__(a) _min_a = (a); \ - __typeof__(b) _min_b = (b); \ +#define SPA_MIN(a,b) \ +({ \ + __typeof__(a) _min_a = (a); \ + __typeof__(b) _min_b = (b); \ SPA_LIKELY(_min_a <= _min_b) ? _min_a : _min_b; \ }) -#define SPA_MAX(a,b) \ -({ \ - __typeof__(a) _max_a = (a); \ - __typeof__(b) _max_b = (b); \ +#define SPA_MAX(a,b) \ +({ \ + __typeof__(a) _max_a = (a); \ + __typeof__(b) _max_b = (b); \ SPA_LIKELY(_max_a >= _max_b) ? _max_a : _max_b; \ }) #define SPA_CLAMP(v,low,high) \ @@ -156,7 +178,7 @@ struct spa_fraction { #define SPA_SWAP(a,b) \ ({ \ __typeof__(a) _t = (a); \ - a = b; b = _t; \ + (a) = b; (b) = _t; \ }) #define SPA_TYPECHECK(type,x) \ @@ -180,7 +202,7 @@ struct spa_fraction { #define SPA_MEMBER(b,o,t) SPA_PTROFF(b,o,t) #define SPA_MEMBER_ALIGN(b,o,a,t) SPA_PTROFF_ALIGN(b,o,a,t) -#define SPA_CONTAINER_OF(p,t,m) ((t*)((uintptr_t)p - offsetof(t,m))) +#define SPA_CONTAINER_OF(p,t,m) ((t*)((uintptr_t)(p) - offsetof(t,m))) #define SPA_PTRDIFF(p1,p2) ((intptr_t)(p1) - (intptr_t)(p2)) @@ -194,7 +216,7 @@ struct spa_fraction { #define SPA_IDX_INVALID ((unsigned int)-1) #define SPA_ID_INVALID ((uint32_t)0xffffffff) -#define SPA_NSEC_PER_SEC (1000000000ll) +#define SPA_NSEC_PER_SEC (1000000000LL) #define SPA_NSEC_PER_MSEC (1000000ll) #define SPA_NSEC_PER_USEC (1000ll) #define SPA_USEC_PER_SEC (1000000ll) @@ -236,11 +258,21 @@ struct spa_fraction { #define SPA_RESTRICT #endif -#define SPA_ROUND_DOWN(num,value) ((num) - ((num) % (value))) -#define SPA_ROUND_UP(num,value) ((((num) + (value) - 1) / (value)) * (value)) +#define SPA_ROUND_DOWN(num,value) \ +({ \ + __typeof__(num) _num = (num); \ + ((_num) - ((_num) % (value))); \ +}) +#define SPA_ROUND_UP(num,value) \ +({ \ + __typeof__(value) _v = (value); \ + ((((num) + (_v) - 1) / (_v)) * (_v)); \ +}) + +#define SPA_ROUND_MASK(num,mask) ((__typeof__(num))((mask)-1)) -#define SPA_ROUND_DOWN_N(num,align) ((num) & ~((align) - 1)) -#define SPA_ROUND_UP_N(num,align) SPA_ROUND_DOWN_N((num) + ((align) - 1),align) +#define SPA_ROUND_DOWN_N(num,align) ((num) & ~SPA_ROUND_MASK(num, align)) +#define SPA_ROUND_UP_N(num,align) ((((num)-1) | SPA_ROUND_MASK(num, align))+1) #define SPA_PTR_ALIGNMENT(p,align) ((intptr_t)(p) & ((align)-1)) #define SPA_IS_ALIGNED(p,align) (SPA_PTR_ALIGNMENT(p,align) == 0) diff --git a/spa/include/spa/utils/dict.h b/spa/include/spa/utils/dict.h index 558c6fd016f3c60167c8f60da3310f2023bf8fef..f9a0b5b6b55a813215a4f39047b41a5a082e1990 100644 --- a/spa/include/spa/utils/dict.h +++ b/spa/include/spa/utils/dict.h @@ -48,7 +48,7 @@ struct spa_dict_item { const char *value; }; -#define SPA_DICT_ITEM_INIT(key,value) (struct spa_dict_item) { key, value } +#define SPA_DICT_ITEM_INIT(key,value) ((struct spa_dict_item) { (key), (value) }) struct spa_dict { #define SPA_DICT_FLAG_SORTED (1<<0) /**< items are sorted */ @@ -57,8 +57,8 @@ struct spa_dict { const struct spa_dict_item *items; }; -#define SPA_DICT_INIT(items,n_items) (struct spa_dict) { 0, n_items, items } -#define SPA_DICT_INIT_ARRAY(items) (struct spa_dict) { 0, SPA_N_ELEMENTS(items), items } +#define SPA_DICT_INIT(items,n_items) ((struct spa_dict) { 0, (n_items), (items) }) +#define SPA_DICT_INIT_ARRAY(items) ((struct spa_dict) { 0, SPA_N_ELEMENTS(items), (items) }) #define spa_dict_for_each(item, dict) \ for ((item) = (dict)->items; \ diff --git a/spa/include/spa/utils/hook.h b/spa/include/spa/utils/hook.h index 953b97445ed05f0986358af25f828693db695467..edcb7164323275ce3d3e7ac565fd6bcbdeb104dd 100644 --- a/spa/include/spa/utils/hook.h +++ b/spa/include/spa/utils/hook.h @@ -142,7 +142,7 @@ struct spa_callbacks { * Initialize the set of functions \a funcs as a \ref spa_callbacks, together * with \a _data. */ -#define SPA_CALLBACKS_INIT(_funcs,_data) (struct spa_callbacks){ _funcs, _data, } +#define SPA_CALLBACKS_INIT(_funcs,_data) ((struct spa_callbacks){ (_funcs), (_data), }) /** \struct spa_interface */ @@ -166,7 +166,7 @@ struct spa_interface { * */ #define SPA_INTERFACE_INIT(_type,_version,_funcs,_data) \ - (struct spa_interface){ _type, _version, SPA_CALLBACKS_INIT(_funcs,_data), } + ((struct spa_interface){ (_type), (_version), SPA_CALLBACKS_INIT(_funcs,_data), }) /** * Invoke method named \a method in the \a callbacks. diff --git a/spa/include/spa/utils/json.h b/spa/include/spa/utils/json.h index 19093169b87fb43da4d72bec9a9f7e1f487faaed..0f93149e6ed966f65ce3424a5a802dc2658c01d2 100644 --- a/spa/include/spa/utils/json.h +++ b/spa/include/spa/utils/json.h @@ -58,20 +58,20 @@ struct spa_json { uint32_t depth; }; -#define SPA_JSON_INIT(data,size) (struct spa_json) { (data), (data)+(size), } +#define SPA_JSON_INIT(data,size) ((struct spa_json) { (data), (data)+(size), }) static inline void spa_json_init(struct spa_json * iter, const char *data, size_t size) { *iter = SPA_JSON_INIT(data, size); } -#define SPA_JSON_ENTER(iter) (struct spa_json) { (iter)->cur, (iter)->end, (iter), } +#define SPA_JSON_ENTER(iter) ((struct spa_json) { (iter)->cur, (iter)->end, (iter), }) static inline void spa_json_enter(struct spa_json * iter, struct spa_json * sub) { *sub = SPA_JSON_ENTER(iter); } -#define SPA_JSON_SAVE(iter) (struct spa_json) { (iter)->cur, (iter)->end, } +#define SPA_JSON_SAVE(iter) ((struct spa_json) { (iter)->cur, (iter)->end, }) /** Get the next token. \a value points to the token and the return value * is the length. */ diff --git a/spa/include/spa/utils/list.h b/spa/include/spa/utils/list.h index 2300905edaf254418f54b543fc4f94084bbce183..b57657a0ff15d983e81cb6f870f52554d893ea9f 100644 --- a/spa/include/spa/utils/list.h +++ b/spa/include/spa/utils/list.h @@ -44,7 +44,7 @@ struct spa_list { struct spa_list *prev; }; -#define SPA_LIST_INIT(list) (struct spa_list){ list, list } +#define SPA_LIST_INIT(list) ((struct spa_list){ (list), (list) }) static inline void spa_list_init(struct spa_list *list) { @@ -98,25 +98,25 @@ static inline void spa_list_remove(struct spa_list *elem) (&(pos)->member == (head)) #define spa_list_next(pos, member) \ - SPA_CONTAINER_OF((pos)->member.next, __typeof__(*pos), member) + SPA_CONTAINER_OF((pos)->member.next, __typeof__(*(pos)), member) #define spa_list_prev(pos, member) \ - SPA_CONTAINER_OF((pos)->member.prev, __typeof__(*pos), member) + SPA_CONTAINER_OF((pos)->member.prev, __typeof__(*(pos)), member) #define spa_list_consume(pos, head, member) \ - for (pos = spa_list_first(head, __typeof__(*pos), member); \ + for ((pos) = spa_list_first(head, __typeof__(*(pos)), member); \ !spa_list_is_empty(head); \ - pos = spa_list_first(head, __typeof__(*pos), member)) + (pos) = spa_list_first(head, __typeof__(*(pos)), member)) #define spa_list_for_each_next(pos, head, curr, member) \ - for (pos = spa_list_first(curr, __typeof__(*pos), member); \ + for ((pos) = spa_list_first(curr, __typeof__(*(pos)), member); \ !spa_list_is_end(pos, head, member); \ - pos = spa_list_next(pos, member)) + (pos) = spa_list_next(pos, member)) #define spa_list_for_each_prev(pos, head, curr, member) \ - for (pos = spa_list_last(curr, __typeof__(*pos), member); \ + for ((pos) = spa_list_last(curr, __typeof__(*(pos)), member); \ !spa_list_is_end(pos, head, member); \ - pos = spa_list_prev(pos, member)) + (pos) = spa_list_prev(pos, member)) #define spa_list_for_each(pos, head, member) \ spa_list_for_each_next(pos, head, head, member) @@ -125,16 +125,16 @@ static inline void spa_list_remove(struct spa_list *elem) spa_list_for_each_prev(pos, head, head, member) #define spa_list_for_each_safe_next(pos, tmp, head, curr, member) \ - for (pos = spa_list_first(curr, __typeof__(*pos), member); \ - tmp = spa_list_next(pos, member), \ + for ((pos) = spa_list_first(curr, __typeof__(*(pos)), member); \ + (tmp) = spa_list_next(pos, member), \ !spa_list_is_end(pos, head, member); \ - pos = tmp) + (pos) = (tmp)) #define spa_list_for_each_safe_prev(pos, tmp, head, curr, member) \ - for (pos = spa_list_last(curr, __typeof__(*pos), member); \ - tmp = spa_list_prev(pos, member), \ + for ((pos) = spa_list_last(curr, __typeof__(*(pos)), member); \ + (tmp) = spa_list_prev(pos, member), \ !spa_list_is_end(pos, head, member); \ - pos = tmp) + (pos) = (tmp)) #define spa_list_for_each_safe(pos, tmp, head, member) \ spa_list_for_each_safe_next(pos, tmp, head, head, member) @@ -146,11 +146,11 @@ static inline void spa_list_remove(struct spa_list *elem) spa_list_prepend(head, &(cursor).member) #define spa_list_for_each_cursor(pos, cursor, head, member) \ - for(pos = spa_list_first(&(cursor).member, __typeof__(*(pos)), member); \ + for((pos) = spa_list_first(&(cursor).member, __typeof__(*(pos)), member); \ spa_list_remove(&(pos)->member), \ spa_list_append(&(cursor).member, &(pos)->member), \ !spa_list_is_end(pos, head, member); \ - pos = spa_list_next(&cursor, member)) + (pos) = spa_list_next(&(cursor), member)) #define spa_list_cursor_end(cursor, member) \ spa_list_remove(&(cursor).member) diff --git a/spa/include/spa/utils/names.h b/spa/include/spa/utils/names.h index 7d60b07ed80579d8eaf3da9e92733542a322e020..f8104b7421e96742307ed36be5b81a61347542cf 100644 --- a/spa/include/spa/utils/names.h +++ b/spa/include/spa/utils/names.h @@ -114,13 +114,15 @@ extern "C" { /** keys for bluez5 factory names */ #define SPA_NAME_API_BLUEZ5_ENUM_DBUS "api.bluez5.enum.dbus" /**< a dbus Device interface */ #define SPA_NAME_API_BLUEZ5_DEVICE "api.bluez5.device" /**< a Device interface */ -#define SPA_NAME_API_BLUEZ5_A2DP_SINK "api.bluez5.a2dp.sink" /**< a playback Node interface for A2DP profiles */ -#define SPA_NAME_API_BLUEZ5_A2DP_SOURCE "api.bluez5.a2dp.source" /**< a capture Node interface for A2DP profiles */ +#define SPA_NAME_API_BLUEZ5_MEDIA_SINK "api.bluez5.media.sink" /**< a playback Node interface for A2DP/BAP profiles */ +#define SPA_NAME_API_BLUEZ5_MEDIA_SOURCE "api.bluez5.media.source" /**< a capture Node interface for A2DP/BAP profiles */ +#define SPA_NAME_API_BLUEZ5_A2DP_SINK "api.bluez5.a2dp.sink" /**< alias for media.sink */ +#define SPA_NAME_API_BLUEZ5_A2DP_SOURCE "api.bluez5.a2dp.source" /**< alias for media.source */ #define SPA_NAME_API_BLUEZ5_SCO_SINK "api.bluez5.sco.sink" /**< a playback Node interface for HSP/HFP profiles */ #define SPA_NAME_API_BLUEZ5_SCO_SOURCE "api.bluez5.sco.source" /**< a capture Node interface for HSP/HFP profiles */ /** keys for codec factory names */ -#define SPA_NAME_API_CODEC_BLUEZ5_A2DP "api.codec.bluez5.a2dp" /**< Bluez5 A2DP codec plugin */ +#define SPA_NAME_API_CODEC_BLUEZ5_MEDIA "api.codec.bluez5.media" /**< Bluez5 Media codec plugin */ /** keys for v4l2 factory names */ #define SPA_NAME_API_V4L2_ENUM_UDEV "api.v4l2.enum.udev" /**< a v4l2 udev Device interface */ diff --git a/spa/include/spa/utils/result.h b/spa/include/spa/utils/result.h index 67ee401a1447146cd6e61c1ef919180c0123d141..05a1ef96b4e3de4dc2e54fc904aef3a318bb7c9a 100644 --- a/spa/include/spa/utils/result.h +++ b/spa/include/spa/utils/result.h @@ -55,7 +55,7 @@ extern "C" { #define spa_strerror(err) \ ({ \ - int _err = -err; \ + int _err = -(err); \ if (SPA_RESULT_IS_ASYNC(err)) \ _err = EINPROGRESS; \ strerror(_err); \ diff --git a/spa/include/spa/utils/ringbuffer.h b/spa/include/spa/utils/ringbuffer.h index 19bfb8675551c66195901ac8c193125c61d91cec..ed14939a78b9a876a0230e46b2fc9d445e0ef9eb 100644 --- a/spa/include/spa/utils/ringbuffer.h +++ b/spa/include/spa/utils/ringbuffer.h @@ -53,7 +53,7 @@ struct spa_ringbuffer { uint32_t writeindex; /*< the current write index */ }; -#define SPA_RINGBUFFER_INIT() (struct spa_ringbuffer) { 0, 0 } +#define SPA_RINGBUFFER_INIT() ((struct spa_ringbuffer) { 0, 0 }) /** * Initialize a spa_ringbuffer with \a size. diff --git a/spa/meson.build b/spa/meson.build index 44b86eaf51968fd526b43e0266a0dea096d9aea8..64cc7b31072d0cbcb04d497eb40c20543764f4bb 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -64,6 +64,12 @@ if get_option('spa-plugins').allowed() summary({'LC3plus': lc3plus_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') opus_dep = dependency('opus', required : get_option('bluez5-codec-opus')) 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') + 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') + endif endif avcodec_dep = dependency('libavcodec', required: get_option('ffmpeg')) jack_dep = dependency('jack', version : '>= 1.9.10', required: get_option('jack')) diff --git a/spa/plugins/alsa/90-pipewire-alsa.rules b/spa/plugins/alsa/90-pipewire-alsa.rules index f451275b692d9fc158b224cd9336389213d8b114..5dd292683d21a8a23ed3cdf6d82a432ad9b351a2 100644 --- a/spa/plugins/alsa/90-pipewire-alsa.rules +++ b/spa/plugins/alsa/90-pipewire-alsa.rules @@ -181,6 +181,9 @@ ATTRS{idVendor}=="1395", ATTRS{idProduct}=="008a", ENV{ACP_PROFILE_SET}="usb-gam # have any digital outputs. ATTRS{idVendor}=="0a12", ATTRS{idProduct}=="4007", ENV{ACP_PROFILE_SET}="analog-only.conf" +# Asus Xonar SE +ATTRS{idVendor}=="0b05", ATTRS{idProduct}=="189d", ENV{ACP_PROFILE_SET}="asus-xonar-se.conf" + GOTO="pipewire_end" LABEL="pipewire_check_pci" diff --git a/spa/plugins/alsa/acp-tool.c b/spa/plugins/alsa/acp-tool.c index bddefab53b800f6f21e3da5d5354e0e319140bf4..0fc92a4f32b1c22e31c990e8d352d818bffffcd2 100644 --- a/spa/plugins/alsa/acp-tool.c +++ b/spa/plugins/alsa/acp-tool.c @@ -584,6 +584,9 @@ static int handle_input(struct data *data) return -errno; buf[r] = 0; + if (r == 0) + return -EPIPE; + if ((p = strchr(buf, '#'))) *p = '\0'; @@ -679,8 +682,12 @@ static int do_prompt(struct data *data) if (err < 0) return -errno; - if (pfds[0].revents & POLLIN) - handle_input(data); + if (pfds[0].revents & POLLIN) { + if ((err = handle_input(data)) < 0) { + if (err == -EPIPE) + break; + } + } if (count < 2) continue; diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index f23232ed3ab9b249180dd4f09bae79ba4d88837a..4bbe5ee927e0eb29e34768beb381dae555ac133a 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -34,6 +34,8 @@ void *_acp_log_data; struct spa_i18n *acp_i18n; +#define DEFAULT_RATE 44100 + #define VOLUME_ACCURACY (PA_VOLUME_NORM/100) /* don't require volume adjustments to be perfectly correct. don't necessarily extend granularity in software unless the differences get greater than this level */ static const uint32_t channel_table[PA_CHANNEL_POSITION_MAX] = { @@ -312,7 +314,7 @@ static int add_pro_profile(pa_card *impl, uint32_t index) snd_pcm_uframes_t try_period_size, try_buffer_size; ss.format = PA_SAMPLE_S32LE; - ss.rate = 48000; + ss.rate = DEFAULT_RATE; ss.channels = 64; ap = pa_xnew0(pa_alsa_profile, 1); @@ -624,7 +626,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_ctl_elem_value_t *elem_value; - bool plugged_in; + bool plugged_in, any_input_port_available; void *state; pa_alsa_jack *jack; struct temp_port_avail *tp, *tports; @@ -735,6 +737,31 @@ static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) if (impl->card.active_profile_index != ACP_INVALID_INDEX) active_available = impl->card.profiles[impl->card.active_profile_index]->available; + /* First round - detect, if we have any input port available. + If the hardware can report the state for all I/O jacks, only speakers + may be plugged in. */ + any_input_port_available = false; + PA_HASHMAP_FOREACH(profile, impl->profiles, state) { + pa_device_port *port; + void *state2; + + if (profile->profile.flags & ACP_PROFILE_OFF) + continue; + + PA_HASHMAP_FOREACH(port, impl->ports, state2) { + if (!pa_hashmap_get(port->profiles, profile->profile.name)) + continue; + + if (port->port.direction == ACP_DIRECTION_CAPTURE && + port->port.available != ACP_AVAILABLE_NO) { + any_input_port_available = true; + goto input_port_found; + } + } + } +input_port_found: + + /* Second round */ PA_HASHMAP_FOREACH(profile, impl->profiles, state) { pa_device_port *port; void *state2; @@ -768,7 +795,7 @@ static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) if (has_input_port && !has_output_port && found_available_input_port) available = ACP_AVAILABLE_YES; - if (has_output_port && !has_input_port && found_available_output_port) + if (has_output_port && (!has_input_port || !any_input_port_available) && found_available_output_port) available = ACP_AVAILABLE_YES; if (has_output_port && has_input_port && found_available_output_port && found_available_input_port) available = ACP_AVAILABLE_YES; @@ -1546,7 +1573,7 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props) } impl->ucm.default_sample_spec.format = PA_SAMPLE_S16NE; - impl->ucm.default_sample_spec.rate = 48000; + impl->ucm.default_sample_spec.rate = DEFAULT_RATE; impl->ucm.default_sample_spec.channels = 2; pa_channel_map_init_extend(&impl->ucm.default_channel_map, impl->ucm.default_sample_spec.channels, PA_CHANNEL_MAP_ALSA); diff --git a/spa/plugins/alsa/acp/acp.h b/spa/plugins/alsa/acp/acp.h index 8db9f8f2783810cd5f78605069a3a48d562d201f..b61ad720097a22409848deeaf9d6fb24196e9a5e 100644 --- a/spa/plugins/alsa/acp/acp.h +++ b/spa/plugins/alsa/acp/acp.h @@ -49,7 +49,7 @@ struct acp_dict_item { const char *key; const char *value; }; -#define ACP_DICT_ITEM_INIT(key,value) (struct acp_dict_item) { key, value } +#define ACP_DICT_ITEM_INIT(key,value) ((struct acp_dict_item) { (key), (value) }) struct acp_dict { uint32_t flags; @@ -115,8 +115,8 @@ struct acp_format { uint32_t map[ACP_MAX_CHANNELS]; }; -#define ACP_DICT_INIT(items,n_items) (struct acp_dict) { 0, n_items, items } -#define ACP_DICT_INIT_ARRAY(items) (struct acp_dict) { 0, sizeof(items)/sizeof((items)[0]), items } +#define ACP_DICT_INIT(items,n_items) ((struct acp_dict) { 0, (n_items), (items) }) +#define ACP_DICT_INIT_ARRAY(items) ((struct acp_dict) { 0, sizeof(items)/sizeof((items)[0]), (items) }) #define acp_dict_for_each(item, dict) \ for ((item) = (dict)->items; \ diff --git a/spa/plugins/alsa/acp/array.h b/spa/plugins/alsa/acp/array.h index ef3b60362540d7adab07ddc03a7e967c886cece0..8a976ca7988accd54942b6e7a85b73d907dd38f8 100644 --- a/spa/plugins/alsa/acp/array.h +++ b/spa/plugins/alsa/acp/array.h @@ -39,7 +39,7 @@ typedef struct pa_array { size_t extend; /**< number of bytes to extend with */ } pa_array; -#define PW_ARRAY_INIT(extend) (struct pa_array) { NULL, 0, 0, extend } +#define PW_ARRAY_INIT(extend) ((struct pa_array) { NULL, 0, 0, (extend) }) #define pa_array_get_len_s(a,s) ((a)->size / (s)) #define pa_array_get_unchecked_s(a,idx,s,t) (t*)((uint8_t*)(a)->data + (int)((idx)*(s))) diff --git a/spa/plugins/alsa/alsa-acp-device.c b/spa/plugins/alsa/alsa-acp-device.c index 5e56d72f815363b63c1a9c1d1896344610394ce5..8e71ebc86c9cd8f1d2f560530da3105327e4185c 100644 --- a/spa/plugins/alsa/alsa-acp-device.c +++ b/spa/plugins/alsa/alsa-acp-device.c @@ -216,7 +216,7 @@ static int emit_info(struct impl *this, bool full) { int err = 0; struct spa_dict_item *items; - uint32_t i, n_items; + uint32_t n_items; const struct acp_dict_item *it; struct acp_card *card = this->card; char path[128]; @@ -241,10 +241,10 @@ static int emit_info(struct impl *this, bool full) #undef ADD_ITEM if (this->info.change_mask & SPA_DEVICE_CHANGE_MASK_PARAMS) { - for (i = 0; i < SPA_N_ELEMENTS(this->params); i++) { - if (this->params[i].user > 0) { - this->params[i].flags ^= SPA_PARAM_INFO_SERIAL; - this->params[i].user = 0; + SPA_FOR_EACH_ELEMENT_VAR(this->params, p) { + if (p->user > 0) { + p->flags ^= SPA_PARAM_INFO_SERIAL; + p->user = 0; } } } diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 6bcb7d851eb8fa2729507ec8c8953e26a7ffa6f1..5e0a60b375e8658f8d4c682543393c6e6c9c0292 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -511,6 +511,9 @@ int spa_alsa_init(struct state *state, const struct spa_dict *info) } CHECK(snd_output_stdio_attach(&state->output, state->log_file, 0), "attach failed"); + state->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; + state->rate_limit.burst = 1; + return 0; } @@ -635,12 +638,10 @@ static const struct format_info format_info[] = { static snd_pcm_format_t spa_format_to_alsa(uint32_t format, bool *planar) { - size_t i; - - for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) { - *planar = format_info[i].spa_pformat == format; - if (format_info[i].spa_format == format || *planar) - return format_info[i].format; + SPA_FOR_EACH_ELEMENT_VAR(format_info, i) { + *planar = i->spa_pformat == format; + if (i->spa_format == format || *planar) + return i->format; } return SND_PCM_FORMAT_UNKNOWN; } @@ -966,7 +967,7 @@ static int enum_pcm_formats(struct state *state, uint32_t index, uint32_t *next, struct spa_pod **result, struct spa_pod_builder *b) { int res, err; - size_t i, j; + size_t j; snd_pcm_t *hndl; snd_pcm_hw_params_t *params; struct spa_pod_frame f[2]; @@ -1017,8 +1018,10 @@ static int enum_pcm_formats(struct state *state, uint32_t index, uint32_t *next, spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0); choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]); - for (i = 1, j = 0; i < SPA_N_ELEMENTS(format_info); i++) { - const struct format_info *fi = &format_info[i]; + j = 0; + SPA_FOR_EACH_ELEMENT_VAR(format_info, fi) { + if (fi->format == SND_PCM_FORMAT_UNKNOWN) + continue; if (snd_pcm_format_mask_test(fmask, fi->format)) { if ((snd_pcm_access_mask_test(amask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED) || @@ -1772,22 +1775,24 @@ recover: state->alsa_started = false; if (state->stream == SND_PCM_STREAM_PLAYBACK) - spa_alsa_silence(state, state->start_delay + state->threshold * 2 + state->headroom); + spa_alsa_silence(state, state->start_delay + state->threshold + state->headroom); return do_start(state); } static int get_avail(struct state *state, uint64_t current_time) { - int res; + int res, missed; snd_pcm_sframes_t avail; if (SPA_UNLIKELY((avail = snd_pcm_avail(state->hndl)) < 0)) { if ((res = alsa_recover(state, avail)) < 0) return res; if ((avail = snd_pcm_avail(state->hndl)) < 0) { - spa_log_warn(state->log, "%s: snd_pcm_avail after recover: %s", - state->props.device, snd_strerror(avail)); + if ((missed = ratelimit_test(&state->rate_limit, current_time)) >= 0) { + spa_log_warn(state->log, "%s: (%d missed) snd_pcm_avail after recover: %s", + state->props.device, missed, snd_strerror(avail)); + } avail = state->threshold * 2; } } else { @@ -1799,7 +1804,7 @@ static int get_avail(struct state *state, uint64_t current_time) #if 0 static int get_avail_htimestamp(struct state *state, uint64_t current_time) { - int res; + int res, missed; snd_pcm_uframes_t avail; snd_htimestamp_t tstamp; uint64_t then; @@ -1808,8 +1813,10 @@ static int get_avail_htimestamp(struct state *state, uint64_t current_time) if ((res = alsa_recover(state, avail)) < 0) return res; if ((res = snd_pcm_htimestamp(state->hndl, &avail, &tstamp)) < 0) { - spa_log_warn(state->log, "%s: snd_pcm_htimestamp error: %s", - state->props.device, snd_strerror(res)); + if ((missed = ratelimit_test(&state->rate_limit, current_time)) >= 0) { + spa_log_warn(state->log, "%s: (%d missed) snd_pcm_htimestamp error: %s", + state->props.device, missed, snd_strerror(res)); + } avail = state->threshold * 2; } } else { @@ -1880,15 +1887,14 @@ static int update_time(struct state *state, uint64_t current_time, snd_pcm_sfram state, follower, state->last_threshold, state->threshold, diff, err); state->last_threshold = state->threshold; state->alsa_sync = true; + state->alsa_sync_warning = false; } if (err > state->max_error) { err = state->max_error; state->alsa_sync = true; - state->alsa_sync_warning = (diff == 0); } else if (err < -state->max_error) { err = -state->max_error; state->alsa_sync = true; - state->alsa_sync_warning = (diff == 0); } if (!follower || state->matching) @@ -1985,7 +1991,7 @@ int spa_alsa_write(struct state *state) 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; + int res = 0, missed; size_t frame_size = state->frame_size; check_position_config(state); @@ -2005,13 +2011,18 @@ int spa_alsa_write(struct state *state) return res; if (SPA_UNLIKELY(state->alsa_sync)) { - if (SPA_UNLIKELY(state->alsa_sync_warning)) { - spa_log_warn(state->log, "%s: follower delay:%ld target:%ld thr:%u, resync", - state->props.device, delay, target, state->threshold); - state->alsa_sync_warning = false; - } else - spa_log_info(state->log, "%s: follower delay:%ld target:%ld thr:%u, resync", - state->props.device, delay, target, state->threshold); + enum spa_log_level lev; + + if (SPA_UNLIKELY(state->alsa_sync_warning)) + lev = SPA_LOG_LEVEL_WARN; + else + lev = SPA_LOG_LEVEL_INFO; + + if ((missed = ratelimit_test(&state->rate_limit, current_time)) >= 0) { + spa_log_lev(state->log, lev, "%s: follower delay:%ld target:%ld thr:%u, " + "resync (%d missed)", state->props.device, delay, + target, state->threshold, missed); + } if (delay > target) snd_pcm_rewind(state->hndl, delay - target); @@ -2019,7 +2030,8 @@ int spa_alsa_write(struct state *state) spa_alsa_silence(state, target - delay); delay = target; state->alsa_sync = false; - } + } else + state->alsa_sync_warning = true; } total_written = 0; @@ -2116,7 +2128,7 @@ again: state->sample_count += total_written; - if (SPA_UNLIKELY(!state->alsa_started && total_written > 0)) + if (SPA_UNLIKELY(!state->alsa_started && (total_written > 0 || frames == 0))) do_start(state); return 0; @@ -2212,7 +2224,7 @@ int spa_alsa_read(struct state *state) const snd_pcm_channel_area_t *my_areas; snd_pcm_uframes_t read, frames, offset; snd_pcm_sframes_t commitres; - int res = 0; + int res = 0, missed; check_position_config(state); @@ -2221,7 +2233,6 @@ int spa_alsa_read(struct state *state) if (state->following && state->alsa_started) { uint64_t current_time; snd_pcm_uframes_t avail, delay, target; - uint32_t threshold = state->threshold; current_time = state->position->clock.nsec; @@ -2234,13 +2245,18 @@ int spa_alsa_read(struct state *state) return res; if (state->alsa_sync) { - if (SPA_UNLIKELY(state->alsa_sync_warning)) { - spa_log_warn(state->log, "%s: follower delay:%lu target:%lu thr:%u, resync", - state->props.device, delay, target, threshold); - state->alsa_sync_warning = false; - } else - spa_log_info(state->log, "%s: follower delay:%lu target:%lu thr:%u, resync", - state->props.device, delay, target, threshold); + enum spa_log_level lev; + + if (SPA_UNLIKELY(state->alsa_sync_warning)) + lev = SPA_LOG_LEVEL_WARN; + else + lev = SPA_LOG_LEVEL_INFO; + + if ((missed = ratelimit_test(&state->rate_limit, current_time)) >= 0) { + spa_log_lev(state->log, lev, "%s: follower delay:%ld target:%ld thr:%u, " + "resync (%d missed)", state->props.device, delay, + target, state->threshold, missed); + } if (delay < target) max_read = target - delay; @@ -2248,7 +2264,8 @@ int spa_alsa_read(struct state *state) snd_pcm_forward(state->hndl, delay - target); delay = target; state->alsa_sync = false; - } + } else + state->alsa_sync_warning = true; if (avail < state->read_size) max_read = 0; @@ -2338,7 +2355,7 @@ static int handle_play(struct state *state, uint64_t current_time, { int res; - if (SPA_UNLIKELY(delay > target + state->max_error)) { + if (state->alsa_started && SPA_UNLIKELY(delay > target + state->max_error)) { spa_log_trace(state->log, "%p: early wakeup %lu %lu", state, delay, target); if (delay > target * 3) delay = target * 3; @@ -2537,13 +2554,14 @@ int spa_alsa_start(struct state *state) reset_buffers(state); state->alsa_sync = true; + state->alsa_sync_warning = false; state->alsa_recovering = false; state->alsa_started = false; + /* start capture now, playback will start after first write */ if (state->stream == SND_PCM_STREAM_PLAYBACK) - spa_alsa_silence(state, state->start_delay + state->threshold * 2 + state->headroom); - - if ((err = do_start(state)) < 0) + spa_alsa_silence(state, state->start_delay + state->threshold + state->headroom); + else if ((err = do_start(state)) < 0) return err; set_timers(state); diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index 91385f40d05342b80937672dac4d19a142e2bf10..c630de3acc349b5fc919f72c602cabefa4997a82 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -87,7 +87,6 @@ struct channel_map { uint32_t pos[SPA_AUDIO_MAX_CHANNELS]; }; - struct card { struct spa_list link; int ref; @@ -98,6 +97,13 @@ struct card { uint32_t rate; }; +struct ratelimit { + uint64_t interval; + uint64_t begin; + unsigned burst; + unsigned n_printed, n_missed; +}; + struct state { struct spa_handle handle; struct spa_node node; @@ -107,6 +113,7 @@ struct state { struct spa_loop *data_loop; FILE *log_file; + struct ratelimit rate_limit; uint32_t card_index; struct card *card; @@ -342,6 +349,23 @@ static inline uint32_t spa_alsa_get_iec958_codecs(struct state *state, uint32_t return i; } +static inline int ratelimit_test(struct ratelimit *r, uint64_t now) +{ + unsigned missed = 0; + if (r->begin + r->interval < now) { + missed = r->n_missed; + r->begin = now; + r->n_printed = 0; + r->n_missed = 0; + } else if (r->n_printed >= r->burst) { + r->n_missed++; + return -1; + } + r->n_printed++; + return missed; +} + + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/spa/plugins/alsa/alsa-seq-bridge.c b/spa/plugins/alsa/alsa-seq-bridge.c index bf29301472d7c0639a35e0dcec91d2e468987c23..4ec8e3c6ed33f2d4895a5ed8c1fbd1eb6f337a6b 100644 --- a/spa/plugins/alsa/alsa-seq-bridge.c +++ b/spa/plugins/alsa/alsa-seq-bridge.c @@ -566,7 +566,7 @@ impl_node_port_enum_params(void *object, int seq, if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + 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)); break; diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 381bbc5e10d3661f4a800939587ccfdd1e7d2078..407b88f84cb08a85f464a5e6856583cd5f9f04b8 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -586,11 +586,11 @@ static int process_read(struct seq_state *state) continue; if (prepare_buffer(state, port) >= 0) { - port->buffer->buf->datas[0].chunk->offset = 0; - port->buffer->buf->datas[0].chunk->size = port->builder.state.offset, - spa_pod_builder_pop(&port->builder, &port->frame); + port->buffer->buf->datas[0].chunk->offset = 0; + port->buffer->buf->datas[0].chunk->size = port->builder.state.offset; + /* move buffer to ready queue */ spa_list_remove(&port->buffer->link); SPA_FLAG_SET(port->buffer->flags, BUFFER_FLAG_OUT); @@ -702,23 +702,28 @@ static int process_write(struct seq_state *state) return res; } -static int update_time(struct seq_state *state, uint64_t nsec, bool follower) +static void update_position(struct seq_state *state) { - snd_seq_queue_status_t *status; - const snd_seq_real_time_t* queue_time; - uint64_t queue_real; - double err, corr; - uint64_t queue_elapsed; - if (state->position) { struct spa_io_clock *clock = &state->position->clock; state->rate = clock->rate; + if (state->rate.num == 0 || state->rate.denom == 0) + state->rate = SPA_FRACTION(1, 48000); state->duration = clock->duration; } else { state->rate = SPA_FRACTION(1, 48000); state->duration = 1024; } state->threshold = state->duration; +} + +static int update_time(struct seq_state *state, uint64_t nsec, bool follower) +{ + snd_seq_queue_status_t *status; + const snd_seq_real_time_t* queue_time; + uint64_t queue_real; + double err, corr; + uint64_t queue_elapsed; corr = 1.0 - (state->dll.z2 + state->dll.z3); @@ -776,6 +781,8 @@ int spa_alsa_seq_process(struct seq_state *state) { int res; + update_position(state); + res = process_recycle(state); if (state->following && state->position) { @@ -800,11 +807,13 @@ static void alsa_on_timeout_event(struct spa_source *source) spa_log_trace(state->log, "timeout %"PRIu64, state->current_time); + update_position(state); + update_time(state, state->current_time, false); res = process_read(state); - if (res > 0) - spa_node_call_ready(&state->callbacks, res); + if (res >= 0) + spa_node_call_ready(&state->callbacks, res | SPA_STATUS_NEED_DATA); set_timeout(state, state->next_time); } @@ -878,15 +887,7 @@ int spa_alsa_seq_start(struct seq_state *state) while (snd_seq_drain_output(state->event.hndl) > 0) sleep(1); - if (state->position) { - struct spa_io_clock *clock = &state->position->clock; - state->rate = clock->rate; - state->duration = clock->duration; - } else { - state->rate = SPA_FRACTION(1, 48000); - state->duration = 1024; - } - state->threshold = state->duration; + update_position(state); state->started = true; diff --git a/spa/plugins/alsa/alsa-udev.c b/spa/plugins/alsa/alsa-udev.c index 8ee217d9dff3e380769770e81b7227d686e5a08b..f89d86375b953034a0c99e769183ca0ec456635b 100644 --- a/spa/plugins/alsa/alsa-udev.c +++ b/spa/plugins/alsa/alsa-udev.c @@ -256,7 +256,7 @@ static int check_device_pcm_class(const char *devname) /* Check device class */ spa_scnprintf(path, sizeof(path), "/sys/class/sound/%s/pcm_class", devname); - f = fopen(path, "r"); + f = fopen(path, "re"); if (f == NULL) return -errno; sz = fread(buf, 1, sizeof(buf) - 1, f); @@ -361,7 +361,7 @@ static int check_device_available(struct impl *this, struct device *device, int spa_scnprintf(path, sizeof(path), "/proc/asound/card%u/%s/%s/status", (unsigned int)device->id, entry->d_name, entry_pcm->d_name); - f = fopen(path, "r"); + f = fopen(path, "re"); if (f == NULL) goto done; sz = fread(buf, 1, 6, f); @@ -465,7 +465,7 @@ static int emit_object_info(struct impl *this, struct device *device) if (str && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS_PATH, str); } - if ((str = udev_device_get_syspath(dev)) && *str) { + if ((str = udev_device_get_devpath(dev)) && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_SYSFS_PATH, str); } if ((str = udev_device_get_property_value(dev, "ID_ID")) && *str) { @@ -651,9 +651,9 @@ static void impl_on_notify_events(struct spa_source *source) { bool deleted = false; struct impl *this = source->data; - struct { + union { struct inotify_event e; - char name[NAME_MAX+1]; + char name[NAME_MAX+1+sizeof(struct inotify_event)]; } buf; while (true) { @@ -670,17 +670,20 @@ static void impl_on_notify_events(struct spa_source *source) e = SPA_PTROFF(&buf, len, void); for (p = &buf; p < e; - p = SPA_PTROFF(p, sizeof(struct inotify_event) + event->len, void)) { + p = SPA_PTROFF(p, sizeof(struct inotify_event) + event->len, void)) { unsigned int id; struct device *device; event = (const struct inotify_event *) p; + spa_assert_se(SPA_PTRDIFF(e, p) >= (ptrdiff_t)sizeof(struct inotify_event) && + SPA_PTRDIFF(e, p) - sizeof(struct inotify_event) >= event->len && + "bad event from kernel"); /* Device becomes accessible or not busy */ if ((event->mask & (IN_ATTRIB | IN_CLOSE_WRITE))) { bool access; if (sscanf(event->name, "controlC%u", &id) != 1 && - sscanf(event->name, "pcmC%uD", &id) != 1) + sscanf(event->name, "pcmC%uD", &id) != 1) continue; if ((device = find_device(this, id)) == NULL) continue; diff --git a/spa/plugins/alsa/mixer/profile-sets/asus-xonar-se.conf b/spa/plugins/alsa/mixer/profile-sets/asus-xonar-se.conf new file mode 100644 index 0000000000000000000000000000000000000000..3e42ea311b22a71a94dddb980de3d1fae3811bc7 --- /dev/null +++ b/spa/plugins/alsa/mixer/profile-sets/asus-xonar-se.conf @@ -0,0 +1,93 @@ +# This file is part of PulseAudio. +# +# PulseAudio is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation; either version 2.1 of the +# License, or (at your option) any later version. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. + +; ASUS Xonar SE card. +; This card has two devices for each rear and front panel jacks. +; +; See default.conf for an explanation on the directives used here. + +[General] +auto-profiles = yes + +[Mapping analog-stereo-front] +description = Analog Stereo Front +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 +device-strings = hw:%f,0 +channel-map = left,right +paths-output = analog-output analog-output-speaker +paths-input = analog-input analog-input-mic analog-input-linein +priority = 14 + +[Mapping analog-surround-21] +device-strings = surround21:%f +channel-map = front-left,front-right,lfe +paths-output = analog-output-speaker +priority = 13 +direction = output + +[Mapping analog-surround-40] +device-strings = surround40:%f +channel-map = front-left,front-right,rear-left,rear-right +paths-output = analog-output-speaker +priority = 12 +direction = output + +[Mapping analog-surround-41] +device-strings = surround41:%f +channel-map = front-left,front-right,rear-left,rear-right,lfe +paths-output = analog-output-speaker +priority = 13 +direction = output + +[Mapping analog-surround-50] +device-strings = surround50:%f +channel-map = front-left,front-right,rear-left,rear-right,front-center +paths-output = analog-output-speaker +priority = 12 +direction = output + +[Mapping analog-surround-51] +device-strings = surround51:%f +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe +paths-output = analog-output-speaker +priority = 13 +direction = output + +[Mapping iec958-stereo] +device-strings = iec958:%f +channel-map = left,right +paths-output = iec958-stereo-output +priority = 5 + +[Mapping iec958-ac3-surround-40] +device-strings = a52:%f +channel-map = front-left,front-right,rear-left,rear-right +paths-output = iec958-stereo-output +priority = 2 +direction = output + +[Mapping iec958-ac3-surround-51] +device-strings = a52:%f +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe +paths-output = iec958-stereo-output +priority = 3 +direction = output diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index dcaed9ad3d8ee32644b0a7929b35eb88ecef4f1e..14bf3e6d970f926d34f36cee4b39018697939e2f 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -336,7 +336,7 @@ static int negotiate_buffers(struct impl *this) struct spa_data *datas; uint32_t follower_flags, conv_flags; - spa_log_debug(this->log, "%p: %d", this, this->n_buffers); + spa_log_debug(this->log, "%p: n_buffers:%d", this, this->n_buffers); if (this->target == this->follower) return 0; @@ -438,6 +438,9 @@ static int configure_format(struct impl *this, uint32_t flags, const struct spa_ { int res; + if (format == NULL && !this->have_format) + return 0; + spa_log_debug(this->log, "%p: configure format:", this); if (format && spa_log_level_enabled(this->log, SPA_LOG_LEVEL_DEBUG)) spa_debug_format(0, NULL, format); @@ -556,30 +559,6 @@ static int reconfigure_mode(struct impl *this, bool passthrough, return 0; } -static int format_audio_raw_parse_opt(const struct spa_pod *format, struct spa_audio_info_raw *info) -{ - struct spa_pod *position = NULL; - uint32_t media_type, media_subtype; - int res; - if ((res = spa_format_parse(format, &media_type, &media_subtype)) < 0) - return res; - if (media_type != SPA_MEDIA_TYPE_audio || - media_subtype != SPA_MEDIA_SUBTYPE_raw) - return -ENOTSUP; - - spa_zero(*info); - res = spa_pod_parse_object(format, - SPA_TYPE_OBJECT_Format, NULL, - SPA_FORMAT_AUDIO_format, SPA_POD_OPT_Id(&info->format), - SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), - SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position)); - if (position == NULL || - !spa_pod_copy_array(position, SPA_TYPE_Id, info->position, SPA_AUDIO_MAX_CHANNELS)) - SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); - - return res; -} - static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { @@ -627,8 +606,18 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, if (format) { struct spa_audio_info info; - if (format_audio_raw_parse_opt(format, &info.info.raw) >= 0) + + spa_zero(info); + if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) + return res; + if (info.media_type != SPA_MEDIA_TYPE_audio || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -ENOTSUP; + + if (spa_format_audio_raw_parse(format, &info.info.raw) >= 0) { + info.info.raw.rate = 0; this->default_format = info; + } } switch (mode) { @@ -738,6 +727,8 @@ static int negotiate_format(struct impl *this) struct spa_pod_builder b = { 0 }; int res; + spa_log_debug(this->log, "%p: have_format:%d", this, this->have_format); + if (this->have_format) return 0; @@ -746,7 +737,6 @@ static int negotiate_format(struct impl *this) spa_pod_builder_init(&b, buffer, sizeof(buffer)); - spa_log_debug(this->log, "%p: negiotiate", this); spa_node_send_command(this->follower, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamBegin)); @@ -813,19 +803,24 @@ static int impl_node_send_command(void *object, const struct spa_command *comman switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: + spa_log_debug(this->log, "%p: starting %d", this, this->started); + if (this->started) + return 0; if ((res = negotiate_format(this)) < 0) return res; if ((res = negotiate_buffers(this)) < 0) return res; + this->started = true; break; case SPA_NODE_COMMAND_Suspend: - configure_format(this, 0, NULL); - SPA_FALLTHROUGH + spa_log_debug(this->log, "%p: suspending", this); + break; + case SPA_NODE_COMMAND_Pause: + spa_log_debug(this->log, "%p: pausing", this); + break; case SPA_NODE_COMMAND_Flush: + spa_log_debug(this->log, "%p: flushing", this); this->io_buffers.status = SPA_STATUS_OK; - SPA_FALLTHROUGH - case SPA_NODE_COMMAND_Pause: - this->started = false; break; default: break; @@ -848,7 +843,17 @@ static int impl_node_send_command(void *object, const struct spa_command *comman } switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: - this->started = true; + spa_log_debug(this->log, "%p: started", this); + break; + case SPA_NODE_COMMAND_Suspend: + configure_format(this, 0, NULL); + SPA_FALLTHROUGH + case SPA_NODE_COMMAND_Pause: + this->started = false; + spa_log_debug(this->log, "%p: stopped", this); + break; + case SPA_NODE_COMMAND_Flush: + spa_log_debug(this->log, "%p: flushed", this); break; } return res; @@ -892,10 +897,11 @@ static void convert_node_info(void *data, const struct spa_node_info *info) (this->params[idx].flags & SPA_PARAM_INFO_SERIAL) | (info->params[i].flags & SPA_PARAM_INFO_READWRITE); - if (!this->add_listener) { - this->params[idx].user++; - spa_log_debug(this->log, "param %d changed", info->params[i].id); - } + if (this->add_listener) + continue; + + this->params[idx].user++; + spa_log_debug(this->log, "param %d changed", info->params[i].id); } } emit_node_info(this, false); @@ -1000,10 +1006,11 @@ static void follower_info(void *data, const struct spa_node_info *info) (this->params[idx].flags & SPA_PARAM_INFO_SERIAL) | (info->params[i].flags & SPA_PARAM_INFO_READWRITE); - if (!this->add_listener) { - this->params[idx].user++; - spa_log_debug(this->log, "param %d changed", info->params[i].id); - } + if (this->add_listener) + continue; + + this->params[idx].user++; + spa_log_debug(this->log, "param %d changed", info->params[i].id); } } emit_node_info(this, false); @@ -1080,26 +1087,32 @@ static void follower_port_info(void *data, default: continue; } + if (!this->add_listener && this->follower_params_flags[idx] == info->params[i].flags) continue; + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; this->follower_params_flags[idx] = info->params[i].flags; this->params[idx].flags = (this->params[idx].flags & SPA_PARAM_INFO_SERIAL) | (info->params[i].flags & SPA_PARAM_INFO_READWRITE); + if (this->add_listener) + continue; + if (idx == IDX_Latency) { res = recalc_latency(this, direction, port_id); spa_log_debug(this->log, "latency: %d (%s)", res, spa_strerror(res)); } - - this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; - if (!this->add_listener) { - this->params[idx].user++; - spa_log_debug(this->log, "param %d changed", info->params[i].id); + if (idx == IDX_EnumFormat) { + spa_log_debug(this->log, "new formats"); + configure_format(this, 0, NULL); } + + this->params[idx].user++; + spa_log_debug(this->log, "param %d changed", info->params[i].id); } } emit_node_info(this, false); @@ -1150,6 +1163,11 @@ static int follower_ready(void *data, int status) spa_log_trace_fp(this->log, "%p: ready %d", this, status); + if (!this->started) { + spa_log_warn(this->log, "%p: ready stopped node", this); + return -EIO; + } + if (this->target != this->follower) { this->driver = true; @@ -1396,6 +1414,11 @@ static int impl_node_process(void *object) struct impl *this = object; int status = 0, fstatus, retry = 8; + if (!this->started) { + spa_log_warn(this->log, "%p: scheduling stopped node", this); + return -EIO; + } + spa_log_trace_fp(this->log, "%p: process convert:%p driver:%d", this, this->convert, this->driver); diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index bb6614d13d2615d5651eff04d20e69d233fa17c2..8e5237bdbf29bb2a0b7e87a8b2fd4ac91fcc478e 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -90,8 +90,8 @@ struct props { struct volumes monitor; unsigned int have_soft_volume:1; unsigned int mix_disabled:1; - unsigned int resample_quality; unsigned int resample_disabled:1; + unsigned int resample_quality; double rate; }; @@ -105,10 +105,11 @@ static void props_reset(struct props *props) init_volumes(&props->channel); init_volumes(&props->soft); init_volumes(&props->monitor); + props->have_soft_volume = false; props->mix_disabled = false; - props->rate = 1.0; - props->resample_quality = RESAMPLE_DEFAULT_QUALITY; props->resample_disabled = false; + props->resample_quality = RESAMPLE_DEFAULT_QUALITY; + props->rate = 1.0; } struct buffer { @@ -215,9 +216,11 @@ struct impl { uint32_t in_offset; uint32_t out_offset; unsigned int started:1; - unsigned int peaks:1; + unsigned int setup:1; + unsigned int resample_peaks:1; unsigned int is_passthrough:1; unsigned int drained:1; + unsigned int rate_adjust:1; uint32_t empty_size; float *empty; @@ -239,16 +242,15 @@ static void set_volume(struct impl *this); static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; - uint32_t i; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { - for (i = 0; i < SPA_N_ELEMENTS(this->params); i++) { - if (this->params[i].user > 0) { - this->params[i].flags ^= SPA_PARAM_INFO_SERIAL; - this->params[i].user = 0; + SPA_FOR_EACH_ELEMENT_VAR(this->params, p) { + if (p->user > 0) { + p->flags ^= SPA_PARAM_INFO_SERIAL; + p->user = 0; } } } @@ -260,7 +262,6 @@ static void emit_node_info(struct impl *this, bool full) static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; - uint32_t i; if (full) port->info.change_mask = port->info_all; @@ -280,10 +281,10 @@ static void emit_port_info(struct impl *this, struct port *port, bool full) port->info.props = &SPA_DICT_INIT(items, n_items); if (port->info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { - for (i = 0; i < SPA_N_ELEMENTS(port->params); i++) { - if (port->params[i].user > 0) { - port->params[i].flags ^= SPA_PARAM_INFO_SERIAL; - port->params[i].user = 0; + SPA_FOR_EACH_ELEMENT_VAR(port->params, p) { + if (p->user > 0) { + p->flags ^= SPA_PARAM_INFO_SERIAL; + p->user = 0; } } } @@ -430,7 +431,6 @@ static int impl_node_enum_params(void *object, int seq, { struct props *p = &this->props; struct spa_pod_frame f[2]; - uint32_t i; switch (result.index) { case 0: @@ -594,9 +594,9 @@ static int impl_node_enum_params(void *object, int seq, spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0); spa_pod_builder_push_struct(&b, &f[1]); - for (i = 0; i < SPA_N_ELEMENTS(channelmix_upmix_info); i++) { - spa_pod_builder_string(&b, channelmix_upmix_info[i].label); - spa_pod_builder_string(&b, channelmix_upmix_info[i].description); + SPA_FOR_EACH_ELEMENT_VAR(channelmix_upmix_info, i) { + spa_pod_builder_string(&b, i->label); + spa_pod_builder_string(&b, i->description); } spa_pod_builder_pop(&b, &f[1]); param = spa_pod_builder_pop(&b, &f[0]); @@ -644,9 +644,9 @@ static int impl_node_enum_params(void *object, int seq, 0); spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0); spa_pod_builder_push_struct(&b, &f[1]); - for (i = 0; i < SPA_N_ELEMENTS(dither_method_info); i++) { - spa_pod_builder_string(&b, dither_method_info[i].label); - spa_pod_builder_string(&b, dither_method_info[i].description); + SPA_FOR_EACH_ELEMENT_VAR(dither_method_info, i) { + spa_pod_builder_string(&b, i->label); + spa_pod_builder_string(&b, i->description); } spa_pod_builder_pop(&b, &f[1]); param = spa_pod_builder_pop(&b, &f[0]); @@ -825,8 +825,11 @@ static int parse_prop_params(struct impl *this, struct spa_pod *params) if (spa_pod_is_string(pod)) { spa_pod_copy_string(pod, sizeof(value), value); } else if (spa_pod_is_float(pod)) { - snprintf(value, sizeof(value), "%f", + spa_dtoa(value, sizeof(value), SPA_POD_VALUE(struct spa_pod_float, pod)); + } else if (spa_pod_is_double(pod)) { + spa_dtoa(value, sizeof(value), + SPA_POD_VALUE(struct spa_pod_double, pod)); } else if (spa_pod_is_int(pod)) { snprintf(value, sizeof(value), "%d", SPA_POD_VALUE(struct spa_pod_int, pod)); @@ -834,12 +837,17 @@ static int parse_prop_params(struct impl *this, struct spa_pod *params) snprintf(value, sizeof(value), "%s", SPA_POD_VALUE(struct spa_pod_bool, pod) ? "true" : "false"); + } else if (spa_pod_is_none(pod)) { + spa_zero(value); } else continue; spa_log_info(this->log, "key:'%s' val:'%s'", name, value); changed += audioconvert_set_param(this, name, value); } + if (changed) { + channelmix_init(&this->mix); + } return changed; } @@ -907,6 +915,11 @@ static int apply_props(struct impl *this, const struct spa_pod *param) break; case SPA_PROP_rate: spa_pod_get_double(&prop->value, &p->rate); + if (!this->rate_adjust && p->rate != 1.0) { + this->rate_adjust = true; + spa_log_info(this->log, "%p: activating adaptive resampler", + this); + } break; case SPA_PROP_params: changed += parse_prop_params(this, &prop->value); @@ -921,7 +934,6 @@ static int apply_props(struct impl *this, const struct spa_pod *param) else if (have_channel_volume) p->have_soft_volume = false; - channelmix_init(&this->mix); set_volume(this); } return changed; @@ -967,6 +979,7 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m } this->monitor = monitor; + this->setup = false; dir->control = control; dir->have_profile = true; dir->mode = mode; @@ -1060,7 +1073,10 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; - if (info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) + if (info.info.raw.format == 0 || + info.info.raw.rate == 0 || + info.info.raw.channels == 0 || + info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) return -EINVAL; infop = &info; @@ -1276,12 +1292,23 @@ static void set_volume(struct impl *this) this->params[IDX_Props].user++; } +static char *format_position(char *str, size_t len, uint32_t channels, uint32_t *position) +{ + uint32_t i, idx = 0; + for (i = 0; i < channels; i++) + idx += snprintf(str + idx, len - idx, "%s%s", i == 0 ? "" : " ", + spa_debug_type_find_short_name(spa_type_audio_channel, + position[i])); + return str; +} + static int setup_channelmix(struct impl *this) { struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; uint32_t i, src_chan, dst_chan, p; uint64_t src_mask, dst_mask; + char str[1024]; int res; src_chan = in->format.info.raw.channels; @@ -1296,6 +1323,11 @@ static int setup_channelmix(struct impl *this) 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), + dst_chan, out->format.info.raw.position), dst_mask); + if (src_mask & 1) src_mask = default_mask(src_chan); if (dst_mask & 1) @@ -1353,7 +1385,9 @@ static int setup_resample(struct impl *this) this->resample.quality = this->props.resample_quality; this->resample.cpu_flags = this->cpu_flags; - if (this->peaks) + this->rate_adjust = this->props.rate != 1.0; + + if (this->resample_peaks) res = resample_peaks_init(&this->resample); else res = resample_native_init(&this->resample); @@ -1463,6 +1497,12 @@ static int setup_convert(struct impl *this) in = &this->dir[SPA_DIRECTION_INPUT]; out = &this->dir[SPA_DIRECTION_OUTPUT]; + spa_log_debug(this->log, "%p: setup:%d in_format:%d out_format:%d", this, + this->setup, in->have_format, out->have_format); + + if (this->setup) + return 0; + if (!in->have_format || !out->have_format) return -EINVAL; @@ -1506,6 +1546,7 @@ static int setup_convert(struct impl *this) this->tmp_datas[1][i] = SPA_PTROFF(this->tmp[1], this->empty_size * i, void); this->tmp_datas[1][i] = SPA_PTR_ALIGN(this->tmp_datas[1][i], MAX_ALIGN, void); } + this->setup = true; emit_node_info(this, false); @@ -1537,13 +1578,14 @@ static int impl_node_send_command(void *object, const struct spa_command *comman this->started = true; break; case SPA_NODE_COMMAND_Suspend: - SPA_FALLTHROUGH; - case SPA_NODE_COMMAND_Flush: - reset_node(this); + this->setup = false; SPA_FALLTHROUGH; case SPA_NODE_COMMAND_Pause: this->started = false; break; + case SPA_NODE_COMMAND_Flush: + reset_node(this); + break; default: return -ENOTSUP; } @@ -1604,7 +1646,6 @@ static int port_enum_formats(void *object, struct spa_pod_builder *builder) { struct impl *this = object; - struct port *port = GET_PORT(this, direction, port_id); switch (index) { case 0: @@ -1618,11 +1659,7 @@ static int port_enum_formats(void *object, 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)); - } else if (port->have_format) { - *param = spa_format_audio_raw_build(builder, - SPA_PARAM_EnumFormat, &this->dir[direction].format.info.raw); - } - else { + } else { uint32_t rate = this->io_position ? this->io_position->clock.rate.denom : DEFAULT_RATE; @@ -1832,7 +1869,7 @@ static int port_set_latency(void *object, const struct spa_pod *latency) { struct impl *this = object; - struct port *port; + struct port *port, *oport; enum spa_direction other = SPA_DIRECTION_REVERSE(direction); uint32_t i; @@ -1854,10 +1891,10 @@ static int port_set_latency(void *object, } for (i = 0; i < this->dir[other].n_ports; i++) { - port = GET_PORT(this, other, i); - port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - port->params[IDX_Latency].user++; - emit_port_info(this, port, false); + oport = GET_PORT(this, other, i); + oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + oport->params[IDX_Latency].user++; + emit_port_info(this, oport, false); } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[IDX_Latency].user++; @@ -1929,9 +1966,13 @@ static int port_set_format(void *object, spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); return res; } - if (info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) { - spa_log_error(this->log, "too many channels %d > %d", - info.info.raw.channels, SPA_AUDIO_MAX_CHANNELS); + if (info.info.raw.format == 0 || + info.info.raw.rate == 0 || + info.info.raw.channels == 0 || + info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) { + spa_log_error(this->log, "invalid format:%d rate:%d channels:%d", + info.info.raw.format, info.info.raw.rate, + info.info.raw.channels); return -EINVAL; } port->stride = calc_width(&info); @@ -1943,6 +1984,7 @@ static int port_set_format(void *object, } this->dir[direction].format = info; this->dir[direction].have_format = true; + this->setup = false; } port->format = info; port->have_format = true; @@ -2260,8 +2302,7 @@ static uint32_t resample_update_rate_match(struct impl *this, bool passthrough, static inline bool resample_is_passthrough(struct impl *this) { return this->resample.i_rate == this->resample.o_rate && this->rate_scale == 1.0 && - this->props.rate == 1.0 && - (this->io_rate_match == NULL || + !this->rate_adjust && (this->io_rate_match == NULL || !SPA_FLAG_IS_SET(this->io_rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE)); } @@ -2467,6 +2508,7 @@ static int impl_node_process(void *object) volume, mon_max); bd->chunk->size = mon_max * port->stride; + bd->chunk->stride = port->stride; spa_log_trace_fp(this->log, "%p: monitor %d %d", this, remap, mon_max); @@ -2667,9 +2709,10 @@ static int impl_node_process(void *object) for (j = 0; j < port->blocks; j++) { bd = &buf->buf->datas[j]; bd->chunk->size = this->out_offset * port->stride; + bd->chunk->stride = port->stride; SPA_FLAG_UPDATE(bd->chunk->flags, SPA_CHUNK_FLAG_EMPTY, in_empty); - spa_log_trace_fp(this->log, "out: %d %d %d", this->out_offset, - port->stride, bd->chunk->size); + spa_log_trace_fp(this->log, "out: offs:%d stride:%d size:%d", + this->out_offset, port->stride, bd->chunk->size); } io->status = SPA_STATUS_HAVE_DATA; io->buffer_id = buf->id; @@ -2678,7 +2721,7 @@ static int impl_node_process(void *object) this->drained = draining; this->out_offset = 0; } - else if (n_samples == 0 && this->peaks) { + else if (n_samples == 0 && this->resample_peaks) { for (i = 0; i < dir->n_ports; i++) { port = GET_OUT_PORT(this, i); if (port->is_monitor || port->is_control) @@ -2841,15 +2884,20 @@ impl_init(const struct spa_handle_factory *factory, if (spa_streq(k, "clock.quantum-limit")) spa_atou32(s, &this->quantum_limit, 0); else if (spa_streq(k, "resample.peaks")) - this->peaks = spa_atob(s); + this->resample_peaks = spa_atob(s); + else if (spa_streq(k, "resample.prefill")) + SPA_FLAG_UPDATE(this->resample.options, + RESAMPLE_OPTION_PREFILL, spa_atob(s)); else if (spa_streq(k, "factory.mode")) { if (spa_streq(s, "merge")) this->direction = SPA_DIRECTION_OUTPUT; else this->direction = SPA_DIRECTION_INPUT; } - else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) - this->props.n_channels = parse_position(this->props.channel_map, s, strlen(s)); + else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) { + if (s != NULL) + this->props.n_channels = parse_position(this->props.channel_map, s, strlen(s)); + } else audioconvert_set_param(this, k, s); } diff --git a/spa/plugins/audioconvert/benchmark-fmt-ops.c b/spa/plugins/audioconvert/benchmark-fmt-ops.c index 491b0730bb08294bb63d1b6c796db42d2a96feb7..2a0d4e80b0f5f1168d8152b8390561389d55281f 100644 --- a/spa/plugins/audioconvert/benchmark-fmt-ops.c +++ b/spa/plugins/audioconvert/benchmark-fmt-ops.c @@ -105,21 +105,17 @@ static void run_test1(const char *name, const char *impl, bool in_packed, bool o static void run_testc(const char *name, const char *impl, bool in_packed, bool out_packed, convert_func_t func, int channel_count) { - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(sample_sizes); i++) { + SPA_FOR_EACH_ELEMENT_VAR(sample_sizes, s) { run_test1(name, impl, in_packed, out_packed, func, channel_count, - (sample_sizes[i] + (channel_count -1)) / channel_count); + (*s + (channel_count -1)) / channel_count); } } static void run_test(const char *name, const char *impl, bool in_packed, bool out_packed, convert_func_t func) { - size_t i, j; - - for (i = 0; i < SPA_N_ELEMENTS(sample_sizes); i++) { - for (j = 0; j < SPA_N_ELEMENTS(channel_counts); j++) { - run_test1(name, impl, in_packed, out_packed, func, channel_counts[j], - (sample_sizes[i] + (channel_counts[j] -1)) / channel_counts[j]); + SPA_FOR_EACH_ELEMENT_VAR(sample_sizes, s) { + SPA_FOR_EACH_ELEMENT_VAR(channel_counts, c) { + run_test1(name, impl, in_packed, out_packed, func, *c, (*s + (*c -1)) / *c); } } } diff --git a/spa/plugins/audioconvert/channelmix-ops-c.c b/spa/plugins/audioconvert/channelmix-ops-c.c index 9a28830042ad2f5fd3610304e46f6a9df7359c9d..f12f35f85aeb322202328b382bb11b06628b6c5e 100644 --- a/spa/plugins/audioconvert/channelmix-ops-c.c +++ b/spa/plugins/audioconvert/channelmix-ops-c.c @@ -46,6 +46,16 @@ static inline void vol_c(float *d, const float *s, float vol, uint32_t n_samples d[n] = s[n] * vol; } } +static inline void conv_c(float *d, const float **s, float *c, uint32_t n_c, uint32_t n_samples) +{ + uint32_t n, j; + for (n = 0; n < n_samples; n++) { + float sum = 0.0f; + for (j = 0; j < n_c; j++) + sum += s[j][n] * c[j]; + d[n] = sum; + } +} static inline void avg_c(float *d, const float *s0, const float *s1, uint32_t n_samples) { @@ -78,7 +88,7 @@ void channelmix_f32_n_m_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { - uint32_t i, j, n, n_dst = mix->dst_chan, n_src = mix->src_chan; + uint32_t i, j, n_dst = mix->dst_chan, n_src = mix->src_chan; float **d = (float **) dst; const float **s = (const float **) src; @@ -94,16 +104,27 @@ channelmix_f32_n_m_c(struct channelmix *mix, void * SPA_RESTRICT dst[], clear_c(d[i], n_samples); } else { - for (n = 0; n < n_samples; n++) { - for (i = 0; i < n_dst; i++) { - float sum = 0.0f; - for (j = 0; j < n_src; j++) - sum += s[j][n] * mix->matrix[i][j]; - d[i][n] = sum; + for (i = 0; i < n_dst; i++) { + float *di = d[i]; + float mj[n_src]; + const float *sj[n_src]; + uint32_t n_j = 0; + + for (j = 0; j < n_src; j++) { + if (mix->matrix[i][j] == 0.0f) + continue; + mj[n_j] = mix->matrix[i][j]; + sj[n_j++] = s[j]; + } + if (n_j == 0) { + clear_c(di, n_samples); + } else if (n_j == 1) { + lr4_process(&mix->lr4[i], di, sj[0], mj[0], n_samples); + } else { + conv_c(di, sj, mj, n_j, n_samples); + lr4_process(&mix->lr4[i], di, di, 1.0f, n_samples); } } - for (i = 0; i < n_dst; i++) - lr4_process(&mix->lr4[i], d[i], d[i], 1.0f, n_samples); } } diff --git a/spa/plugins/audioconvert/channelmix-ops-sse.c b/spa/plugins/audioconvert/channelmix-ops-sse.c index 37a02e22648b102a5b16f91c058f6c5b9426cfed..8311fb430648946f77fcca9c1bf34569c45f27ec 100644 --- a/spa/plugins/audioconvert/channelmix-ops-sse.c +++ b/spa/plugins/audioconvert/channelmix-ops-sse.c @@ -68,6 +68,97 @@ static inline void vol_sse(float *d, const float *s, float vol, uint32_t n_sampl } } +static inline void conv_sse(float *d, const float **s, float *c, uint32_t n_c, uint32_t n_samples) +{ + __m128 mi[n_c], sum[2]; + uint32_t n, j, unrolled; + bool aligned = true; + + for (j = 0; j < n_c; j++) { + mi[j] = _mm_set1_ps(c[j]); + aligned &= SPA_IS_ALIGNED(s[j], 16); + } + + if (aligned && SPA_IS_ALIGNED(d, 16)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for (n = 0; n < unrolled; n += 8) { + sum[0] = sum[1] = _mm_setzero_ps(); + for (j = 0; j < n_c; j++) { + sum[0] = _mm_add_ps(sum[0], _mm_mul_ps(_mm_load_ps(&s[j][n + 0]), mi[j])); + sum[1] = _mm_add_ps(sum[1], _mm_mul_ps(_mm_load_ps(&s[j][n + 4]), mi[j])); + } + _mm_store_ps(&d[n + 0], sum[0]); + _mm_store_ps(&d[n + 4], sum[1]); + } + for (; n < n_samples; n++) { + sum[0] = _mm_setzero_ps(); + for (j = 0; j < n_c; j++) + sum[0] = _mm_add_ss(sum[0], _mm_mul_ss(_mm_load_ss(&s[j][n]), mi[j])); + _mm_store_ss(&d[n], sum[0]); + } +} + +static inline void avg_sse(float *d, const float *s0, const float *s1, uint32_t n_samples) +{ + uint32_t n, unrolled; + __m128 half = _mm_set1_ps(0.5f); + + if (SPA_IS_ALIGNED(d, 16) && + SPA_IS_ALIGNED(s0, 16) && + SPA_IS_ALIGNED(s1, 16)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for (n = 0; n < unrolled; n += 8) { + _mm_store_ps(&d[n + 0], + _mm_mul_ps( + _mm_add_ps( + _mm_load_ps(&s0[n + 0]), + _mm_load_ps(&s1[n + 0])), + half)); + _mm_store_ps(&d[n + 4], + _mm_mul_ps( + _mm_add_ps( + _mm_load_ps(&s0[n + 4]), + _mm_load_ps(&s1[n + 4])), + half)); + } + + for (; n < n_samples; n++) + _mm_store_ss(&d[n], + _mm_mul_ss( + _mm_add_ss( + _mm_load_ss(&s0[n]), + _mm_load_ss(&s1[n])), + half)); +} + +static inline void sub_sse(float *d, const float *s0, const float *s1, uint32_t n_samples) +{ + uint32_t n, unrolled; + + if (SPA_IS_ALIGNED(d, 16) && + SPA_IS_ALIGNED(s0, 16) && + SPA_IS_ALIGNED(s1, 16)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for (n = 0; n < unrolled; n += 8) { + _mm_store_ps(&d[n + 0], + _mm_sub_ps(_mm_load_ps(&s0[n + 0]), _mm_load_ps(&s1[n + 0]))); + _mm_store_ps(&d[n + 4], + _mm_sub_ps(_mm_load_ps(&s0[n + 4]), _mm_load_ps(&s1[n + 4]))); + } + for (; n < n_samples; n++) + _mm_store_ss(&d[n], + _mm_sub_ss(_mm_load_ss(&s0[n]), _mm_load_ss(&s1[n]))); +} + void channelmix_copy_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { @@ -78,6 +169,167 @@ void channelmix_copy_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], vol_sse(d[i], s[i], mix->matrix[i][i], n_samples); } +void +channelmix_f32_n_m_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + float **d = (float **) dst; + const float **s = (const float **) src; + uint32_t i, j, n_dst = mix->dst_chan, n_src = mix->src_chan; + + for (i = 0; i < n_dst; i++) { + float *di = d[i]; + float mj[n_src]; + const float *sj[n_src]; + uint32_t n_j = 0; + + for (j = 0; j < n_src; j++) { + if (mix->matrix[i][j] == 0.0f) + continue; + mj[n_j] = mix->matrix[i][j]; + sj[n_j++] = s[j]; + } + if (n_j == 0) { + clear_sse(di, n_samples); + } else if (n_j == 1) { + if (mix->lr4[i].active) + lr4_process(&mix->lr4[i], di, sj[0], mj[0], n_samples); + else + vol_sse(di, sj[0], mj[0], n_samples); + } else { + conv_sse(di, sj, mj, n_j, n_samples); + lr4_process(&mix->lr4[i], di, di, 1.0f, n_samples); + } + } +} + +void +channelmix_f32_2_3p1_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, n, unrolled, n_dst = mix->dst_chan; + float **d = (float **)dst; + const float **s = (const float **)src; + const float v0 = mix->matrix[0][0]; + const float v1 = mix->matrix[1][1]; + const float v2 = (mix->matrix[2][0] + mix->matrix[2][1]) * 0.5f; + const float v3 = (mix->matrix[3][0] + mix->matrix[3][1]) * 0.5f; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + for (i = 0; i < n_dst; i++) + clear_sse(d[i], n_samples); + } + else { + if (mix->widen == 0.0f) { + vol_sse(d[0], s[0], v0, n_samples); + vol_sse(d[1], s[1], v1, n_samples); + avg_sse(d[2], s[0], s[1], n_samples); + } else { + const __m128 mv0 = _mm_set1_ps(mix->matrix[0][0]); + const __m128 mv1 = _mm_set1_ps(mix->matrix[1][1]); + const __m128 mw = _mm_set1_ps(mix->widen); + const __m128 mh = _mm_set1_ps(0.5f); + __m128 t0[1], t1[1], w[1], c[1]; + + if (SPA_IS_ALIGNED(s[0], 16) && + SPA_IS_ALIGNED(s[1], 16) && + SPA_IS_ALIGNED(d[0], 16) && + SPA_IS_ALIGNED(d[1], 16) && + SPA_IS_ALIGNED(d[2], 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + t0[0] = _mm_load_ps(&s[0][n]); + t1[0] = _mm_load_ps(&s[1][n]); + c[0] = _mm_add_ps(t0[0], t1[0]); + w[0] = _mm_mul_ps(c[0], mw); + _mm_store_ps(&d[0][n], _mm_mul_ps(_mm_sub_ps(t0[0], w[0]), mv0)); + _mm_store_ps(&d[1][n], _mm_mul_ps(_mm_sub_ps(t1[0], w[0]), mv1)); + _mm_store_ps(&d[2][n], _mm_mul_ps(c[0], mh)); + } + for (; n < n_samples; n++) { + t0[0] = _mm_load_ss(&s[0][n]); + t1[0] = _mm_load_ss(&s[1][n]); + c[0] = _mm_add_ss(t0[0], t1[0]); + w[0] = _mm_mul_ss(c[0], mw); + _mm_store_ss(&d[0][n], _mm_mul_ss(_mm_sub_ss(t0[0], w[0]), mv0)); + _mm_store_ss(&d[1][n], _mm_mul_ss(_mm_sub_ss(t1[0], w[0]), mv1)); + _mm_store_ss(&d[2][n], _mm_mul_ss(c[0], mh)); + } + } + lr4_process(&mix->lr4[3], d[3], d[2], v3, n_samples); + lr4_process(&mix->lr4[2], d[2], d[2], v2, n_samples); + } +} + +void +channelmix_f32_2_5p1_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, n_dst = mix->dst_chan; + float **d = (float **)dst; + const float **s = (const float **)src; + const float v4 = mix->matrix[4][0]; + const float v5 = mix->matrix[5][1]; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + for (i = 0; i < n_dst; i++) + clear_sse(d[i], n_samples); + } + else { + channelmix_f32_2_3p1_sse(mix, dst, src, n_samples); + + if (mix->upmix != CHANNELMIX_UPMIX_PSD) { + vol_sse(d[4], s[0], v4, n_samples); + vol_sse(d[5], s[1], v5, n_samples); + } else { + sub_sse(d[4], s[0], s[1], n_samples); + + delay_convolve_run(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, + mix->taps, mix->n_taps, d[5], d[4], -v5, n_samples); + delay_convolve_run(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, + mix->taps, mix->n_taps, d[4], d[4], v4, n_samples); + } + } +} + +void +channelmix_f32_2_7p1_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, n_dst = mix->dst_chan; + float **d = (float **)dst; + const float **s = (const float **)src; + const float v4 = mix->matrix[4][0]; + const float v5 = mix->matrix[5][1]; + const float v6 = mix->matrix[6][0]; + const float v7 = mix->matrix[7][1]; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + for (i = 0; i < n_dst; i++) + clear_sse(d[i], n_samples); + } + else { + channelmix_f32_2_3p1_sse(mix, dst, src, n_samples); + + vol_sse(d[4], s[0], v4, n_samples); + vol_sse(d[5], s[1], v5, n_samples); + + if (mix->upmix != CHANNELMIX_UPMIX_PSD) { + vol_sse(d[6], s[0], v6, n_samples); + vol_sse(d[7], s[1], v7, n_samples); + } else { + sub_sse(d[6], s[0], s[1], n_samples); + + delay_convolve_run(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, + mix->taps, mix->n_taps, d[7], d[6], -v7, n_samples); + delay_convolve_run(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, + mix->taps, mix->n_taps, d[6], d[6], v6, n_samples); + } + } +} /* FL+FR+FC+LFE -> FL+FR */ void channelmix_f32_3p1_2_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], diff --git a/spa/plugins/audioconvert/channelmix-ops.c b/spa/plugins/audioconvert/channelmix-ops.c index 56faa7ea6cadeda7748873e4ec770fcdf6ac33e9..cfc449f0b0d739d0292ef6d68229c68e98817aa4 100644 --- a/spa/plugins/audioconvert/channelmix-ops.c +++ b/spa/plugins/audioconvert/channelmix-ops.c @@ -30,6 +30,7 @@ #include <spa/support/cpu.h> #include <spa/support/log.h> #include <spa/utils/defs.h> +#include <spa/debug/types.h> #include "channelmix-ops.h" #include "hilbert.h" @@ -69,8 +70,17 @@ static const struct channelmix_info { MAKE(4, MASK_QUAD, 1, MASK_MONO, channelmix_f32_4_1_c), MAKE(4, MASK_3_1, 1, MASK_MONO, channelmix_f32_4_1_c), MAKE(2, MASK_STEREO, 4, MASK_QUAD, channelmix_f32_2_4_c), +#if defined (HAVE_SSE) + MAKE(2, MASK_STEREO, 4, MASK_3_1, channelmix_f32_2_3p1_sse, SPA_CPU_FLAG_SSE), +#endif MAKE(2, MASK_STEREO, 4, MASK_3_1, channelmix_f32_2_3p1_c), +#if defined (HAVE_SSE) + MAKE(2, MASK_STEREO, 6, MASK_5_1, channelmix_f32_2_5p1_sse, SPA_CPU_FLAG_SSE), +#endif MAKE(2, MASK_STEREO, 6, MASK_5_1, channelmix_f32_2_5p1_c), +#if defined (HAVE_SSE) + MAKE(2, MASK_STEREO, 8, MASK_7_1, channelmix_f32_2_7p1_sse, SPA_CPU_FLAG_SSE), +#endif MAKE(2, MASK_STEREO, 8, MASK_7_1, channelmix_f32_2_7p1_c), #if defined (HAVE_SSE) MAKE(4, MASK_3_1, 2, MASK_STEREO, channelmix_f32_3p1_2_sse, SPA_CPU_FLAG_SSE), @@ -94,6 +104,9 @@ static const struct channelmix_info { MAKE(8, MASK_7_1, 4, MASK_QUAD, channelmix_f32_7p1_4_c), MAKE(8, MASK_7_1, 4, MASK_3_1, channelmix_f32_7p1_3p1_c), +#if defined (HAVE_SSE) + MAKE(ANY, 0, ANY, 0, channelmix_f32_n_m_sse, SPA_CPU_FLAG_SSE), +#endif MAKE(ANY, 0, ANY, 0, channelmix_f32_n_m_c), }; #undef MAKE @@ -105,19 +118,18 @@ static const struct channelmix_info { static const struct channelmix_info *find_channelmix_info(uint32_t src_chan, uint64_t src_mask, uint32_t dst_chan, uint64_t dst_mask, uint32_t cpu_flags) { - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(channelmix_table); i++) { - if (!MATCH_CPU_FLAGS(channelmix_table[i].cpu_flags, cpu_flags)) + SPA_FOR_EACH_ELEMENT_VAR(channelmix_table, info) { + if (!MATCH_CPU_FLAGS(info->cpu_flags, cpu_flags)) continue; if (src_chan == dst_chan && src_mask == dst_mask) - return &channelmix_table[i]; + return info; - if (MATCH_CHAN(channelmix_table[i].src_chan, src_chan) && - MATCH_CHAN(channelmix_table[i].dst_chan, dst_chan) && - MATCH_MASK(channelmix_table[i].src_mask, src_mask) && - MATCH_MASK(channelmix_table[i].dst_mask, dst_mask)) - return &channelmix_table[i]; + if (MATCH_CHAN(info->src_chan, src_chan) && + MATCH_CHAN(info->dst_chan, dst_chan) && + MATCH_MASK(info->src_mask, src_mask) && + MATCH_MASK(info->dst_mask, dst_mask)) + return info; } return NULL; } @@ -142,6 +154,8 @@ static int make_matrix(struct channelmix *mix) float matrix[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS] = {{ 0.0f }}; uint64_t src_mask = mix->src_mask; uint64_t dst_mask = mix->dst_mask; + uint32_t src_chan = mix->src_chan; + uint32_t dst_chan = mix->dst_chan; uint64_t unassigned, keep; uint32_t i, j, ic, jc, matrix_encoding = MATRIX_NORMAL; float clev = SQRT1_2; @@ -157,7 +171,7 @@ static int make_matrix(struct channelmix *mix) /* move the MONO mask to FRONT so that the lower bits can be shifted * away. */ if ((src_mask & (1Ull << SPA_AUDIO_CHANNEL_MONO)) != 0) { - if (mix->src_chan == 1) + if (src_chan == 1) src_mask = 0; else src_mask |= (1ULL << SPA_AUDIO_CHANNEL_FC); @@ -171,19 +185,19 @@ static int make_matrix(struct channelmix *mix) /* unknown channels or just 1 channel */ if (src_mask == 0 || dst_mask == 0) { - if (mix->src_chan == 1) { + if (src_chan == 1) { /* one FC/MONO src goes everywhere */ - spa_log_debug(mix->log, "distribute FC/MONO"); + spa_log_debug(mix->log, "distribute FC/MONO (%f)", 1.0f); for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) matrix[i][0]= 1.0f; - } else if (mix->dst_chan == 1) { + } else if (dst_chan == 1) { /* one FC/MONO dst get average of everything */ - spa_log_debug(mix->log, "average FC/MONO"); + spa_log_debug(mix->log, "average FC/MONO (%f)", 1.0f / src_chan); for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) - matrix[0][i]= 1.0f / mix->src_chan; + matrix[0][i]= 1.0f / src_chan; } else { /* just pair channels */ - spa_log_debug(mix->log, "pairing channels"); + spa_log_debug(mix->log, "pairing channels (%f)", 1.0f); for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) matrix[i][i]= 1.0f; } @@ -197,7 +211,7 @@ static int make_matrix(struct channelmix *mix) spa_log_debug(mix->log, "matching channels"); for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) { if ((src_mask & dst_mask & (1ULL << i))) { - spa_log_debug(mix->log, "matched %u", i); + spa_log_debug(mix->log, "matched channel %u (%f)", i, 1.0f); matrix[i][i]= 1.0f; } } @@ -222,11 +236,12 @@ static int make_matrix(struct channelmix *mix) if (unassigned & FRONT){ if ((dst_mask & STEREO) == STEREO){ - spa_log_debug(mix->log, "assign FC to STEREO"); if(src_mask & STEREO) { + spa_log_debug(mix->log, "assign FC to STEREO (%f)", clev); _MATRIX(FL,FC) += clev; _MATRIX(FR,FC) += clev; } else { + spa_log_debug(mix->log, "assign FC to STEREO (%f)", SQRT1_2); _MATRIX(FL,FC) += SQRT1_2; _MATRIX(FR,FC) += SQRT1_2; } @@ -237,11 +252,13 @@ static int make_matrix(struct channelmix *mix) if (unassigned & STEREO){ if (dst_mask & FRONT) { - spa_log_debug(mix->log, "assign STEREO to FC"); + spa_log_debug(mix->log, "assign STEREO to FC (%f)", SQRT1_2); _MATRIX(FC,FL) += SQRT1_2; _MATRIX(FC,FR) += SQRT1_2; - if (src_mask & FRONT) + if (src_mask & FRONT) { + spa_log_debug(mix->log, "assign FC to FC (%f)", clev * SQRT2); _MATRIX(FC,FC) = clev * SQRT2; + } keep &= ~FRONT; } else { spa_log_warn(mix->log, "can't assign STEREO"); @@ -250,11 +267,11 @@ static int make_matrix(struct channelmix *mix) if (unassigned & _MASK(RC)) { if (dst_mask & REAR){ - spa_log_debug(mix->log, "assign RC to RL+RR"); + spa_log_debug(mix->log, "assign RC to RL+RR (%f)", SQRT1_2); _MATRIX(RL,RC) += SQRT1_2; _MATRIX(RR,RC) += SQRT1_2; } else if (dst_mask & SIDE) { - spa_log_debug(mix->log, "assign RC to SL+SR"); + spa_log_debug(mix->log, "assign RC to SL+SR (%f)", SQRT1_2); _MATRIX(SL,RC) += SQRT1_2; _MATRIX(SR,RC) += SQRT1_2; } else if(dst_mask & STEREO) { @@ -273,7 +290,7 @@ static int make_matrix(struct channelmix *mix) _MATRIX(FR,RC) += slev * SQRT1_2; } } else if (dst_mask & FRONT) { - spa_log_debug(mix->log, "assign RC to FC"); + spa_log_debug(mix->log, "assign RC to FC (%f)", slev * SQRT1_2); _MATRIX(FC,RC) += slev * SQRT1_2; } else { spa_log_warn(mix->log, "can't assign RC"); @@ -294,8 +311,9 @@ static int make_matrix(struct channelmix *mix) _MATRIX(SL,RL) += 1.0f; _MATRIX(SR,RR) += 1.0f; } + keep &= ~SIDE; } else if (dst_mask & STEREO) { - spa_log_debug(mix->log, "assign RL+RR to FL+FR %f", slev); + spa_log_debug(mix->log, "assign RL+RR to FL+FR (%f)", slev); if (matrix_encoding == MATRIX_DOLBY) { _MATRIX(FL,RL) -= slev * SQRT1_2; _MATRIX(FL,RR) -= slev * SQRT1_2; @@ -311,7 +329,8 @@ static int make_matrix(struct channelmix *mix) _MATRIX(FR,RR) += slev; } } else if (dst_mask & FRONT) { - spa_log_debug(mix->log, "assign RL+RR to FC"); + spa_log_debug(mix->log, "assign RL+RR to FC (%f)", + slev * SQRT1_2); _MATRIX(FC,RL)+= slev * SQRT1_2; _MATRIX(FC,RR)+= slev * SQRT1_2; } else { @@ -321,36 +340,42 @@ static int make_matrix(struct channelmix *mix) if (unassigned & SIDE) { if (dst_mask & REAR) { - spa_log_debug(mix->log, "assign SL+SR to RL+RR"); if (src_mask & _MASK(RL)) { + spa_log_debug(mix->log, "assign SL+SR to RL+RR (%f)", SQRT1_2); _MATRIX(RL,SL) += SQRT1_2; _MATRIX(RR,SR) += SQRT1_2; } else { + spa_log_debug(mix->log, "assign SL+SR to RL+RR (%f)", 1.0f); _MATRIX(RL,SL) += 1.0f; _MATRIX(RR,SR) += 1.0f; } + keep &= ~REAR; } else if (dst_mask & _MASK(RC)) { - spa_log_debug(mix->log, "assign SL+SR to RC"); + spa_log_debug(mix->log, "assign SL+SR to RC (%f)", SQRT1_2); _MATRIX(RC,SL)+= SQRT1_2; _MATRIX(RC,SR)+= SQRT1_2; } else if (dst_mask & STEREO) { - spa_log_debug(mix->log, "assign SL+SR to FL+FR"); if (matrix_encoding == MATRIX_DOLBY) { + spa_log_debug(mix->log, "assign SL+SR to FL+FR (%f)", + slev * SQRT1_2); _MATRIX(FL,SL) -= slev * SQRT1_2; _MATRIX(FL,SR) -= slev * SQRT1_2; _MATRIX(FR,SL) += slev * SQRT1_2; _MATRIX(FR,SR) += slev * SQRT1_2; } else if (matrix_encoding == MATRIX_DPLII) { + spa_log_debug(mix->log, "assign SL+SR to FL+FR (%f / %f)", + slev * SQRT3_2, slev * SQRT1_2); _MATRIX(FL,SL) -= slev * SQRT3_2; _MATRIX(FL,SR) -= slev * SQRT1_2; _MATRIX(FR,SL) += slev * SQRT1_2; _MATRIX(FR,SR) += slev * SQRT3_2; } else { + spa_log_debug(mix->log, "assign SL+SR to FL+FR (%f)", slev); _MATRIX(FL,SL) += slev; _MATRIX(FR,SR) += slev; } } else if (dst_mask & FRONT) { - spa_log_debug(mix->log, "assign SL+SR to FC"); + spa_log_debug(mix->log, "assign SL+SR to FC (%f)", slev * SQRT1_2); _MATRIX(FC,SL) += slev * SQRT1_2; _MATRIX(FC,SR) += slev * SQRT1_2; } else { @@ -360,11 +385,11 @@ static int make_matrix(struct channelmix *mix) if (unassigned & _MASK(FLC)) { if (dst_mask & STEREO) { - spa_log_debug(mix->log, "assign FLC+FRC to FL+FR"); + spa_log_debug(mix->log, "assign FLC+FRC to FL+FR (%f)", 1.0f); _MATRIX(FL,FLC)+= 1.0f; _MATRIX(FR,FRC)+= 1.0f; } else if(dst_mask & FRONT) { - spa_log_debug(mix->log, "assign FLC+FRC to FC"); + spa_log_debug(mix->log, "assign FLC+FRC to FC (%f)", SQRT1_2); _MATRIX(FC,FLC)+= SQRT1_2; _MATRIX(FC,FRC)+= SQRT1_2; } else { @@ -374,10 +399,11 @@ static int make_matrix(struct channelmix *mix) if (unassigned & _MASK(LFE) && SPA_FLAG_IS_SET(mix->options, CHANNELMIX_OPTION_MIX_LFE)) { if (dst_mask & FRONT) { - spa_log_debug(mix->log, "assign LFE to FC"); + spa_log_debug(mix->log, "assign LFE to FC (%f)", llev); _MATRIX(FC,LFE) += llev; } else if (dst_mask & STEREO) { - spa_log_debug(mix->log, "assign LFE to FL+FR"); + spa_log_debug(mix->log, "assign LFE to FL+FR (%f)", + llev * SQRT1_2); _MATRIX(FL,LFE) += llev * SQRT1_2; _MATRIX(FR,LFE) += llev * SQRT1_2; } else { @@ -392,7 +418,7 @@ static int make_matrix(struct channelmix *mix) if (unassigned & STEREO) { if ((src_mask & FRONT) == FRONT) { - spa_log_debug(mix->log, "produce STEREO from FC"); + spa_log_debug(mix->log, "produce STEREO from FC (%f)", clev); _MATRIX(FL,FC) += clev; _MATRIX(FR,FC) += clev; } else { @@ -401,7 +427,7 @@ static int make_matrix(struct channelmix *mix) } if (unassigned & FRONT) { if ((src_mask & STEREO) == STEREO) { - spa_log_debug(mix->log, "produce FC from STEREO"); + spa_log_debug(mix->log, "produce FC from STEREO (%f)", clev); _MATRIX(FC,FL) += clev; _MATRIX(FC,FR) += clev; filter_fc = true; @@ -411,12 +437,12 @@ static int make_matrix(struct channelmix *mix) } if (unassigned & _MASK(LFE)) { if ((src_mask & STEREO) == STEREO) { - spa_log_debug(mix->log, "produce LFE from STEREO"); + spa_log_debug(mix->log, "produce LFE from STEREO (%f)", llev); _MATRIX(LFE,FL) += llev; _MATRIX(LFE,FR) += llev; filter_lfe = true; } else if ((src_mask & FRONT) == FRONT) { - spa_log_debug(mix->log, "produce LFE from FC"); + spa_log_debug(mix->log, "produce LFE from FC (%f)", llev); _MATRIX(LFE,FC) += llev; filter_lfe = true; } else { @@ -425,16 +451,16 @@ static int make_matrix(struct channelmix *mix) } if (unassigned & SIDE) { if ((src_mask & REAR) == REAR) { - spa_log_debug(mix->log, "produce SIDE from REAR"); + spa_log_debug(mix->log, "produce SIDE from REAR (%f)", 1.0f); _MATRIX(SL,RL) += 1.0f; _MATRIX(SR,RR) += 1.0f; } else if ((src_mask & STEREO) == STEREO) { - spa_log_debug(mix->log, "produce SIDE from STEREO"); + spa_log_debug(mix->log, "produce SIDE from STEREO (%f)", slev); _MATRIX(SL,FL) += slev; _MATRIX(SR,FR) += slev; } else if ((src_mask & FRONT) == FRONT && mix->upmix == CHANNELMIX_UPMIX_SIMPLE) { - spa_log_debug(mix->log, "produce SIDE from FC"); + spa_log_debug(mix->log, "produce SIDE from FC (%f)", clev); _MATRIX(SL,FC) += clev; _MATRIX(SR,FC) += clev; } else { @@ -443,34 +469,77 @@ static int make_matrix(struct channelmix *mix) } if (unassigned & REAR) { if ((src_mask & SIDE) == SIDE) { - spa_log_debug(mix->log, "produce REAR from SIDE"); + spa_log_debug(mix->log, "produce REAR from SIDE (%f)", 1.0f); _MATRIX(RL,SL) += 1.0f; _MATRIX(RR,SR) += 1.0f; } else if ((src_mask & STEREO) == STEREO) { - spa_log_debug(mix->log, "produce REAR from STEREO"); + spa_log_debug(mix->log, "produce REAR from STEREO (%f)", slev); _MATRIX(RL,FL) += slev; _MATRIX(RR,FR) += slev; } else if ((src_mask & FRONT) == FRONT && mix->upmix == CHANNELMIX_UPMIX_SIMPLE) { - spa_log_debug(mix->log, "produce REAR from FC"); + spa_log_debug(mix->log, "produce REAR from FC (%f)", clev); _MATRIX(RL,FC) += clev; _MATRIX(RR,FC) += clev; } else { spa_log_debug(mix->log, "won't produce SIDE"); } } + if (unassigned & _MASK(RC)) { + if ((src_mask & REAR) == REAR) { + spa_log_debug(mix->log, "produce RC from REAR (%f)", 0.5f); + _MATRIX(RC,RL) += 0.5f; + _MATRIX(RC,RR) += 0.5f; + } else if ((src_mask & SIDE) == SIDE) { + spa_log_debug(mix->log, "produce RC from SIDE (%f)", 0.5f); + _MATRIX(RC,SL) += 0.5f; + _MATRIX(RC,SR) += 0.5f; + } else if ((src_mask & STEREO) == STEREO) { + spa_log_debug(mix->log, "produce RC from STEREO (%f)", 0.5f); + _MATRIX(RC,FL) += 0.5f; + _MATRIX(RC,FR) += 0.5f; + } else if ((src_mask & FRONT) == FRONT && + mix->upmix == CHANNELMIX_UPMIX_SIMPLE) { + spa_log_debug(mix->log, "produce RC from FC (%f)", slev); + _MATRIX(RC,FC) += slev; + } else { + spa_log_debug(mix->log, "won't produce RC"); + } + } done: for (jc = 0, ic = 0, i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) { float sum = 0.0f; + char str[1024], str2[1024]; + int idx = 0, idx2 = 0; if ((dst_mask & (1UL << i)) == 0) continue; for (jc = 0, j = 0; j < SPA_AUDIO_MAX_CHANNELS; j++) { if ((src_mask & (1UL << j)) == 0) continue; + if (ic >= dst_chan || jc >= src_chan) + continue; + + if (i == 0) + idx2 += snprintf(str2 + idx2, sizeof(str2) - idx2, "%-4.4s ", + spa_debug_type_find_short_name(spa_type_audio_channel, j + 3)); + mix->matrix_orig[ic][jc++] = matrix[i][j]; sum += fabs(matrix[i][j]); + + if (matrix[i][j] == 0.0f) + idx += snprintf(str + idx, sizeof(str) - idx, " "); + else + idx += snprintf(str + idx, sizeof(str) - idx, "%1.3f ", matrix[i][j]); } + if (dst_mask != 0 && src_mask != 0 && sum > 0.0f) { + if (i == 0) + spa_log_info(mix->log, " %s", str2); + spa_log_info(mix->log, "%-4.4s %s %f", + spa_debug_type_find_short_name(spa_type_audio_channel, i + 3), + str, sum); + } + maxsum = SPA_MAX(maxsum, sum); if (i == _CH(LFE) && mix->lfe_cutoff > 0.0f && filter_lfe) { spa_log_info(mix->log, "channel %d is LFE cutoff:%f", ic, mix->lfe_cutoff); @@ -486,11 +555,10 @@ done: if (SPA_FLAG_IS_SET(mix->options, CHANNELMIX_OPTION_NORMALIZE) && maxsum > 1.0f) { spa_log_debug(mix->log, "normalize %f", maxsum); - for (i = 0; i < ic; i++) - for (j = 0; j < jc; j++) + for (i = 0; i < dst_chan; i++) + for (j = 0; j < src_chan; j++) mix->matrix_orig[i][j] /= maxsum; } - return 0; } @@ -524,6 +592,12 @@ static void impl_channelmix_set_volume(struct channelmix *mix, float volume, boo mix->matrix[i][j] = mix->matrix_orig[i][j] * volumes[i]; } } + } else if (n_channel_volumes == 0) { + for (i = 0; i < dst_chan; i++) { + for (j = 0; j < src_chan; j++) { + mix->matrix[i][j] = mix->matrix_orig[i][j] * vol; + } + } } SPA_FLAG_SET(mix->flags, CHANNELMIX_FLAG_ZERO); diff --git a/spa/plugins/audioconvert/channelmix-ops.h b/spa/plugins/audioconvert/channelmix-ops.h index 89737c315fc090a5d003080cfc9bd49bc98c9296..c134a9677994462730222b7b8f23ca5697b65326 100644 --- a/spa/plugins/audioconvert/channelmix-ops.h +++ b/spa/plugins/audioconvert/channelmix-ops.h @@ -109,10 +109,9 @@ static const struct channelmix_upmix_info { static inline uint32_t channelmix_upmix_from_label(const char *label) { - uint32_t i; - for (i = 0; i < SPA_N_ELEMENTS(channelmix_upmix_info); i++) { - if (spa_streq(channelmix_upmix_info[i].label, label)) - return channelmix_upmix_info[i].upmix; + SPA_FOR_EACH_ELEMENT_VAR(channelmix_upmix_info, i) { + if (spa_streq(i->label, label)) + return i->upmix; } return CHANNELMIX_UPMIX_NONE; } @@ -147,6 +146,10 @@ DEFINE_FUNCTION(f32_7p1_4, c); #if defined (HAVE_SSE) DEFINE_FUNCTION(copy, sse); +DEFINE_FUNCTION(f32_n_m, sse); +DEFINE_FUNCTION(f32_2_3p1, sse); +DEFINE_FUNCTION(f32_2_5p1, sse); +DEFINE_FUNCTION(f32_2_7p1, sse); DEFINE_FUNCTION(f32_3p1_2, sse); DEFINE_FUNCTION(f32_5p1_2, sse); DEFINE_FUNCTION(f32_5p1_3p1, sse); diff --git a/spa/plugins/audioconvert/fmt-ops-c.c b/spa/plugins/audioconvert/fmt-ops-c.c index f3d91d0356544404020dc902edc2ad6358f4e9cd..92ecb5a0e116c340eae041d2d3dd5d87fdb29a81 100644 --- a/spa/plugins/audioconvert/fmt-ops-c.c +++ b/spa/plugins/audioconvert/fmt-ops-c.c @@ -230,38 +230,57 @@ lcnoise(uint32_t *state) return (int32_t)(*state); } -static inline void update_noise_c(struct convert *conv, uint32_t n_samples) +void conv_noise_none_c(struct convert *conv, float *noise, uint32_t n_samples) +{ + memset(noise, 0, n_samples * sizeof(float)); +} + +void conv_noise_rect_c(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + uint32_t *state = &conv->random[0]; + const float scale = conv->scale; + + for (n = 0; n < n_samples; n++) + noise[n] = lcnoise(state) * scale; +} + +void conv_noise_tri_c(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + const float scale = conv->scale; + uint32_t *state = &conv->random[0]; + + for (n = 0; n < n_samples; n++) + noise[n] = (lcnoise(state) - lcnoise(state)) * scale; +} + +void conv_noise_tri_hf_c(struct convert *conv, float *noise, uint32_t n_samples) { uint32_t n; - float *noise = conv->noise, scale = conv->scale; + const float scale = conv->scale; uint32_t *state = &conv->random[0]; int32_t *prev = &conv->prev[0], old, new; - switch (conv->noise_method) { - case NOISE_METHOD_RECTANGULAR: - for (n = 0; n < n_samples; n++) - noise[n] = lcnoise(state) * scale; - break; - case NOISE_METHOD_TRIANGULAR: - for (n = 0; n < n_samples; n++) - noise[n] = (lcnoise(state) - lcnoise(state)) * scale; - break; - case NOISE_METHOD_TRIANGULAR_HF: - old = *prev; - for (n = 0; n < n_samples; n++) { - new = lcnoise(state); - noise[n] = (new - old) * scale; - old = new; - } - *prev = old; - break; - case NOISE_METHOD_PATTERN: - old = *prev; - for (n = 0; n < n_samples; n++) - noise[n] = conv->scale * (1-((old++>>10)&1)); - *prev = old; - break; + old = *prev; + for (n = 0; n < n_samples; n++) { + new = lcnoise(state); + noise[n] = (new - old) * scale; + old = new; } + *prev = old; +} + +void conv_noise_pattern_c(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + const float scale = conv->scale; + int32_t *prev = &conv->prev[0], old; + + old = *prev; + for (n = 0; n < n_samples; n++) + noise[n] = scale * (1-((old++>>10)&1)); + *prev = old; } #define MAKE_D_noise(dname,dtype,func) \ @@ -271,7 +290,7 @@ void conv_f32d_to_ ##dname## d_noise_c(struct convert *conv, \ { \ uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ float *noise = conv->noise; \ - update_noise_c(conv, SPA_MIN(n_samples, noise_size)); \ + convert_update_noise(conv, noise, SPA_MIN(n_samples, noise_size)); \ for (i = 0; i < n_channels; i++) { \ const float *s = src[i]; \ dtype *d = dst[i]; \ @@ -292,7 +311,7 @@ void conv_f32d_to_ ##dname## _noise_c(struct convert *conv, \ dtype *d = dst[0]; \ uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ float *noise = conv->noise; \ - update_noise_c(conv, SPA_MIN(n_samples, noise_size)); \ + convert_update_noise(conv, noise, SPA_MIN(n_samples, noise_size)); \ for (j = 0; j < n_samples;) { \ chunk = SPA_MIN(n_samples - j, noise_size); \ for (k = 0; k < chunk; k++, j++) { \ @@ -342,9 +361,10 @@ void conv_f32d_to_ ##dname## d_shaped_c(struct convert *conv, \ uint32_t n_samples) \ { \ uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ - const float *noise = conv->noise, *ns = conv->ns; \ + float *noise = conv->noise; \ + const float *ns = conv->ns; \ uint32_t n, n_ns = conv->n_ns; \ - update_noise_c(conv, SPA_MIN(n_samples, noise_size)); \ + convert_update_noise(conv, noise, SPA_MIN(n_samples, noise_size)); \ for (i = 0; i < n_channels; i++) { \ const float *s = src[i]; \ dtype *d = dst[i]; \ @@ -366,9 +386,10 @@ void conv_f32d_to_ ##dname## _shaped_c(struct convert *conv, \ { \ dtype *d0 = dst[0]; \ uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ - const float *noise = conv->noise, *ns = conv->ns; \ + float *noise = conv->noise; \ + const float *ns = conv->ns; \ uint32_t n, n_ns = conv->n_ns; \ - update_noise_c(conv, SPA_MIN(n_samples, noise_size)); \ + convert_update_noise(conv, noise, SPA_MIN(n_samples, noise_size)); \ for (i = 0; i < n_channels; i++) { \ const float *s = src[i]; \ dtype *d = &d0[i]; \ diff --git a/spa/plugins/audioconvert/fmt-ops-sse2.c b/spa/plugins/audioconvert/fmt-ops-sse2.c index 917bfa0dc80c0a36e68d0b9b5179913d846c5ff4..4e2fce72ffa30f4511890f04e6f50e8cfbc5cd29 100644 --- a/spa/plugins/audioconvert/fmt-ops-sse2.c +++ b/spa/plugins/audioconvert/fmt-ops-sse2.c @@ -576,61 +576,64 @@ conv_f32d_to_s32_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const voi i; \ }) +void conv_noise_rect_sse2(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + const uint32_t *r = conv->random; + __m128 scale = _mm_set1_ps(conv->scale); + __m128i in[1]; + __m128 out[1]; + + for (n = 0; n < n_samples; n += 4) { + in[0] = _MM_XORSHIFT_EPI32(r); + out[0] = _mm_cvtepi32_ps(in[0]); + out[0] = _mm_mul_ps(out[0], scale); + _mm_store_ps(&noise[n], out[0]); + } +} -static inline void update_noise_sse2(struct convert *conv, uint32_t n_samples) +void conv_noise_tri_sse2(struct convert *conv, float *noise, uint32_t n_samples) { uint32_t n; - const uint32_t *r = SPA_PTR_ALIGN(conv->random, 16, uint32_t); - int32_t *p = SPA_PTR_ALIGN(conv->prev, 16, int32_t), op; + const uint32_t *r = conv->random; __m128 scale = _mm_set1_ps(conv->scale); + __m128i in[1]; __m128 out[1]; - float *noise = SPA_PTR_ALIGN(conv->noise, 16, float); + + for (n = 0; n < n_samples; n += 4) { + in[0] = _mm_sub_epi32( _MM_XORSHIFT_EPI32(r), _MM_XORSHIFT_EPI32(r)); + out[0] = _mm_cvtepi32_ps(in[0]); + out[0] = _mm_mul_ps(out[0], scale); + _mm_store_ps(&noise[n], out[0]); + } +} + +void conv_noise_tri_hf_sse2(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + int32_t *p = conv->prev; + const uint32_t *r = conv->random; + __m128 scale = _mm_set1_ps(conv->scale); __m128i in[1], old[1], new[1]; + __m128 out[1]; - switch (conv->noise_method) { - case DITHER_METHOD_RECTANGULAR: - for (n = 0; n < n_samples; n += 4) { - in[0] = _MM_XORSHIFT_EPI32(r); - out[0] = _mm_cvtepi32_ps(_MM_XORSHIFT_EPI32(r)); - out[0] = _mm_mul_ps(out[0], scale); - _mm_store_ps(&noise[n], out[0]); - } - break; - case DITHER_METHOD_TRIANGULAR: - for (n = 0; n < n_samples; n += 4) { - in[0] = _mm_sub_epi32( _MM_XORSHIFT_EPI32(r), _MM_XORSHIFT_EPI32(r)); - out[0] = _mm_cvtepi32_ps(in[0]); - out[0] = _mm_mul_ps(out[0], scale); - _mm_store_ps(&noise[n], out[0]); - } - break; - case DITHER_METHOD_TRIANGULAR_HF: - old[0] = _mm_load_si128((__m128i*)p); - for (n = 0; n < n_samples; n += 4) { - new[0] = _MM_XORSHIFT_EPI32(r); - in[0] = _mm_sub_epi32(old[0], new[0]); - old[0] = new[0]; - out[0] = _mm_cvtepi32_ps(in[0]); - out[0] = _mm_mul_ps(out[0], scale); - _mm_store_ps(&noise[n], out[0]); - } - _mm_store_si128((__m128i*)p, old[0]); - break; - case NOISE_METHOD_PATTERN: - op = *p; - for (n = 0; n < n_samples; n++) - noise[n] = conv->scale * (1-((op++>>10)&1)); - *p = op; - break; + old[0] = _mm_load_si128((__m128i*)p); + for (n = 0; n < n_samples; n += 4) { + new[0] = _MM_XORSHIFT_EPI32(r); + in[0] = _mm_sub_epi32(old[0], new[0]); + old[0] = new[0]; + out[0] = _mm_cvtepi32_ps(in[0]); + out[0] = _mm_mul_ps(out[0], scale); + _mm_store_ps(&noise[n], out[0]); } + _mm_store_si128((__m128i*)p, old[0]); } static void conv_f32d_to_s32_1s_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, - uint32_t n_channels, uint32_t n_samples) + float *noise, uint32_t n_channels, uint32_t n_samples) { const float *s = src; - float *noise = SPA_PTR_ALIGN(conv->noise, 16, float); int32_t *d = dst; uint32_t n, unrolled; __m128 in[1]; @@ -676,14 +679,16 @@ conv_f32d_to_s32_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst[], con { int32_t *d = dst[0]; uint32_t i, k, chunk, n_channels = conv->n_channels; + float *noise = conv->noise; - update_noise_sse2(conv, SPA_MIN(n_samples, conv->noise_size)); + convert_update_noise(conv, noise, SPA_MIN(n_samples, conv->noise_size)); for(i = 0; i < n_channels; i++) { const float *s = src[i]; for(k = 0; k < n_samples; k += chunk) { chunk = SPA_MIN(n_samples - k, conv->noise_size); - conv_f32d_to_s32_1s_noise_sse2(conv, &d[i + k*n_channels], &s[k], n_channels, chunk); + conv_f32d_to_s32_1s_noise_sse2(conv, &d[i + k*n_channels], + &s[k], noise, n_channels, chunk); } } } @@ -1261,11 +1266,10 @@ conv_f32d_to_s16_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const voi static void conv_f32d_to_s16_1s_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, - uint32_t n_channels, uint32_t n_samples) + const float *noise, uint32_t n_channels, uint32_t n_samples) { const float *s0 = src; int16_t *d = dst; - float *noise = SPA_PTR_ALIGN(conv->noise, 16, float); uint32_t n, unrolled; __m128 in[2]; __m128i out[2]; @@ -1312,25 +1316,26 @@ conv_f32d_to_s16_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst[], con { int16_t *d = dst[0]; uint32_t i, k, chunk, n_channels = conv->n_channels; + float *noise = conv->noise; - update_noise_sse2(conv, SPA_MIN(n_samples, conv->noise_size)); + convert_update_noise(conv, noise, SPA_MIN(n_samples, conv->noise_size)); for(i = 0; i < n_channels; i++) { const float *s = src[i]; for(k = 0; k < n_samples; k += chunk) { chunk = SPA_MIN(n_samples - k, conv->noise_size); - conv_f32d_to_s16_1s_noise_sse2(conv, &d[i + k*n_channels], &s[k], n_channels, chunk); + conv_f32d_to_s16_1s_noise_sse2(conv, &d[i + k*n_channels], + &s[k], noise, n_channels, chunk); } } } static void conv_f32_to_s16_1_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, - uint32_t n_samples) + const float *noise, uint32_t n_samples) { const float *s = src; int16_t *d = dst; - float *noise = SPA_PTR_ALIGN(conv->noise, 16, float); uint32_t n, unrolled; __m128 in[2]; __m128i out[2]; @@ -1366,15 +1371,16 @@ conv_f32d_to_s16d_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst[], co uint32_t n_samples) { uint32_t i, k, chunk, n_channels = conv->n_channels; + float *noise = conv->noise; - update_noise_sse2(conv, SPA_MIN(n_samples, conv->noise_size)); + convert_update_noise(conv, noise, SPA_MIN(n_samples, conv->noise_size)); for(i = 0; i < n_channels; i++) { const float *s = src[i]; int16_t *d = dst[i]; for(k = 0; k < n_samples; k += chunk) { chunk = SPA_MIN(n_samples - k, conv->noise_size); - conv_f32_to_s16_1_noise_sse2(conv, &d[k], &s[k], chunk); + conv_f32_to_s16_1_noise_sse2(conv, &d[k], &s[k], noise, chunk); } } } diff --git a/spa/plugins/audioconvert/fmt-ops.c b/spa/plugins/audioconvert/fmt-ops.c index 443d59eec729b7b0ffb61ad2c554bddde9e1b918..4999a208d7fc3bd7d48cc11ee48864ade431f852 100644 --- a/spa/plugins/audioconvert/fmt-ops.c +++ b/spa/plugins/audioconvert/fmt-ops.c @@ -32,7 +32,8 @@ #include "fmt-ops.h" -#define DITHER_SIZE (1<<10) +#define NOISE_SIZE (1<<10) +#define RANDOM_SIZE (16) typedef void (*convert_func_t) (struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples); @@ -358,15 +359,53 @@ static struct conv_info conv_table[] = static const struct conv_info *find_conv_info(uint32_t src_fmt, uint32_t dst_fmt, uint32_t n_channels, uint32_t cpu_flags, uint32_t conv_flags) { - size_t i; - - for (i = 0; i < SPA_N_ELEMENTS(conv_table); i++) { - if (conv_table[i].src_fmt == src_fmt && - conv_table[i].dst_fmt == dst_fmt && - MATCH_CHAN(conv_table[i].n_channels, n_channels) && - MATCH_CPU_FLAGS(conv_table[i].cpu_flags, cpu_flags) && - MATCH_DITHER(conv_table[i].conv_flags, conv_flags)) - return &conv_table[i]; + SPA_FOR_EACH_ELEMENT_VAR(conv_table, c) { + if (c->src_fmt == src_fmt && + c->dst_fmt == dst_fmt && + MATCH_CHAN(c->n_channels, n_channels) && + MATCH_CPU_FLAGS(c->cpu_flags, cpu_flags) && + MATCH_DITHER(c->conv_flags, conv_flags)) + return c; + } + return NULL; +} + +typedef void (*noise_func_t) (struct convert *conv, float * noise, uint32_t n_samples); + +struct noise_info { + uint32_t method; + + noise_func_t noise; + const char *name; + + uint32_t cpu_flags; +}; + +#define MAKE(method,func,...) \ + { NOISE_METHOD_ ##method, func, #func , __VA_ARGS__ } + +static struct noise_info noise_table[] = +{ +#if defined (HAVE_SSE2) + MAKE(RECTANGULAR, conv_noise_rect_sse2, SPA_CPU_FLAG_SSE2), + MAKE(TRIANGULAR, conv_noise_tri_sse2, SPA_CPU_FLAG_SSE2), + MAKE(TRIANGULAR_HF, conv_noise_tri_hf_sse2, SPA_CPU_FLAG_SSE2), +#endif + MAKE(NONE, conv_noise_none_c), + MAKE(RECTANGULAR, conv_noise_rect_c), + MAKE(TRIANGULAR, conv_noise_tri_c), + MAKE(TRIANGULAR_HF, conv_noise_tri_hf_c), + MAKE(PATTERN, conv_noise_pattern_c), +}; +#undef MAKE + +static const struct noise_info *find_noise_info(uint32_t method, + uint32_t cpu_flags) +{ + SPA_FOR_EACH_ELEMENT_VAR(noise_table, t) { + if (t->method == method && + MATCH_CPU_FLAGS(t->cpu_flags, cpu_flags)) + return t; } return NULL; } @@ -374,8 +413,8 @@ static const struct conv_info *find_conv_info(uint32_t src_fmt, uint32_t dst_fmt static void impl_convert_free(struct convert *conv) { conv->process = NULL; - free(conv->noise); - conv->noise = NULL; + free(conv->data); + conv->data = NULL; } static bool need_dither(uint32_t format) @@ -430,17 +469,14 @@ static const struct dither_info { static const struct dither_info *find_dither_info(uint32_t method, uint32_t rate) { - size_t i; - - for (i = 0; i < SPA_N_ELEMENTS(dither_info); i++) { - const struct dither_info *di = &dither_info[i]; + SPA_FOR_EACH_ELEMENT_VAR(dither_info, di) { if (di->method != method) continue; /* don't use shaped for too low rates, it moves the noise to * audible ranges */ if (di->ns != NULL && rate < di->rate * 3 / 4) return find_dither_info(DITHER_METHOD_TRIANGULAR_HF, rate); - return &dither_info[i]; + return di; } return NULL; } @@ -449,7 +485,8 @@ int convert_init(struct convert *conv) { const struct conv_info *info; const struct dither_info *dinfo; - uint32_t i, conv_flags; + const struct noise_info *ninfo; + uint32_t i, conv_flags, data_size[3]; conv->scale = 1.0f / (float)(INT32_MAX); @@ -494,17 +531,31 @@ int convert_init(struct convert *conv) if (info == NULL) return -ENOTSUP; - conv->noise_size = DITHER_SIZE; - conv->noise = calloc(conv->noise_size + 16 + - FMT_OPS_MAX_ALIGN / sizeof(float), sizeof(float)); - if (conv->noise == NULL) + ninfo = find_noise_info(conv->noise_method, conv->cpu_flags); + if (ninfo == NULL) + return -ENOTSUP; + + conv->noise_size = NOISE_SIZE; + + data_size[0] = SPA_ROUND_UP(conv->noise_size * sizeof(float), FMT_OPS_MAX_ALIGN); + data_size[1] = SPA_ROUND_UP(RANDOM_SIZE * sizeof(uint32_t), FMT_OPS_MAX_ALIGN); + data_size[2] = SPA_ROUND_UP(RANDOM_SIZE * sizeof(int32_t), FMT_OPS_MAX_ALIGN); + + conv->data = calloc(FMT_OPS_MAX_ALIGN + + data_size[0] + data_size[1] + data_size[2], 1); + if (conv->data == NULL) return -errno; - for (i = 0; i < SPA_N_ELEMENTS(conv->random); i++) + conv->noise = SPA_PTR_ALIGN(conv->data, FMT_OPS_MAX_ALIGN, float); + conv->random = SPA_PTROFF(conv->noise, data_size[0], uint32_t); + conv->prev = SPA_PTROFF(conv->random, data_size[1], int32_t); + + for (i = 0; i < RANDOM_SIZE; i++) conv->random[i] = random(); conv->is_passthrough = conv->src_fmt == conv->dst_fmt; conv->cpu_flags = info->cpu_flags; + conv->update_noise = ninfo->noise; conv->process = info->process; conv->free = impl_convert_free; conv->func_name = info->name; diff --git a/spa/plugins/audioconvert/fmt-ops.h b/spa/plugins/audioconvert/fmt-ops.h index a4cd1de1125e0ea06b6cc777b03421a3fb6d508d..9280b8cb0f19b1e9f5c0908b90b7569472f16509 100644 --- a/spa/plugins/audioconvert/fmt-ops.h +++ b/spa/plugins/audioconvert/fmt-ops.h @@ -226,8 +226,8 @@ struct convert { unsigned int is_passthrough:1; float scale; - uint32_t random[16 + FMT_OPS_MAX_ALIGN/4]; - int32_t prev[16 + FMT_OPS_MAX_ALIGN/4]; + uint32_t *random; + int32_t *prev; #define NOISE_METHOD_NONE 0 #define NOISE_METHOD_RECTANGULAR 1 #define NOISE_METHOD_TRIANGULAR 2 @@ -240,9 +240,12 @@ struct convert { uint32_t n_ns; struct shaper shaper[64]; + void (*update_noise) (struct convert *conv, float *noise, uint32_t n_samples); void (*process) (struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples); void (*free) (struct convert *conv); + + void *data; }; int convert_init(struct convert *conv); @@ -268,20 +271,37 @@ static const struct dither_method_info { static inline uint32_t dither_method_from_label(const char *label) { - uint32_t i; - for (i = 0; i < SPA_N_ELEMENTS(dither_method_info); i++) { - if (spa_streq(dither_method_info[i].label, label)) - return dither_method_info[i].method; + SPA_FOR_EACH_ELEMENT_VAR(dither_method_info, i) { + if (spa_streq(i->label, label)) + return i->method; } return DITHER_METHOD_NONE; } +#define convert_update_noise(conv,...) (conv)->update_noise(conv, __VA_ARGS__) #define convert_process(conv,...) (conv)->process(conv, __VA_ARGS__) #define convert_free(conv) (conv)->free(conv) -#define DEFINE_FUNCTION(name,arch) \ +#define DEFINE_NOISE_FUNCTION(name,arch) \ +void conv_noise_##name##_##arch(struct convert *conv, float *noise, \ + uint32_t n_samples) + +DEFINE_NOISE_FUNCTION(none, c); +DEFINE_NOISE_FUNCTION(rect, c); +DEFINE_NOISE_FUNCTION(tri, c); +DEFINE_NOISE_FUNCTION(tri_hf, c); +DEFINE_NOISE_FUNCTION(pattern, c); +#if defined(HAVE_SSE2) +DEFINE_NOISE_FUNCTION(rect, sse2); +DEFINE_NOISE_FUNCTION(tri, sse2); +DEFINE_NOISE_FUNCTION(tri_hf, sse2); +#endif + +#undef DEFINE_NOISE_FUNCTION + +#define DEFINE_FUNCTION(name,arch) \ void conv_##name##_##arch(struct convert *conv, void * SPA_RESTRICT dst[], \ - const void * SPA_RESTRICT src[], uint32_t n_samples) \ + const void * SPA_RESTRICT src[], uint32_t n_samples) DEFINE_FUNCTION(copy8d, c); DEFINE_FUNCTION(copy8, c); diff --git a/spa/plugins/audioconvert/meson.build b/spa/plugins/audioconvert/meson.build index ab0581e765ec5e44556f712b81f00196fe19f392..f08527a5a60581b9dc3aceaabedaab681b31328e 100644 --- a/spa/plugins/audioconvert/meson.build +++ b/spa/plugins/audioconvert/meson.build @@ -12,8 +12,8 @@ audioconvert_c = static_library('audioconvert_c', 'biquad.c', 'crossover.c', 'volume-ops-c.c', + 'peaks-ops-c.c', 'resample-native-c.c', - 'resample-peaks-c.c', 'fmt-ops-c.c' ], c_args : ['-Ofast', '-ffast-math'], dependencies : [ spa_dep ], @@ -24,8 +24,8 @@ simd_dependencies += audioconvert_c if have_sse audioconvert_sse = static_library('audioconvert_sse', ['resample-native-sse.c', - 'resample-peaks-sse.c', 'volume-ops-sse.c', + 'peaks-ops-sse.c', 'channelmix-ops-sse.c' ], c_args : [sse_args, '-Ofast', '-DHAVE_SSE'], dependencies : [ spa_dep ], @@ -101,6 +101,7 @@ endif audioconvert_lib = static_library('audioconvert', ['fmt-ops.c', 'channelmix-ops.c', + 'peaks-ops.c', 'resample-native.c', 'resample-peaks.c', 'volume-ops.c' ], @@ -132,6 +133,7 @@ test_apps = [ 'test-audioconvert', 'test-channelmix', 'test-fmt-ops', + 'test-peaks', 'test-resample', ] diff --git a/spa/plugins/audioconvert/resample-peaks-sse.c b/spa/plugins/audioconvert/peaks-ops-c.c similarity index 57% rename from spa/plugins/audioconvert/resample-peaks-sse.c rename to spa/plugins/audioconvert/peaks-ops-c.c index 26adb8b91a4b2035d20ebec13dfed70eac754dc2..45ab1dc694f15d0020044598a09598d81d0c8c92 100644 --- a/spa/plugins/audioconvert/resample-peaks-sse.c +++ b/spa/plugins/audioconvert/peaks-ops-c.c @@ -1,6 +1,6 @@ /* Spa * - * Copyright © 2018 Wim Taymans + * 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"), @@ -24,41 +24,27 @@ #include <math.h> -#include <xmmintrin.h> +#include "peaks-ops.h" -#include "resample-peaks-impl.h" - -static inline float hmax_ps(__m128 val) +void peaks_min_max_c(struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float *min, float *max) { - __m128 t = _mm_movehl_ps(val, val); - t = _mm_max_ps(t, val); - val = _mm_shuffle_ps(t, t, 0x55); - val = _mm_max_ss(t, val); - return _mm_cvtss_f32(val); + uint32_t n; + float t, mi = *min, ma = *max; + for (n = 0; n < n_samples; n++) { + t = src[n]; + mi = fminf(mi, t); + ma = fmaxf(ma, t); + } + *min = mi; + *max = ma; } -static inline float find_abs_max_sse(const float *s, uint32_t n_samples, float m) +float peaks_abs_max_c(struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float max) { - __m128 in[2], max; - uint32_t n, unrolled; - const __m128 mask = _mm_set1_ps(-0.0f); - - max = _mm_set1_ps(m); - - unrolled = n_samples & ~7; - - for (n = 0; n < unrolled; n += 8) { - in[0] = _mm_loadu_ps(&s[n + 0]); - in[1] = _mm_loadu_ps(&s[n + 4]); - in[0] = _mm_andnot_ps(mask, in[0]); - in[1] = _mm_andnot_ps(mask, in[1]); - max = _mm_max_ps(max, in[0]); - max = _mm_max_ps(max, in[1]); - } - for (; n < n_samples; n++) - m = fmaxf(fabsf(s[n]), m); - - return fmaxf(hmax_ps(max), m); + uint32_t n; + for (n = 0; n < n_samples; n++) + max = fmaxf(fabsf(src[n]), max); + return max; } - -MAKE_PEAKS(sse); diff --git a/spa/plugins/audioconvert/peaks-ops-sse.c b/spa/plugins/audioconvert/peaks-ops-sse.c new file mode 100644 index 0000000000000000000000000000000000000000..7ceb2a8c6e8c33a2e2884cea8245e24b8e574b6a --- /dev/null +++ b/spa/plugins/audioconvert/peaks-ops-sse.c @@ -0,0 +1,122 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <math.h> + +#include <xmmintrin.h> + +#include "peaks-ops.h" + +static inline float hmin_ps(__m128 val) +{ + __m128 t = _mm_movehl_ps(val, val); + t = _mm_min_ps(t, val); + val = _mm_shuffle_ps(t, t, 0x55); + val = _mm_min_ss(t, val); + return _mm_cvtss_f32(val); +} + +static inline float hmax_ps(__m128 val) +{ + __m128 t = _mm_movehl_ps(val, val); + t = _mm_max_ps(t, val); + val = _mm_shuffle_ps(t, t, 0x55); + val = _mm_max_ss(t, val); + return _mm_cvtss_f32(val); +} + +void peaks_min_max_sse(struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float *min, float *max) +{ + uint32_t n; + __m128 in; + __m128 mi = _mm_set1_ps(*min); + __m128 ma = _mm_set1_ps(*max); + + for (n = 0; n < n_samples; n++) { + if (SPA_IS_ALIGNED(&src[n], 16)) + break; + in = _mm_set1_ps(src[n]); + mi = _mm_min_ps(mi, in); + ma = _mm_max_ps(ma, in); + } + for (; n + 15 < n_samples; n += 16) { + in = _mm_load_ps(&src[n + 0]); + mi = _mm_min_ps(mi, in); + ma = _mm_max_ps(ma, in); + in = _mm_load_ps(&src[n + 4]); + mi = _mm_min_ps(mi, in); + ma = _mm_max_ps(ma, in); + in = _mm_load_ps(&src[n + 8]); + mi = _mm_min_ps(mi, in); + ma = _mm_max_ps(ma, in); + in = _mm_load_ps(&src[n + 12]); + mi = _mm_min_ps(mi, in); + ma = _mm_max_ps(ma, in); + } + for (; n < n_samples; n++) { + in = _mm_set1_ps(src[n]); + mi = _mm_min_ps(mi, in); + ma = _mm_max_ps(ma, in); + } + *min = hmin_ps(mi); + *max = hmax_ps(ma); +} + +float peaks_abs_max_sse(struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float max) +{ + uint32_t n; + __m128 in; + __m128 ma = _mm_set1_ps(max); + const __m128 mask = _mm_set1_ps(-0.0f); + + for (n = 0; n < n_samples; n++) { + if (SPA_IS_ALIGNED(&src[n], 16)) + break; + in = _mm_set1_ps(src[n]); + in = _mm_andnot_ps(mask, in); + ma = _mm_max_ps(ma, in); + } + for (; n + 15 < n_samples; n += 16) { + in = _mm_load_ps(&src[n + 0]); + in = _mm_andnot_ps(mask, in); + ma = _mm_max_ps(ma, in); + in = _mm_load_ps(&src[n + 4]); + in = _mm_andnot_ps(mask, in); + ma = _mm_max_ps(ma, in); + in = _mm_load_ps(&src[n + 8]); + in = _mm_andnot_ps(mask, in); + ma = _mm_max_ps(ma, in); + in = _mm_load_ps(&src[n + 12]); + in = _mm_andnot_ps(mask, in); + ma = _mm_max_ps(ma, in); + } + for (; n < n_samples; n++) { + in = _mm_set1_ps(src[n]); + in = _mm_andnot_ps(mask, in); + ma = _mm_max_ps(ma, in); + } + return hmax_ps(ma); +} diff --git a/spa/plugins/audioconvert/peaks-ops.c b/spa/plugins/audioconvert/peaks-ops.c new file mode 100644 index 0000000000000000000000000000000000000000..b5cb25286588570982d48a66de015fc605d4524e --- /dev/null +++ b/spa/plugins/audioconvert/peaks-ops.c @@ -0,0 +1,89 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> +#include <math.h> +#include <errno.h> + +#include <spa/support/cpu.h> +#include <spa/support/log.h> +#include <spa/utils/defs.h> + +#include "peaks-ops.h" + +typedef void (*peaks_min_max_func_t) (struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float *min, float *max); +typedef float (*peaks_abs_max_func_t) (struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float max); + +#define MAKE(min_max,abs_max,...) \ + { min_max, abs_max, #min_max , __VA_ARGS__ } + +static const struct peaks_info { + peaks_min_max_func_t min_max; + peaks_abs_max_func_t abs_max; + const char *name; + uint32_t cpu_flags; +} peaks_table[] = +{ +#if defined (HAVE_SSE) + MAKE(peaks_min_max_sse, peaks_abs_max_sse, SPA_CPU_FLAG_SSE), +#endif + MAKE(peaks_min_max_c, peaks_abs_max_c), +}; +#undef MAKE + +#define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) + +static const struct peaks_info *find_peaks_info(uint32_t cpu_flags) +{ + SPA_FOR_EACH_ELEMENT_VAR(peaks_table, t) { + if (MATCH_CPU_FLAGS(t->cpu_flags, cpu_flags)) + return t; + } + return NULL; +} + +static void impl_peaks_free(struct peaks *peaks) +{ + peaks->min_max = NULL; + peaks->abs_max = NULL; +} + +int peaks_init(struct peaks *peaks) +{ + const struct peaks_info *info; + + info = find_peaks_info(peaks->cpu_flags); + if (info == NULL) + return -ENOTSUP; + + peaks->cpu_flags = info->cpu_flags; + peaks->func_name = info->name; + peaks->free = impl_peaks_free; + peaks->min_max = info->min_max; + peaks->abs_max = info->abs_max; + return 0; +} diff --git a/spa/plugins/audioconvert/peaks-ops.h b/spa/plugins/audioconvert/peaks-ops.h new file mode 100644 index 0000000000000000000000000000000000000000..29da794100d6b9851b81bda2322414e62b3865bc --- /dev/null +++ b/spa/plugins/audioconvert/peaks-ops.h @@ -0,0 +1,72 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> + +#include <spa/utils/defs.h> + +struct peaks { + uint32_t cpu_flags; + const char *func_name; + + struct spa_log *log; + + uint32_t flags; + + void (*min_max) (struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float *min, float *max); + float (*abs_max) (struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float max); + + void (*free) (struct peaks *peaks); +}; + +int peaks_init(struct peaks *peaks); + +#define peaks_min_max(peaks,...) (peaks)->min_max(peaks, __VA_ARGS__) +#define peaks_abs_max(peaks,...) (peaks)->abs_max(peaks, __VA_ARGS__) +#define peaks_free(peaks) (peaks)->free(peaks) + +#define DEFINE_MIN_MAX_FUNCTION(arch) \ +void peaks_min_max_##arch(struct peaks *peaks, \ + const float * SPA_RESTRICT src, \ + uint32_t n_samples, float *min, float *max); + +#define DEFINE_ABS_MAX_FUNCTION(arch) \ +float peaks_abs_max_##arch(struct peaks *peaks, \ + const float * SPA_RESTRICT src, \ + uint32_t n_samples, float max); + +#define PEAKS_OPS_MAX_ALIGN 16 + +DEFINE_MIN_MAX_FUNCTION(c); +DEFINE_ABS_MAX_FUNCTION(c); + +#if defined (HAVE_SSE) +DEFINE_MIN_MAX_FUNCTION(sse); +DEFINE_ABS_MAX_FUNCTION(sse); +#endif + +#undef DEFINE_FUNCTION diff --git a/spa/plugins/audioconvert/resample-native-impl.h b/spa/plugins/audioconvert/resample-native-impl.h index a6c87fc3640ea8c6cd0b096ff656be912d1db387..5dfc40e6f1c5e8d4c165503095f8915b43499506 100644 --- a/spa/plugins/audioconvert/resample-native-impl.h +++ b/spa/plugins/audioconvert/resample-native-impl.h @@ -94,6 +94,14 @@ DEFINE_RESAMPLER(copy,arch) \ *out_len = ooffs; \ } +#define INC(index,phase,n_phases) \ + index += inc; \ + phase += frac; \ + if (phase >= n_phases) { \ + phase -= n_phases; \ + index += 1; \ + } + #define MAKE_RESAMPLER_FULL(arch) \ DEFINE_RESAMPLER(full,arch) \ { \ @@ -114,17 +122,10 @@ DEFINE_RESAMPLER(full,arch) \ phase = data->phase; \ \ for (o = ooffs; o < olen && index + n_taps <= ilen; o++) { \ - const float *ip, *taps; \ - \ - ip = &s[index]; \ - taps = &data->filter[phase * stride]; \ - index += inc; \ - phase += frac; \ - if (phase >= n_phases) { \ - phase -= n_phases; \ - index += 1; \ - } \ - inner_product_##arch(&d[o], ip, taps, n_taps); \ + inner_product_##arch(&d[o], &s[index], \ + &data->filter[phase * stride], \ + n_taps); \ + INC(index, phase, n_phases); \ } \ } \ *in_len = index; \ @@ -153,24 +154,13 @@ DEFINE_RESAMPLER(inter,arch) \ phase = data->phase; \ \ for (o = ooffs; o < olen && index + n_taps <= ilen; o++) { \ - const float *ip, *t0, *t1; \ - float ph, x; \ - uint32_t offset; \ - \ - ip = &s[index]; \ - ph = (float)phase * n_phases / out_rate; \ - offset = floor(ph); \ - x = ph - (float)offset; \ - \ - t0 = &data->filter[(offset + 0) * stride]; \ - t1 = &data->filter[(offset + 1) * stride]; \ - index += inc; \ - phase += frac; \ - if (phase >= out_rate) { \ - phase -= out_rate; \ - index += 1; \ - } \ - inner_product_ip_##arch(&d[o], ip, t0, t1, x, n_taps); \ + float ph = (float)phase * n_phases / out_rate; \ + uint32_t offset = floorf(ph); \ + inner_product_ip_##arch(&d[o], &s[index], \ + &data->filter[(offset + 0) * stride], \ + &data->filter[(offset + 1) * stride], \ + ph - offset, n_taps); \ + INC(index, phase, out_rate); \ } \ } \ *in_len = index; \ diff --git a/spa/plugins/audioconvert/resample-native.c b/spa/plugins/audioconvert/resample-native.c index b46a09fc1f0cf0e223b0a70083e107da4824bb4f..05ce54ba3192ed7e5f0f9db9e30ee5ba683d7854 100644 --- a/spa/plugins/audioconvert/resample-native.c +++ b/spa/plugins/audioconvert/resample-native.c @@ -66,6 +66,7 @@ static inline double window_blackman(double x, double n_taps) (alpha / 2.0) * cos(2.0 * x); return r; } + static inline double window_cosh(double x, double n_taps) { double r; @@ -80,7 +81,7 @@ static inline double window_cosh(double x, double n_taps) return r; } -#define window window_cosh +#define window (1 ? window_cosh : window_blackman) static int build_filter(float *taps, uint32_t stride, uint32_t n_taps, uint32_t n_phases, double cutoff) { @@ -125,11 +126,10 @@ static struct resample_info resample_table[] = #define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) static const struct resample_info *find_resample_info(uint32_t format, uint32_t cpu_flags) { - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(resample_table); i++) { - if (resample_table[i].format == format && - MATCH_CPU_FLAGS(resample_table[i].cpu_flags, cpu_flags)) - return &resample_table[i]; + SPA_FOR_EACH_ELEMENT_VAR(resample_table, t) { + if (t->format == format && + MATCH_CPU_FLAGS(t->cpu_flags, cpu_flags)) + return t; } return NULL; } @@ -304,7 +304,10 @@ static void impl_native_reset (struct resample *r) if (d == NULL) return; memset(d->hist_mem, 0, r->channels * sizeof(float) * d->n_taps * 2); - d->hist = (d->n_taps / 2) - 1; + if (r->options & RESAMPLE_OPTION_PREFILL) + d->hist = d->n_taps - 1; + else + d->hist = (d->n_taps / 2) - 1; d->phase = 0; } diff --git a/spa/plugins/audioconvert/resample-peaks-impl.h b/spa/plugins/audioconvert/resample-peaks-impl.h deleted file mode 100644 index 9d9d55cff12023bb3fedbcdab4a5d718f7e749c9..0000000000000000000000000000000000000000 --- a/spa/plugins/audioconvert/resample-peaks-impl.h +++ /dev/null @@ -1,92 +0,0 @@ -/* Spa - * - * Copyright © 2020 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include <math.h> - -#include <spa/utils/defs.h> - -#include "resample.h" - -struct peaks_data { - uint32_t o_count; - uint32_t i_count; - float max_f[]; -}; - -#define DEFINE_PEAKS(arch) \ -void resample_peaks_process_##arch(struct resample *r, \ - const void * SPA_RESTRICT src[], uint32_t *in_len, \ - void * SPA_RESTRICT dst[], uint32_t *out_len) - -#define MAKE_PEAKS(arch) \ -DEFINE_PEAKS(arch) \ -{ \ - struct peaks_data *pd = r->data; \ - uint32_t c, i, o, end, chunk, i_count, o_count; \ - \ - if (SPA_UNLIKELY(r->channels == 0)) \ - return; \ - \ - for (c = 0; c < r->channels; c++) { \ - const float *s = src[c]; \ - float *d = dst[c], m = pd->max_f[c]; \ - \ - o_count = pd->o_count; \ - i_count = pd->i_count; \ - o = i = 0; \ - \ - while (i < *in_len && o < *out_len) { \ - end = ((uint64_t) (o_count + 1) \ - * r->i_rate) / r->o_rate; \ - end = end > i_count ? end - i_count : 0; \ - chunk = SPA_MIN(end, *in_len); \ - \ - m = find_abs_max_##arch(&s[i], chunk - i, m); \ - \ - i += chunk; \ - \ - if (i == end) { \ - d[o++] = m; \ - m = 0.0f; \ - o_count++; \ - } \ - } \ - pd->max_f[c] = m; \ - } \ - *out_len = o; \ - *in_len = i; \ - pd->o_count = o_count; \ - pd->i_count = i_count + i; \ - \ - while (pd->i_count >= r->i_rate) { \ - pd->i_count -= r->i_rate; \ - pd->o_count -= r->o_rate; \ - } \ -} - - -DEFINE_PEAKS(c); -#if defined (HAVE_SSE) -DEFINE_PEAKS(sse); -#endif diff --git a/spa/plugins/audioconvert/resample-peaks.c b/spa/plugins/audioconvert/resample-peaks.c index 2fa52e875e4a65a55b89842f16759a15fb2a92ff..c151d60f32a9699deeea4db21a0fc1505b6a1151 100644 --- a/spa/plugins/audioconvert/resample-peaks.c +++ b/spa/plugins/audioconvert/resample-peaks.c @@ -27,40 +27,70 @@ #include <spa/param/audio/format.h> -#include "resample-peaks-impl.h" - -struct resample_info { - uint32_t format; - uint32_t cpu_flags; - void (*process) (struct resample *r, - const void * SPA_RESTRICT src[], uint32_t *in_len, - void * SPA_RESTRICT dst[], uint32_t *out_len); +#include "peaks-ops.h" +#include "resample.h" + +struct peaks_data { + uint32_t o_count; + uint32_t i_count; + struct peaks peaks; + float max_f[]; }; -static struct resample_info resample_table[] = +static void resample_peaks_process(struct resample *r, + const void * SPA_RESTRICT src[], uint32_t *in_len, + void * SPA_RESTRICT dst[], uint32_t *out_len) { -#if defined (HAVE_SSE) - { SPA_AUDIO_FORMAT_F32, SPA_CPU_FLAG_SSE, resample_peaks_process_sse, }, -#endif - { SPA_AUDIO_FORMAT_F32, 0, resample_peaks_process_c, }, -}; + struct peaks_data *pd = r->data; + uint32_t c, i, o, end, chunk, i_count, o_count; -#define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) -static const struct resample_info *find_resample_info(uint32_t format, uint32_t cpu_flags) -{ - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(resample_table); i++) { - if (resample_table[i].format == format && - MATCH_CPU_FLAGS(resample_table[i].cpu_flags, cpu_flags)) { - return &resample_table[i]; + if (SPA_UNLIKELY(r->channels == 0)) + return; + + for (c = 0; c < r->channels; c++) { + const float *s = src[c]; + float *d = dst[c], m = pd->max_f[c]; + + o_count = pd->o_count; + i_count = pd->i_count; + o = i = 0; + + while (i < *in_len && o < *out_len) { + end = ((uint64_t) (o_count + 1) + * r->i_rate) / r->o_rate; + end = end > i_count ? end - i_count : 0; + chunk = SPA_MIN(end, *in_len); + + m = peaks_abs_max(&pd->peaks, &s[i], chunk - i, m); + + i += chunk; + + if (i == end) { + d[o++] = m; + m = 0.0f; + o_count++; + } } + pd->max_f[c] = m; + } + *out_len = o; + *in_len = i; + pd->o_count = o_count; + pd->i_count = i_count + i; + + while (pd->i_count >= r->i_rate) { + pd->i_count -= r->i_rate; + pd->o_count -= r->o_rate; } - return NULL; } static void impl_peaks_free(struct resample *r) { - free(r->data); + struct peaks_data *d = r->data; + if (d != NULL) { + peaks_free(&d->peaks); + free(d); + } r->data = NULL; } @@ -87,27 +117,32 @@ static void impl_peaks_reset (struct resample *r) int resample_peaks_init(struct resample *r) { struct peaks_data *d; - const struct resample_info *info; + int res; r->free = impl_peaks_free; r->update_rate = impl_peaks_update_rate; - if ((info = find_resample_info(SPA_AUDIO_FORMAT_F32, r->cpu_flags)) == NULL) - return -ENOTSUP; + d = calloc(1, sizeof(struct peaks_data) + sizeof(float) * r->channels); + if (d == NULL) + return -errno; + + d->peaks.log = r->log; + d->peaks.cpu_flags = r->cpu_flags; + if ((res = peaks_init(&d->peaks)) < 0) { + free(d); + return res; + } - r->process = info->process; + r->data = d; + r->process = resample_peaks_process; r->reset = impl_peaks_reset; r->delay = impl_peaks_delay; r->in_len = impl_peaks_in_len; - d = r->data = calloc(1, sizeof(struct peaks_data) + sizeof(float) * r->channels); - if (r->data == NULL) - return -errno; - spa_log_debug(r->log, "peaks %p: in:%d out:%d features:%08x:%08x", r, - r->i_rate, r->o_rate, r->cpu_flags, info->cpu_flags); + r->i_rate, r->o_rate, r->cpu_flags, d->peaks.cpu_flags); - r->cpu_flags = info->cpu_flags; + r->cpu_flags = d->peaks.cpu_flags; d->i_count = d->o_count = 0; return 0; } diff --git a/spa/plugins/audioconvert/resample.h b/spa/plugins/audioconvert/resample.h index 0b9180ccb37cdb736d8b32a0edd81fa184d9f27e..b1c89d593bf929f9ea8050cb44e0c1c9d4f760cc 100644 --- a/spa/plugins/audioconvert/resample.h +++ b/spa/plugins/audioconvert/resample.h @@ -32,6 +32,8 @@ struct resample { struct spa_log *log; +#define RESAMPLE_OPTION_PREFILL (1<<0) + uint32_t options; uint32_t cpu_flags; const char *func_name; diff --git a/spa/plugins/audioconvert/test-audioconvert.c b/spa/plugins/audioconvert/test-audioconvert.c index 883803d06c30d1e562d4b60076984c404748325c..99ba86681fbe0358c34c45672f5c585beb91cfc1 100644 --- a/spa/plugins/audioconvert/test-audioconvert.c +++ b/spa/plugins/audioconvert/test-audioconvert.c @@ -681,9 +681,9 @@ static int run_convert(struct context *ctx, struct data *in_data, res = memcmp(b->datas[j].data, out_data->data[k], out_data->size); if (res != 0) { - fprintf(stderr, "error plane %d\n", j); + fprintf(stderr, "error port %d plane %d\n", i, j); spa_debug_mem(0, b->datas[j].data, out_data->size); - spa_debug_mem(0, out_data->data[j], out_data->size); + spa_debug_mem(0, out_data->data[k], out_data->size); } spa_assert_se(res == 0); @@ -702,7 +702,9 @@ static const float data_f32p_2[] = { 0.2f, 0.2f, 0.2f, 0.2f }; static const float data_f32p_3[] = { 0.3f, 0.3f, 0.3f, 0.3f }; static const float data_f32p_4[] = { 0.4f, 0.4f, 0.4f, 0.4f }; static const float data_f32p_5[] = { 0.5f, 0.5f, 0.5f, 0.5f }; +static const float data_f32p_5_6p1[] = { 0.953553438f, 0.953553438f, 0.953553438f, 0.953553438f }; static const float data_f32p_6[] = { 0.6f, 0.6f, 0.6f, 0.6f }; +static const float data_f32p_6_6p1[] = { 1.053553343f, 1.053553343f, 1.053553343f, 1.053553343f }; static const float data_f32p_7[] = { 0.7f, 0.7f, 0.7f, 0.7f }; static const float data_f32p_8[] = { 0.8f, 0.8f, 0.8f, 0.8f }; @@ -710,6 +712,14 @@ static const float data_f32_5p1[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f }; +static const float data_f32_6p1[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, + 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, + 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, + 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f }; +static const float data_f32_6p1_from_5p1[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.55f, 0.5f, 0.6f, + 0.1f, 0.2f, 0.3f, 0.4f, 0.55f, 0.5f, 0.6f, + 0.1f, 0.2f, 0.3f, 0.4f, 0.55f, 0.5f, 0.6f, + 0.1f, 0.2f, 0.3f, 0.4f, 0.55f, 0.5f, 0.6f }; static const float data_f32_7p1_remapped[] = { 0.1f, 0.2f, 0.5f, 0.6f, 0.7f, 0.8f, 0.3f, 0.4f, 0.1f, 0.2f, 0.5f, 0.6f, 0.7f, 0.8f, 0.3f, 0.4f, @@ -740,6 +750,27 @@ struct data dsp_5p1 = { .size = sizeof(float) * 4 }; +struct data dsp_5p1_from_6p1 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + }), + .ports = 6, + .planes = 1, + .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_5_6p1, data_f32p_6_6p1, }, + .size = sizeof(float) * 4 +}; + + struct data dsp_5p1_remapped = { .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, .info = SPA_AUDIO_INFO_RAW_INIT( @@ -760,6 +791,68 @@ struct data dsp_5p1_remapped = { .size = sizeof(float) * 4 }; +struct data dsp_5p1_remapped_from_6p1 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + }), + .ports = 6, + .planes = 1, + .data = { data_f32p_1, data_f32p_2, data_f32p_5_6p1, data_f32p_6_6p1, data_f32p_3, data_f32p_4, }, + .size = sizeof(float) * 4 +}; + +struct data dsp_6p1 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 7, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RC, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + }), + .ports = 7, + .planes = 1, + .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, data_f32p_7 }, + .size = sizeof(float) * 4 +}; + +struct data dsp_6p1_side = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 7, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RC, + SPA_AUDIO_CHANNEL_SL, + SPA_AUDIO_CHANNEL_SR, + }), + .ports = 7, + .planes = 1, + .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, data_f32p_7 }, + .size = sizeof(float) * 4 +}; + struct data dsp_7p1_remapped = { .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, .info = SPA_AUDIO_INFO_RAW_INIT( @@ -862,6 +955,90 @@ struct data conv_f32p_48000_5p1 = { .size = sizeof(float) * 4 }; +struct data conv_f32_48000_6p1 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 7, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RC, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + }), + .ports = 1, + .planes = 1, + .data = { data_f32_6p1 }, + .size = sizeof(data_f32_6p1) +}; + +struct data conv_f32_48000_6p1_from_5p1 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 7, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RC, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + }), + .ports = 1, + .planes = 1, + .data = { data_f32_6p1_from_5p1 }, + .size = sizeof(data_f32_6p1_from_5p1) +}; + +struct data conv_f32_48000_6p1_side = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 7, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RC, + SPA_AUDIO_CHANNEL_SL, + SPA_AUDIO_CHANNEL_SR, + }), + .ports = 1, + .planes = 1, + .data = { data_f32_6p1 }, + .size = sizeof(data_f32_6p1) +}; + +struct data conv_f32p_48000_6p1 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32P, + .rate = 48000, + .channels = 7, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RC, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + }), + .ports = 1, + .planes = 7, + .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, data_f32p_7 }, + .size = sizeof(float) * 4 +}; + struct data conv_f32p_48000_5p1_remapped = { .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, .info = SPA_AUDIO_INFO_RAW_INIT( @@ -918,6 +1095,11 @@ static int test_convert_remap_dsp(struct context *ctx) run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32p_48000_5p1); run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32_48000_5p1_remapped); run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32p_48000_5p1_remapped); + run_convert(ctx, &dsp_6p1, &conv_f32p_48000_6p1); + run_convert(ctx, &dsp_6p1, &conv_f32_48000_6p1); + run_convert(ctx, &dsp_6p1_side, &conv_f32_48000_6p1_side); + + run_convert(ctx, &dsp_5p1, &conv_f32_48000_6p1_from_5p1); return 0; } @@ -933,9 +1115,16 @@ static int test_convert_remap_conv(struct context *ctx) run_convert(ctx, &conv_f32_48000_5p1_remapped, &dsp_5p1_remapped); run_convert(ctx, &conv_f32_48000_5p1_remapped, &dsp_5p1_remapped_2); run_convert(ctx, &conv_f32p_48000_5p1_remapped, &dsp_5p1); + run_convert(ctx, &conv_f32p_48000_6p1, &dsp_6p1); + run_convert(ctx, &conv_f32_48000_6p1, &dsp_6p1); + run_convert(ctx, &conv_f32_48000_6p1_side, &dsp_6p1_side); run_convert(ctx, &conv_f32p_48000_5p1_remapped, &dsp_5p1_remapped); run_convert(ctx, &conv_f32_48000_7p1_remapped, &dsp_7p1_remapped); run_convert(ctx, &conv_f32p_48000_5p1_remapped, &dsp_5p1_remapped_2); + + run_convert(ctx, &conv_f32_48000_6p1, &dsp_5p1_from_6p1); + run_convert(ctx, &conv_f32_48000_6p1_side, &dsp_5p1_from_6p1); + run_convert(ctx, &conv_f32_48000_6p1, &dsp_5p1_remapped_from_6p1); return 0; } diff --git a/spa/plugins/audioconvert/test-channelmix.c b/spa/plugins/audioconvert/test-channelmix.c index 705ddf4de34372443c9ae2250351fbafba544896..9e052e20d85a284bbadf079a4ac99aebd44b6fdf 100644 --- a/spa/plugins/audioconvert/test-channelmix.c +++ b/spa/plugins/audioconvert/test-channelmix.c @@ -22,6 +22,8 @@ * DEALINGS IN THE SOFTWARE. */ +#include "config.h" + #include <string.h> #include <stdio.h> #include <stdlib.h> @@ -32,20 +34,26 @@ #include <spa/support/log-impl.h> #include <spa/debug/mem.h> +static uint32_t cpu_flags; + SPA_LOG_IMPL(logger); #define MATRIX(...) (float[]) { __VA_ARGS__ } +#include "test-helper.h" #include "channelmix-ops.c" + +#define CLOSE_ENOUGH(a,b) (fabs((a)-(b)) < 0.000001f) + static void dump_matrix(struct channelmix *mix, float *coeff) { uint32_t i, j; for (i = 0; i < mix->dst_chan; i++) { for (j = 0; j < mix->src_chan; j++) { - float v = mix->matrix_orig[i][j]; + float v = mix->matrix[i][j]; spa_log_debug(mix->log, "%d %d: %f <-> %f", i, j, v, *coeff); - spa_assert_se(fabs(v - *coeff) < 0.000001); + spa_assert_se(CLOSE_ENOUGH(v, *coeff)); coeff++; } } @@ -65,7 +73,8 @@ static void test_mix(uint32_t src_chan, uint32_t src_mask, uint32_t dst_chan, ui mix.dst_mask = dst_mask; mix.log = &logger.log; - channelmix_init(&mix); + spa_assert_se(channelmix_init(&mix) == 0); + channelmix_set_volume(&mix, 1.0f, false, 0, NULL); dump_matrix(&mix, coeff); } @@ -211,6 +220,46 @@ static void test_5p1_N(void) 0.0, 0.0, 0.0, 0.0, 0.0, 1.0)); } +static void test_6p1_N(void) +{ + test_mix(7, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(RC)|_M(SL)|_M(SR), 1, _M(MONO), 0, + MATRIX(0.707107, 0.707107, 1.0, 0.0, 0.5, 0.5, 0.5)); + test_mix(7, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR)|_M(RC), + 6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 0, + MATRIX(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.707107, + 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.707107)); + test_mix(7, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR)|_M(RC), + 6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(RL)|_M(RR), 0, + MATRIX(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.707107, + 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.707107)); + test_mix(7, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(RC)|_M(RL)|_M(RR), + 6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(RL)|_M(RR), 0, + MATRIX(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.707107, 1.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.707107, 0.0, 1.0)); + test_mix(7, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR)|_M(RC), + 8, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR)|_M(RL)|_M(RR), 0, + MATRIX(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.707107, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.707107)); +} + static void test_7p1_N(void) { test_mix(8, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR)|_M(RL)|_M(RR), 1, _M(MONO), 0, @@ -220,17 +269,106 @@ static void test_7p1_N(void) 0.0, 1.0, 0.707107, 0.0, 0.0, 0.707107, 0.0, 0.707107)); } +static void check_samples(float **s1, float **s2, uint32_t n_s, uint32_t n_samples) +{ + uint32_t i, j; + for (i = 0; i < n_s; i++) { + for (j = 0; j < n_samples; j++) { + spa_assert_se(CLOSE_ENOUGH(s1[i][j], s2[i][j])); + } + } +} + +static void run_n_m_impl(struct channelmix *mix, const void **src, uint32_t n_samples) +{ + uint32_t dst_chan = mix->dst_chan, i; + float dst_c_data[dst_chan][n_samples]; + float dst_x_data[dst_chan][n_samples]; + void *dst_c[dst_chan], *dst_x[dst_chan]; + + for (i = 0; i < dst_chan; i++) { + dst_c[i] = dst_c_data[i]; + dst_x[i] = dst_x_data[i]; + } + + channelmix_f32_n_m_c(mix, dst_c, src, n_samples); + + channelmix_f32_n_m_c(mix, dst_x, src, n_samples); + check_samples((float**)dst_c, (float**)dst_x, dst_chan, n_samples); + +#if defined(HAVE_SSE) + if (cpu_flags & SPA_CPU_FLAG_SSE) { + channelmix_f32_n_m_sse(mix, dst_x, src, n_samples); + check_samples((float**)dst_c, (float**)dst_x, dst_chan, n_samples); + } +#endif +} + +static void test_n_m_impl(void) +{ + struct channelmix mix; + unsigned int i, j; +#define N_SAMPLES 251 + float src_data[16][N_SAMPLES], *src[16]; + + spa_log_debug(&logger.log, "start"); + + for (i = 0; i < 16; i++) { + for (j = 0; j < N_SAMPLES; j++) + src_data[i][j] = (drand48() - 0.5f) * 2.5f; + src[i] = src_data[i]; + } + + spa_zero(mix); + mix.src_chan = 16; + mix.dst_chan = 12; + mix.log = &logger.log; + mix.cpu_flags = cpu_flags; + spa_assert_se(channelmix_init(&mix) == 0); + channelmix_set_volume(&mix, 1.0f, false, 0, NULL); + + /* identity matrix */ + run_n_m_impl(&mix, (const void**)src, N_SAMPLES); + + /* some zero destination */ + mix.matrix_orig[2][2] = 0.0f; + mix.matrix_orig[7][7] = 0.0f; + channelmix_set_volume(&mix, 1.0f, false, 0, NULL); + run_n_m_impl(&mix, (const void**)src, N_SAMPLES); + + /* random matrix */ + for (i = 0; i < mix.dst_chan; i++) { + for (j = 0; j < mix.src_chan; j++) { + mix.matrix_orig[i][j] = drand48() - 0.5f; + } + } + channelmix_set_volume(&mix, 1.0f, false, 0, NULL); + + run_n_m_impl(&mix, (const void**)src, N_SAMPLES); +} + int main(int argc, char *argv[]) { + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + srand48(SPA_TIMESPEC_TO_NSEC(&ts)); + logger.log.level = SPA_LOG_LEVEL_TRACE; + cpu_flags = get_cpu_flags(); + printf("got CPU flags %d\n", cpu_flags); + test_1_N_MONO(); test_1_N_FC(); test_N_1(); test_3p1_N(); test_4_N(); test_5p1_N(); + test_6p1_N(); test_7p1_N(); + test_n_m_impl(); + return 0; } diff --git a/spa/plugins/audioconvert/test-peaks.c b/spa/plugins/audioconvert/test-peaks.c new file mode 100644 index 0000000000000000000000000000000000000000..3f7d093ff908ad4132ddd34338c1015427ac9790 --- /dev/null +++ b/spa/plugins/audioconvert/test-peaks.c @@ -0,0 +1,128 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +#include <spa/support/log-impl.h> +#include <spa/debug/mem.h> + +SPA_LOG_IMPL(logger); + +static uint32_t cpu_flags; + +#include "test-helper.h" + +#include "peaks-ops.c" + +static void test_impl(void) +{ + struct peaks peaks; + unsigned int i; + float vals[1038]; + float min[2] = { 0.0f, 0.0f }, max[2] = { 0.0f, 0.0f }, absmax[2] = { 0.0f, 0.0f }; + + for (i = 0; i < SPA_N_ELEMENTS(vals); i++) + vals[i] = (drand48() - 0.5f) * 2.5f; + + peaks_min_max_c(&peaks, &vals[1], SPA_N_ELEMENTS(vals) - 1, &min[0], &max[0]); + printf("c peaks min:%f max:%f\n", min[0], max[0]); + + absmax[0] = peaks_abs_max_c(&peaks, &vals[1], SPA_N_ELEMENTS(vals) - 1, 0.0f); + printf("c peaks abs-max:%f\n", absmax[0]); + +#if defined(HAVE_SSE) + if (cpu_flags & SPA_CPU_FLAG_SSE) { + peaks_min_max_sse(&peaks, &vals[1], SPA_N_ELEMENTS(vals) - 1, &min[1], &max[1]); + printf("sse peaks min:%f max:%f\n", min[1], max[1]); + + absmax[1] = peaks_abs_max_sse(&peaks, &vals[1], SPA_N_ELEMENTS(vals) - 1, 0.0f); + printf("sse peaks abs-max:%f\n", absmax[1]); + + spa_assert(min[0] == min[1]); + spa_assert(max[0] == max[1]); + spa_assert(absmax[0] == absmax[1]); + } +#endif + +} + +static void test_min_max(void) +{ + struct peaks peaks; + const float vals[] = { 0.0f, 0.5f, -0.5f, 0.0f, 0.6f, -0.8f, -0.5f, 0.0f }; + float min = 0.0f, max = 0.0f; + + spa_zero(peaks); + peaks.log = &logger.log; + peaks.cpu_flags = cpu_flags; + peaks_init(&peaks); + + peaks_min_max(&peaks, vals, SPA_N_ELEMENTS(vals), &min, &max); + + spa_assert(min == -0.8f); + spa_assert(max == 0.6f); +} + +static void test_abs_max(void) +{ + struct peaks peaks; + const float vals[] = { 0.0f, 0.5f, -0.5f, 0.0f, 0.6f, -0.8f, -0.5f, 0.0f }; + float max = 0.0f; + + spa_zero(peaks); + peaks.log = &logger.log; + peaks.cpu_flags = cpu_flags; + peaks_init(&peaks); + + max = peaks_abs_max(&peaks, vals, SPA_N_ELEMENTS(vals), max); + + spa_assert(max == 0.8f); +} + +int main(int argc, char *argv[]) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + srand48(SPA_TIMESPEC_TO_NSEC(&ts)); + + logger.log.level = SPA_LOG_LEVEL_TRACE; + + cpu_flags = get_cpu_flags(); + printf("got CPU flags %d\n", cpu_flags); + + test_impl(); + + test_min_max(); + test_abs_max(); + + return 0; +} diff --git a/spa/plugins/audioconvert/test-source.c b/spa/plugins/audioconvert/test-source.c index 55a65c5d554ab88736174358a579d543efdb293d..b352323fd0e82d1e042aa574cb6f4a0f89bdf5a9 100644 --- a/spa/plugins/audioconvert/test-source.c +++ b/spa/plugins/audioconvert/test-source.c @@ -535,8 +535,12 @@ static int calc_width(struct spa_audio_info *info) case SPA_AUDIO_FORMAT_S24: case SPA_AUDIO_FORMAT_S24_OE: return 3; - default: + case SPA_AUDIO_FORMAT_S32P: + case SPA_AUDIO_FORMAT_S32: + case SPA_AUDIO_FORMAT_S32_OE: return 4; + default: + return 0; } } @@ -571,6 +575,12 @@ static int port_set_format(void *object, return res; port->stride = calc_width(&info); + if (port->stride == 0) + return -EINVAL; + if (info.info.raw.rate == 0 || + info.info.raw.channels == 0) + return -EINVAL; + if (SPA_AUDIO_FORMAT_IS_PLANAR(info.info.raw.format)) { port->blocks = info.info.raw.channels; } diff --git a/spa/plugins/audioconvert/volume-ops.c b/spa/plugins/audioconvert/volume-ops.c index 98887e46f5217b708bc2f76cf0b09fada094ba1d..6890cfa7d4389eabea138cb0b15727d8264afa0a 100644 --- a/spa/plugins/audioconvert/volume-ops.c +++ b/spa/plugins/audioconvert/volume-ops.c @@ -56,11 +56,9 @@ static const struct volume_info { static const struct volume_info *find_volume_info(uint32_t cpu_flags) { - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(volume_table); i++) { - if (!MATCH_CPU_FLAGS(volume_table[i].cpu_flags, cpu_flags)) - continue; - return &volume_table[i]; + SPA_FOR_EACH_ELEMENT_VAR(volume_table, t) { + if (MATCH_CPU_FLAGS(t->cpu_flags, cpu_flags)) + return t; } return NULL; } diff --git a/spa/plugins/audiomixer/audiomixer.c b/spa/plugins/audiomixer/audiomixer.c index 6aba1a0120361f7713f5d439bcba5b9f9a5735e0..41e7e3f7ee5dcd5c527718c328bcf2c150a64f4f 100644 --- a/spa/plugins/audiomixer/audiomixer.c +++ b/spa/plugins/audiomixer/audiomixer.c @@ -566,6 +566,10 @@ static int port_set_format(void *object, if (memcmp(&info, &this->format, sizeof(struct spa_audio_info))) return -EINVAL; } else { + if (info.info.raw.format == 0 || + info.info.raw.channels == 0) + return -EINVAL; + this->ops.fmt = info.info.raw.format; this->ops.n_channels = info.info.raw.channels; this->ops.cpu_flags = this->cpu_flags; diff --git a/spa/plugins/audiomixer/mix-ops-avx.c b/spa/plugins/audiomixer/mix-ops-avx.c index a5e3b5b11763874aa123a3dd05e22c82c9ca4691..3c5aa6bf7486a81d7a6430cfecf3ade14c9ced5d 100644 --- a/spa/plugins/audiomixer/mix-ops-avx.c +++ b/spa/plugins/audiomixer/mix-ops-avx.c @@ -32,62 +32,6 @@ #include <immintrin.h> -static inline void mix_4(float * dst, - const float * SPA_RESTRICT src0, - const float * SPA_RESTRICT src1, - const float * SPA_RESTRICT src2, - uint32_t n_samples) -{ - uint32_t n, unrolled; - - if (SPA_IS_ALIGNED(src0, 32) && - SPA_IS_ALIGNED(src1, 32) && - SPA_IS_ALIGNED(src2, 32) && - SPA_IS_ALIGNED(dst, 32)) - unrolled = n_samples & ~15; - else - unrolled = 0; - - for (n = 0; n < unrolled; n += 16) { - __m256 in1[4], in2[4]; - - in1[0] = _mm256_load_ps(&dst[n + 0]); - in2[0] = _mm256_load_ps(&dst[n + 8]); - in1[1] = _mm256_load_ps(&src0[n + 0]); - in2[1] = _mm256_load_ps(&src0[n + 8]); - in1[2] = _mm256_load_ps(&src1[n + 0]); - in2[2] = _mm256_load_ps(&src1[n + 8]); - in1[3] = _mm256_load_ps(&src2[n + 0]); - in2[3] = _mm256_load_ps(&src2[n + 8]); - - in1[0] = _mm256_add_ps(in1[0], in1[1]); - in2[0] = _mm256_add_ps(in2[0], in2[1]); - in1[2] = _mm256_add_ps(in1[2], in1[3]); - in2[2] = _mm256_add_ps(in2[2], in2[3]); - in1[0] = _mm256_add_ps(in1[0], in1[2]); - in2[0] = _mm256_add_ps(in2[0], in2[2]); - - _mm256_store_ps(&dst[n + 0], in1[0]); - _mm256_store_ps(&dst[n + 8], in2[0]); - } - for (; n < n_samples; n++) { - __m128 in[4]; - in[0] = _mm_load_ss(&dst[n]), - in[1] = _mm_load_ss(&src0[n]), - in[2] = _mm_load_ss(&src1[n]), - in[3] = _mm_load_ss(&src2[n]), - in[0] = _mm_add_ss(in[0], in[1]); - in[2] = _mm_add_ss(in[2], in[3]); - in[0] = _mm_add_ss(in[0], in[2]); - _mm_store_ss(&dst[n], in[0]); - } -} - - -static inline void mix_2(float * dst, const float * SPA_RESTRICT src, uint32_t n_samples) -{ -} - void mix_f32_avx(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples) diff --git a/spa/plugins/audiomixer/mix-ops.c b/spa/plugins/audiomixer/mix-ops.c index 816f44d1e4b66de60707ed5a3b3b371bf70d5c09..b4599268758f74a617c769145ca9af1142f8cdad 100644 --- a/spa/plugins/audiomixer/mix-ops.c +++ b/spa/plugins/audiomixer/mix-ops.c @@ -98,13 +98,11 @@ static struct mix_info mix_table[] = static const struct mix_info *find_mix_info(uint32_t fmt, uint32_t n_channels, uint32_t cpu_flags) { - size_t i; - - for (i = 0; i < SPA_N_ELEMENTS(mix_table); i++) { - if (mix_table[i].fmt == fmt && - MATCH_CHAN(mix_table[i].n_channels, n_channels) && - MATCH_CPU_FLAGS(mix_table[i].cpu_flags, cpu_flags)) - return &mix_table[i]; + SPA_FOR_EACH_ELEMENT_VAR(mix_table, t) { + if (t->fmt == fmt && + MATCH_CHAN(t->n_channels, n_channels) && + MATCH_CPU_FLAGS(t->cpu_flags, cpu_flags)) + return t; } return NULL; } diff --git a/spa/plugins/audiotestsrc/audiotestsrc.c b/spa/plugins/audiotestsrc/audiotestsrc.c index ff58e6a2aa7225617349eb79ef65e026f1a08e9f..f009b9a29b5a40c78c32d5c7852c3317fb7cabd6 100644 --- a/spa/plugins/audiotestsrc/audiotestsrc.c +++ b/spa/plugins/audiotestsrc/audiotestsrc.c @@ -743,6 +743,10 @@ port_set_format(struct impl *this, if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; + if (info.info.raw.rate == 0 || + info.info.raw.channels == 0) + return -EINVAL; + switch (info.info.raw.format) { case SPA_AUDIO_FORMAT_S16: idx = 0; diff --git a/spa/plugins/avb/avb-pcm.c b/spa/plugins/avb/avb-pcm.c index c7ccb05b666753640a1ceafe5f8d793747de81da..8fe9503a379be7f684d7907a6cc29a9c47b95698 100644 --- a/spa/plugins/avb/avb-pcm.c +++ b/spa/plugins/avb/avb-pcm.c @@ -439,7 +439,7 @@ static int spa_format_to_aaf(uint32_t format) } } -static int frame_size(uint32_t format) +static int calc_frame_size(uint32_t format) { switch(format) { case SPA_AUDIO_FORMAT_F32_BE: @@ -647,7 +647,7 @@ static int setup_packet(struct state *state, struct spa_audio_info *fmt) SPA_AVBTP_PACKET_AAF_SET_FORMAT(pdu, spa_format_to_aaf(state->format)); SPA_AVBTP_PACKET_AAF_SET_NSR(pdu, spa_rate_to_aaf(state->rate)); SPA_AVBTP_PACKET_AAF_SET_CHAN_PER_FRAME(pdu, state->channels); - SPA_AVBTP_PACKET_AAF_SET_BIT_DEPTH(pdu, frame_size(state->format)*8); + SPA_AVBTP_PACKET_AAF_SET_BIT_DEPTH(pdu, calc_frame_size(state->format)*8); SPA_AVBTP_PACKET_AAF_SET_DATA_LEN(pdu, payload_size); SPA_AVBTP_PACKET_AAF_SET_SP(pdu, SPA_AVBTP_AAF_PCM_SP_NORMAL); } @@ -690,14 +690,22 @@ int spa_avb_clear_format(struct state *state) int spa_avb_set_format(struct state *state, struct spa_audio_info *fmt, uint32_t flags) { - int res; + int res, frame_size; struct props *p = &state->props; + frame_size = calc_frame_size(fmt->info.raw.format); + if (frame_size == 0) + return -EINVAL; + + if (fmt->info.raw.rate == 0 || + fmt->info.raw.channels == 0) + return -EINVAL; + state->format = fmt->info.raw.format; state->rate = fmt->info.raw.rate; state->channels = fmt->info.raw.channels; state->blocks = 1; - state->stride = state->channels * frame_size(state->format); + state->stride = state->channels * frame_size; if ((res = setup_socket(state)) < 0) return res; @@ -877,7 +885,7 @@ static int flush_write(struct state *state, uint64_t current_time) SPA_AVBTP_PACKET_AAF_SET_SEQ_NUM(pdu, state->pdu_seq++); SPA_AVBTP_PACKET_AAF_SET_TIMESTAMP(pdu, ptime); - n = sendmsg(state->sockfd, &state->msg, 0); + n = sendmsg(state->sockfd, &state->msg, MSG_NOSIGNAL); if (n < 0 || n != (ssize_t)state->pdu_size) { spa_log_error(state->log, "sendmdg() failed: %m"); } diff --git a/spa/plugins/bluez5/a2dp-codec-aac.c b/spa/plugins/bluez5/a2dp-codec-aac.c index c14ff2af9af32e7fbc2f9f103b8dd4abe57e2535..46a87407b686d363126e830d9bbe5e29b4481922 100644 --- a/spa/plugins/bluez5/a2dp-codec-aac.c +++ b/spa/plugins/bluez5/a2dp-codec-aac.c @@ -34,7 +34,7 @@ #include <fdk-aac/aacdecoder_lib.h> #include "rtp.h" -#include "a2dp-codecs.h" +#include "media-codecs.h" static struct spa_log *log; static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.codecs.aac"); @@ -65,7 +65,7 @@ struct impl { int samplesize; }; -static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, +static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { static const a2dp_aac_t a2dp_aac = { @@ -98,7 +98,7 @@ static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, return sizeof(a2dp_aac); } -static const struct a2dp_codec_config +static const struct media_codec_config aac_frequencies[] = { { AAC_SAMPLING_FREQ_48000, 48000, 11 }, { AAC_SAMPLING_FREQ_44100, 44100, 10 }, @@ -114,7 +114,7 @@ aac_frequencies[] = { { AAC_SAMPLING_FREQ_8000, 8000, 0 }, }; -static const struct a2dp_codec_config +static const struct media_codec_config aac_channel_modes[] = { { AAC_CHANNELS_2, 2, 1 }, { AAC_CHANNELS_1, 1, 0 }, @@ -130,9 +130,9 @@ static int get_valid_aac_bitrate(a2dp_aac_t *conf) } } -static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info, + const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) { a2dp_aac_t conf; @@ -154,7 +154,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, else return -ENOTSUP; - if ((i = a2dp_codec_select_config(aac_frequencies, + if ((i = media_codec_select_config(aac_frequencies, SPA_N_ELEMENTS(aac_frequencies), AAC_GET_FREQUENCY(conf), info ? info->rate : A2DP_CODEC_DEFAULT_RATE @@ -162,7 +162,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return -ENOTSUP; AAC_SET_FREQUENCY(conf, aac_frequencies[i].config); - if ((i = a2dp_codec_select_config(aac_channel_modes, + if ((i = media_codec_select_config(aac_channel_modes, SPA_N_ELEMENTS(aac_channel_modes), conf.channels, info ? info->channels : A2DP_CODEC_DEFAULT_CHANNELS @@ -177,7 +177,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return sizeof(conf); } -static int codec_enum_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { @@ -206,11 +206,11 @@ static int codec_enum_config(const struct a2dp_codec *codec, uint32_t flags, spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0); choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]); i = 0; - for (size_t j = 0; j < SPA_N_ELEMENTS(aac_frequencies); j++) { - if (AAC_GET_FREQUENCY(conf) & aac_frequencies[j].config) { + SPA_FOR_EACH_ELEMENT_VAR(aac_frequencies, f) { + if (AAC_GET_FREQUENCY(conf) & f->config) { if (i++ == 0) - spa_pod_builder_int(b, aac_frequencies[j].value); - spa_pod_builder_int(b, aac_frequencies[j].value); + spa_pod_builder_int(b, f->value); + spa_pod_builder_int(b, f->value); } } if (i == 0) @@ -246,7 +246,7 @@ static int codec_enum_config(const struct a2dp_codec *codec, uint32_t flags, return *param == NULL ? -EIO : 1; } -static int codec_validate_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { @@ -272,14 +272,15 @@ static int codec_validate_config(const struct a2dp_codec *codec, uint32_t flags, if (!(conf.object_type & (AAC_OBJECT_TYPE_MPEG2_AAC_LC | AAC_OBJECT_TYPE_MPEG4_AAC_LC))) return -EINVAL; - - for (j = 0; j < SPA_N_ELEMENTS(aac_frequencies); ++j) { - if (AAC_GET_FREQUENCY(conf) & aac_frequencies[j].config) { - info->info.raw.rate = aac_frequencies[j].value; + j = 0; + SPA_FOR_EACH_ELEMENT_VAR(aac_frequencies, f) { + if (AAC_GET_FREQUENCY(conf) & f->config) { + info->info.raw.rate = f->value; + j++; break; } } - if (j == SPA_N_ELEMENTS(aac_frequencies)) + if (j == 0) return -EINVAL; if (conf.channels & AAC_CHANNELS_2) { @@ -296,7 +297,7 @@ static int codec_validate_config(const struct a2dp_codec *codec, uint32_t flags, return 0; } -static void *codec_init_props(const struct a2dp_codec *codec, uint32_t flags, const struct spa_dict *settings) +static void *codec_init_props(const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings) { struct props *p = calloc(1, sizeof(struct props)); const char *str; @@ -316,7 +317,7 @@ static void codec_clear_props(void *props) free(props); } -static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, +static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { @@ -609,7 +610,7 @@ static int codec_change_bitrate(struct impl *this, int new_bitrate) if (res != AACENC_OK) return -EINVAL; - return 0; + return this->cur_bitrate; } static int codec_reduce_bitpool(void *data) @@ -630,7 +631,7 @@ static void codec_set_log(struct spa_log *global_log) spa_log_topic_init(log, &log_topic); } -const struct a2dp_codec a2dp_codec_aac = { +const struct media_codec a2dp_codec_aac = { .id = SPA_BLUETOOTH_AUDIO_CODEC_AAC, .codec_id = A2DP_CODEC_MPEG24, .name = "aac", @@ -654,7 +655,7 @@ const struct a2dp_codec a2dp_codec_aac = { .set_log = codec_set_log, }; -A2DP_CODEC_EXPORT_DEF( +MEDIA_CODEC_EXPORT_DEF( "aac", &a2dp_codec_aac ); diff --git a/spa/plugins/bluez5/a2dp-codec-aptx.c b/spa/plugins/bluez5/a2dp-codec-aptx.c index 721b90599adbae76bd627b8d964f93625cd63011..6938e479c45732a6247e480b0616ff43c5b9b39d 100644 --- a/spa/plugins/bluez5/a2dp-codec-aptx.c +++ b/spa/plugins/bluez5/a2dp-codec-aptx.c @@ -35,7 +35,7 @@ #include <freeaptx.h> #include "rtp.h" -#include "a2dp-codecs.h" +#include "media-codecs.h" #define APTX_LL_LEVEL1(level) (((level) >> 8) & 0xFF) #define APTX_LL_LEVEL2(level) (((level) >> 0) & 0xFF) @@ -71,19 +71,19 @@ struct msbc_impl { sbc_t msbc; }; -static inline bool codec_is_hd(const struct a2dp_codec *codec) +static inline bool codec_is_hd(const struct media_codec *codec) { return codec->vendor.codec_id == APTX_HD_CODEC_ID && codec->vendor.vendor_id == APTX_HD_VENDOR_ID; } -static inline bool codec_is_ll(const struct a2dp_codec *codec) +static inline bool codec_is_ll(const struct media_codec *codec) { return (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL) || (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX); } -static inline size_t codec_get_caps_size(const struct a2dp_codec *codec) +static inline size_t codec_get_caps_size(const struct media_codec *codec) { if (codec_is_hd(codec)) return sizeof(a2dp_aptx_hd_t); @@ -93,7 +93,7 @@ static inline size_t codec_get_caps_size(const struct a2dp_codec *codec) return sizeof(a2dp_aptx_t); } -static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, +static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { size_t actual_conf_size = codec_get_caps_size(codec); @@ -119,7 +119,7 @@ static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, return actual_conf_size; } -static const struct a2dp_codec_config +static const struct media_codec_config aptx_frequencies[] = { { APTX_SAMPLING_FREQ_48000, 48000, 3 }, { APTX_SAMPLING_FREQ_44100, 44100, 2 }, @@ -127,9 +127,9 @@ aptx_frequencies[] = { { APTX_SAMPLING_FREQ_16000, 16000, 0 }, }; -static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info, + const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) { a2dp_aptx_t conf; @@ -145,7 +145,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, codec->vendor.codec_id != conf.info.codec_id) return -ENOTSUP; - if ((i = a2dp_codec_select_config(aptx_frequencies, + if ((i = media_codec_select_config(aptx_frequencies, SPA_N_ELEMENTS(aptx_frequencies), conf.frequency, info ? info->rate : A2DP_CODEC_DEFAULT_RATE @@ -163,9 +163,9 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return actual_conf_size; } -static int codec_select_config_ll(const struct a2dp_codec *codec, uint32_t flags, +static int codec_select_config_ll(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info, + const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) { a2dp_aptx_ll_ext_t conf = { 0 }; @@ -218,7 +218,7 @@ static int codec_select_config_ll(const struct a2dp_codec *codec, uint32_t flags return actual_conf_size; } -static int codec_enum_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { @@ -315,7 +315,7 @@ static int codec_get_block_size(void *data) return this->codesize; } -static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, +static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { @@ -458,7 +458,7 @@ static int codec_decode(void *data, * When connected as SRC to SNK, aptX-LL sink may send back mSBC data. */ -static int msbc_enum_config(const struct a2dp_codec *codec, uint32_t flags, +static int msbc_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { @@ -479,7 +479,7 @@ static int msbc_enum_config(const struct a2dp_codec *codec, uint32_t flags, return *param == NULL ? -EIO : 1; } -static int msbc_validate_config(const struct a2dp_codec *codec, uint32_t flags, +static int msbc_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { @@ -508,7 +508,7 @@ static int msbc_get_block_size(void *data) return MSBC_DECODED_SIZE; } -static void *msbc_init(const struct a2dp_codec *codec, uint32_t flags, +static void *msbc_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { @@ -610,7 +610,7 @@ static int msbc_decode(void *data, } -const struct a2dp_codec a2dp_codec_aptx = { +const struct media_codec a2dp_codec_aptx = { .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX, .codec_id = A2DP_CODEC_VENDOR, .vendor = { .vendor_id = APTX_VENDOR_ID, @@ -633,7 +633,7 @@ const struct a2dp_codec a2dp_codec_aptx = { }; -const struct a2dp_codec a2dp_codec_aptx_hd = { +const struct media_codec a2dp_codec_aptx_hd = { .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD, .codec_id = A2DP_CODEC_VENDOR, .vendor = { .vendor_id = APTX_HD_VENDOR_ID, @@ -671,7 +671,7 @@ const struct a2dp_codec a2dp_codec_aptx_hd = { .increase_bitpool = codec_increase_bitpool -const struct a2dp_codec a2dp_codec_aptx_ll_0 = { +const struct media_codec a2dp_codec_aptx_ll_0 = { APTX_LL_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL, .vendor = { .vendor_id = APTX_LL_VENDOR_ID, @@ -680,7 +680,7 @@ const struct a2dp_codec a2dp_codec_aptx_ll_0 = { .endpoint_name = "aptx_ll_0", }; -const struct a2dp_codec a2dp_codec_aptx_ll_1 = { +const struct media_codec a2dp_codec_aptx_ll_1 = { APTX_LL_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL, .vendor = { .vendor_id = APTX_LL_VENDOR_ID2, @@ -690,7 +690,7 @@ const struct a2dp_codec a2dp_codec_aptx_ll_1 = { }; /* Voice channel mSBC, not a real A2DP codec */ -static const struct a2dp_codec aptx_ll_msbc = { +static const struct media_codec aptx_ll_msbc = { .codec_id = A2DP_CODEC_VENDOR, .name = "aptx_ll_msbc", .description = "aptX-LL mSBC", @@ -715,7 +715,7 @@ static const struct spa_dict_item duplex_info_items[] = { }; static const struct spa_dict duplex_info = SPA_DICT_INIT_ARRAY(duplex_info_items); -const struct a2dp_codec a2dp_codec_aptx_ll_duplex_0 = { +const struct media_codec a2dp_codec_aptx_ll_duplex_0 = { APTX_LL_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, .vendor = { .vendor_id = APTX_LL_VENDOR_ID, @@ -726,7 +726,7 @@ const struct a2dp_codec a2dp_codec_aptx_ll_duplex_0 = { .info = &duplex_info, }; -const struct a2dp_codec a2dp_codec_aptx_ll_duplex_1 = { +const struct media_codec a2dp_codec_aptx_ll_duplex_1 = { APTX_LL_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, .vendor = { .vendor_id = APTX_LL_VENDOR_ID2, @@ -737,7 +737,7 @@ const struct a2dp_codec a2dp_codec_aptx_ll_duplex_1 = { .info = &duplex_info, }; -A2DP_CODEC_EXPORT_DEF( +MEDIA_CODEC_EXPORT_DEF( "aptx", &a2dp_codec_aptx_hd, &a2dp_codec_aptx, diff --git a/spa/plugins/bluez5/a2dp-codec-faststream.c b/spa/plugins/bluez5/a2dp-codec-faststream.c index 03911945c3b49d3ac5f08ecaf21f8356629e8220..a579eadd0682bdd9294588c7a882354346188469 100644 --- a/spa/plugins/bluez5/a2dp-codec-faststream.c +++ b/spa/plugins/bluez5/a2dp-codec-faststream.c @@ -36,7 +36,7 @@ #include <sbc/sbc.h> -#include "a2dp-codecs.h" +#include "media-codecs.h" struct impl { sbc_t sbc; @@ -51,7 +51,7 @@ struct duplex_impl { sbc_t sbc; }; -static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, +static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { const a2dp_faststream_t a2dp_faststream = { @@ -69,20 +69,20 @@ static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, return sizeof(a2dp_faststream); } -static const struct a2dp_codec_config +static const struct media_codec_config frequencies[] = { { FASTSTREAM_SINK_SAMPLING_FREQ_48000, 48000, 1 }, { FASTSTREAM_SINK_SAMPLING_FREQ_44100, 44100, 0 }, }; -static const struct a2dp_codec_config +static const struct media_codec_config duplex_frequencies[] = { { FASTSTREAM_SOURCE_SAMPLING_FREQ_16000, 16000, 0 }, }; -static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info, + const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) { a2dp_faststream_t conf; @@ -108,7 +108,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, if (codec->duplex_codec) conf.direction |= FASTSTREAM_DIRECTION_SOURCE; - if ((i = a2dp_codec_select_config(frequencies, + if ((i = media_codec_select_config(frequencies, SPA_N_ELEMENTS(frequencies), conf.sink_frequency, info ? info->rate : A2DP_CODEC_DEFAULT_RATE @@ -116,7 +116,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return -ENOTSUP; conf.sink_frequency = frequencies[i].config; - if ((i = a2dp_codec_select_config(duplex_frequencies, + if ((i = media_codec_select_config(duplex_frequencies, SPA_N_ELEMENTS(duplex_frequencies), conf.source_frequency, 16000 @@ -129,7 +129,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return sizeof(conf); } -static int codec_enum_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { @@ -209,7 +209,7 @@ static size_t ceil2(size_t v) return v; } -static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, +static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { @@ -372,7 +372,7 @@ static SPA_UNUSED int codec_decode(void *data, * When connected as SRC to SNK, FastStream sink may send back SBC data. */ -static int duplex_enum_config(const struct a2dp_codec *codec, uint32_t flags, +static int duplex_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { @@ -411,7 +411,7 @@ static int duplex_enum_config(const struct a2dp_codec *codec, uint32_t flags, return *param == NULL ? -EIO : 1; } -static int duplex_validate_config(const struct a2dp_codec *codec, uint32_t flags, +static int duplex_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { @@ -441,7 +441,7 @@ static int duplex_get_block_size(void *data) return 0; } -static void *duplex_init(const struct a2dp_codec *codec, uint32_t flags, +static void *duplex_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { @@ -577,7 +577,7 @@ static int duplex_decode(void *data, } /* Voice channel SBC, not a real A2DP codec */ -static const struct a2dp_codec duplex_codec = { +static const struct media_codec duplex_codec = { .codec_id = A2DP_CODEC_VENDOR, .name = "faststream_sbc", .description = "FastStream duplex SBC", @@ -614,7 +614,7 @@ static const struct a2dp_codec duplex_codec = { .reduce_bitpool = codec_reduce_bitpool, \ .increase_bitpool = codec_increase_bitpool -static const struct a2dp_codec a2dp_codec_faststream = { +const struct media_codec a2dp_codec_faststream = { FASTSTREAM_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM, .name = "faststream", @@ -625,7 +625,7 @@ static const struct spa_dict_item duplex_info_items[] = { }; static const struct spa_dict duplex_info = SPA_DICT_INIT_ARRAY(duplex_info_items); -const struct a2dp_codec a2dp_codec_faststream_duplex = { +const struct media_codec a2dp_codec_faststream_duplex = { FASTSTREAM_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX, .name = "faststream_duplex", @@ -633,7 +633,7 @@ const struct a2dp_codec a2dp_codec_faststream_duplex = { .info = &duplex_info, }; -A2DP_CODEC_EXPORT_DEF( +MEDIA_CODEC_EXPORT_DEF( "faststream", &a2dp_codec_faststream, &a2dp_codec_faststream_duplex diff --git a/spa/plugins/bluez5/a2dp-codec-lc3plus.c b/spa/plugins/bluez5/a2dp-codec-lc3plus.c index 40ca0e0db142fa809560a9599ecbc08d7ef2a1e3..289662442b9da247025e461c42b48ad3bbdef713 100644 --- a/spa/plugins/bluez5/a2dp-codec-lc3plus.c +++ b/spa/plugins/bluez5/a2dp-codec-lc3plus.c @@ -41,7 +41,7 @@ #endif #include "rtp.h" -#include "a2dp-codecs.h" +#include "media-codecs.h" #define BITRATE_MIN 96000 #define BITRATE_MAX 512000 @@ -86,7 +86,7 @@ struct impl { int32_t buf[2][LC3PLUS_MAX_SAMPLES]; }; -static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, +static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { const a2dp_lc3plus_hr_t a2dp_lc3plus_hr = { @@ -102,9 +102,9 @@ static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, return sizeof(a2dp_lc3plus_hr); } -static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info, + const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) { a2dp_lc3plus_hr_t conf; @@ -150,8 +150,8 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return sizeof(conf); } -static int codec_caps_preference_cmp(const struct a2dp_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, - const void *caps2, size_t caps2_size, const struct a2dp_codec_audio_info *info, const struct spa_dict *global_settings) +static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, + const void *caps2, size_t caps2_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings) { a2dp_lc3plus_hr_t conf1, conf2; a2dp_lc3plus_hr_t *conf; @@ -160,7 +160,7 @@ static int codec_caps_preference_cmp(const struct a2dp_codec *codec, uint32_t fl /* Order selected configurations by preference */ res1 = codec->select_config(codec, 0, caps1, caps1_size, info, NULL, (uint8_t *)&conf1); - res2 = codec->select_config(codec, 0, caps2, caps2_size, info, NULL, (uint8_t *)&conf2); + res2 = codec->select_config(codec, 0, caps2, caps2_size, info , NULL, (uint8_t *)&conf2); #define PREFER_EXPR(expr) \ do { \ @@ -190,7 +190,7 @@ static int codec_caps_preference_cmp(const struct a2dp_codec *codec, uint32_t fl #undef PREFER_BOOL } -static int codec_enum_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { @@ -263,7 +263,7 @@ static int codec_enum_config(const struct a2dp_codec *codec, uint32_t flags, return *param == NULL ? -EIO : 1; } -static int codec_validate_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { @@ -342,7 +342,7 @@ static bool check_mtu_vs_frame_dms(struct impl *this) return (size_t)this->mtu >= header_size + ceildiv(payload_size, max_fragments); } -static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, +static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { @@ -747,18 +747,20 @@ static SPA_UNUSED int codec_decode(void *data, static int codec_reduce_bitpool(void *data) { struct impl *this = data; - this->e.next_bitrate = this->bitrate * 3 / 4; - return 0; + this->e.next_bitrate = SPA_CLAMP(this->bitrate * 3 / 4, + BITRATE_MIN * this->channels, BITRATE_MAX * this->channels); + return this->e.next_bitrate; } static int codec_increase_bitpool(void *data) { struct impl *this = data; - this->e.next_bitrate = this->bitrate * 5 / 4; - return 0; + this->e.next_bitrate = SPA_CLAMP(this->bitrate * 5 / 4, + BITRATE_MIN * this->channels, BITRATE_MAX * this->channels); + return this->e.next_bitrate; } -const struct a2dp_codec a2dp_codec_lc3plus_hr = { +const struct media_codec a2dp_codec_lc3plus_hr = { .id = SPA_BLUETOOTH_AUDIO_CODEC_LC3PLUS_HR, .name = "lc3plus_hr", .codec_id = A2DP_CODEC_VENDOR, @@ -782,7 +784,7 @@ const struct a2dp_codec a2dp_codec_lc3plus_hr = { .increase_bitpool = codec_increase_bitpool }; -A2DP_CODEC_EXPORT_DEF( +MEDIA_CODEC_EXPORT_DEF( "lc3plus", &a2dp_codec_lc3plus_hr ); diff --git a/spa/plugins/bluez5/a2dp-codec-ldac.c b/spa/plugins/bluez5/a2dp-codec-ldac.c index 3906104e87ab4b03d7bfb7cfffceb5550c10c174..6649c772e2c1ba0cf8c94d326b1c504e4599d62c 100644 --- a/spa/plugins/bluez5/a2dp-codec-ldac.c +++ b/spa/plugins/bluez5/a2dp-codec-ldac.c @@ -40,7 +40,7 @@ #endif #include "rtp.h" -#include "a2dp-codecs.h" +#include "media-codecs.h" #define LDACBT_EQMID_AUTO -1 @@ -79,7 +79,7 @@ struct impl { int frame_count; }; -static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]) +static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { static const a2dp_ldac_t a2dp_ldac = { .info.vendor_id = LDAC_VENDOR_ID, @@ -97,7 +97,7 @@ static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, uint8 return sizeof(a2dp_ldac); } -static const struct a2dp_codec_config +static const struct media_codec_config ldac_frequencies[] = { { LDACBT_SAMPLING_FREQ_044100, 44100, 3 }, { LDACBT_SAMPLING_FREQ_048000, 48000, 2 }, @@ -105,16 +105,16 @@ ldac_frequencies[] = { { LDACBT_SAMPLING_FREQ_096000, 96000, 0 }, }; -static const struct a2dp_codec_config +static const struct media_codec_config ldac_channel_modes[] = { { LDACBT_CHANNEL_MODE_STEREO, 2, 2 }, { LDACBT_CHANNEL_MODE_DUAL_CHANNEL, 2, 1 }, { LDACBT_CHANNEL_MODE_MONO, 1, 0 }, }; -static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info, + const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) { a2dp_ldac_t conf; @@ -129,7 +129,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, codec->vendor.codec_id != conf.info.codec_id) return -ENOTSUP; - if ((i = a2dp_codec_select_config(ldac_frequencies, + if ((i = media_codec_select_config(ldac_frequencies, SPA_N_ELEMENTS(ldac_frequencies), conf.frequency, info ? info->rate : A2DP_CODEC_DEFAULT_RATE @@ -137,7 +137,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return -ENOTSUP; conf.frequency = ldac_frequencies[i].config; - if ((i = a2dp_codec_select_config(ldac_channel_modes, + if ((i = media_codec_select_config(ldac_channel_modes, SPA_N_ELEMENTS(ldac_channel_modes), conf.channel_mode, info ? info->channels : A2DP_CODEC_DEFAULT_CHANNELS @@ -150,7 +150,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return sizeof(conf); } -static int codec_enum_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { @@ -284,7 +284,7 @@ static int string_to_eqmid(const char * eqmid) return LDACBT_EQMID_AUTO; } -static void *codec_init_props(const struct a2dp_codec *codec, uint32_t flags, const struct spa_dict *settings) +static void *codec_init_props(const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings) { struct props *p = calloc(1, sizeof(struct props)); const char *str; @@ -385,7 +385,7 @@ static int codec_set_props(void *props, const struct spa_pod *param) return prev_eqmid != p->eqmid; } -static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, +static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { @@ -570,7 +570,7 @@ static int codec_encode(void *data, return src_used; } -const struct a2dp_codec a2dp_codec_ldac = { +const struct media_codec a2dp_codec_ldac = { .id = SPA_BLUETOOTH_AUDIO_CODEC_LDAC, .codec_id = A2DP_CODEC_VENDOR, .vendor = { .vendor_id = LDAC_VENDOR_ID, @@ -598,7 +598,7 @@ const struct a2dp_codec a2dp_codec_ldac = { .increase_bitpool = codec_increase_bitpool, }; -A2DP_CODEC_EXPORT_DEF( +MEDIA_CODEC_EXPORT_DEF( "ldac", &a2dp_codec_ldac ); diff --git a/spa/plugins/bluez5/a2dp-codec-opus.c b/spa/plugins/bluez5/a2dp-codec-opus.c index 7f1df12df331ed17d565a558ebdc3b9a2b3cf8e5..32ae2908695c5ad0072d1a9d76900275324ab4ab 100644 --- a/spa/plugins/bluez5/a2dp-codec-opus.c +++ b/spa/plugins/bluez5/a2dp-codec-opus.c @@ -44,7 +44,7 @@ #include <opus_multistream.h> #include "rtp.h" -#include "a2dp-codecs.h" +#include "media-codecs.h" static struct spa_log *log; static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.codecs.opus"); @@ -395,7 +395,7 @@ static void parse_settings(struct props *props, const struct spa_dict *settings) props->bidi_application = OPUS_APPLICATION_RESTRICTED_LOWDELAY; } -static int set_channel_conf(const struct a2dp_codec *codec, a2dp_opus_05_t *caps, const struct props *props) +static int set_channel_conf(const struct media_codec *codec, a2dp_opus_05_t *caps, const struct props *props) { /* * Predefined codec profiles @@ -469,7 +469,7 @@ static int set_channel_conf(const struct a2dp_codec *codec, a2dp_opus_05_t *caps return 0; } -static void get_default_bitrates(const struct a2dp_codec *codec, bool bidi, int *min, int *max, int *init) +static void get_default_bitrates(const struct media_codec *codec, bool bidi, int *min, int *max, int *init) { int tmp; @@ -514,7 +514,7 @@ static void get_default_bitrates(const struct a2dp_codec *codec, bool bidi, int }; } -static int get_mapping(const struct a2dp_codec *codec, const a2dp_opus_05_direction_t *conf, +static int get_mapping(const struct media_codec *codec, const a2dp_opus_05_direction_t *conf, bool use_surround_encoder, uint8_t *streams_ret, uint8_t *coupled_streams_ret, const uint8_t **surround_mapping, uint32_t *positions) { @@ -576,7 +576,7 @@ static int get_mapping(const struct a2dp_codec *codec, const a2dp_opus_05_direct return 0; } -static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, +static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { a2dp_opus_05_t a2dp_opus_05 = { @@ -613,9 +613,9 @@ static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, return sizeof(a2dp_opus_05); } -static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info, + const struct media_codec_audio_info *info, const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) { struct props props; @@ -715,8 +715,8 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return sizeof(conf); } -static int codec_caps_preference_cmp(const struct a2dp_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, - const void *caps2, size_t caps2_size, const struct a2dp_codec_audio_info *info, +static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, + const void *caps2, size_t caps2_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings) { a2dp_opus_05_t conf1, conf2, cap1, cap2; @@ -768,12 +768,12 @@ static int codec_caps_preference_cmp(const struct a2dp_codec *codec, uint32_t fl #undef PREFER_BOOL } -static bool is_duplex_codec(const struct a2dp_codec *codec) +static bool is_duplex_codec(const struct media_codec *codec) { return codec->id == 0; } -static bool use_surround_encoder(const struct a2dp_codec *codec, bool is_sink) +static bool use_surround_encoder(const struct media_codec *codec, bool is_sink) { if (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO) return false; @@ -784,11 +784,11 @@ static bool use_surround_encoder(const struct a2dp_codec *codec, bool is_sink) return !is_sink; } -static int codec_enum_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { - const bool surround_encoder = use_surround_encoder(codec, flags & A2DP_CODEC_FLAG_SINK); + const bool surround_encoder = use_surround_encoder(codec, flags & MEDIA_CODEC_FLAG_SINK); a2dp_opus_05_t conf; a2dp_opus_05_direction_t *dir; struct spa_pod_frame f[1]; @@ -823,11 +823,11 @@ static int codec_enum_config(const struct a2dp_codec *codec, uint32_t flags, return *param == NULL ? -EIO : 1; } -static int codec_validate_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { - const bool surround_encoder = use_surround_encoder(codec, flags & A2DP_CODEC_FLAG_SINK); + const bool surround_encoder = use_surround_encoder(codec, flags & MEDIA_CODEC_FLAG_SINK); const a2dp_opus_05_direction_t *dir1, *dir2; const a2dp_opus_05_t *conf; @@ -898,7 +898,7 @@ static int parse_frame_dms(int bitfield) } } -static void *codec_init_props(const struct a2dp_codec *codec, uint32_t flags, const struct spa_dict *settings) +static void *codec_init_props(const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings) { struct props *p; @@ -919,11 +919,11 @@ static void codec_clear_props(void *props) free(props); } -static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, +static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { - const bool surround_encoder = use_surround_encoder(codec, flags & A2DP_CODEC_FLAG_SINK); + const bool surround_encoder = use_surround_encoder(codec, flags & MEDIA_CODEC_FLAG_SINK); a2dp_opus_05_t *conf = config; a2dp_opus_05_direction_t *dir; struct impl *this = NULL; @@ -1360,7 +1360,6 @@ static void codec_set_log(struct spa_log *global_log) .codec_id = A2DP_CODEC_VENDOR, \ .vendor = { .vendor_id = OPUS_05_VENDOR_ID, \ .codec_id = OPUS_05_CODEC_ID }, \ - .fill_caps = codec_fill_caps, \ .select_config = codec_select_config, \ .enum_config = codec_enum_config, \ .validate_config = codec_validate_config, \ @@ -1380,44 +1379,50 @@ static void codec_set_log(struct spa_log *global_log) .start_decode = codec_start_decode, \ .decode = codec_decode -const struct a2dp_codec a2dp_codec_opus_05 = { +const struct media_codec a2dp_codec_opus_05 = { OPUS_05_COMMON_FULL_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05, .name = "opus_05", .description = "Opus", + .fill_caps = codec_fill_caps, }; -const struct a2dp_codec a2dp_codec_opus_05_51 = { +const struct media_codec a2dp_codec_opus_05_51 = { OPUS_05_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_51, .name = "opus_05_51", .description = "Opus 5.1 Surround", + .endpoint_name = "opus_05", + .fill_caps = NULL, }; -const struct a2dp_codec a2dp_codec_opus_05_71 = { +const struct media_codec a2dp_codec_opus_05_71 = { OPUS_05_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_71, .name = "opus_05_71", .description = "Opus 7.1 Surround", + .endpoint_name = "opus_05", + .fill_caps = NULL, }; /* Bidi return channel codec: doesn't have endpoints */ -const struct a2dp_codec a2dp_codec_opus_05_return = { +const struct media_codec a2dp_codec_opus_05_return = { OPUS_05_COMMON_FULL_DEFS, .id = 0, .name = "opus_05_duplex_bidi", .description = "Opus Duplex Bidi channel", }; -const struct a2dp_codec a2dp_codec_opus_05_duplex = { +const struct media_codec a2dp_codec_opus_05_duplex = { OPUS_05_COMMON_FULL_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX, .name = "opus_05_duplex", .description = "Opus Duplex", .duplex_codec = &a2dp_codec_opus_05_return, + .fill_caps = codec_fill_caps, }; -const struct a2dp_codec a2dp_codec_opus_05_pro = { +const struct media_codec a2dp_codec_opus_05_pro = { OPUS_05_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO, .name = "opus_05_pro", @@ -1425,9 +1430,11 @@ const struct a2dp_codec a2dp_codec_opus_05_pro = { .init_props = codec_init_props, .clear_props = codec_clear_props, .duplex_codec = &a2dp_codec_opus_05_return, + .endpoint_name = "opus_05_duplex", + .fill_caps = NULL, }; -A2DP_CODEC_EXPORT_DEF( +MEDIA_CODEC_EXPORT_DEF( "opus", &a2dp_codec_opus_05, &a2dp_codec_opus_05_51, diff --git a/spa/plugins/bluez5/a2dp-codec-sbc.c b/spa/plugins/bluez5/a2dp-codec-sbc.c index 35d55355a1c028bc765909932d3d15b794acb5f6..27a57bded3958bce656ef91bd72953315c66c02e 100644 --- a/spa/plugins/bluez5/a2dp-codec-sbc.c +++ b/spa/plugins/bluez5/a2dp-codec-sbc.c @@ -33,7 +33,7 @@ #include <sbc/sbc.h> #include "rtp.h" -#include "a2dp-codecs.h" +#include "media-codecs.h" #define MAX_FRAME_COUNT 16 @@ -51,7 +51,7 @@ struct impl { int max_bitpool; }; -static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, +static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { static const a2dp_sbc_t a2dp_sbc = { @@ -121,7 +121,7 @@ static uint8_t default_bitpool(uint8_t freq, uint8_t mode, bool xq) } -static const struct a2dp_codec_config +static const struct media_codec_config sbc_frequencies[] = { { SBC_SAMPLING_FREQ_48000, 48000, 3 }, { SBC_SAMPLING_FREQ_44100, 44100, 2 }, @@ -129,13 +129,13 @@ sbc_frequencies[] = { { SBC_SAMPLING_FREQ_16000, 16000, 0 }, }; -static const struct a2dp_codec_config +static const struct media_codec_config sbc_xq_frequencies[] = { { SBC_SAMPLING_FREQ_44100, 44100, 1 }, { SBC_SAMPLING_FREQ_48000, 48000, 0 }, }; -static const struct a2dp_codec_config +static const struct media_codec_config sbc_channel_modes[] = { { SBC_CHANNEL_MODE_JOINT_STEREO, 2, 3 }, { SBC_CHANNEL_MODE_STEREO, 2, 2 }, @@ -143,22 +143,22 @@ sbc_channel_modes[] = { { SBC_CHANNEL_MODE_MONO, 1, 0 }, }; -static const struct a2dp_codec_config +static const struct media_codec_config sbc_xq_channel_modes[] = { { SBC_CHANNEL_MODE_DUAL_CHANNEL, 2, 2 }, { SBC_CHANNEL_MODE_JOINT_STEREO, 2, 1 }, { SBC_CHANNEL_MODE_STEREO, 2, 0 }, }; -static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info, + const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) { a2dp_sbc_t conf; int bitpool, i; size_t n; - const struct a2dp_codec_config *configs; + const struct media_codec_config *configs; bool xq = false; @@ -176,7 +176,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, configs = sbc_frequencies; n = SPA_N_ELEMENTS(sbc_frequencies); } - if ((i = a2dp_codec_select_config(configs, n, conf.frequency, + if ((i = media_codec_select_config(configs, n, conf.frequency, info ? info->rate : A2DP_CODEC_DEFAULT_RATE )) < 0) return -ENOTSUP; @@ -189,7 +189,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, configs = sbc_channel_modes; n = SPA_N_ELEMENTS(sbc_channel_modes); } - if ((i = a2dp_codec_select_config(configs, n, conf.channel_mode, + if ((i = media_codec_select_config(configs, n, conf.channel_mode, info ? info->channels : A2DP_CODEC_DEFAULT_CHANNELS )) < 0) return -ENOTSUP; @@ -229,8 +229,8 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return sizeof(conf); } -static int codec_caps_preference_cmp(const struct a2dp_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, - const void *caps2, size_t caps2_size, const struct a2dp_codec_audio_info *info, const struct spa_dict *global_settings) +static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, + const void *caps2, size_t caps2_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings) { a2dp_sbc_t conf1, conf2; a2dp_sbc_t *conf; @@ -275,7 +275,7 @@ static int codec_caps_preference_cmp(const struct a2dp_codec *codec, uint32_t fl #undef PREFER_BOOL } -static int codec_validate_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { @@ -356,7 +356,7 @@ static int codec_set_bitpool(struct impl *this, int bitpool) return this->sbc.bitpool; } -static int codec_enum_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { @@ -453,7 +453,7 @@ static int codec_get_block_size(void *data) return this->codesize; } -static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, +static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { @@ -638,7 +638,7 @@ static int codec_decode(void *data, return res; } -const struct a2dp_codec a2dp_codec_sbc = { +const struct media_codec a2dp_codec_sbc = { .id = SPA_BLUETOOTH_AUDIO_CODEC_SBC, .codec_id = A2DP_CODEC_SBC, .name = "sbc", @@ -660,7 +660,7 @@ const struct a2dp_codec a2dp_codec_sbc = { .increase_bitpool = codec_increase_bitpool, }; -const struct a2dp_codec a2dp_codec_sbc_xq = { +const struct media_codec a2dp_codec_sbc_xq = { .id = SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ, .codec_id = A2DP_CODEC_SBC, .name = "sbc_xq", @@ -682,7 +682,7 @@ const struct a2dp_codec a2dp_codec_sbc_xq = { .increase_bitpool = codec_increase_bitpool, }; -A2DP_CODEC_EXPORT_DEF( +MEDIA_CODEC_EXPORT_DEF( "sbc", &a2dp_codec_sbc, &a2dp_codec_sbc_xq diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index c38656bbbd5b4e9d188e55c5910b593204c2dcb7..eed23f66e4fdab4b3598a183c014284cd9770ee2 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -51,6 +51,9 @@ #include <libusb.h> #endif +#include "modemmanager.h" +#include "upower.h" + static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.native"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic @@ -60,12 +63,39 @@ static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.native"); #define HFP_CODEC_SWITCH_INITIAL_TIMEOUT_MSEC 5000 #define HFP_CODEC_SWITCH_TIMEOUT_MSEC 20000 +#define INTERNATIONAL_NUMBER 145 +#define NATIONAL_NUMBER 129 + +#define MAX_HF_INDICATORS 16 + enum { HFP_AG_INITIAL_CODEC_SETUP_NONE = 0, HFP_AG_INITIAL_CODEC_SETUP_SEND, HFP_AG_INITIAL_CODEC_SETUP_WAIT }; +#define CIND_INDICATORS "(\"service\",(0-1)),(\"call\",(0-1)),(\"callsetup\",(0-3)),(\"callheld\",(0-2)),(\"signal\",(0-5)),(\"roam\",(0-1)),\"battchg\",(0-5))" +enum { + CIND_SERVICE = 1, + CIND_CALL, + CIND_CALLSETUP, + CIND_CALLHELD, + CIND_SIGNAL, + CIND_ROAM, + CIND_BATTERY_LEVEL, + CIND_MAX +}; + +struct modem { + bool network_has_service; + unsigned int signal_strength; + bool network_is_roaming; + char *operator_name; + char *own_number; + bool active_call; + unsigned int call_setup; +}; + struct impl { struct spa_bt_backend this; @@ -78,7 +108,7 @@ struct impl { struct spa_dbus *dbus; DBusConnection *conn; -#define DEFAULT_ENABLED_PROFILES (SPA_BT_PROFILE_HSP_HS | SPA_BT_PROFILE_HFP_AG) +#define DEFAULT_ENABLED_PROFILES (SPA_BT_PROFILE_HFP_HF | SPA_BT_PROFILE_HFP_AG) enum spa_bt_profile enabled_profiles; struct spa_source sco; @@ -87,6 +117,13 @@ struct impl { struct spa_list rfcomm_list; unsigned int defer_setup_enabled:1; + + struct modem modem; + unsigned int battery_level; + + void *modemmanager; + struct spa_source *ring_timer; + void *upower; }; struct transport_data { @@ -142,9 +179,13 @@ struct rfcomm { unsigned int hfp_ag_initial_codec_setup:2; unsigned int cind_call_active:1; unsigned int cind_call_notify:1; + unsigned int extended_error_reporting:1; + unsigned int clip_notify:1; enum hfp_hf_state hf_state; enum hsp_hs_state hs_state; unsigned int codec; + uint32_t cind_enabled_indicators; + char *hf_indicators[MAX_HF_INDICATORS]; #endif }; @@ -233,6 +274,11 @@ static void volume_sync_stop_timer(struct rfcomm *rfcomm); static void rfcomm_free(struct rfcomm *rfcomm) { codec_switch_stop_timer(rfcomm); + for (int i = 0; i < MAX_HF_INDICATORS; i++) { + if (rfcomm->hf_indicators[i]) { + free(rfcomm->hf_indicators[i]); + } + } spa_list_remove(&rfcomm->link); if (rfcomm->path) free(rfcomm->path); @@ -259,6 +305,7 @@ static void rfcomm_free(struct rfcomm *rfcomm) #define RFCOMM_MESSAGE_MAX_LENGTH 256 +/* from HF/HS to AG */ SPA_PRINTF_FUNC(2, 3) static ssize_t rfcomm_send_cmd(const struct rfcomm *rfcomm, const char *format, ...) { @@ -279,7 +326,14 @@ static ssize_t rfcomm_send_cmd(const struct rfcomm *rfcomm, const char *format, spa_log_debug(backend->log, "RFCOMM >> %s", message); - message[len] = '\n'; + /* + * The format of an AT command from the HF to the AG shall be: <AT command><cr> + * - HFP 1.8, 4.34.1 + * + * The format for a command from the HS to the AG is thus: AT<cmd>=<value><cr> + * - HSP 1.2, 4.8.1 + */ + message[len] = '\r'; /* `message` is no longer null-terminated */ len = write(rfcomm->source.fd, message, len + 1); @@ -293,6 +347,7 @@ static ssize_t rfcomm_send_cmd(const struct rfcomm *rfcomm, const char *format, return len; } +/* from AG to HF/HS */ SPA_PRINTF_FUNC(2, 3) static ssize_t rfcomm_send_reply(const struct rfcomm *rfcomm, const char *format, ...) { @@ -313,6 +368,18 @@ static ssize_t rfcomm_send_reply(const struct rfcomm *rfcomm, const char *format spa_log_debug(backend->log, "RFCOMM >> %s", &message[2]); + /* + * The format of the OK code from the AG to the HF shall be: <cr><lf>OK<cr><lf> + * The format of the generic ERROR code from the AG to the HF shall be: <cr><lf>ERROR<cr><lf> + * The format of an unsolicited result code from the AG to the HF shall be: <cr><lf><result code><cr><lf> + * - HFP 1.8, 4.34.1 + * + * If the command is processed successfully, the resulting response from the AG to the HS is: <cr><lf>OK<cr><lf> + * If the command is not processed successfully, or is not recognized, + * the resulting response from the AG to the HS is: <cr><lf>ERROR<cr><lf> + * The format for an unsolicited result code (such as RING) from the AG to the HS is: <cr><lf><result code><cr><lf> + * - HSP 1.2, 4.8.1 + */ message[0] = '\r'; message[1] = '\n'; message[len + 2] = '\r'; @@ -330,6 +397,14 @@ static ssize_t rfcomm_send_reply(const struct rfcomm *rfcomm, const char *format return len; } +static void rfcomm_send_error(const struct rfcomm *rfcomm, enum cmee_error error) +{ + if (rfcomm->extended_error_reporting) + rfcomm_send_reply(rfcomm, "+CME ERROR: %d", error); + else + rfcomm_send_reply(rfcomm, "ERROR"); +} + static bool rfcomm_volume_enabled(struct rfcomm *rfcomm) { return rfcomm->device != NULL @@ -699,6 +774,7 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) unsigned int selected_codec; unsigned int indicator; unsigned int indicator_value; + unsigned int value; int xapl_vendor; int xapl_product; int xapl_features; @@ -740,6 +816,7 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) } /* send reply to HF with the features supported by Audio Gateway (=computer) */ + ag_features |= mm_supported_features(); ag_features |= SPA_BT_HFP_AG_FEATURE_HF_INDICATORS; rfcomm_send_reply(rfcomm, "+BRSF: %u", ag_features); rfcomm_send_reply(rfcomm, "OK"); @@ -747,35 +824,32 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) /* retrieve supported codecs */ /* response has the form AT+BAC=<codecID1>,<codecID2>,<codecIDx> strategy: split the string into tokens */ - static const char separators[] = "=,"; char* token; int cntr = 0; - token = strtok (buf, separators); - while (token != NULL) - { + while ((token = strsep(&buf, "=,"))) { + unsigned int codec_id; + /* skip token 0 i.e. the "AT+BAC=" part */ - if (cntr > 0) { - int codec_id; - sscanf (token, "%u", &codec_id); + if (cntr > 0 && sscanf(token, "%u", &codec_id) == 1) { spa_log_debug(backend->log, "RFCOMM AT+BAC found codec %u", codec_id); if (codec_id == HFP_AUDIO_CODEC_MSBC) { rfcomm->msbc_supported_by_hfp = true; spa_log_debug(backend->log, "RFCOMM headset supports mSBC codec"); } } - /* get next token */ - token = strtok (NULL, separators); cntr++; } rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+CIND=?")) { - rfcomm_send_reply(rfcomm, "+CIND:(\"service\",(0-1)),(\"call\",(0-1)),(\"callsetup\",(0-3)),(\"callheld\",(0-2))"); + rfcomm_send_reply(rfcomm, "+CIND:%s", CIND_INDICATORS); rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+CIND?")) { - rfcomm_send_reply(rfcomm, "+CIND: 0,%d,0,0", rfcomm->cind_call_active); + rfcomm_send_reply(rfcomm, "+CIND: %d,%d,%d,0,%d,%d,%d", backend->modem.network_has_service, + backend->modem.active_call, backend->modem.call_setup, backend->modem.signal_strength, + backend->modem.network_is_roaming, backend->battery_level); rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+CMER")) { int mode, keyp, disp, ind; @@ -808,8 +882,13 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) } } else if (!rfcomm->slc_configured) { spa_log_warn(backend->log, "RFCOMM receive command before SLC completed: %s", buf); - rfcomm_send_reply(rfcomm, "ERROR"); - return false; + rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); + return true; + + /* ***** + * Following commands requires a Service Level Connection + * ***** */ + } else if (sscanf(buf, "AT+BCS=%u", &selected_codec) == 1) { /* parse BCS(=Bluetooth Codec Selection) reply */ bool was_switching_codec = rfcomm->hfp_ag_switching_codec && (rfcomm->device != NULL); @@ -820,7 +899,7 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) if (selected_codec != HFP_AUDIO_CODEC_CVSD && selected_codec != HFP_AUDIO_CODEC_MSBC) { spa_log_warn(backend->log, "unsupported codec negotiation: %d", selected_codec); - rfcomm_send_reply(rfcomm, "ERROR"); + rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); if (was_switching_codec) spa_bt_device_emit_codec_switched(rfcomm->device, -EIO); return true; @@ -838,7 +917,7 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) if (rfcomm->transport == NULL) { spa_log_warn(backend->log, "can't create transport: %m"); // TODO: We should manage the missing transport - rfcomm_send_reply(rfcomm, "ERROR"); + rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); if (was_switching_codec) spa_bt_device_emit_codec_switched(rfcomm->device, -ENOMEM); return true; @@ -851,10 +930,105 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) if (was_switching_codec) spa_bt_device_emit_codec_switched(rfcomm->device, 0); } else if (spa_strstartswith(buf, "AT+BIA=")) { - /* We only support 'call' indicator, which HFP 4.35.1 defines as - always active (assuming CMER enabled it), so we don't need to - parse anything here. */ + /* retrieve indicators activation + * form: AT+BIA=[indrep1],[indrep2],[indrepx] */ + char *str = buf + 7; + unsigned int ind = 1; + + while (*str && ind < CIND_MAX && *str != '\r' && *str != '\n') { + if (*str == ',') { + ind++; + goto next_indicator; + } + + /* Ignore updates to mandantory indicators which are always ON */ + if (ind == CIND_CALL || ind == CIND_CALLSETUP || ind == CIND_CALLHELD) + goto next_indicator; + + switch (*str) { + case '0': + rfcomm->cind_enabled_indicators &= ~(1 << ind); + break; + case '1': + rfcomm->cind_enabled_indicators |= (1 << ind); + break; + default: + spa_log_warn(backend->log, "Unsupported entry in %s: %c", buf, *str); + } +next_indicator: + str++; + } + + rfcomm_send_reply(rfcomm, "OK"); + } else if (spa_strstartswith(buf, "AT+CLCC")) { + struct spa_list *calls; + struct call *call; + unsigned int type; + + if (backend->modemmanager) { + calls = mm_get_calls(backend->modemmanager); + spa_list_for_each(call, calls, link) { + if (!call->number) { + rfcomm_send_reply(rfcomm, "+CLCC: %u,%u,%u,0,%u", call->index, call->direction, call->state, call->multiparty); + } else { + if (spa_strstartswith(call->number, "+")) + type = INTERNATIONAL_NUMBER; + else + type = NATIONAL_NUMBER; + rfcomm_send_reply(rfcomm, "+CLCC: %u,%u,%u,0,%u,\"%s\",%d", call->index, call->direction, call->state, + call->multiparty, call->number, type); + } + } + } + + rfcomm_send_reply(rfcomm, "OK"); + } else if (sscanf(buf, "AT+CLIP=%u", &value) == 1) { + if (value > 1) { + spa_log_debug(backend->log, "Unsupported AT+CLIP value: %u", value); + rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); + return true; + } + + rfcomm->clip_notify = value; + rfcomm_send_reply(rfcomm, "OK"); + } else if (sscanf(buf, "AT+CMEE=%u", &value) == 1) { + if (value > 1) { + spa_log_debug(backend->log, "Unsupported AT+CMEE value: %u", value); + rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); + return true; + } + + rfcomm->extended_error_reporting = value; rfcomm_send_reply(rfcomm, "OK"); + } else if (spa_strstartswith(buf, "AT+CNUM")) { + if (backend->modem.own_number) { + unsigned int type; + if (spa_strstartswith(backend->modem.own_number, "+")) + type = INTERNATIONAL_NUMBER; + else + type = NATIONAL_NUMBER; + rfcomm_send_reply(rfcomm, "+CNUM: ,\"%s\",%u", backend->modem.own_number, type); + } + rfcomm_send_reply(rfcomm, "OK"); + } else if (spa_strstartswith(buf, "AT+COPS=")) { + unsigned int mode, val; + + if (sscanf(buf, "AT+COPS=%u,%u", &mode, &val) != 2 || + mode != 3 || val != 0) { + rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); + } else { + rfcomm_send_reply(rfcomm, "OK"); + } + } else if (spa_strstartswith(buf, "AT+COPS?")) { + if (!backend->modem.network_has_service) { + rfcomm_send_error(rfcomm, CMEE_NO_NETWORK_SERVICE); + } else { + if (backend->modem.operator_name) + rfcomm_send_reply(rfcomm, "+COPS: 0,0,\"%s\"", backend->modem.operator_name); + else + rfcomm_send_reply(rfcomm, "+COPS: 0,,"); + rfcomm_send_reply(rfcomm, "OK"); + } } else if (sscanf(buf, "AT+VGM=%u", &gain) == 1) { if (gain <= SPA_BT_VOLUME_HS_MAX) { if (!rfcomm->broken_mic_hw_volume) @@ -862,7 +1036,7 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) rfcomm_send_reply(rfcomm, "OK"); } else { spa_log_debug(backend->log, "RFCOMM receive unsupported VGM gain: %s", buf); - rfcomm_send_reply(rfcomm, "ERROR"); + rfcomm_send_error(rfcomm, CMEE_OPERATION_NOT_ALLOWED); } } else if (sscanf(buf, "AT+VGS=%u", &gain) == 1) { if (gain <= SPA_BT_VOLUME_HS_MAX) { @@ -870,7 +1044,7 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) rfcomm_send_reply(rfcomm, "OK"); } else { spa_log_debug(backend->log, "RFCOMM receive unsupported VGS gain: %s", buf); - rfcomm_send_reply(rfcomm, "ERROR"); + rfcomm_send_error(rfcomm, CMEE_OPERATION_NOT_ALLOWED); } } else if (spa_strstartswith(buf, "AT+BIND=?")) { rfcomm_send_reply(rfcomm, "+BIND: (2)"); @@ -909,6 +1083,69 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) } else if (spa_strstartswith(buf, "AT+APLSIRI?")) { // This command is sent when we activate Apple extensions rfcomm_send_reply(rfcomm, "OK"); + } else if (!mm_is_available(backend->modemmanager)) { + spa_log_warn(backend->log, "RFCOMM receive command but modem not available: %s", buf); + rfcomm_send_error(rfcomm, CMEE_NO_CONNECTION_TO_PHONE); + return true; + + /* ***** + * Following commands requires a Service Level Connection + * and acces to a modem + * ***** */ + + } else if (!backend->modem.network_has_service) { + spa_log_warn(backend->log, "RFCOMM receive command but network not available: %s", buf); + rfcomm_send_error(rfcomm, CMEE_NO_NETWORK_SERVICE); + return true; + + /* ***** + * Following commands requires a Service Level Connection, + * acces to a modem and to the network + * ***** */ + + } else if (spa_strstartswith(buf, "ATA")) { + enum cmee_error error; + + if (!mm_answer_call(backend->modemmanager, rfcomm, &error)) { + rfcomm_send_error(rfcomm, error); + return true; + } + } else if (spa_strstartswith(buf, "ATD")) { + char number[31], sep; + enum cmee_error error; + + if (sscanf(buf, "ATD%30[^;]%c", number, &sep) != 2 || sep != ';') { + spa_log_debug(backend->log, "Failed to parse ATD: \"%s\"", buf); + rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); + return true; + } + + if (!mm_do_call(backend->modemmanager, number, rfcomm, &error)) { + rfcomm_send_error(rfcomm, error); + return true; + } + } else if (spa_strstartswith(buf, "AT+CHUP")) { + enum cmee_error error; + + if (!mm_hangup_call(backend->modemmanager, rfcomm, &error)) { + rfcomm_send_error(rfcomm, error); + return true; + } + } else if (spa_strstartswith(buf, "AT+VTS=")) { + char *dtmf; + enum cmee_error error; + + dtmf = calloc(1, 2); + if (sscanf(buf, "AT+VTS=%1s", dtmf) != 1) { + spa_log_debug(backend->log, "Failed to parse AT+VTS: \"%s\"", buf); + rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); + return true; + } + + if (!mm_send_dtmf(backend->modemmanager, dtmf, rfcomm, &error)) { + rfcomm_send_error(rfcomm, error); + return true; + } } else { return false; } @@ -918,29 +1155,16 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* buf) { - static const char separators[] = "\r\n:"; - struct impl *backend = rfcomm->backend; - unsigned int features; - unsigned int gain; - unsigned int selected_codec; + unsigned int features, gain, selected_codec, indicator, value; char* token; - token = strtok(buf, separators); - while (token != NULL) - { - if (spa_strstartswith(token, "+BRSF")) { - /* get next token */ - token = strtok(NULL, separators); - features = atoi(token); + while ((token = strsep(&buf, "\r\n"))) { + if (sscanf(token, "+BRSF:%u", &features) == 1) { if (((features & (SPA_BT_HFP_AG_FEATURE_CODEC_NEGOTIATION)) != 0) && rfcomm->msbc_supported_by_hfp) rfcomm->codec_negotiation_supported = true; - } else if (spa_strstartswith(token, "+BCS") && rfcomm->codec_negotiation_supported) { - /* get next token */ - token = strtok(NULL, separators); - selected_codec = atoi(token); - + } else if (sscanf(token, "+BCS:%u", &selected_codec) == 1 && rfcomm->codec_negotiation_supported) { if (selected_codec != HFP_AUDIO_CODEC_CVSD && selected_codec != HFP_AUDIO_CODEC_MSBC) { spa_log_warn(backend->log, "unsupported codec negotiation: %d", selected_codec); } else { @@ -965,29 +1189,59 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* buf) } } } - } else if (spa_strstartswith(token, "+CIND")) { - /* get next token and discard it */ - token = strtok(NULL, separators); - } else if (spa_strstartswith(token, "+VGM")) { - /* get next token */ - token = strtok(NULL, separators); - gain = atoi(token); - + } else if (sscanf(token, "+VGM%*1[:=]%u", &gain) == 1) { if (gain <= SPA_BT_VOLUME_HS_MAX) { rfcomm_emit_volume_changed(rfcomm, SPA_BT_VOLUME_ID_TX, gain); } else { spa_log_debug(backend->log, "RFCOMM receive unsupported VGM gain: %s", token); } - } else if (spa_strstartswith(token, "+VGS")) { - /* get next token */ - token = strtok(NULL, separators); - gain = atoi(token); - + } else if (sscanf(token, "+VGS%*1[:=]%u", &gain) == 1) { if (gain <= SPA_BT_VOLUME_HS_MAX) { rfcomm_emit_volume_changed(rfcomm, SPA_BT_VOLUME_ID_RX, gain); } else { spa_log_debug(backend->log, "RFCOMM receive unsupported VGS gain: %s", token); } + } else if (spa_strstartswith(token, "+CIND: (")) { + uint8_t i = 1; + while (strstr(token, "\"")) { + token += strcspn(token, "\"") + 1; + token[strcspn(token, "\"")] = 0; + rfcomm->hf_indicators[i] = strdup(token); + token += strcspn(token, "\"") + 1; + i++; + if (i == MAX_HF_INDICATORS) { + break; + } + } + } else if (spa_strstartswith(token, "+CIND: ")) { + token[strcspn(token, "\r")] = 0; + token[strcspn(token, "\n")] = 0; + token += strlen("+CIND: "); + uint8_t i = 1; + while (strlen(token)) { + if (i >= MAX_HF_INDICATORS || !rfcomm->hf_indicators[i]) { + break; + } + token[strcspn(token, ",")] = 0; + spa_log_info(backend->log, "AG indicator state: %s = %i", rfcomm->hf_indicators[i], atoi(token)); + + if (spa_streq(rfcomm->hf_indicators[i], "battchg")) { + spa_bt_device_report_battery_level(rfcomm->device, atoi(token) * 100 / 5); + } + + token += strcspn(token, "\0") + 1; + i++; + } + } else if (sscanf(token, "+CIEV: %u,%u", &indicator, &value) == 2) { + if (indicator >= MAX_HF_INDICATORS || !rfcomm->hf_indicators[indicator]) { + spa_log_warn(backend->log, "indicator %u has not been registered, ignoring", indicator); + } else { + spa_log_info(backend->log, "AG indicator update: %s = %u", rfcomm->hf_indicators[indicator], value); + + if (spa_streq(rfcomm->hf_indicators[indicator], "battchg")) { + spa_bt_device_report_battery_level(rfcomm->device, value * 100 / 5); + } + } } else if (spa_strstartswith(token, "OK")) { switch(rfcomm->hf_state) { case hfp_hf_brsf: @@ -1008,7 +1262,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* buf) rfcomm->hf_state = hfp_hf_cind2; break; case hfp_hf_cind2: - rfcomm_send_cmd(rfcomm, "AT+CMER=3,0,0,0"); + rfcomm_send_cmd(rfcomm, "AT+CMER=3,0,0,1"); rfcomm->hf_state = hfp_hf_cmer; break; case hfp_hf_cmer: @@ -1041,8 +1295,6 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* buf) break; } } - /* get next token */ - token = strtok(NULL, separators); } return true; @@ -1097,7 +1349,7 @@ static void rfcomm_event(struct spa_source *source) if (!res) { spa_log_debug(backend->log, "RFCOMM received unsupported command: %s", buf); - rfcomm_send_reply(rfcomm, "ERROR"); + rfcomm_send_error(rfcomm, CMEE_OPERATION_NOT_SUPPORTED); } } } @@ -1866,6 +2118,9 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag rfcomm->source.fd = fd; rfcomm->source.mask = SPA_IO_IN; rfcomm->source.rmask = 0; + /* By default all indicators are enabled */ + rfcomm->cind_enabled_indicators = 0xFFFFFFFF; + memset(rfcomm->hf_indicators, 0, sizeof rfcomm->hf_indicators); for (int i = 0; i < SPA_BT_VOLUME_ID_TERM; ++i) { if (rfcomm->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) @@ -2250,6 +2505,161 @@ static int backend_native_unregister_profiles(void *data) return 0; } +static void send_ciev_for_each_rfcomm(struct impl *backend, int indicator, int value) +{ + struct rfcomm *rfcomm; + + spa_list_for_each(rfcomm, &backend->rfcomm_list, link) { + if (rfcomm->slc_configured && + ((indicator == CIND_CALL || indicator == CIND_CALLSETUP || indicator == CIND_CALLHELD) || + (rfcomm->cind_call_notify && (rfcomm->cind_enabled_indicators & (1 << indicator))))) + rfcomm_send_reply(rfcomm, "+CIEV: %d,%d", indicator, value); + } +} + +static void ring_timer_event(void *data, uint64_t expirations) +{ + struct impl *backend = data; + const char *number; + unsigned int type; + struct timespec ts; + const uint64_t timeout = 1 * SPA_NSEC_PER_SEC; + struct rfcomm *rfcomm; + + number = mm_get_incoming_call_number(backend->modemmanager); + if (number) { + if (spa_strstartswith(number, "+")) + type = INTERNATIONAL_NUMBER; + else + type = NATIONAL_NUMBER; + } + + ts.tv_sec = timeout / SPA_NSEC_PER_SEC; + ts.tv_nsec = timeout % SPA_NSEC_PER_SEC; + spa_loop_utils_update_timer(backend->loop_utils, backend->ring_timer, &ts, NULL, false); + + spa_list_for_each(rfcomm, &backend->rfcomm_list, link) { + if (rfcomm->slc_configured) { + rfcomm_send_reply(rfcomm, "RING"); + if (rfcomm->clip_notify && number) + rfcomm_send_reply(rfcomm, "+CLIP: \"%s\",%u", number, type); + } + } +} + +static void set_call_active(bool active, void *user_data) +{ + struct impl *backend = user_data; + + if (backend->modem.active_call != active) { + backend->modem.active_call = active; + send_ciev_for_each_rfcomm(backend, CIND_CALL, active); + } +} + +static void set_call_setup(enum call_setup value, void *user_data) +{ + struct impl *backend = user_data; + enum call_setup old = backend->modem.call_setup; + + if (backend->modem.call_setup != value) { + backend->modem.call_setup = value; + send_ciev_for_each_rfcomm(backend, CIND_CALLSETUP, value); + } + + if (value == CIND_CALLSETUP_INCOMING) { + if (backend->ring_timer == NULL) + backend->ring_timer = spa_loop_utils_add_timer(backend->loop_utils, ring_timer_event, backend); + + if (backend->ring_timer == NULL) { + spa_log_warn(backend->log, "Failed to create ring timer"); + return; + } + + ring_timer_event(backend, 0); + } else if (old == CIND_CALLSETUP_INCOMING) { + spa_loop_utils_update_timer(backend->loop_utils, backend->ring_timer, NULL, NULL, false); + } +} + +void set_battery_level(unsigned int level, void *user_data) +{ + struct impl *backend = user_data; + + if (backend->battery_level != level) { + backend->battery_level = level; + send_ciev_for_each_rfcomm(backend, CIND_BATTERY_LEVEL, level); + } +} + +static void set_modem_operator_name(const char *name, void *user_data) +{ + struct impl *backend = user_data; + + if (backend->modem.operator_name) { + free(backend->modem.operator_name); + backend->modem.operator_name = NULL; + } + + if (name) + backend->modem.operator_name = strdup(name); +} + +static void set_modem_roaming(bool is_roaming, void *user_data) +{ + struct impl *backend = user_data; + + if (backend->modem.network_is_roaming != is_roaming) { + backend->modem.network_is_roaming = is_roaming; + send_ciev_for_each_rfcomm(backend, CIND_ROAM, is_roaming); + } +} + +static void set_modem_own_number(const char *number, void *user_data) +{ + struct impl *backend = user_data; + + if (backend->modem.own_number) { + free(backend->modem.own_number); + backend->modem.own_number = NULL; + } + + if (number) + backend->modem.own_number = strdup(number); +} + +static void set_modem_service(bool available, void *user_data) +{ + struct impl *backend = user_data; + + if (backend->modem.network_has_service != available) { + backend->modem.network_has_service = available; + send_ciev_for_each_rfcomm(backend, CIND_SERVICE, available); + } +} + +static void set_modem_signal_strength(unsigned int strength, void *user_data) +{ + struct impl *backend = user_data; + + if (backend->modem.signal_strength != strength) { + backend->modem.signal_strength = strength; + send_ciev_for_each_rfcomm(backend, CIND_SIGNAL, strength); + } +} + +static void send_cmd_result(bool success, enum cmee_error error, void *user_data) +{ + struct rfcomm *rfcomm = user_data; + + if (success) { + rfcomm_send_reply(rfcomm, "OK"); + return; + } + + rfcomm_send_error(rfcomm, error); +} + static int backend_native_free(void *data) { struct impl *backend = data; @@ -2258,6 +2668,19 @@ static int backend_native_free(void *data) sco_close(backend); + if (backend->modemmanager) { + mm_unregister(backend); + backend->modemmanager = NULL; + } + + if (backend->upower) { + upower_unregister(backend->upower); + backend->upower = NULL; + } + + if (backend->ring_timer) + spa_loop_utils_destroy_source(backend->loop_utils, backend->ring_timer); + #ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE dbus_connection_unregister_object_path(backend->conn, PROFILE_HSP_AG); dbus_connection_unregister_object_path(backend->conn, PROFILE_HSP_HS); @@ -2271,6 +2694,8 @@ static int backend_native_free(void *data) spa_list_consume(rfcomm, &backend->rfcomm_list, link) rfcomm_free(rfcomm); + if (backend->modem.operator_name) + free(backend->modem.operator_name); free(backend); return 0; @@ -2305,6 +2730,17 @@ static const struct spa_bt_backend_implementation backend_impl = { .supports_codec = backend_native_supports_codec, }; +static const struct mm_ops mm_ops = { + .send_cmd_result = send_cmd_result, + .set_modem_service = set_modem_service, + .set_modem_signal_strength = set_modem_signal_strength, + .set_modem_operator_name = set_modem_operator_name, + .set_modem_own_number = set_modem_own_number, + .set_modem_roaming = set_modem_roaming, + .set_call_active = set_call_active, + .set_call_setup = set_call_setup, +}; + struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor, void *dbus_connection, const struct spa_dict *info, @@ -2370,6 +2806,9 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor, } #endif + backend->modemmanager = mm_register(backend->log, backend->conn, info, &mm_ops, backend); + backend->upower = upower_register(backend->log, backend->conn, set_battery_level, backend); + return &backend->this; #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE diff --git a/spa/plugins/bluez5/bap-codec-caps.h b/spa/plugins/bluez5/bap-codec-caps.h new file mode 100644 index 0000000000000000000000000000000000000000..7bfac3529caec8483623401d4d4ade9b465080f6 --- /dev/null +++ b/spa/plugins/bluez5/bap-codec-caps.h @@ -0,0 +1,142 @@ +/* Spa BAP codec API + * + * Copyright © 2022 Collabora + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef SPA_BLUEZ5_BAP_CODEC_CAPS_H_ +#define SPA_BLUEZ5_BAP_CODEC_CAPS_H_ + +#define BAP_CODEC_LC3 0x06 + +#define LC3_TYPE_FREQ 0x01 +#define LC3_FREQ_8KHZ (1 << 0) +#define LC3_FREQ_11KHZ (1 << 1) +#define LC3_FREQ_16KHZ (1 << 2) +#define LC3_FREQ_22KHZ (1 << 3) +#define LC3_FREQ_24KHZ (1 << 4) +#define LC3_FREQ_32KHZ (1 << 5) +#define LC3_FREQ_44KHZ (1 << 6) +#define LC3_FREQ_48KHZ (1 << 7) +#define LC3_FREQ_ANY (LC3_FREQ_8KHZ | \ + LC3_FREQ_11KHZ | \ + LC3_FREQ_16KHZ | \ + LC3_FREQ_22KHZ | \ + LC3_FREQ_24KHZ | \ + LC3_FREQ_32KHZ | \ + LC3_FREQ_44KHZ | \ + LC3_FREQ_48KHZ) + +#define LC3_TYPE_DUR 0x02 +#define LC3_DUR_7_5 (1 << 0) +#define LC3_DUR_10 (1 << 1) +#define LC3_DUR_ANY (LC3_DUR_7_5 | \ + LC3_DUR_10) + +#define LC3_TYPE_CHAN 0x03 +#define LC3_CHAN_1 (1 << 0) +#define LC3_CHAN_2 (1 << 1) + +#define LC3_TYPE_FRAMELEN 0x04 +#define LC3_TYPE_BLKS 0x05 + +/* LC3 config parameters */ +#define LC3_CONFIG_FREQ_8KHZ 0x01 +#define LC3_CONFIG_FREQ_11KHZ 0x02 +#define LC3_CONFIG_FREQ_16KHZ 0x03 +#define LC3_CONFIG_FREQ_22KHZ 0x04 +#define LC3_CONFIG_FREQ_24KHZ 0x05 +#define LC3_CONFIG_FREQ_32KHZ 0x06 +#define LC3_CONFIG_FREQ_44KHZ 0x07 +#define LC3_CONFIG_FREQ_48KHZ 0x08 + +#define LC3_CONFIG_DURATION_7_5 0x00 +#define LC3_CONFIG_DURATION_10 0x01 + +#define LC3_CONFIG_CHNL_NOT_ALLOWED 0x00000000 +#define LC3_CONFIG_CHNL_FL 0x00000001 /* front left */ +#define LC3_CONFIG_CHNL_FR 0x00000002 /* front right */ +#define LC3_CONFIG_CHNL_FC 0x00000004 /* front center */ +#define LC3_CONFIG_CHNL_LFE 0x00000008 /* LFE */ +#define LC3_CONFIG_CHNL_BL 0x00000010 /* back left */ +#define LC3_CONFIG_CHNL_BR 0x00000020 /* back right */ +#define LC3_CONFIG_CHNL_FLC 0x00000040 /* front left center */ +#define LC3_CONFIG_CHNL_FRC 0x00000080 /* front right center */ +#define LC3_CONFIG_CHNL_BC 0x00000100 /* back center */ +#define LC3_CONFIG_CHNL_LFE2 0x00000200 /* LFE 2 */ +#define LC3_CONFIG_CHNL_SL 0x00000400 /* side left */ +#define LC3_CONFIG_CHNL_SR 0x00000800 /* side right */ +#define LC3_CONFIG_CHNL_TFL 0x00001000 /* top front left */ +#define LC3_CONFIG_CHNL_TFR 0x00002000 /* top front right */ +#define LC3_CONFIG_CHNL_TFC 0x00004000 /* top front center */ +#define LC3_CONFIG_CHNL_TC 0x00008000 /* top center */ +#define LC3_CONFIG_CHNL_TBL 0x00010000 /* top back left */ +#define LC3_CONFIG_CHNL_TBR 0x00020000 /* top back right */ +#define LC3_CONFIG_CHNL_TSL 0x00040000 /* top side left */ +#define LC3_CONFIG_CHNL_TSR 0x00080000 /* top side right */ +#define LC3_CONFIG_CHNL_TBC 0x00100000 /* top back center */ +#define LC3_CONFIG_CHNL_BFC 0x00200000 /* bottom front center */ +#define LC3_CONFIG_CHNL_BFL 0x00400000 /* bottom front left */ +#define LC3_CONFIG_CHNL_BFR 0x00800000 /* bottom front right */ +#define LC3_CONFIG_CHNL_FLW 0x01000000 /* front left wide */ +#define LC3_CONFIG_CHNL_FRW 0x02000000 /* front right wide */ +#define LC3_CONFIG_CHNL_LS 0x04000000 /* left surround */ +#define LC3_CONFIG_CHNL_RS 0x08000000 /* right surround */ + +#define LC3_MAX_CHANNELS 28 + +typedef struct { + uint8_t rate; + uint8_t frame_duration; + uint32_t channels; + uint16_t framelen; + uint8_t n_blks; +} __attribute__ ((packed)) bap_lc3_t; + +#define BT_ISO_QOS_CIG_UNSET 0xff +#define BT_ISO_QOS_CIS_UNSET 0xff + +#define BT_ISO_QOS_TARGET_LATENCY_LOW 0x01 +#define BT_ISO_QOS_TARGET_LATENCY_BALANCED 0x02 +#define BT_ISO_QOS_TARGET_LATENCY_RELIABILITY 0x03 + +struct bap_endpoint_qos { + uint8_t framing; + uint8_t phy; + uint8_t retransmission; + uint16_t latency; + uint32_t delay_min; + uint32_t delay_max; + uint32_t preferred_delay_min; + uint32_t preferred_delay_max; +}; + +struct bap_codec_qos { + uint32_t interval; + uint8_t framing; + uint8_t phy; + uint16_t sdu; + uint8_t retransmission; + uint16_t latency; + uint32_t delay; + uint8_t target_latency; +}; + +#endif diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c new file mode 100644 index 0000000000000000000000000000000000000000..29e09f1c9410c6d3e9744bb1b495cef79bf034e2 --- /dev/null +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -0,0 +1,790 @@ +/* Spa BAP LC3 codec + * + * Copyright © 2020 Wim Taymans + * Copyright © 2022 Pauli Virtanen + * Copyright © 2022 Collabora + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <unistd.h> +#include <stddef.h> +#include <errno.h> +#include <arpa/inet.h> +#include <bluetooth/bluetooth.h> + +#include <spa/param/audio/format.h> +#include <spa/param/audio/format-utils.h> + +#include <lc3.h> + +#include "media-codecs.h" +#include "bap-codec-caps.h" + +struct impl { + lc3_encoder_t enc[LC3_MAX_CHANNELS]; + lc3_decoder_t dec[LC3_MAX_CHANNELS]; + + int mtu; + int samplerate; + int channels; + int frame_dus; + int framelen; + int samples; + unsigned int codesize; +}; + +struct ltv { + uint8_t len; + uint8_t type; + uint8_t value[0]; +} __packed; + +static int write_ltv(uint8_t *dest, uint8_t type, void* value, size_t len) +{ + struct ltv *ltv = (struct ltv *)dest; + + ltv->len = len + 1; + ltv->type = type; + memcpy(ltv->value, value, len); + + return len + 2; +} + +static int write_ltv_uint8(uint8_t *dest, uint8_t type, uint8_t value) +{ + return write_ltv(dest, type, &value, sizeof(value)); +} + +static int write_ltv_uint16(uint8_t *dest, uint8_t type, uint16_t value) +{ + return write_ltv(dest, type, &value, sizeof(value)); +} + +static int write_ltv_uint32(uint8_t *dest, uint8_t type, uint32_t value) +{ + return write_ltv(dest, type, &value, sizeof(value)); +} + +static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, + uint8_t caps[A2DP_MAX_CAPS_SIZE]) +{ + uint8_t *data = caps; + uint16_t framelen[2] = {htobs(LC3_MIN_FRAME_BYTES), htobs(LC3_MAX_FRAME_BYTES)}; + + data += write_ltv_uint16(data, LC3_TYPE_FREQ, + htobs(LC3_FREQ_48KHZ | LC3_FREQ_24KHZ | LC3_FREQ_16KHZ | LC3_FREQ_8KHZ)); + data += write_ltv_uint8(data, LC3_TYPE_DUR, LC3_DUR_ANY); + data += write_ltv_uint8(data, LC3_TYPE_CHAN, LC3_CHAN_1 | LC3_CHAN_2); + data += write_ltv(data, LC3_TYPE_FRAMELEN, framelen, sizeof(framelen)); + data += write_ltv_uint8(data, LC3_TYPE_BLKS, 2); + + return data - caps; +} + +static bool parse_capabilities(bap_lc3_t *conf, const uint8_t *data, size_t data_size) +{ + uint16_t framelen_min = 0, framelen_max = 0; + + if (!data_size) + return false; + memset(conf, 0, sizeof(*conf)); + + conf->frame_duration = 0xFF; + + while (data_size > 0) { + struct ltv *ltv = (struct ltv *)data; + + if (ltv->len > data_size) + return false; + + switch (ltv->type) { + case LC3_TYPE_FREQ: + spa_return_val_if_fail(ltv->len == 3, false); + { + uint16_t rate = ltv->value[0] + (ltv->value[1] << 8); + if (rate & LC3_FREQ_48KHZ) + conf->rate = LC3_CONFIG_FREQ_48KHZ; + else if (rate & LC3_FREQ_24KHZ) + conf->rate = LC3_CONFIG_FREQ_24KHZ; + else if (rate & LC3_FREQ_16KHZ) + conf->rate = LC3_CONFIG_FREQ_16KHZ; + else if (rate & LC3_FREQ_8KHZ) + conf->rate = LC3_CONFIG_FREQ_8KHZ; + else + return false; + } + break; + case LC3_TYPE_DUR: + spa_return_val_if_fail(ltv->len == 2, false); + { + uint8_t duration = ltv->value[0]; + if (duration & LC3_DUR_10) + conf->frame_duration = LC3_CONFIG_DURATION_10; + else if (duration & LC3_DUR_7_5) + conf->frame_duration = LC3_CONFIG_DURATION_7_5; + else + return false; + } + break; + case LC3_TYPE_CHAN: + spa_return_val_if_fail(ltv->len == 2, false); + { + uint8_t channels = ltv->value[0]; + /* Only mono or stereo streams are currently supported, + * in both case Audio location is defined as both Front Left + * and Front Right, difference is done by the n_blks parameter. + */ + if ((channels & LC3_CHAN_2) || (channels & LC3_CHAN_1)) + conf->channels = LC3_CONFIG_CHNL_FR | LC3_CONFIG_CHNL_FL; + else + return false; + } + break; + case LC3_TYPE_FRAMELEN: + spa_return_val_if_fail(ltv->len == 5, false); + framelen_min = ltv->value[0] + (ltv->value[1] << 8); + framelen_max = ltv->value[2] + (ltv->value[3] << 8); + break; + case LC3_TYPE_BLKS: + spa_return_val_if_fail(ltv->len == 2, false); + conf->n_blks = ltv->value[0]; + if (!conf->n_blks) + return false; + break; + default: + return false; + } + data_size -= ltv->len + 1; + data += ltv->len + 1; + } + + if (framelen_min < LC3_MIN_FRAME_BYTES || framelen_max > LC3_MAX_FRAME_BYTES) + return false; + if (conf->frame_duration == 0xFF || !conf->rate) + return false; + if (!conf->channels) + conf->channels = LC3_CONFIG_CHNL_FL; + + switch (conf->rate) { + case LC3_CONFIG_FREQ_48KHZ: + if (conf->frame_duration == LC3_CONFIG_DURATION_7_5) + conf->framelen = 117; + else + conf->framelen = 120; + break; + case LC3_CONFIG_FREQ_24KHZ: + if (conf->frame_duration == LC3_CONFIG_DURATION_7_5) + conf->framelen = 45; + else + conf->framelen = 60; + break; + case LC3_CONFIG_FREQ_16KHZ: + if (conf->frame_duration == LC3_CONFIG_DURATION_7_5) + conf->framelen = 30; + else + conf->framelen = 40; + break; + case LC3_CONFIG_FREQ_8KHZ: + if (conf->frame_duration == LC3_CONFIG_DURATION_7_5) + conf->framelen = 26; + else + conf->framelen = 30; + break; + default: + return false; + } + + return true; +} + +static bool parse_conf(bap_lc3_t *conf, const uint8_t *data, size_t data_size) +{ + if (!data_size) + return false; + memset(conf, 0, sizeof(*conf)); + + conf->frame_duration = 0xFF; + + while (data_size > 0) { + struct ltv *ltv = (struct ltv *)data; + + if (ltv->len > data_size) + return false; + + switch (ltv->type) { + case LC3_TYPE_FREQ: + spa_return_val_if_fail(ltv->len == 2, false); + conf->rate = ltv->value[0]; + break; + case LC3_TYPE_DUR: + spa_return_val_if_fail(ltv->len == 2, false); + conf->frame_duration = ltv->value[0]; + break; + case LC3_TYPE_CHAN: + spa_return_val_if_fail(ltv->len == 5, false); + conf->channels = ltv->value[0] + (ltv->value[1] << 8) + (ltv->value[2] << 16) + (ltv->value[3] << 24); + break; + case LC3_TYPE_FRAMELEN: + spa_return_val_if_fail(ltv->len == 3, false); + conf->framelen = ltv->value[0] + (ltv->value[1] << 8); + break; + case LC3_TYPE_BLKS: + spa_return_val_if_fail(ltv->len == 2, false); + conf->n_blks = ltv->value[0]; + if (!conf->n_blks) + return false; + break; + default: + return false; + } + data_size -= ltv->len + 1; + data += ltv->len + 1; + } + + if (conf->frame_duration == 0xFF || !conf->rate) + return false; + + return true; +} + +static int codec_select_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, + const struct media_codec_audio_info *info, + const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) +{ + bap_lc3_t conf; + uint8_t *data = config; + + if (caps == NULL) + return -EINVAL; + + if (!parse_capabilities(&conf, caps, caps_size)) + return -ENOTSUP; + + data += write_ltv_uint8(data, LC3_TYPE_FREQ, conf.rate); + data += write_ltv_uint8(data, LC3_TYPE_DUR, conf.frame_duration); + data += write_ltv_uint32(data, LC3_TYPE_CHAN, htobl(conf.channels)); + data += write_ltv_uint16(data, LC3_TYPE_FRAMELEN, htobs(conf.framelen)); + data += write_ltv_uint8(data, LC3_TYPE_BLKS, conf.n_blks); + + return data - config; +} + +static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, + const void *caps2, size_t caps2_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings) +{ + bap_lc3_t conf1, conf2; + bap_lc3_t *conf; + int res1, res2; + int a, b; + + /* Order selected configurations by preference */ + res1 = codec->select_config(codec, 0, caps1, caps1_size, info, NULL, (uint8_t *)&conf1); + res2 = codec->select_config(codec, 0, caps2, caps2_size, info , NULL, (uint8_t *)&conf2); + +#define PREFER_EXPR(expr) \ + do { \ + conf = &conf1; \ + a = (expr); \ + conf = &conf2; \ + b = (expr); \ + if (a != b) \ + return b - a; \ + } while (0) + +#define PREFER_BOOL(expr) PREFER_EXPR((expr) ? 1 : 0) + + /* Prefer valid */ + a = (res1 > 0 && (size_t)res1 == sizeof(bap_lc3_t)) ? 1 : 0; + b = (res2 > 0 && (size_t)res2 == sizeof(bap_lc3_t)) ? 1 : 0; + if (!a || !b) + return b - a; + + PREFER_BOOL(conf->channels & LC3_CHAN_2); + PREFER_BOOL(conf->rate & (LC3_CONFIG_FREQ_48KHZ | LC3_CONFIG_FREQ_24KHZ | LC3_CONFIG_FREQ_16KHZ | LC3_CONFIG_FREQ_8KHZ)); + PREFER_BOOL(conf->rate & LC3_CONFIG_FREQ_48KHZ); + + return 0; + +#undef PREFER_EXPR +#undef PREFER_BOOL +} + +static uint8_t channels_to_positions(uint32_t channels, uint8_t n_channels, uint32_t *position) +{ + uint8_t n_positions = 0; + + spa_assert(n_channels <= SPA_AUDIO_MAX_CHANNELS); + + /* First check if stream is configure for Mono, i.e. 1 block for both Front + * Left anf Front Right, + * else map LE Audio locations to PipeWire locations in the ascending order + * which will be used as block order in stream. + */ + if ((channels & (LC3_CONFIG_CHNL_FR | LC3_CONFIG_CHNL_FL)) == (LC3_CONFIG_CHNL_FR | LC3_CONFIG_CHNL_FL) && + n_channels == 1) { + position[0] = SPA_AUDIO_CHANNEL_MONO; + n_positions = 1; + } else { +#define CHANNEL_2_SPACHANNEL(channel,spa_channel) if (channels & channel) position[n_positions++] = spa_channel; + + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_FL, SPA_AUDIO_CHANNEL_FL); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_FR, SPA_AUDIO_CHANNEL_FR); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_FC, SPA_AUDIO_CHANNEL_FC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_LFE, SPA_AUDIO_CHANNEL_LFE); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_BL, SPA_AUDIO_CHANNEL_RL); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_BR, SPA_AUDIO_CHANNEL_RR); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_FLC, SPA_AUDIO_CHANNEL_FLC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_FRC, SPA_AUDIO_CHANNEL_FRC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_BC, SPA_AUDIO_CHANNEL_BC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_LFE2, SPA_AUDIO_CHANNEL_LFE2); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_SL, SPA_AUDIO_CHANNEL_SL); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_SR, SPA_AUDIO_CHANNEL_SR); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_TFL, SPA_AUDIO_CHANNEL_TFL); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_TFR, SPA_AUDIO_CHANNEL_TFR); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_TFC, SPA_AUDIO_CHANNEL_TFC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_TC, SPA_AUDIO_CHANNEL_TC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_TBL, SPA_AUDIO_CHANNEL_TRL); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_TBR, SPA_AUDIO_CHANNEL_TRR); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_TSL, SPA_AUDIO_CHANNEL_TSL); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_TSR, SPA_AUDIO_CHANNEL_TSR); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_TBC, SPA_AUDIO_CHANNEL_TRC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_BFC, SPA_AUDIO_CHANNEL_BC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_BFL, SPA_AUDIO_CHANNEL_BLC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_BFR, SPA_AUDIO_CHANNEL_BRC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_FLW, SPA_AUDIO_CHANNEL_FLW); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_FRW, SPA_AUDIO_CHANNEL_FRW); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_LS, SPA_AUDIO_CHANNEL_LLFE); /* is it the right mapping? */ + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_RS, SPA_AUDIO_CHANNEL_RLFE); /* is it the right mapping? */ + +#undef CHANNEL_2_SPACHANNEL + } + + return n_positions; +} + +static int codec_enum_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, uint32_t id, uint32_t idx, + struct spa_pod_builder *b, struct spa_pod **param) +{ + bap_lc3_t conf; + struct spa_pod_frame f[2]; + struct spa_pod_choice *choice; + uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t i = 0; + uint8_t res; + + if (!parse_conf(&conf, caps, caps_size)) + return -EINVAL; + + if (idx > 0) + return 0; + + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(b, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S24_32), + 0); + spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0); + + spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0); + choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]); + i = 0; + if (conf.rate & LC3_CONFIG_FREQ_48KHZ) { + if (i++ == 0) + spa_pod_builder_int(b, 48000); + spa_pod_builder_int(b, 48000); + } + if (conf.rate & LC3_CONFIG_FREQ_24KHZ) { + if (i++ == 0) + spa_pod_builder_int(b, 24000); + spa_pod_builder_int(b, 24000); + } + if (conf.rate & LC3_CONFIG_FREQ_16KHZ) { + if (i++ == 0) + spa_pod_builder_int(b, 16000); + spa_pod_builder_int(b, 16000); + } + if (conf.rate & LC3_CONFIG_FREQ_8KHZ) { + if (i++ == 0) + spa_pod_builder_int(b, 8000); + spa_pod_builder_int(b, 8000); + } + if (i == 0) + return -EINVAL; + if (i > 1) + choice->body.type = SPA_CHOICE_Enum; + spa_pod_builder_pop(b, &f[1]); + + res = channels_to_positions(conf.channels, conf.n_blks, position); + if (res == 0) + return -EINVAL; + spa_pod_builder_add(b, + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(res), + SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, res, position), + 0); + + *param = spa_pod_builder_pop(b, &f[0]); + return *param == NULL ? -EIO : 1; +} + +static int codec_validate_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, + struct spa_audio_info *info) +{ + bap_lc3_t conf; + uint8_t res; + + if (caps == NULL) + return -EINVAL; + + if (!parse_conf(&conf, caps, caps_size)) + return -ENOTSUP; + + spa_zero(*info); + info->media_type = SPA_MEDIA_TYPE_audio; + info->media_subtype = SPA_MEDIA_SUBTYPE_raw; + info->info.raw.format = SPA_AUDIO_FORMAT_S24_32; + + switch (conf.rate) { + case LC3_CONFIG_FREQ_48KHZ: + info->info.raw.rate = 48000U; + break; + case LC3_CONFIG_FREQ_24KHZ: + info->info.raw.rate = 24000U; + break; + case LC3_CONFIG_FREQ_16KHZ: + info->info.raw.rate = 16000U; + break; + case LC3_CONFIG_FREQ_8KHZ: + info->info.raw.rate = 8000U; + break; + default: + return -EINVAL; + } + + res = channels_to_positions(conf.channels, conf.n_blks, info->info.raw.position); + if (res == 0) + return -EINVAL; + info->info.raw.channels = res; + + switch (conf.frame_duration) { + case LC3_CONFIG_DURATION_10: + case LC3_CONFIG_DURATION_7_5: + break; + default: + return -EINVAL; + } + + return 0; +} + +static int codec_get_qos(const struct media_codec *codec, + const void *config, size_t config_size, + const struct bap_endpoint_qos *endpoint_qos, + struct bap_codec_qos *qos) +{ + bap_lc3_t conf; + + spa_zero(*qos); + + if (!parse_conf(&conf, config, config_size)) + return -EINVAL; + + qos->framing = false; + if (endpoint_qos->phy & 0x2) + qos->phy = 0x2; + else if (endpoint_qos->phy & 0x1) + qos->phy = 0x1; + else + qos->phy = 0x2; + qos->retransmission = 2; /* default */ + qos->sdu = conf.framelen * conf.n_blks; + qos->latency = 20; /* default */ + qos->delay = 40000U; + qos->interval = (conf.frame_duration == LC3_CONFIG_DURATION_7_5 ? 7500 : 10000); + qos->target_latency = BT_ISO_QOS_TARGET_LATENCY_BALANCED; + + switch (conf.rate) { + case LC3_CONFIG_FREQ_8KHZ: + case LC3_CONFIG_FREQ_16KHZ: + case LC3_CONFIG_FREQ_24KHZ: + case LC3_CONFIG_FREQ_32KHZ: + qos->retransmission = 2; + qos->latency = (conf.frame_duration == LC3_CONFIG_DURATION_7_5 ? 8 : 10); + break; + case LC3_CONFIG_FREQ_48KHZ: + qos->retransmission = 5; + qos->latency = (conf.frame_duration == LC3_CONFIG_DURATION_7_5 ? 15 : 20); + break; + } + + /* Clamp to ASE values */ + if (endpoint_qos->latency >= 0x0005 && endpoint_qos->latency <= 0x0FA0) + /* Values outside the range are RFU */ + qos->latency = SPA_MAX(qos->latency, endpoint_qos->latency); + + if (endpoint_qos->delay_min) + qos->delay = SPA_MAX(qos->delay, endpoint_qos->delay_min); + if (endpoint_qos->delay_max) + qos->delay = SPA_MIN(qos->delay, endpoint_qos->delay_max); + + return 0; +} + +static void *codec_init(const struct media_codec *codec, uint32_t flags, + void *config, size_t config_len, const struct spa_audio_info *info, + void *props, size_t mtu) +{ + bap_lc3_t conf; + struct impl *this = NULL; + struct spa_audio_info config_info; + int res, ich; + + if (info->media_type != SPA_MEDIA_TYPE_audio || + info->media_subtype != SPA_MEDIA_SUBTYPE_raw || + info->info.raw.format != SPA_AUDIO_FORMAT_S24_32) { + res = -EINVAL; + goto error; + } + + if ((this = calloc(1, sizeof(struct impl))) == NULL) + goto error_errno; + + if ((res = codec_validate_config(codec, flags, config, config_len, &config_info)) < 0) + goto error; + + if (!parse_conf(&conf, config, config_len)) { + res = -ENOTSUP; + goto error; + } + + this->mtu = mtu; + this->samplerate = config_info.info.raw.rate; + this->channels = config_info.info.raw.channels; + this->framelen = conf.framelen; + + switch (conf.frame_duration) { + case LC3_CONFIG_DURATION_10: + this->frame_dus = 10000; + break; + case LC3_CONFIG_DURATION_7_5: + this->frame_dus = 7500; + break; + default: + res = -EINVAL; + goto error; + } + + this->samples = lc3_frame_samples(this->frame_dus, this->samplerate); + if (this->samples < 0) { + res = -EINVAL; + goto error; + } + this->codesize = this->samples * this->channels * sizeof(int32_t); + + if (!(flags & MEDIA_CODEC_FLAG_SINK)) { + for (ich = 0; ich < this->channels; ich++) { + this->enc[ich] = lc3_setup_encoder(this->frame_dus, this->samplerate, 0, calloc(1, lc3_encoder_size(this->frame_dus, this->samplerate))); + if (this->enc[ich] == NULL) { + res = -EINVAL; + goto error; + } + } + } else { + for (ich = 0; ich < this->channels; ich++) { + this->dec[ich] = lc3_setup_decoder(this->frame_dus, this->samplerate, 0, calloc(1, lc3_decoder_size(this->frame_dus, this->samplerate))); + if (this->dec[ich] == NULL) { + res = -EINVAL; + goto error; + } + } + } + + return this; + +error_errno: + res = -errno; + goto error; + +error: + if (this) { + for (ich = 0; ich < this->channels; ich++) { + if (this->enc[ich]) + free(this->enc[ich]); + if (this->dec[ich]) + free(this->dec[ich]); + } + } + free(this); + errno = -res; + return NULL; +} + +static void codec_deinit(void *data) +{ + struct impl *this = data; + int ich; + + for (ich = 0; ich < this->channels; ich++) { + if (this->enc[ich]) + free(this->enc[ich]); + if (this->dec[ich]) + free(this->dec[ich]); + } + free(this); +} + +static int codec_get_block_size(void *data) +{ + struct impl *this = data; + return this->codesize; +} + +static int codec_abr_process (void *data, size_t unsent) +{ + return -ENOTSUP; +} + +static int codec_start_encode (void *data, + void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) +{ + return 0; +} + +static int codec_encode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out, int *need_flush) +{ + struct impl *this = data; + int frame_bytes; + int ich, res; + int size, processed; + + frame_bytes = lc3_frame_bytes(this->frame_dus, this->samplerate); + processed = 0; + size = 0; + + if (src_size < (size_t)this->codesize) + goto done; + if (dst_size < (size_t)frame_bytes) + goto done; + + for (ich = 0; ich < this->channels; ich++) { + uint8_t *in = (uint8_t *)src + (ich * 4); + uint8_t *out = (uint8_t *)dst + ich * this->framelen; + res = lc3_encode(this->enc[ich], LC3_PCM_FORMAT_S24, in, this->channels, this->framelen, out); + size += this->framelen; + if (SPA_UNLIKELY(res != 0)) + return -EINVAL; + } + *dst_out = size; + + processed += this->codesize; + +done: + spa_assert(size <= this->mtu); + *need_flush = NEED_FLUSH_ALL; + + return processed; +} + +static SPA_UNUSED int codec_start_decode (void *data, + const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) +{ + return 0; +} + +static SPA_UNUSED int codec_decode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out) +{ + struct impl *this = data; + int ich, res; + int consumed; + int samples; + + spa_return_val_if_fail((size_t)(this->framelen * this->channels) == src_size, -EINVAL); + consumed = 0; + + samples = lc3_frame_samples(this->frame_dus, this->samplerate); + if (samples == -1) + return -EINVAL; + if (dst_size < this->codesize) + return -EINVAL; + + for (ich = 0; ich < this->channels; ich++) { + uint8_t *in = (uint8_t *)src + ich * this->framelen; + uint8_t *out = (uint8_t *)dst + (ich * 4); + res = lc3_decode(this->dec[ich], in, this->framelen, LC3_PCM_FORMAT_S24, out, this->channels); + if (SPA_UNLIKELY(res < 0)) + return -EINVAL; + consumed += this->framelen; + } + + *dst_out = this->codesize; + + return consumed; +} + +static int codec_reduce_bitpool(void *data) +{ + return -ENOTSUP; +} + +static int codec_increase_bitpool(void *data) +{ + return -ENOTSUP; +} + +const struct media_codec bap_codec_lc3 = { + .id = SPA_BLUETOOTH_AUDIO_CODEC_LC3, + .name = "lc3", + .codec_id = BAP_CODEC_LC3, + .bap = true, + .description = "LC3", + .fill_caps = codec_fill_caps, + .select_config = codec_select_config, + .enum_config = codec_enum_config, + .validate_config = codec_validate_config, + .get_qos = codec_get_qos, + .caps_preference_cmp = codec_caps_preference_cmp, + .init = codec_init, + .deinit = codec_deinit, + .get_block_size = codec_get_block_size, + .abr_process = codec_abr_process, + .start_encode = codec_start_encode, + .encode = codec_encode, + .start_decode = codec_start_decode, + .decode = codec_decode, + .reduce_bitpool = codec_reduce_bitpool, + .increase_bitpool = codec_increase_bitpool +}; + +MEDIA_CODEC_EXPORT_DEF( + "lc3", + &bap_codec_lc3 +); diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 93f8e72171a31e4cafb3886e156035f65ed463e9..ab7e7989a833edc9e083cfa2845210b8bf084c58 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -100,7 +100,7 @@ struct spa_bt_monitor { uint32_t id; - const struct a2dp_codec * const * a2dp_codecs; + const struct media_codec * const * media_codecs; /* * Lists of BlueZ objects, kept up-to-date by following DBus events @@ -113,6 +113,7 @@ struct spa_bt_monitor { unsigned int filters_added:1; unsigned int objects_listed:1; + DBusPendingCall *get_managed_objects_call; struct spa_bt_backend *backend; struct spa_bt_backend *backends[BACKEND_NUM]; @@ -130,7 +131,9 @@ struct spa_bt_monitor { struct spa_dict global_settings; /* A reference audio info for A2DP codec configuration. */ - struct a2dp_codec_audio_info default_audio_info; + struct media_codec_audio_info default_audio_info; + + bool le_audio_supported; }; /* Stream endpoints owned by BlueZ for each device */ @@ -146,6 +149,7 @@ struct spa_bt_remote_endpoint { uint8_t *capabilities; int capabilities_len; bool delay_reporting; + bool acceptor; }; /* @@ -155,7 +159,7 @@ struct spa_bt_remote_endpoint { * with the desired capabilities. * The codec switch struct tracks candidates still to be tried. */ -struct spa_bt_a2dp_codec_switch { +struct spa_bt_media_codec_switch { struct spa_bt_device *device; struct spa_list device_link; @@ -172,10 +176,10 @@ struct spa_bt_a2dp_codec_switch { * Called asynchronously, so endpoint paths instead of pointers (which may be * invalidated in the meantime). */ - const struct a2dp_codec **codecs; + const struct media_codec **codecs; char **paths; - const struct a2dp_codec **codec_iter; /**< outer iterator over codecs */ + const struct media_codec **codec_iter; /**< outer iterator over codecs */ char **path_iter; /**< inner iterator over endpoint paths */ uint16_t retries; @@ -430,10 +434,17 @@ static void register_battery_provider(struct spa_bt_device *device) } } -static int a2dp_codec_to_endpoint(const struct a2dp_codec *codec, - const char * endpoint, +static int media_codec_to_endpoint(const struct media_codec *codec, + enum spa_bt_media_direction direction, char** object_path) { + const char * endpoint; + + if (direction == SPA_BT_MEDIA_SOURCE) + endpoint = codec->bap ? BAP_SOURCE_ENDPOINT : A2DP_SOURCE_ENDPOINT; + else + endpoint = codec->bap ? BAP_SINK_ENDPOINT : A2DP_SINK_ENDPOINT; + *object_path = spa_aprintf("%s/%s", endpoint, codec->endpoint_name ? codec->endpoint_name : codec->name); if (*object_path == NULL) @@ -441,10 +452,11 @@ static int a2dp_codec_to_endpoint(const struct a2dp_codec *codec, return 0; } -static const struct a2dp_codec *a2dp_endpoint_to_codec(struct spa_bt_monitor *monitor, const char *endpoint, bool *sink) +static const struct media_codec *media_endpoint_to_codec(struct spa_bt_monitor *monitor, const char *endpoint, bool *sink, const struct media_codec *preferred) { const char *ep_name; - const struct a2dp_codec * const * const a2dp_codecs = monitor->a2dp_codecs; + const struct media_codec * const * const media_codecs = monitor->media_codecs; + const struct media_codec *found = NULL; int i; if (spa_strstartswith(endpoint, A2DP_SINK_ENDPOINT "/")) { @@ -453,36 +465,81 @@ static const struct a2dp_codec *a2dp_endpoint_to_codec(struct spa_bt_monitor *mo } else if (spa_strstartswith(endpoint, A2DP_SOURCE_ENDPOINT "/")) { ep_name = endpoint + strlen(A2DP_SOURCE_ENDPOINT "/"); *sink = false; + } else if (spa_strstartswith(endpoint, BAP_SOURCE_ENDPOINT "/")) { + ep_name = endpoint + strlen(BAP_SOURCE_ENDPOINT "/"); + *sink = false; + } else if (spa_strstartswith(endpoint, BAP_SINK_ENDPOINT "/")) { + ep_name = endpoint + strlen(BAP_SINK_ENDPOINT "/"); + *sink = true; } else { + *sink = true; return NULL; } - for (i = 0; a2dp_codecs[i]; i++) { - const struct a2dp_codec *codec = a2dp_codecs[i]; + for (i = 0; media_codecs[i]; i++) { + const struct media_codec *codec = media_codecs[i]; const char *codec_ep_name = codec->endpoint_name ? codec->endpoint_name : codec->name; - if (spa_streq(ep_name, codec_ep_name)) - return codec; + + if (!spa_streq(ep_name, codec_ep_name)) + continue; + if ((*sink && !codec->decode) || (!*sink && !codec->encode)) + continue; + + /* Same endpoint may be shared with multiple codec objects, + * which may e.g. correspond to different encoder settings. + * Look up which one we selected. + */ + if ((preferred && codec == preferred) || found == NULL) + found = codec; } - return NULL; + return found; } -static int a2dp_endpoint_to_profile(const char *endpoint) +static int media_endpoint_to_profile(const char *endpoint) { if (spa_strstartswith(endpoint, A2DP_SINK_ENDPOINT "/")) return SPA_BT_PROFILE_A2DP_SOURCE; else if (spa_strstartswith(endpoint, A2DP_SOURCE_ENDPOINT "/")) return SPA_BT_PROFILE_A2DP_SINK; + else if (spa_strstartswith(endpoint, BAP_SINK_ENDPOINT "/")) + return SPA_BT_PROFILE_BAP_SOURCE; + else if (spa_strstartswith(endpoint, BAP_SOURCE_ENDPOINT "/")) + return SPA_BT_PROFILE_BAP_SINK; else return SPA_BT_PROFILE_NULL; } -static bool is_a2dp_codec_enabled(struct spa_bt_monitor *monitor, const struct a2dp_codec *codec) +static bool is_media_codec_enabled(struct spa_bt_monitor *monitor, const struct media_codec *codec) { return spa_dict_lookup(&monitor->enabled_codecs, codec->name) != NULL; } +static bool codec_has_direction(const struct media_codec *codec, enum spa_bt_media_direction direction) +{ + switch (direction) { + case SPA_BT_MEDIA_SOURCE: + return codec->encode; + case SPA_BT_MEDIA_SINK: + return codec->decode; + 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) +{ + /* Codecs with fill_caps == NULL share endpoint with another codec, + * and don't have their own endpoint + */ + return is_media_codec_enabled(monitor, codec) && + codec_has_direction(codec, direction) && + codec->fill_caps; +} + static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) { struct spa_bt_monitor *monitor = userdata; @@ -492,7 +549,7 @@ static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBu DBusMessage *r; DBusError err; int size, res; - const struct a2dp_codec *codec; + const struct media_codec *codec; bool sink; dbus_error_init(&err); @@ -508,14 +565,21 @@ static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBu spa_log_info(monitor->log, "%p: %s select conf %d", monitor, path, size); spa_log_hexdump(monitor->log, SPA_LOG_LEVEL_DEBUG, 2, cap, (size_t)size); - codec = a2dp_endpoint_to_codec(monitor, path, &sink); + /* For codecs sharing the same endpoint, BlueZ-initiated connections + * always pick the default one. The session manager will + * switch the codec to a saved value after connection, so this generally + * does not matter. + */ + codec = media_endpoint_to_codec(monitor, path, &sink, NULL); + spa_log_debug(monitor->log, "%p: %s codec:%s", monitor, path, codec ? codec->name : "<null>"); + if (codec != NULL) /* FIXME: We can't determine which device the SelectConfiguration() * call is associated with, therefore device settings are not passed. * This causes inconsistency with SelectConfiguration() triggered * by codec switching. */ - res = codec->select_config(codec, sink ? A2DP_CODEC_FLAG_SINK : 0, cap, size, &monitor->default_audio_info, + res = codec->select_config(codec, sink ? MEDIA_CODEC_FLAG_SINK : 0, cap, size, &monitor->default_audio_info, &monitor->global_settings, config); else res = -ENOTSUP; @@ -536,7 +600,7 @@ static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBu DBUS_TYPE_BYTE, &pconf, size, DBUS_TYPE_INVALID)) return DBUS_HANDLER_RESULT_NEED_MEMORY; - exit_send: +exit_send: if (!dbus_connection_send(conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; @@ -545,6 +609,242 @@ static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBu return DBUS_HANDLER_RESULT_HANDLED; } +static void append_basic_variant_dict_entry(DBusMessageIter *dict, const char* key, int variant_type_int, const char* variant_type_str, void* variant); +static void append_basic_array_variant_dict_entry(DBusMessageIter *dict, const char* key, const char* variant_type_str, const char* array_type_str, int array_type_int, void* data, int data_size); +static struct spa_bt_remote_endpoint *remote_endpoint_find(struct spa_bt_monitor *monitor, const char *path); + +static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMessage *m, void *userdata) +{ + struct spa_bt_monitor *monitor = userdata; + const char *path; + DBusMessageIter args, props, iter; + DBusMessage *r = NULL; + int res; + const struct media_codec *codec; + bool sink; + const char *err_msg = "Unknown error"; + + const char *endpoint_path = NULL; + uint8_t caps[A2DP_MAX_CAPS_SIZE]; + uint8_t config[A2DP_MAX_CAPS_SIZE]; + int caps_size = 0; + DBusMessageIter dict; + struct bap_endpoint_qos endpoint_qos; + + spa_zero(endpoint_qos); + + if (!dbus_message_iter_init(m, &args) || !spa_streq(dbus_message_get_signature(m), "a{sv}")) { + spa_log_error(monitor->log, "Invalid signature for method SelectProperties()"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + dbus_message_iter_recurse(&args, &props); + if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + path = dbus_message_get_path(m); + + /* TODO: for codecs with shared endpoint, this currently always picks the default + * one. However, currently we don't have BAP codecs with shared endpoint, so + * this does not matter, but in case they are needed later we should pick the + * right one here. + */ + codec = media_endpoint_to_codec(monitor, path, &sink, NULL); + spa_log_debug(monitor->log, "%p: %s codec:%s", monitor, path, codec ? codec->name : "<null>"); + if (!codec) { + spa_log_error(monitor->log, "Unsupported codec"); + err_msg = "Unsupported codec"; + goto error; + } + + /* Parse transport properties */ + while (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + int type; + + dbus_message_iter_recurse(&props, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + type = dbus_message_iter_get_arg_type(&value); + + if (spa_streq(key, "Capabilities")) { + DBusMessageIter array; + uint8_t *buf; + + if (type != DBUS_TYPE_ARRAY) { + spa_log_error(monitor->log, "Property %s of wrong type %c", key, (char)type); + goto error_invalid; + } + + dbus_message_iter_recurse(&value, &array); + type = dbus_message_iter_get_arg_type(&array); + if (type != DBUS_TYPE_BYTE) { + spa_log_error(monitor->log, "%s is an array of wrong type %c", key, (char)type); + goto error_invalid; + } + + dbus_message_iter_get_fixed_array(&array, &buf, &caps_size); + memcpy(caps, buf, caps_size); + + spa_log_info(monitor->log, "%p: %s %s size:%d", monitor, path, key, caps_size); + spa_log_hexdump(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', caps, (size_t)caps_size); + } else if (spa_streq(key, "Endpoint")) { + if (type != DBUS_TYPE_OBJECT_PATH) { + spa_log_error(monitor->log, "Property %s of wrong type %c", key, (char)type); + goto error_invalid; + } + + dbus_message_iter_get_basic(&value, &endpoint_path); + + spa_log_info(monitor->log, "%p: %s %s %s", monitor, path, key, endpoint_path); + } else if (type == DBUS_TYPE_BYTE) { + uint8_t v; + dbus_message_iter_get_basic(&value, &v); + + spa_log_info(monitor->log, "%p: %s %s 0x%x", monitor, path, key, (unsigned int)v); + + if (spa_streq(key, "Framing")) + endpoint_qos.framing = v; + else if (spa_streq(key, "PHY")) + endpoint_qos.phy = v; + else + spa_log_info(monitor->log, "Unknown property %s", key); + } else if (type == DBUS_TYPE_UINT16) { + dbus_uint16_t v; + dbus_message_iter_get_basic(&value, &v); + + spa_log_info(monitor->log, "%p: %s %s 0x%x", monitor, path, key, (unsigned int)v); + + if (spa_streq(key, "Latency")) + endpoint_qos.latency = v; + else + spa_log_info(monitor->log, "Unknown property %s", key); + } else if (type == DBUS_TYPE_UINT32) { + dbus_uint32_t v; + dbus_message_iter_get_basic(&value, &v); + + spa_log_info(monitor->log, "%p: %s %s 0x%x", monitor, path, key, (unsigned int)v); + + if (spa_streq(key, "MinimumDelay")) + endpoint_qos.delay_min = v; + else if (spa_streq(key, "MaximumDelay")) + endpoint_qos.delay_max = v; + else if (spa_streq(key, "PreferredMinimumDelay")) + endpoint_qos.preferred_delay_min = v; + else if (spa_streq(key, "PreferredMaximumDelay")) + endpoint_qos.preferred_delay_max = v; + else + spa_log_info(monitor->log, "Unknown property %s", key); + } else { + spa_log_info(monitor->log, "Unknown property %s", key); + } + + dbus_message_iter_next(&props); + } + + if (codec->bap) { + struct spa_bt_remote_endpoint *ep; + + ep = remote_endpoint_find(monitor, endpoint_path); + if (!ep) { + spa_log_warn(monitor->log, "Unable to find remote endpoint for %s", endpoint_path); + goto error_invalid; + } + + /* Call of SelectProperties means that local device acts as an initiator + * and therefor remote endpoint is an acceptor + */ + ep->acceptor = true; + } + + /* TODO: determine which device the SelectConfiguration() call is associated + * with; it's known here based on the remote endpoint. + */ + res = codec->select_config(codec, 0, caps, caps_size, &monitor->default_audio_info, NULL, config); + + if (res < 0 || res != caps_size) { + spa_log_error(monitor->log, "can't select config: %d (%s)", + res, spa_strerror(res)); + goto error_invalid; + } + spa_log_info(monitor->log, "%p: selected conf %d", monitor, caps_size); + spa_log_hexdump(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', (uint8_t *)config, (size_t)caps_size); + + if ((r = dbus_message_new_method_return(m)) == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + dbus_message_iter_init_append(r, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + append_basic_array_variant_dict_entry(&dict, "Capabilities", "ay", "y", DBUS_TYPE_BYTE, &config, caps_size); + + if (codec->get_qos) { + struct bap_codec_qos qos; + dbus_bool_t framing; + const char *phy_str; + + spa_zero(qos); + + res = codec->get_qos(codec, config, caps_size, &endpoint_qos, &qos); + if (res < 0) { + spa_log_error(monitor->log, "can't select QOS config: %d (%s)", + res, spa_strerror(res)); + goto error_invalid; + } + + append_basic_variant_dict_entry(&dict, "Interval", DBUS_TYPE_UINT32, "u", &qos.interval); + framing = (qos.framing ? TRUE : FALSE); + append_basic_variant_dict_entry(&dict, "Framing", DBUS_TYPE_BOOLEAN, "b", &framing); + if (qos.phy == 0x1) + phy_str = "1M"; + else if (qos.phy == 0x2) + phy_str = "2M"; + else + spa_assert_not_reached(); + append_basic_variant_dict_entry(&dict, "PHY", DBUS_TYPE_STRING, "s", &phy_str); + append_basic_variant_dict_entry(&dict, "SDU", DBUS_TYPE_UINT16, "q", &qos.sdu); + append_basic_variant_dict_entry(&dict, "Retransmissions", DBUS_TYPE_BYTE, "y", &qos.retransmission); + append_basic_variant_dict_entry(&dict, "Latency", DBUS_TYPE_UINT16, "q", &qos.latency); + append_basic_variant_dict_entry(&dict, "Delay", DBUS_TYPE_UINT32, "u", &qos.delay); + append_basic_variant_dict_entry(&dict, "TargetLatency", DBUS_TYPE_BYTE, "y", &qos.target_latency); + } + + dbus_message_iter_close_container(&iter, &dict); + + if (r) { + if (!dbus_connection_send(conn, r, NULL)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + dbus_message_unref(r); + } + + return DBUS_HANDLER_RESULT_HANDLED; + +error_invalid: + err_msg = "Invalid property"; + goto error; + +error: + if (r) + dbus_message_unref(r); + if ((r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", err_msg)) == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + if (!dbus_connection_send(conn, r, NULL)) { + dbus_message_unref(r); + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + dbus_message_unref(r); + return DBUS_HANDLER_RESULT_HANDLED; +} + static struct spa_bt_adapter *adapter_find(struct spa_bt_monitor *monitor, const char *path) { struct spa_bt_adapter *d; @@ -570,9 +870,9 @@ static int parse_modalias(const char *modalias, uint16_t *source, uint16_t *vend char *pos; unsigned int src, i, j, k; - if (strncmp(modalias, "bluetooth:", strlen("bluetooth:")) == 0) + if (spa_strstartswith(modalias, "bluetooth:")) src = SOURCE_ID_BLUETOOTH; - else if (strncmp(modalias, "usb:", strlen("usb:")) == 0) + else if (spa_strstartswith(modalias, "usb:")) src = SOURCE_ID_USB; else return -EINVAL; @@ -683,6 +983,12 @@ static int adapter_update_props(struct spa_bt_adapter *adapter, if (profile && (adapter->profiles & profile) == 0) { spa_log_debug(monitor->log, "adapter %p: add UUID=%s", adapter, uuid); adapter->profiles |= profile; + } else if (strcasecmp(uuid, SPA_BT_UUID_PACS) == 0 && + (adapter->profiles & SPA_BT_PROFILE_BAP_SINK) == 0) { + spa_log_debug(monitor->log, "adapter %p: add UUID=%s", adapter, SPA_BT_UUID_BAP_SINK); + adapter->profiles |= SPA_BT_PROFILE_BAP_SINK; + spa_log_debug(monitor->log, "adapter %p: add UUID=%s", adapter, SPA_BT_UUID_BAP_SOURCE); + adapter->profiles |= SPA_BT_PROFILE_BAP_SOURCE; } dbus_message_iter_next(&iter); } @@ -690,7 +996,7 @@ static int adapter_update_props(struct spa_bt_adapter *adapter, else spa_log_debug(monitor->log, "adapter %p: unhandled key %s", adapter, key); - next: +next: dbus_message_iter_next(props_iter); } return 0; @@ -757,7 +1063,7 @@ static int adapter_init_modalias(struct spa_bt_monitor *monitor, struct spa_bt_a if (str == NULL) goto fail; snprintf(path, sizeof(path), "/sys/class/bluetooth/%s/device/modalias", str); - if ((f = fopen(path, "rb")) == NULL) { + if ((f = fopen(path, "rbe")) == NULL) { res = -errno; goto fail; } @@ -837,6 +1143,11 @@ static uint32_t adapter_connectable_profiles(struct spa_bt_adapter *adapter) if (profiles & SPA_BT_PROFILE_A2DP_SOURCE) mask |= SPA_BT_PROFILE_A2DP_SINK; + if (profiles & SPA_BT_PROFILE_BAP_SINK) + mask |= SPA_BT_PROFILE_BAP_SOURCE; + if (profiles & SPA_BT_PROFILE_BAP_SOURCE) + mask |= SPA_BT_PROFILE_BAP_SINK; + if (profiles & SPA_BT_PROFILE_HSP_AG) mask |= SPA_BT_PROFILE_HSP_HS; if (profiles & SPA_BT_PROFILE_HSP_HS) @@ -905,7 +1216,7 @@ static struct spa_bt_device *device_create(struct spa_bt_monitor *monitor, const static int device_stop_timer(struct spa_bt_device *device); -static void a2dp_codec_switch_free(struct spa_bt_a2dp_codec_switch *sw); +static void media_codec_switch_free(struct spa_bt_media_codec_switch *sw); static void device_clear_sub(struct spa_bt_device *device) { @@ -916,7 +1227,7 @@ static void device_clear_sub(struct spa_bt_device *device) static void device_free(struct spa_bt_device *device) { struct spa_bt_remote_endpoint *ep, *tep; - struct spa_bt_a2dp_codec_switch *sw; + struct spa_bt_media_codec_switch *sw; struct spa_bt_transport *t, *tt; struct spa_bt_monitor *monitor = device->monitor; @@ -946,7 +1257,7 @@ static void device_free(struct spa_bt_device *device) } spa_list_consume(sw, &device->codec_switch_list, device_link) - a2dp_codec_switch_free(sw); + media_codec_switch_free(sw); spa_list_remove(&device->link); free(device->path); @@ -1130,7 +1441,7 @@ int spa_bt_device_add_profile(struct spa_bt_device *device, enum spa_bt_profile static int device_try_connect_profile(struct spa_bt_device *device, - const char *profile_uuid) + const char *profile_uuid) { struct spa_bt_monitor *monitor = device->monitor; DBusMessage *m; @@ -1141,9 +1452,9 @@ static int device_try_connect_profile(struct spa_bt_device *device, /* Call org.bluez.Device1.ConnectProfile() on device, ignoring result */ m = dbus_message_new_method_call(BLUEZ_SERVICE, - device->path, - BLUEZ_DEVICE_INTERFACE, - "ConnectProfile"); + device->path, + BLUEZ_DEVICE_INTERFACE, + "ConnectProfile"); if (m == NULL) return -ENOMEM; dbus_message_append_args(m, DBUS_TYPE_STRING, &profile_uuid, DBUS_TYPE_INVALID); @@ -1208,6 +1519,10 @@ static int reconnect_device_profiles(struct spa_bt_device *device) device_try_connect_profile(device, SPA_BT_UUID_A2DP_SINK); if (reconnect & SPA_BT_PROFILE_A2DP_SOURCE) device_try_connect_profile(device, SPA_BT_UUID_A2DP_SOURCE); + if (reconnect & SPA_BT_PROFILE_BAP_SINK) + device_try_connect_profile(device, SPA_BT_UUID_BAP_SINK); + if (reconnect & SPA_BT_PROFILE_BAP_SOURCE) + device_try_connect_profile(device, SPA_BT_UUID_BAP_SOURCE); return reconnect; } @@ -1222,7 +1537,7 @@ static void device_timer_event(struct spa_source *source) uint64_t exp; if (spa_system_timerfd_read(monitor->main_system, source->fd, &exp) < 0) - spa_log_warn(monitor->log, "error reading timerfd: %s", strerror(errno)); + spa_log_warn(monitor->log, "error reading timerfd: %s", strerror(errno)); spa_log_debug(monitor->log, "device %p: timeout %08x %08x", device, device->profiles, device->connected_profiles); @@ -1278,11 +1593,11 @@ static int device_stop_timer(struct spa_bt_device *device) spa_log_debug(monitor->log, "device %p: stop timer", device); spa_loop_remove_source(monitor->main_loop, &device->timer); - 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(monitor->main_system, device->timer.fd, 0, &ts, NULL); + 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(monitor->main_system, device->timer.fd, 0, &ts, NULL); spa_system_close(monitor->main_system, device->timer.fd); device->timer.data = NULL; return 0; @@ -1295,8 +1610,8 @@ int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force) uint32_t connectable_profiles = device->adapter ? adapter_connectable_profiles(device->adapter) : 0; uint32_t direction_masks[3] = { - SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_HEADSET_HEAD_UNIT, - SPA_BT_PROFILE_A2DP_SOURCE, + SPA_BT_PROFILE_MEDIA_SINK | SPA_BT_PROFILE_HEADSET_HEAD_UNIT, + SPA_BT_PROFILE_MEDIA_SOURCE, SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY, }; bool direction_connected = false; @@ -1347,9 +1662,9 @@ static void device_set_connected(struct spa_bt_device *device, int connected) spa_bt_device_check_profiles(device, false); else { /* Stop codec switch on disconnect */ - struct spa_bt_a2dp_codec_switch *sw; + struct spa_bt_media_codec_switch *sw; spa_list_consume(sw, &device->codec_switch_list, device_link) - a2dp_codec_switch_free(sw); + media_codec_switch_free(sw); if (device->reconnect_state != BT_DEVICE_RECONNECT_INIT) device_stop_timer(device); @@ -1514,12 +1829,12 @@ static int device_update_props(struct spa_bt_device *device, profile = spa_bt_profile_from_uuid(uuid); - /* Only add A2DP profiles if HSP/HFP backed is none. + /* Only add A2DP/BAP profiles if HSP/HFP backed is none. * This allows BT device to connect instantly instead of waiting for * profile timeout, because all available profiles are connected. */ if (monitor->backend_selection != BACKEND_NONE || (monitor->backend_selection == BACKEND_NONE && - profile & (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_A2DP_SOURCE))) { + profile & (SPA_BT_PROFILE_MEDIA_SINK | SPA_BT_PROFILE_MEDIA_SOURCE))) { if (profile && (device->profiles & profile) == 0) { spa_log_debug(monitor->log, "device %p: add UUID=%s", device, uuid); device->profiles |= profile; @@ -1535,7 +1850,7 @@ static int device_update_props(struct spa_bt_device *device, else spa_log_debug(monitor->log, "device %p: unhandled key %s type %d", device, key, type); - next: +next: dbus_message_iter_next(props_iter); } return 0; @@ -1550,7 +1865,7 @@ static bool device_props_ready(struct spa_bt_device *device) return device->adapter && device->address; } -bool spa_bt_device_supports_a2dp_codec(struct spa_bt_device *device, const struct a2dp_codec *codec, bool sink) +bool spa_bt_device_supports_media_codec(struct spa_bt_device *device, const struct media_codec *codec, bool sink) { struct spa_bt_monitor *monitor = device->monitor; struct spa_bt_remote_endpoint *ep; @@ -1563,7 +1878,7 @@ bool spa_bt_device_supports_a2dp_codec(struct spa_bt_device *device, const struc }; size_t i; - if (!is_a2dp_codec_enabled(device->monitor, codec)) + if (!is_media_codec_enabled(device->monitor, codec)) return false; if (!device->adapter->application_registered) { @@ -1587,13 +1902,17 @@ bool spa_bt_device_supports_a2dp_codec(struct spa_bt_device *device, const struc spa_list_for_each(ep, &device->remote_endpoint_list, device_link) { const enum spa_bt_profile profile = spa_bt_profile_from_uuid(ep->uuid); - const enum spa_bt_profile expected = sink ? - SPA_BT_PROFILE_A2DP_SINK : SPA_BT_PROFILE_A2DP_SOURCE; + enum spa_bt_profile expected; + + if (codec->bap) + expected = sink ? SPA_BT_PROFILE_BAP_SINK : SPA_BT_PROFILE_BAP_SOURCE; + else + expected = sink ? SPA_BT_PROFILE_A2DP_SINK : SPA_BT_PROFILE_A2DP_SOURCE; if (profile != expected) continue; - if (a2dp_codec_check_caps(codec, ep->codec, ep->capabilities, ep->capabilities_len, + if (media_codec_check_caps(codec, ep->codec, ep->capabilities, ep->capabilities_len, &ep->monitor->default_audio_info, &monitor->global_settings)) return true; } @@ -1601,34 +1920,34 @@ bool spa_bt_device_supports_a2dp_codec(struct spa_bt_device *device, const struc return false; } -const struct a2dp_codec **spa_bt_device_get_supported_a2dp_codecs(struct spa_bt_device *device, size_t *count, bool sink) +const struct media_codec **spa_bt_device_get_supported_media_codecs(struct spa_bt_device *device, size_t *count, bool sink) { struct spa_bt_monitor *monitor = device->monitor; - const struct a2dp_codec * const * const a2dp_codecs = monitor->a2dp_codecs; - const struct a2dp_codec **supported_codecs; + const struct media_codec * const * const media_codecs = monitor->media_codecs; + const struct media_codec **supported_codecs; size_t i, j, size; *count = 0; size = 8; - supported_codecs = malloc(size * sizeof(const struct a2dp_codec *)); + supported_codecs = malloc(size * sizeof(const struct media_codec *)); if (supported_codecs == NULL) return NULL; j = 0; - for (i = 0; a2dp_codecs[i] != NULL; ++i) { - if (spa_bt_device_supports_a2dp_codec(device, a2dp_codecs[i], sink)) { - supported_codecs[j] = a2dp_codecs[i]; + for (i = 0; media_codecs[i] != NULL; ++i) { + if (spa_bt_device_supports_media_codec(device, media_codecs[i], sink)) { + supported_codecs[j] = media_codecs[i]; ++j; } if (j >= size) { - const struct a2dp_codec **p; + const struct media_codec **p; size = size * 2; #ifdef HAVE_REALLOCARRRAY - p = reallocarray(supported_codecs, size, sizeof(const struct a2dp_codec *)); + p = reallocarray(supported_codecs, size, sizeof(const struct media_codec *)); #else - p = realloc(supported_codecs, size * sizeof(const struct a2dp_codec *)); + p = realloc(supported_codecs, size * sizeof(const struct media_codec *)); #endif if (p == NULL) { free(supported_codecs); @@ -1755,7 +2074,7 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en else spa_log_debug(monitor->log, "remote_endpoint %p: unhandled key %s", remote_endpoint, key); - next: +next: dbus_message_iter_next(props_iter); } return 0; @@ -1804,8 +2123,8 @@ struct spa_bt_transport *spa_bt_transport_find(struct spa_bt_monitor *monitor, c } struct spa_bt_transport *spa_bt_transport_find_full(struct spa_bt_monitor *monitor, - bool (*callback) (struct spa_bt_transport *t, const void *data), - const void *data) + bool (*callback) (struct spa_bt_transport *t, const void *data), + const void *data) { struct spa_bt_transport *t; @@ -1832,6 +2151,7 @@ struct spa_bt_transport *spa_bt_transport_create(struct spa_bt_monitor *monitor, t->delay = SPA_BT_UNKNOWN_DELAY; 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); spa_list_append(&monitor->transport_list, &t->link); @@ -1911,6 +2231,8 @@ void spa_bt_transport_free(struct spa_bt_transport *transport) if (device && device->connected_profiles != prev_connected) spa_bt_device_emit_profiles_changed(device, device->profiles, prev_connected); + spa_list_remove(&transport->bap_transport_linked); + free(transport->endpoint_path); free(transport->path); free(transport); @@ -2131,7 +2453,7 @@ static void spa_bt_transport_volume_timer_event(struct spa_source *source) uint64_t exp; if (spa_system_timerfd_read(monitor->main_system, source->fd, &exp) < 0) - spa_log_warn(monitor->log, "error reading timerfd: %s", strerror(errno)); + spa_log_warn(monitor->log, "error reading timerfd: %s", strerror(errno)); spa_bt_transport_volume_changed(transport); } @@ -2170,10 +2492,10 @@ int64_t spa_bt_transport_get_delay_nsec(struct spa_bt_transport *t) /* Fallback values when device does not provide information */ - if (t->a2dp_codec == NULL) + if (t->media_codec == NULL) return 30 * SPA_NSEC_PER_MSEC; - switch (t->a2dp_codec->id) { + switch (t->media_codec->id) { case SPA_BLUETOOTH_AUDIO_CODEC_SBC: case SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ: return 200 * SPA_NSEC_PER_MSEC; @@ -2189,6 +2511,7 @@ int64_t spa_bt_transport_get_delay_nsec(struct spa_bt_transport *t) case SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX: case SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM: case SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX: + case SPA_BLUETOOTH_AUDIO_CODEC_LC3: return 40 * SPA_NSEC_PER_MSEC; default: break; @@ -2229,6 +2552,12 @@ static int transport_update_props(struct spa_bt_transport *transport, case SPA_BT_PROFILE_A2DP_SINK: transport->profile = SPA_BT_PROFILE_A2DP_SOURCE; break; + case SPA_BT_PROFILE_BAP_SOURCE: + transport->profile = SPA_BT_PROFILE_BAP_SINK; + break; + case SPA_BT_PROFILE_BAP_SINK: + transport->profile = SPA_BT_PROFILE_BAP_SOURCE; + break; default: spa_log_warn(monitor->log, "unknown profile %s", value); break; @@ -2249,6 +2578,16 @@ static int transport_update_props(struct spa_bt_transport *transport, spa_log_warn(monitor->log, "could not find device %s", value); } } + else if (spa_streq(key, "Endpoint")) { + struct spa_bt_remote_endpoint *ep = remote_endpoint_find(monitor, value); + if (!ep) { + spa_log_warn(monitor->log, "Unable to find remote endpoint for %s", value); + goto next; + } + + // If the remote endpoint is an acceptor this transport is an initiator + transport->bap_initiator = ep->acceptor; + } } else if (spa_streq(key, "Codec")) { uint8_t value; @@ -2321,7 +2660,48 @@ static int transport_update_props(struct spa_bt_transport *transport, transport->delay = value; spa_bt_transport_emit_delay_changed(transport); } - next: + else if (spa_streq(key, "PresentationDelay")) { + uint32_t value; + + if (type != DBUS_TYPE_UINT32) + goto next; + dbus_message_iter_get_basic(&it[1], &value); + + spa_log_debug(monitor->log, "transport %p: %s=%02x", transport, key, value); + + transport->delay = value / 100; + spa_bt_transport_emit_delay_changed(transport); + } + else if (spa_streq(key, "Links")) { + DBusMessageIter iter; + + if (!check_iter_signature(&it[1], "ao")) + goto next; + + dbus_message_iter_recurse(&it[1], &iter); + while (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INVALID) { + const char *transport_path; + struct spa_bt_transport *t; + + dbus_message_iter_get_basic(&iter, &transport_path); + + spa_log_debug(monitor->log, "transport %p: Linked with=%s", transport, transport_path); + t = spa_bt_transport_find(monitor, transport_path); + if (!t) { + spa_log_warn(monitor->log, "Unable to find linked transport"); + dbus_message_iter_next(&iter); + continue; + } + + if (spa_list_is_empty(&t->bap_transport_linked)) + spa_list_append(&transport->bap_transport_linked, &t->bap_transport_linked); + else if (spa_list_is_empty(&transport->bap_transport_linked)) + spa_list_append(&t->bap_transport_linked, &transport->bap_transport_linked); + + dbus_message_iter_next(&iter); + } + } +next: dbus_message_iter_next(props_iter); } return 0; @@ -2404,10 +2784,27 @@ static int transport_acquire(void *data, bool optional) { struct spa_bt_transport *transport = data; struct spa_bt_monitor *monitor = transport->monitor; - DBusMessage *m, *r; + DBusMessage *m, *r = NULL; DBusError err; int ret = 0; const char *method = optional ? "TryAcquire" : "Acquire"; + struct spa_bt_transport *t_linked; + + /* 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; + } + } m = dbus_message_new_method_call(BLUEZ_SERVICE, transport->path, @@ -2451,6 +2848,7 @@ static int transport_acquire(void *data, bool optional) ret = -EIO; goto finish; } +done: spa_log_debug(monitor->log, "transport %p: %s %s, fd %d MTU %d:%d", transport, method, transport->path, transport->fd, transport->read_mtu, transport->write_mtu); @@ -2459,7 +2857,8 @@ static int transport_acquire(void *data, bool optional) transport_sync_volume(transport); finish: - dbus_message_unref(r); + if (r) + dbus_message_unref(r); return ret; } @@ -2470,12 +2869,30 @@ static int transport_release(void *data) DBusMessage *m, *r; DBusError err; bool is_idle = (transport->state == SPA_BT_TRANSPORT_STATE_IDLE); + struct spa_bt_transport *t_linked; + bool linked = false; spa_log_debug(monitor->log, "transport %p: Release %s", transport, transport->path); spa_bt_player_set_state(transport->device->adapter->dummy_player, SPA_BT_PLAYER_STOPPED); + /* 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) { + linked = true; + break; + } + } + if (linked) { + spa_log_info(monitor->log, "Linked transport %s released", transport->path); + transport->fd = -1; + return 0; + } + close(transport->fd); transport->fd = -1; @@ -2523,23 +2940,21 @@ static const struct spa_bt_transport_implementation transport_impl = { .set_volume = transport_set_volume, }; -static void append_basic_array_variant_dict_entry(DBusMessageIter *dict, const char* key, const char* variant_type_str, const char* array_type_str, int array_type_int, void* data, int data_size); - -static void a2dp_codec_switch_reply(DBusPendingCall *pending, void *userdata); +static void media_codec_switch_reply(DBusPendingCall *pending, void *userdata); -static int a2dp_codec_switch_cmp(const void *a, const void *b); +static int media_codec_switch_cmp(const void *a, const void *b); -static struct spa_bt_a2dp_codec_switch *a2dp_codec_switch_cmp_sw; /* global for qsort */ +static struct spa_bt_media_codec_switch *media_codec_switch_cmp_sw; /* global for qsort */ -static int a2dp_codec_switch_start_timer(struct spa_bt_a2dp_codec_switch *sw, uint64_t timeout); +static int media_codec_switch_start_timer(struct spa_bt_media_codec_switch *sw, uint64_t timeout); -static int a2dp_codec_switch_stop_timer(struct spa_bt_a2dp_codec_switch *sw); +static int media_codec_switch_stop_timer(struct spa_bt_media_codec_switch *sw); -static void a2dp_codec_switch_free(struct spa_bt_a2dp_codec_switch *sw) +static void media_codec_switch_free(struct spa_bt_media_codec_switch *sw) { char **p; - a2dp_codec_switch_stop_timer(sw); + media_codec_switch_stop_timer(sw); if (sw->pending != NULL) { dbus_pending_call_cancel(sw->pending); @@ -2558,7 +2973,7 @@ static void a2dp_codec_switch_free(struct spa_bt_a2dp_codec_switch *sw) free(sw); } -static void a2dp_codec_switch_next(struct spa_bt_a2dp_codec_switch *sw) +static void media_codec_switch_next(struct spa_bt_media_codec_switch *sw) { spa_assert(*sw->codec_iter != NULL && *sw->path_iter != NULL); @@ -2571,13 +2986,13 @@ static void a2dp_codec_switch_next(struct spa_bt_a2dp_codec_switch *sw) sw->retries = CODEC_SWITCH_RETRIES; } -static bool a2dp_codec_switch_process_current(struct spa_bt_a2dp_codec_switch *sw) +static bool media_codec_switch_process_current(struct spa_bt_media_codec_switch *sw) { struct spa_bt_remote_endpoint *ep; struct spa_bt_transport *t; - const struct a2dp_codec *codec; + const struct media_codec *codec; uint8_t config[A2DP_MAX_CAPS_SIZE]; - char *local_endpoint_base; + enum spa_bt_media_direction direction; char *local_endpoint = NULL; int res, config_size; dbus_bool_t dbus_ret; @@ -2590,43 +3005,43 @@ static bool a2dp_codec_switch_process_current(struct spa_bt_a2dp_codec_switch *s codec = *sw->codec_iter; - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: consider codec %s for remote endpoint %s", + spa_log_debug(sw->device->monitor->log, "media codec switch %p: consider codec %s for remote endpoint %s", sw, (*sw->codec_iter)->name, *sw->path_iter); ep = device_remote_endpoint_find(sw->device, *sw->path_iter); if (ep == NULL || ep->capabilities == NULL || ep->uuid == NULL) { - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: endpoint %s not valid, try next", + spa_log_debug(sw->device->monitor->log, "media codec switch %p: endpoint %s not valid, try next", sw, *sw->path_iter); goto next; } /* Setup and check compatible configuration */ if (ep->codec != codec->codec_id) { - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: different codec, try next", sw); + spa_log_debug(sw->device->monitor->log, "media codec switch %p: different codec, try next", sw); goto next; } if (!(sw->profile & spa_bt_profile_from_uuid(ep->uuid))) { - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: wrong uuid (%s) for profile, try next", + spa_log_debug(sw->device->monitor->log, "media codec switch %p: wrong uuid (%s) for profile, try next", sw, ep->uuid); goto next; } - if (sw->profile & SPA_BT_PROFILE_A2DP_SINK) { - local_endpoint_base = A2DP_SOURCE_ENDPOINT; + if ((sw->profile & SPA_BT_PROFILE_A2DP_SINK) || (sw->profile & SPA_BT_PROFILE_BAP_SINK) ) { + direction = SPA_BT_MEDIA_SOURCE; sink = false; - } else if (sw->profile & SPA_BT_PROFILE_A2DP_SOURCE) { - local_endpoint_base = A2DP_SINK_ENDPOINT; + } else if ((sw->profile & SPA_BT_PROFILE_A2DP_SOURCE) || (sw->profile & SPA_BT_PROFILE_BAP_SOURCE) ) { + direction = SPA_BT_MEDIA_SINK; sink = true; } else { - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: bad profile (%d), try next", + spa_log_debug(sw->device->monitor->log, "media codec switch %p: bad profile (%d), try next", sw, sw->profile); goto next; } - if (a2dp_codec_to_endpoint(codec, local_endpoint_base, &local_endpoint) < 0) { - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: no endpoint for codec %s, try next", + if (media_codec_to_endpoint(codec, direction, &local_endpoint) < 0) { + spa_log_debug(sw->device->monitor->log, "media codec switch %p: no endpoint for codec %s, try next", sw, codec->name); goto next; } @@ -2638,36 +3053,39 @@ static bool a2dp_codec_switch_process_current(struct spa_bt_a2dp_codec_switch *s if (t->device->adapter != sw->device->adapter) continue; if (spa_streq(t->endpoint_path, local_endpoint)) { - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: endpoint %s in use, try next", + spa_log_debug(sw->device->monitor->log, "media codec switch %p: endpoint %s in use, try next", sw, local_endpoint); goto next; } } - res = codec->select_config(codec, sink ? A2DP_CODEC_FLAG_SINK : 0, ep->capabilities, ep->capabilities_len, + res = codec->select_config(codec, sink ? MEDIA_CODEC_FLAG_SINK : 0, ep->capabilities, ep->capabilities_len, &sw->device->monitor->default_audio_info, &sw->device->monitor->global_settings, config); if (res < 0) { - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: incompatible capabilities (%d), try next", + spa_log_debug(sw->device->monitor->log, "media codec switch %p: incompatible capabilities (%d), try next", sw, res); goto next; } config_size = res; - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: configuration %d", sw, config_size); + spa_log_debug(sw->device->monitor->log, "media codec switch %p: configuration %d", sw, config_size); for (i = 0; i < config_size; i++) - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: %d: %02x", sw, i, config[i]); + spa_log_debug(sw->device->monitor->log, "media codec switch %p: %d: %02x", sw, i, config[i]); + + /* Codecs may share the same endpoint, so indicate which one we are using */ + sw->device->preferred_codec = codec; /* org.bluez.MediaEndpoint1.SetConfiguration on remote endpoint */ m = dbus_message_new_method_call(BLUEZ_SERVICE, ep->path, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SetConfiguration"); if (m == NULL) { - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: dbus allocation failure, try next", sw); + spa_log_debug(sw->device->monitor->log, "media codec switch %p: dbus allocation failure, try next", sw); goto next; } spa_bt_device_update_last_bluez_action_time(sw->device); - spa_log_info(sw->device->monitor->log, "a2dp codec switch %p: trying codec %s for endpoint %s, local endpoint %s", + spa_log_info(sw->device->monitor->log, "media codec switch %p: trying codec %s for endpoint %s, local endpoint %s", sw, codec->name, ep->path, local_endpoint); dbus_message_iter_init_append(m, &iter); @@ -2680,16 +3098,16 @@ static bool a2dp_codec_switch_process_current(struct spa_bt_a2dp_codec_switch *s dbus_ret = dbus_connection_send_with_reply(sw->device->monitor->conn, m, &sw->pending, -1); if (!dbus_ret || sw->pending == NULL) { - spa_log_error(sw->device->monitor->log, "a2dp codec switch %p: dbus call failure, try next", sw); + spa_log_error(sw->device->monitor->log, "media codec switch %p: dbus call failure, try next", sw); dbus_message_unref(m); goto next; } - dbus_ret = dbus_pending_call_set_notify(sw->pending, a2dp_codec_switch_reply, sw, NULL); + dbus_ret = dbus_pending_call_set_notify(sw->pending, media_codec_switch_reply, sw, NULL); dbus_message_unref(m); if (!dbus_ret) { - spa_log_error(sw->device->monitor->log, "a2dp codec switch %p: dbus set notify failure", sw); + spa_log_error(sw->device->monitor->log, "media codec switch %p: dbus set notify failure", sw); goto next; } @@ -2701,7 +3119,7 @@ next: return false; } -static void a2dp_codec_switch_process(struct spa_bt_a2dp_codec_switch *sw) +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; @@ -2713,80 +3131,80 @@ static void a2dp_codec_switch_process(struct spa_bt_a2dp_codec_switch *sw) threshold = sw->device->last_bluez_action_time + BLUEZ_ACTION_RATE_MSEC * SPA_NSEC_PER_MSEC; if (now < threshold) { /* Wait for timeout */ - a2dp_codec_switch_start_timer(sw, threshold - now); + media_codec_switch_start_timer(sw, threshold - now); return; } if (sw->path_iter == sw->paths && (*sw->codec_iter)->caps_preference_cmp) { /* Sort endpoints according to codec preference, when at a new codec. */ - a2dp_codec_switch_cmp_sw = sw; - qsort(sw->paths, sw->num_paths, sizeof(char *), a2dp_codec_switch_cmp); + media_codec_switch_cmp_sw = sw; + qsort(sw->paths, sw->num_paths, sizeof(char *), media_codec_switch_cmp); } - if (a2dp_codec_switch_process_current(sw)) { + if (media_codec_switch_process_current(sw)) { /* Wait for dbus reply */ return; } - a2dp_codec_switch_next(sw); + media_codec_switch_next(sw); }; /* Didn't find any suitable endpoint. Report failure. */ - spa_log_info(sw->device->monitor->log, "a2dp codec switch %p: failed to get an endpoint", sw); + spa_log_info(sw->device->monitor->log, "media codec switch %p: failed to get an endpoint", sw); spa_bt_device_emit_codec_switched(sw->device, -ENODEV); spa_bt_device_check_profiles(sw->device, false); - a2dp_codec_switch_free(sw); + media_codec_switch_free(sw); } -static bool a2dp_codec_switch_goto_active(struct spa_bt_a2dp_codec_switch *sw) +static bool media_codec_switch_goto_active(struct spa_bt_media_codec_switch *sw) { struct spa_bt_device *device = sw->device; - struct spa_bt_a2dp_codec_switch *active_sw; + struct spa_bt_media_codec_switch *active_sw; - active_sw = spa_list_first(&device->codec_switch_list, struct spa_bt_a2dp_codec_switch, device_link); + active_sw = spa_list_first(&device->codec_switch_list, struct spa_bt_media_codec_switch, device_link); if (active_sw != sw) { - struct spa_bt_a2dp_codec_switch *t; + struct spa_bt_media_codec_switch *t; /* This codec switch has been canceled. Switch to the newest one. */ spa_log_debug(sw->device->monitor->log, - "a2dp codec switch %p: canceled, go to new switch", sw); + "media codec switch %p: canceled, go to new switch", sw); spa_list_for_each_safe(sw, t, &device->codec_switch_list, device_link) { if (sw != active_sw) - a2dp_codec_switch_free(sw); + media_codec_switch_free(sw); } - a2dp_codec_switch_process(active_sw); + media_codec_switch_process(active_sw); return false; } return true; } -static void a2dp_codec_switch_timer_event(struct spa_source *source) +static void media_codec_switch_timer_event(struct spa_source *source) { - struct spa_bt_a2dp_codec_switch *sw = source->data; + struct spa_bt_media_codec_switch *sw = source->data; struct spa_bt_device *device = sw->device; struct spa_bt_monitor *monitor = device->monitor; uint64_t exp; if (spa_system_timerfd_read(monitor->main_system, source->fd, &exp) < 0) - spa_log_warn(monitor->log, "error reading timerfd: %s", strerror(errno)); + spa_log_warn(monitor->log, "error reading timerfd: %s", strerror(errno)); - spa_log_debug(monitor->log, "a2dp codec switch %p: rate limit timer event", sw); + spa_log_debug(monitor->log, "media codec switch %p: rate limit timer event", sw); - a2dp_codec_switch_stop_timer(sw); + media_codec_switch_stop_timer(sw); - if (!a2dp_codec_switch_goto_active(sw)) + if (!media_codec_switch_goto_active(sw)) return; - a2dp_codec_switch_process(sw); + media_codec_switch_process(sw); } -static void a2dp_codec_switch_reply(DBusPendingCall *pending, void *user_data) +static void media_codec_switch_reply(DBusPendingCall *pending, void *user_data) { - struct spa_bt_a2dp_codec_switch *sw = user_data; + struct spa_bt_media_codec_switch *sw = user_data; struct spa_bt_device *device = sw->device; DBusMessage *r; @@ -2798,7 +3216,7 @@ static void a2dp_codec_switch_reply(DBusPendingCall *pending, void *user_data) spa_bt_device_update_last_bluez_action_time(device); - if (!a2dp_codec_switch_goto_active(sw)) { + if (!media_codec_switch_goto_active(sw)) { if (r != NULL) dbus_message_unref(r); return; @@ -2806,14 +3224,14 @@ static void a2dp_codec_switch_reply(DBusPendingCall *pending, void *user_data) if (r == NULL) { spa_log_error(sw->device->monitor->log, - "a2dp codec switch %p: empty reply from dbus, trying next", + "media codec switch %p: empty reply from dbus, trying next", sw); goto next; } if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_debug(sw->device->monitor->log, - "a2dp codec switch %p: failed (%s), trying next", + "media codec switch %p: failed (%s), trying next", sw, dbus_message_get_error_name(r)); dbus_message_unref(r); goto next; @@ -2822,34 +3240,34 @@ static void a2dp_codec_switch_reply(DBusPendingCall *pending, void *user_data) dbus_message_unref(r); /* Success */ - spa_log_info(sw->device->monitor->log, "a2dp codec switch %p: success", sw); + spa_log_info(sw->device->monitor->log, "media codec switch %p: success", sw); spa_bt_device_emit_codec_switched(sw->device, 0); spa_bt_device_check_profiles(sw->device, false); - a2dp_codec_switch_free(sw); + media_codec_switch_free(sw); return; next: if (sw->retries > 0) --sw->retries; else - a2dp_codec_switch_next(sw); + media_codec_switch_next(sw); - a2dp_codec_switch_process(sw); + media_codec_switch_process(sw); return; } -static int a2dp_codec_switch_start_timer(struct spa_bt_a2dp_codec_switch *sw, uint64_t timeout) +static int media_codec_switch_start_timer(struct spa_bt_media_codec_switch *sw, uint64_t timeout) { struct spa_bt_monitor *monitor = sw->device->monitor; struct itimerspec ts; spa_assert(sw->timer.data == NULL); - spa_log_debug(monitor->log, "a2dp codec switch %p: starting rate limit timer", sw); + spa_log_debug(monitor->log, "media codec switch %p: starting rate limit timer", sw); if (sw->timer.data == NULL) { sw->timer.data = sw; - sw->timer.func = a2dp_codec_switch_timer_event; + sw->timer.func = media_codec_switch_timer_event; sw->timer.fd = spa_system_timerfd_create(monitor->main_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); sw->timer.mask = SPA_IO_IN; @@ -2864,7 +3282,7 @@ static int a2dp_codec_switch_start_timer(struct spa_bt_a2dp_codec_switch *sw, ui return 0; } -static int a2dp_codec_switch_stop_timer(struct spa_bt_a2dp_codec_switch *sw) +static int media_codec_switch_stop_timer(struct spa_bt_media_codec_switch *sw) { struct spa_bt_monitor *monitor = sw->device->monitor; struct itimerspec ts; @@ -2872,7 +3290,7 @@ static int a2dp_codec_switch_stop_timer(struct spa_bt_a2dp_codec_switch *sw) if (sw->timer.data == NULL) return 0; - spa_log_debug(monitor->log, "a2dp codec switch %p: stopping rate limit timer", sw); + spa_log_debug(monitor->log, "media codec switch %p: stopping rate limit timer", sw); spa_loop_remove_source(monitor->main_loop, &sw->timer); ts.it_value.tv_sec = 0; @@ -2885,10 +3303,10 @@ static int a2dp_codec_switch_stop_timer(struct spa_bt_a2dp_codec_switch *sw) return 0; } -static int a2dp_codec_switch_cmp(const void *a, const void *b) +static int media_codec_switch_cmp(const void *a, const void *b) { - struct spa_bt_a2dp_codec_switch *sw = a2dp_codec_switch_cmp_sw; - const struct a2dp_codec *codec = *sw->codec_iter; + struct spa_bt_media_codec_switch *sw = media_codec_switch_cmp_sw; + const struct media_codec *codec = *sw->codec_iter; const char *path1 = *(char **)a, *path2 = *(char **)b; struct spa_bt_remote_endpoint *ep1, *ep2; uint32_t flags; @@ -2912,7 +3330,10 @@ static int a2dp_codec_switch_cmp(const void *a, const void *b) else if (ep2 == NULL) return -1; - flags = spa_streq(ep1->uuid, SPA_BT_UUID_A2DP_SOURCE) ? A2DP_CODEC_FLAG_SINK : 0; + if (codec->bap) + flags = spa_streq(ep1->uuid, SPA_BT_UUID_BAP_SOURCE) ? MEDIA_CODEC_FLAG_SINK : 0; + else + flags = spa_streq(ep1->uuid, SPA_BT_UUID_A2DP_SOURCE) ? MEDIA_CODEC_FLAG_SINK : 0; return codec->caps_preference_cmp(codec, flags, ep1->capabilities, ep1->capabilities_len, ep2->capabilities, ep2->capabilities_len, &sw->device->monitor->default_audio_info, @@ -2920,12 +3341,12 @@ static int a2dp_codec_switch_cmp(const void *a, const void *b) } /* Ensure there's a transport for at least one of the listed codecs */ -int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a2dp_codec * const *codecs) +int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct media_codec * const *codecs) { - struct spa_bt_a2dp_codec_switch *sw; + struct spa_bt_media_codec_switch *sw; struct spa_bt_remote_endpoint *ep; struct spa_bt_transport *t; - const struct a2dp_codec *preferred_codec = NULL; + const struct media_codec *preferred_codec = NULL; size_t i, j, num_codecs, num_eps; if (!device->adapter->application_registered) { @@ -2934,7 +3355,7 @@ int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a } for (i = 0; codecs[i] != NULL; ++i) { - if (spa_bt_device_supports_a2dp_codec(device, codecs[i], true)) { + if (spa_bt_device_supports_media_codec(device, codecs[i], true)) { preferred_codec = codecs[i]; break; } @@ -2946,7 +3367,7 @@ int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a */ if (spa_list_is_empty(&device->codec_switch_list) && preferred_codec != NULL) { spa_list_for_each(t, &device->transport_list, device_link) { - if (t->a2dp_codec != preferred_codec) + if (t->media_codec != preferred_codec) continue; if ((device->connected_profiles & t->profile) != t->profile) @@ -2959,7 +3380,7 @@ int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a /* Setup and start iteration */ - sw = calloc(1, sizeof(struct spa_bt_a2dp_codec_switch)); + sw = calloc(1, sizeof(struct spa_bt_media_codec_switch)); if (sw == NULL) return -ENOMEM; @@ -2971,17 +3392,17 @@ int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a while (codecs[num_codecs] != NULL) ++num_codecs; - sw->codecs = calloc(num_codecs + 1, sizeof(const struct a2dp_codec *)); + sw->codecs = calloc(num_codecs + 1, sizeof(const struct media_codec *)); sw->paths = calloc(num_eps + 1, sizeof(char *)); sw->num_paths = num_eps; if (sw->codecs == NULL || sw->paths == NULL) { - a2dp_codec_switch_free(sw); + media_codec_switch_free(sw); return -ENOMEM; } for (i = 0, j = 0; i < num_codecs; ++i) { - if (is_a2dp_codec_enabled(device->monitor, codecs[i])) { + if (is_media_codec_enabled(device->monitor, codecs[i])) { sw->codecs[j] = codecs[i]; ++j; } @@ -2992,7 +3413,7 @@ int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a spa_list_for_each(ep, &device->remote_endpoint_list, device_link) { sw->paths[i] = strdup(ep->path); if (sw->paths[i] == NULL) { - a2dp_codec_switch_free(sw); + media_codec_switch_free(sw); return -ENOMEM; } ++i; @@ -3018,13 +3439,13 @@ int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a * to wait to pass in any case, so we don't cancel it either. */ spa_log_debug(sw->device->monitor->log, - "a2dp codec switch %p: already in progress, canceling previous", + "media codec switch %p: already in progress, canceling previous", sw); spa_list_prepend(&device->codec_switch_list, &sw->device_link); } else { spa_list_prepend(&device->codec_switch_list, &sw->device_link); - a2dp_codec_switch_process(sw); + media_codec_switch_process(sw); } return 0; @@ -3050,7 +3471,7 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, DBusMessageIter it[2]; DBusMessage *r; struct spa_bt_transport *transport; - const struct a2dp_codec *codec; + const struct media_codec *codec; int profile; bool sink; @@ -3060,8 +3481,8 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, } endpoint = dbus_message_get_path(m); - profile = a2dp_endpoint_to_profile(endpoint); - codec = a2dp_endpoint_to_codec(monitor, endpoint, &sink); + profile = media_endpoint_to_profile(endpoint); + codec = media_endpoint_to_codec(monitor, endpoint, &sink, NULL); if (codec == NULL) { spa_log_warn(monitor->log, "unknown SetConfiguration() codec"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; @@ -3102,7 +3523,7 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, free(transport->endpoint_path); transport->endpoint_path = strdup(endpoint); transport->profile = profile; - transport->a2dp_codec = codec; + transport->media_codec = codec; transport_update_props(transport, &it[1], NULL); if (transport->device == NULL || transport->device->adapter == NULL) { @@ -3110,6 +3531,12 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } + /* If multiple codecs share the endpoint, pick the one we wanted */ + transport->media_codec = codec = media_endpoint_to_codec(monitor, endpoint, &sink, + transport->device->preferred_codec); + spa_assert(codec != NULL); + spa_log_debug(monitor->log, "%p: %s codec:%s", monitor, path, codec ? codec->name : "<null>"); + spa_bt_device_update_last_bluez_action_time(transport->device); if (profile & SPA_BT_PROFILE_A2DP_SOURCE) { @@ -3122,7 +3549,7 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, if (codec->validate_config) { struct spa_audio_info info; - if (codec->validate_config(codec, sink ? A2DP_CODEC_FLAG_SINK : 0, + if (codec->validate_config(codec, sink ? MEDIA_CODEC_FLAG_SINK : 0, transport->configuration, transport->configuration_len, &info) < 0) { spa_log_error(monitor->log, "invalid transport configuration"); @@ -3202,8 +3629,9 @@ static DBusHandlerResult endpoint_release(DBusConnection *conn, DBusMessage *m, { DBusMessage *r; - r = dbus_message_new_error(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE ".Error.NotImplemented", - "Method not implemented"); + r = dbus_message_new_error(m, + BLUEZ_MEDIA_ENDPOINT_INTERFACE ".Error.NotImplemented", + "Method not implemented"); if (r == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_connection_send(conn, r, NULL)) @@ -3244,6 +3672,8 @@ static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, voi res = endpoint_set_configuration(c, path, m, userdata); else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SelectConfiguration")) res = endpoint_select_configuration(c, m, userdata); + else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SelectProperties")) + res = endpoint_select_properties(c, m, userdata); else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "ClearConfiguration")) res = endpoint_clear_configuration(c, m, userdata); else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "Release")) @@ -3260,6 +3690,8 @@ static void bluez_register_endpoint_reply(DBusPendingCall *pending, void *user_d DBusMessage *r; r = dbus_pending_call_steal_reply(pending); + dbus_pending_call_unref(pending); + if (r == NULL) return; @@ -3273,9 +3705,8 @@ static void bluez_register_endpoint_reply(DBusPendingCall *pending, void *user_d goto finish; } - finish: +finish: dbus_message_unref(r); - dbus_pending_call_unref(pending); } static void append_basic_variant_dict_entry(DBusMessageIter *dict, const char* key, int variant_type_int, const char* variant_type_str, void* variant) { @@ -3303,24 +3734,25 @@ static void append_basic_array_variant_dict_entry(DBusMessageIter *dict, const c } static int bluez_register_endpoint(struct spa_bt_monitor *monitor, - const char *path, const char *endpoint, - const char *uuid, const struct a2dp_codec *codec) { - char *object_path = NULL; + const char *path, enum spa_bt_media_direction direction, + const char *uuid, const struct media_codec *codec) +{ + char *object_path = NULL; DBusMessage *m; DBusMessageIter object_it, dict_it; DBusPendingCall *call; uint8_t caps[A2DP_MAX_CAPS_SIZE]; int ret, caps_size; uint16_t codec_id = codec->codec_id; - bool sink; + bool sink = (direction == SPA_BT_MEDIA_SINK); - ret = a2dp_codec_to_endpoint(codec, endpoint, &object_path); + spa_assert(codec->fill_caps); + + ret = media_codec_to_endpoint(codec, direction, &object_path); if (ret < 0) goto error; - sink = spa_streq(endpoint, A2DP_SINK_ENDPOINT); - - ret = caps_size = codec->fill_caps(codec, sink ? A2DP_CODEC_FLAG_SINK : 0, caps); + ret = caps_size = codec->fill_caps(codec, sink ? MEDIA_CODEC_FLAG_SINK : 0, caps); if (ret < 0) goto error; @@ -3357,37 +3789,10 @@ error: return ret; } -static int register_a2dp_endpoint(struct spa_bt_monitor *monitor, - const struct a2dp_codec *codec, const char *endpoint) -{ - int ret; - char* object_path = NULL; - const DBusObjectPathVTable vtable_endpoint = { - .message_function = endpoint_handler, - }; - - ret = a2dp_codec_to_endpoint(codec, endpoint, &object_path); - if (ret < 0) - return ret; - - spa_log_info(monitor->log, "Registering endpoint: %s", object_path); - - if (!dbus_connection_register_object_path(monitor->conn, - object_path, - &vtable_endpoint, monitor)) { - free(object_path); - return -EIO; - } - - free(object_path); - return 0; - -} - static int adapter_register_endpoints(struct spa_bt_adapter *a) { struct spa_bt_monitor *monitor = a->monitor; - const struct a2dp_codec * const * const a2dp_codecs = monitor->a2dp_codecs; + const struct media_codec * const * const media_codecs = monitor->media_codecs; int i; int err = 0; @@ -3398,31 +3803,33 @@ static int adapter_register_endpoints(struct spa_bt_adapter *a) * It doesn't make sense to register codecs other than SBC * as bluez5 will probably use SBC anyway and we have no control over it * let's incentivize users to upgrade their bluez5 daemon - * if they want proper a2dp codec support + * if they want proper media codec support * */ - spa_log_warn(monitor->log, "Using legacy bluez5 API for A2DP - only SBC will be supported. " - "Please upgrade bluez5."); + spa_log_warn(monitor->log, + "Using legacy bluez5 API for A2DP - only SBC will be supported. " + "Please upgrade bluez5."); - for (i = 0; a2dp_codecs[i]; i++) { - const struct a2dp_codec *codec = a2dp_codecs[i]; + for (i = 0; media_codecs[i]; i++) { + const struct media_codec *codec = media_codecs[i]; - if (!is_a2dp_codec_enabled(monitor, codec)) + if (codec->id != SPA_BLUETOOTH_AUDIO_CODEC_SBC) continue; - if (!(codec->codec_id == A2DP_CODEC_SBC && spa_streq(codec->name, "sbc"))) - continue; - - if ((err = bluez_register_endpoint(monitor, a->path, - A2DP_SOURCE_ENDPOINT, - SPA_BT_UUID_A2DP_SOURCE, - codec))) - goto out; + if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SOURCE)) { + if ((err = bluez_register_endpoint(monitor, a->path, + SPA_BT_MEDIA_SOURCE, + SPA_BT_UUID_A2DP_SOURCE, + codec))) + goto out; + } - if ((err = bluez_register_endpoint(monitor, a->path, - A2DP_SINK_ENDPOINT, - SPA_BT_UUID_A2DP_SINK, - codec))) - goto out; + if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SINK)) { + if ((err = bluez_register_endpoint(monitor, a->path, + SPA_BT_MEDIA_SINK, + SPA_BT_UUID_A2DP_SINK, + codec))) + goto out; + } a->endpoints_registered = true; break; @@ -3434,14 +3841,14 @@ static int adapter_register_endpoints(struct spa_bt_adapter *a) err = -ENOSYS; } - out: +out: if (err) { spa_log_error(monitor->log, "Failed to register bluez5 endpoints"); } return err; } -static void append_a2dp_object(DBusMessageIter *iter, const char *endpoint, +static void append_media_object(DBusMessageIter *iter, const char *endpoint, const char *uuid, uint8_t codec_id, uint8_t *caps, size_t caps_size) { const char *interface_name = BLUEZ_MEDIA_ENDPOINT_INTERFACE; @@ -3475,7 +3882,7 @@ static void append_a2dp_object(DBusMessageIter *iter, const char *endpoint, static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *m, void *user_data) { struct spa_bt_monitor *monitor = user_data; - const struct a2dp_codec * const * const a2dp_codecs = monitor->a2dp_codecs; + const struct media_codec * const * const media_codecs = monitor->media_codecs; const char *path, *interface, *member; char *endpoint; DBusMessage *r; @@ -3509,38 +3916,52 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage * dbus_message_iter_init_append(r, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{oa{sa{sv}}}", &array); - for (i = 0; a2dp_codecs[i]; i++) { - const struct a2dp_codec *codec = a2dp_codecs[i]; + for (i = 0; media_codecs[i]; i++) { + const struct media_codec *codec = media_codecs[i]; uint8_t caps[A2DP_MAX_CAPS_SIZE]; int caps_size, ret; uint16_t codec_id = codec->codec_id; - if (!is_a2dp_codec_enabled(monitor, codec)) + if (!is_media_codec_enabled(monitor, codec)) + continue; + + if (codec->bap && !monitor->le_audio_supported) { + /* The legacy bluez5 api doesn't support LE Audio + * It doesn't make sense to register unsupported codecs as it prevents + * registration of A2DP codecs + * let's incentivize users to upgrade their bluez5 daemon + * if they want proper media codec support + * */ + spa_log_warn(monitor->log, "Trying to use legacy bluez5 API for LE Audio - only A2DP will be supported. " + "Please upgrade bluez5."); continue; + } - if (codec->decode != NULL) { - caps_size = codec->fill_caps(codec, A2DP_CODEC_FLAG_SINK, caps); + if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SINK)) { + caps_size = codec->fill_caps(codec, MEDIA_CODEC_FLAG_SINK, caps); if (caps_size < 0) continue; - ret = a2dp_codec_to_endpoint(codec, A2DP_SINK_ENDPOINT, &endpoint); + ret = media_codec_to_endpoint(codec, SPA_BT_MEDIA_SINK, &endpoint); if (ret == 0) { - spa_log_info(monitor->log, "register A2DP sink codec %s: %s", a2dp_codecs[i]->name, endpoint); - append_a2dp_object(&array, endpoint, SPA_BT_UUID_A2DP_SINK, + spa_log_info(monitor->log, "register media sink codec %s: %s", media_codecs[i]->name, endpoint); + append_media_object(&array, endpoint, + codec->bap ? SPA_BT_UUID_BAP_SINK : SPA_BT_UUID_A2DP_SINK, codec_id, caps, caps_size); free(endpoint); } } - if (codec->encode != NULL) { + if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SOURCE)) { caps_size = codec->fill_caps(codec, 0, caps); if (caps_size < 0) continue; - ret = a2dp_codec_to_endpoint(codec, A2DP_SOURCE_ENDPOINT, &endpoint); + ret = media_codec_to_endpoint(codec, SPA_BT_MEDIA_SOURCE, &endpoint); if (ret == 0) { - spa_log_info(monitor->log, "register A2DP source codec %s: %s", a2dp_codecs[i]->name, endpoint); - append_a2dp_object(&array, endpoint, SPA_BT_UUID_A2DP_SOURCE, + spa_log_info(monitor->log, "register media source codec %s: %s", media_codecs[i]->name, endpoint); + append_media_object(&array, endpoint, + codec->bap ? SPA_BT_UUID_BAP_SOURCE : SPA_BT_UUID_A2DP_SOURCE, codec_id, caps, caps_size); free(endpoint); } @@ -3566,6 +3987,8 @@ static void bluez_register_application_reply(DBusPendingCall *pending, void *use bool fallback = true; r = dbus_pending_call_steal_reply(pending); + dbus_pending_call_unref(pending); + if (r == NULL) return; @@ -3585,71 +4008,100 @@ static void bluez_register_application_reply(DBusPendingCall *pending, void *use finish: dbus_message_unref(r); - dbus_pending_call_unref(pending); if (fallback) adapter_register_endpoints(adapter); } +static int register_media_endpoint(struct spa_bt_monitor *monitor, + const struct media_codec *codec, + enum spa_bt_media_direction direction) +{ + static const DBusObjectPathVTable vtable_endpoint = { + .message_function = endpoint_handler, + }; + + if (!endpoint_should_be_registered(monitor, codec, direction)) + return 0; + + char *object_path = NULL; + int ret = media_codec_to_endpoint(codec, direction, &object_path); + if (ret < 0) + return ret; + + spa_log_info(monitor->log, "registering endpoint: %s", object_path); + + if (!dbus_connection_register_object_path(monitor->conn, + object_path, + &vtable_endpoint, monitor)) + { + ret = -EIO; + } + + free(object_path); + return ret; +} + static int register_media_application(struct spa_bt_monitor * monitor) { - const struct a2dp_codec * const * const a2dp_codecs = monitor->a2dp_codecs; + const struct media_codec * const * const media_codecs = monitor->media_codecs; const DBusObjectPathVTable vtable_object_manager = { .message_function = object_manager_handler, }; - spa_log_info(monitor->log, "Registering media application object: " A2DP_OBJECT_MANAGER_PATH); + spa_log_info(monitor->log, "Registering media application object: " MEDIA_OBJECT_MANAGER_PATH); if (!dbus_connection_register_object_path(monitor->conn, - A2DP_OBJECT_MANAGER_PATH, + MEDIA_OBJECT_MANAGER_PATH, &vtable_object_manager, monitor)) return -EIO; - for (int i = 0; a2dp_codecs[i]; i++) { - const struct a2dp_codec *codec = a2dp_codecs[i]; - - if (!is_a2dp_codec_enabled(monitor, codec)) - continue; + for (int i = 0; media_codecs[i]; i++) { + const struct media_codec *codec = media_codecs[i]; - if (codec->encode != NULL) - register_a2dp_endpoint(monitor, codec, A2DP_SOURCE_ENDPOINT); - if (codec->decode != NULL) - register_a2dp_endpoint(monitor, codec, A2DP_SINK_ENDPOINT); + register_media_endpoint(monitor, codec, SPA_BT_MEDIA_SOURCE); + register_media_endpoint(monitor, codec, SPA_BT_MEDIA_SINK); } return 0; } -static void unregister_media_application(struct spa_bt_monitor * monitor) +static void unregister_media_endpoint(struct spa_bt_monitor *monitor, + const struct media_codec *codec, + enum spa_bt_media_direction direction) { - const struct a2dp_codec * const * const a2dp_codecs = monitor->a2dp_codecs; - int ret; + if (!endpoint_should_be_registered(monitor, codec, direction)) + return; + char *object_path = NULL; + int ret = media_codec_to_endpoint(codec, direction, &object_path); + if (ret < 0) + return; - for (int i = 0; a2dp_codecs[i]; i++) { - const struct a2dp_codec *codec = a2dp_codecs[i]; + spa_log_info(monitor->log, "unregistering endpoint: %s", object_path); - if (!is_a2dp_codec_enabled(monitor, codec)) - continue; + if (!dbus_connection_unregister_object_path(monitor->conn, object_path)) + spa_log_warn(monitor->log, "failed to unregister %s\n", object_path); - ret = a2dp_codec_to_endpoint(codec, A2DP_SOURCE_ENDPOINT, &object_path); - if (ret == 0) { - dbus_connection_unregister_object_path(monitor->conn, object_path); - free(object_path); - } + free(object_path); +} - ret = a2dp_codec_to_endpoint(codec, A2DP_SINK_ENDPOINT, &object_path); - if (ret == 0) { - dbus_connection_unregister_object_path(monitor->conn, object_path); - free(object_path); - } +static void unregister_media_application(struct spa_bt_monitor * monitor) +{ + const struct media_codec * const * const media_codecs = monitor->media_codecs; + + for (int i = 0; media_codecs[i]; i++) { + const struct media_codec *codec = media_codecs[i]; + + unregister_media_endpoint(monitor, codec, SPA_BT_MEDIA_SOURCE); + unregister_media_endpoint(monitor, codec, SPA_BT_MEDIA_SINK); } - dbus_connection_unregister_object_path(monitor->conn, A2DP_OBJECT_MANAGER_PATH); + dbus_connection_unregister_object_path(monitor->conn, MEDIA_OBJECT_MANAGER_PATH); } static int adapter_register_application(struct spa_bt_adapter *a) { - const char *object_manager_path = A2DP_OBJECT_MANAGER_PATH; + const char *object_manager_path = MEDIA_OBJECT_MANAGER_PATH; struct spa_bt_monitor *monitor = a->monitor; DBusMessage *m; DBusMessageIter i, d; @@ -3745,6 +4197,48 @@ static void reselect_backend(struct spa_bt_monitor *monitor, bool silent) backend ? backend->name : "none"); } +static int media_update_props(struct spa_bt_monitor *monitor, + DBusMessageIter *props_iter, + DBusMessageIter *invalidated_iter) +{ + while (dbus_message_iter_get_arg_type(props_iter) != DBUS_TYPE_INVALID) { + DBusMessageIter it[2]; + const char *key; + + dbus_message_iter_recurse(props_iter, &it[0]); + dbus_message_iter_get_basic(&it[0], &key); + dbus_message_iter_next(&it[0]); + dbus_message_iter_recurse(&it[0], &it[1]); + + if (spa_streq(key, "SupportedUUIDs")) { + DBusMessageIter iter; + + if (!check_iter_signature(&it[1], "as")) + goto next; + + dbus_message_iter_recurse(&it[1], &iter); + + while (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INVALID) { + const char *uuid; + + dbus_message_iter_get_basic(&iter, &uuid); + + if (spa_streq(uuid, SPA_BT_UUID_BAP_SINK)) { + monitor->le_audio_supported = true; + spa_log_info(monitor->log, "LE Audio supported"); + } + dbus_message_iter_next(&iter); + } + } + else + spa_log_debug(monitor->log, "media: unhandled key %s", key); + +next: + dbus_message_iter_next(props_iter); + } + return 0; +} + static void interface_added(struct spa_bt_monitor *monitor, DBusConnection *conn, const char *object_path, @@ -3818,6 +4312,9 @@ static void interface_added(struct spa_bt_monitor *monitor, if (d) spa_bt_device_emit_profiles_changed(d, d->profiles, d->connected_profiles); } + else if (spa_streq(interface_name, BLUEZ_MEDIA_INTERFACE)) { + media_update_props(monitor, props_iter, NULL); + } } static void interfaces_added(struct spa_bt_monitor *monitor, DBusMessageIter *arg_iter) @@ -3892,7 +4389,12 @@ static void get_managed_objects_reply(DBusPendingCall *pending, void *user_data) DBusMessage *r; DBusMessageIter it[6]; + spa_assert(pending == monitor->get_managed_objects_call); + monitor->get_managed_objects_call = NULL; + r = dbus_pending_call_steal_reply(pending); + dbus_pending_call_unref(pending); + if (r == NULL) return; @@ -3927,14 +4429,16 @@ static void get_managed_objects_reply(DBusPendingCall *pending, void *user_data) monitor->objects_listed = true; - finish: +finish: dbus_message_unref(r); - dbus_pending_call_unref(pending); return; } static void get_managed_objects(struct spa_bt_monitor *monitor) { + if (monitor->objects_listed || monitor->get_managed_objects_call) + return; + DBusMessage *m; DBusPendingCall *call; @@ -3943,74 +4447,13 @@ static void get_managed_objects(struct spa_bt_monitor *monitor) "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); - dbus_connection_send_with_reply(monitor->conn, m, &call, -1); - dbus_pending_call_set_notify(call, get_managed_objects_reply, monitor, NULL); - dbus_message_unref(m); -} - -static void check_name_owner_reply(DBusPendingCall *pending, void *user_data) -{ - struct spa_bt_monitor *monitor = user_data; - DBusMessage *r; - DBusError error; - bool running; - - r = dbus_pending_call_steal_reply(pending); - if (r == NULL) - return; - - if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { - spa_log_warn(monitor->log, "BlueZ D-Bus ObjectManager not available"); - goto finish; - } - if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { - spa_log_error(monitor->log, "NameHasOwner() failed: %s", - dbus_message_get_error_name(r)); - goto finish; - } - if (!spa_streq(dbus_message_get_signature(r), "b")) { - spa_log_error(monitor->log, "Invalid reply signature for NameHasOwner()"); - goto finish; - } - - dbus_error_init(&error); - dbus_message_get_args(r, &error, DBUS_TYPE_BOOLEAN, &running, DBUS_TYPE_INVALID); - - if (dbus_error_is_set(&error)) { - spa_log_error(monitor->log, "Could not check bluetooth service: %s", error.message); - dbus_error_free(&error); - goto finish; - } - - spa_log_info(monitor->log, "bluetooth service running: %s", - running ? "yes" : "no"); - if (running) - get_managed_objects(monitor); - -finish: - dbus_message_unref(r); - dbus_pending_call_unref(pending); - return; -} - -static void check_name_owner(struct spa_bt_monitor *monitor) -{ - DBusMessage *m; - DBusPendingCall *call; - const char *service = BLUEZ_SERVICE; - - m = dbus_message_new_method_call("org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "NameHasOwner"); - if (m == NULL) - return; - - dbus_message_append_args(m, DBUS_TYPE_STRING, &service, DBUS_TYPE_INVALID); + dbus_message_set_auto_start(m, false); dbus_connection_send_with_reply(monitor->conn, m, &call, -1); - dbus_pending_call_set_notify(call, check_name_owner_reply, monitor, NULL); + dbus_pending_call_set_notify(call, get_managed_objects_reply, monitor, NULL); dbus_message_unref(m); + + monitor->get_managed_objects_call = call; } static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *user_data) @@ -4189,7 +4632,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us transport_update_props(transport, &it[1], NULL); } - } + } fail: dbus_error_free(&err); @@ -4271,7 +4714,13 @@ impl_device_add_listener(void *object, struct spa_hook *listener, spa_hook_list_isolate(&this->hooks, &save, listener, events, data); add_filters(this); - check_name_owner(this); + get_managed_objects(this); + + struct spa_bt_device *device; + spa_list_for_each(device, &this->device_list, link) { + if (device->added) + emit_device_info(this, device, this->connection_info_supported); + } spa_hook_list_join(&this->hooks, &save); @@ -4325,6 +4774,11 @@ static int impl_clear(struct spa_handle *handle) monitor->filters_added = false; } + if (monitor->get_managed_objects_call) { + dbus_pending_call_cancel(monitor->get_managed_objects_call); + dbus_pending_call_unref(monitor->get_managed_objects_call); + } + spa_list_consume(t, &monitor->transport_list, link) spa_bt_transport_free(t); spa_list_consume(ep, &monitor->remote_endpoint_list, link) @@ -4361,7 +4815,7 @@ static int impl_clear(struct spa_handle *handle) spa_bt_quirks_destroy(monitor->quirks); - free_a2dp_codecs(monitor->a2dp_codecs); + free_media_codecs(monitor->media_codecs); return 0; } @@ -4397,6 +4851,10 @@ int spa_bt_profiles_from_json_array(const char *str) profiles |= SPA_BT_PROFILE_A2DP_SINK; } else if (spa_streq(role_name, "a2dp_source")) { profiles |= SPA_BT_PROFILE_A2DP_SOURCE; + } else if (spa_streq(role_name, "bap_sink")) { + profiles |= SPA_BT_PROFILE_BAP_SINK; + } else if (spa_streq(role_name, "bap_source")) { + profiles |= SPA_BT_PROFILE_BAP_SOURCE; } } @@ -4405,7 +4863,7 @@ int spa_bt_profiles_from_json_array(const char *str) static int parse_codec_array(struct spa_bt_monitor *this, const struct spa_dict *info) { - const struct a2dp_codec * const * const a2dp_codecs = this->a2dp_codecs; + const struct media_codec * const * const media_codecs = this->media_codecs; const char *str; struct spa_dict_item *codecs; struct spa_json it, it_array; @@ -4416,7 +4874,7 @@ static int parse_codec_array(struct spa_bt_monitor *this, const struct spa_dict /* Parse bluez5.codecs property to a dict of enabled codecs */ num_codecs = 0; - while (a2dp_codecs[num_codecs]) + while (media_codecs[num_codecs]) ++num_codecs; codecs = calloc(num_codecs, sizeof(struct spa_dict_item)); @@ -4438,8 +4896,8 @@ static int parse_codec_array(struct spa_bt_monitor *this, const struct spa_dict while (spa_json_get_string(&it_array, codec_name, sizeof(codec_name)) > 0) { int i; - for (i = 0; a2dp_codecs[i]; ++i) { - const struct a2dp_codec *codec = a2dp_codecs[i]; + for (i = 0; media_codecs[i]; ++i) { + const struct media_codec *codec = media_codecs[i]; if (!spa_streq(codec->name, codec_name)) continue; @@ -4461,16 +4919,16 @@ static int parse_codec_array(struct spa_bt_monitor *this, const struct spa_dict spa_dict_qsort(&this->enabled_codecs); - for (i = 0; a2dp_codecs[i]; ++i) { - const struct a2dp_codec *codec = a2dp_codecs[i]; - if (!is_a2dp_codec_enabled(this, codec)) + for (i = 0; media_codecs[i]; ++i) { + const struct media_codec *codec = media_codecs[i]; + if (!is_media_codec_enabled(this, codec)) spa_log_debug(this->log, "disabling codec %s", codec->name); } return 0; fallback: - for (i = 0; a2dp_codecs[i]; ++i) { - const struct a2dp_codec *codec = a2dp_codecs[i]; + for (i = 0; media_codecs[i]; ++i) { + const struct media_codec *codec = media_codecs[i]; spa_log_debug(this->log, "enabling codec %s", codec->name); codecs[i].key = codec->name; codecs[i].value = "true"; @@ -4536,14 +4994,14 @@ impl_init(const struct spa_handle_factory *factory, return -EINVAL; } - this->a2dp_codecs = NULL; + this->media_codecs = NULL; this->quirks = NULL; this->conn = NULL; this->dbus_connection = NULL; - this->a2dp_codecs = load_a2dp_codecs(this->plugin_loader, this->log); - if (this->a2dp_codecs == NULL) { - spa_log_error(this->log, "failed to load required A2DP codec plugins"); + this->media_codecs = load_media_codecs(this->plugin_loader, this->log); + if (this->media_codecs == NULL) { + spa_log_error(this->log, "failed to load required media codec plugins"); res = -EIO; goto fail; } @@ -4640,15 +5098,15 @@ impl_init(const struct spa_handle_factory *factory, return 0; fail: - if (this->a2dp_codecs) - free_a2dp_codecs(this->a2dp_codecs); + if (this->media_codecs) + free_media_codecs(this->media_codecs); if (this->quirks) spa_bt_quirks_destroy(this->quirks); if (this->conn) dbus_connection_unref(this->conn); if (this->dbus_connection) spa_dbus_connection_destroy(this->dbus_connection); - this->a2dp_codecs = NULL; + this->media_codecs = NULL; this->quirks = NULL; this->conn = NULL; this->dbus_connection = NULL; diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 5a23ea4409c1c0842957ae9952b1be1cba2c6d68..f3c2d721f3b05f031f3d6284d1785047b52de317 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -51,7 +51,7 @@ #include <spa/debug/pod.h> #include "defs.h" -#include "a2dp-codecs.h" +#include "media-codecs.h" static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.device"); #undef SPA_LOG_TOPIC_DEFAULT @@ -73,6 +73,8 @@ enum { DEVICE_PROFILE_AG = 1, DEVICE_PROFILE_A2DP = 2, DEVICE_PROFILE_HSP_HFP = 3, + DEVICE_PROFILE_BAP = 4, + DEVICE_PROFILE_LAST = DEVICE_PROFILE_BAP, }; struct props { @@ -140,11 +142,11 @@ struct impl { unsigned int save_profile:1; uint32_t prev_bt_connected_profiles; - const struct a2dp_codec **supported_codecs; + const struct media_codec **supported_codecs; size_t supported_codec_count; - struct dynamic_node dyn_a2dp_source; - struct dynamic_node dyn_a2dp_sink; + struct dynamic_node dyn_media_source; + struct dynamic_node dyn_media_sink; struct dynamic_node dyn_sco_source; struct dynamic_node dyn_sco_sink; @@ -167,9 +169,9 @@ static void init_node(struct impl *this, struct node *node, uint32_t id) } } -static void get_a2dp_codecs(struct impl *this, enum spa_bluetooth_audio_codec id, const struct a2dp_codec **codecs, size_t size) +static void get_media_codecs(struct impl *this, enum spa_bluetooth_audio_codec id, const struct media_codec **codecs, size_t size) { - const struct a2dp_codec * const *c; + const struct media_codec * const *c; spa_assert(size > 0); spa_assert(this->supported_codecs); @@ -184,18 +186,18 @@ static void get_a2dp_codecs(struct impl *this, enum spa_bluetooth_audio_codec id *codecs = NULL; } -static const struct a2dp_codec *get_supported_a2dp_codec(struct impl *this, enum spa_bluetooth_audio_codec id, size_t *idx) +static const struct media_codec *get_supported_media_codec(struct impl *this, enum spa_bluetooth_audio_codec id, size_t *idx) { - const struct a2dp_codec *a2dp_codec = NULL; + const struct media_codec *media_codec = NULL; size_t i; for (i = 0; i < this->supported_codec_count; ++i) { if (this->supported_codecs[i]->id == id) { - a2dp_codec = this->supported_codecs[i]; + media_codec = this->supported_codecs[i]; if (idx) *idx = i; } } - return a2dp_codec; + return media_codec; } static unsigned int get_hfp_codec(enum spa_bluetooth_audio_codec id) @@ -245,10 +247,10 @@ static const char *get_hfp_codec_name(unsigned int codec) static const char *get_codec_name(struct spa_bt_transport *t, bool a2dp_duplex) { - if (t->a2dp_codec != NULL) { - if (a2dp_duplex && t->a2dp_codec->duplex_codec) - return t->a2dp_codec->duplex_codec->name; - return t->a2dp_codec->name; + if (t->media_codec != NULL) { + if (a2dp_duplex && t->media_codec->duplex_codec) + return t->media_codec->duplex_codec->name; + return t->media_codec->name; } return get_hfp_codec_name(t->codec); } @@ -301,7 +303,7 @@ static void emit_info(struct impl *this, bool full); static float get_soft_volume_boost(struct node *node) { - const struct a2dp_codec *codec = node->transport ? node->transport->a2dp_codec : NULL; + const struct media_codec *codec = node->transport ? node->transport->media_codec : NULL; /* * For A2DP duplex, the duplex microphone channel sometimes does not appear @@ -351,6 +353,7 @@ static bool node_update_volume_from_transport(struct node *node, bool reset) /* PW is the controller for remote device. */ if (impl->profile != DEVICE_PROFILE_A2DP + && impl->profile != DEVICE_PROFILE_BAP && impl->profile != DEVICE_PROFILE_HSP_HFP) return false; @@ -406,16 +409,16 @@ static const struct spa_bt_transport_events transport_events = { static void get_channels(struct spa_bt_transport *t, bool a2dp_duplex, uint32_t *n_channels, uint32_t *channels) { - const struct a2dp_codec *codec; + const struct media_codec *codec; struct spa_audio_info info = { 0 }; - if (!a2dp_duplex || !t->a2dp_codec || !t->a2dp_codec->duplex_codec) { + if (!a2dp_duplex || !t->media_codec || !t->media_codec->duplex_codec) { *n_channels = t->n_channels; memcpy(channels, t->channels, t->n_channels * sizeof(uint32_t)); return; } - codec = t->a2dp_codec->duplex_codec; + codec = t->media_codec->duplex_codec; if (!codec->validate_config || codec->validate_config(codec, 0, @@ -514,7 +517,7 @@ static struct spa_bt_transport *find_transport(struct impl *this, int profile, e spa_list_for_each(t, &device->transport_list, device_link) { bool codec_ok = codec == 0 || - (t->a2dp_codec != NULL && t->a2dp_codec->id == codec) || + (t->media_codec != NULL && t->media_codec->id == codec) || get_hfp_codec_id(t->codec) == codec; if ((t->profile & device->connected_profiles) && @@ -676,15 +679,15 @@ static int emit_nodes(struct impl *this) 1, SPA_NAME_API_BLUEZ5_SCO_SINK, false); } } - if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) { + if (this->bt_dev->connected_profiles & (SPA_BT_PROFILE_A2DP_SOURCE)) { t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE, 0); if (t) { - this->props.codec = t->a2dp_codec->id; - emit_dynamic_node(&this->dyn_a2dp_source, this, t, + this->props.codec = t->media_codec->id; + emit_dynamic_node(&this->dyn_media_source, this, t, 2, SPA_NAME_API_BLUEZ5_A2DP_SOURCE, false); - if (t->a2dp_codec->duplex_codec) { - emit_dynamic_node(&this->dyn_a2dp_sink, this, t, + if (t->media_codec->duplex_codec) { + emit_dynamic_node(&this->dyn_media_sink, this, t, 3, SPA_NAME_API_BLUEZ5_A2DP_SINK, true); } } @@ -694,11 +697,11 @@ static int emit_nodes(struct impl *this) if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) { t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE, 0); if (t) { - this->props.codec = t->a2dp_codec->id; - emit_dynamic_node(&this->dyn_a2dp_source, this, t, + this->props.codec = t->media_codec->id; + emit_dynamic_node(&this->dyn_media_source, this, t, DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_A2DP_SOURCE, false); - if (t->a2dp_codec->duplex_codec) { + if (t->media_codec->duplex_codec) { emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_A2DP_SINK, true); } @@ -708,17 +711,45 @@ static int emit_nodes(struct impl *this) if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) { t = find_transport(this, SPA_BT_PROFILE_A2DP_SINK, this->props.codec); if (t) { - this->props.codec = t->a2dp_codec->id; + this->props.codec = t->media_codec->id; emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_A2DP_SINK, false); - if (t->a2dp_codec->duplex_codec) { + if (t->media_codec->duplex_codec) { emit_node(this, t, DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_A2DP_SOURCE, true); } } } - if (get_supported_a2dp_codec(this, this->props.codec, NULL) == NULL) + if (get_supported_media_codec(this, this->props.codec, NULL) == NULL) + this->props.codec = 0; + break; + case DEVICE_PROFILE_BAP: + if (this->bt_dev->connected_profiles & (SPA_BT_PROFILE_BAP_SOURCE)) { + t = find_transport(this, SPA_BT_PROFILE_BAP_SOURCE, 0); + if (t) { + this->props.codec = t->media_codec->id; + if (t->bap_initiator) + emit_node(this, t, DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_MEDIA_SOURCE, false); + else + emit_dynamic_node(&this->dyn_media_source, this, t, + DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_MEDIA_SOURCE, false); + } + } + + if (this->bt_dev->connected_profiles & (SPA_BT_PROFILE_BAP_SINK)) { + t = find_transport(this, SPA_BT_PROFILE_BAP_SINK, this->props.codec); + if (t) { + this->props.codec = t->media_codec->id; + if (t->bap_initiator) + emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_MEDIA_SINK, false); + else + emit_dynamic_node(&this->dyn_media_sink, this, t, + DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_MEDIA_SINK, false); + } + } + + if (get_supported_media_codec(this, this->props.codec, NULL) == NULL) this->props.codec = 0; break; case DEVICE_PROFILE_HSP_HFP: @@ -766,8 +797,8 @@ static void emit_info(struct impl *this, bool full) static void emit_remove_nodes(struct impl *this) { - remove_dynamic_node (&this->dyn_a2dp_source); - remove_dynamic_node (&this->dyn_a2dp_sink); + remove_dynamic_node (&this->dyn_media_source); + remove_dynamic_node (&this->dyn_media_sink); remove_dynamic_node (&this->dyn_sco_source); remove_dynamic_node (&this->dyn_sco_sink); @@ -800,6 +831,7 @@ static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_a if (this->profile == profile && (this->profile != DEVICE_PROFILE_A2DP || codec == this->props.codec) && + (this->profile != DEVICE_PROFILE_BAP || codec == this->props.codec) && (this->profile != DEVICE_PROFILE_HSP_HFP || codec == this->props.codec)) return 0; @@ -812,19 +844,20 @@ static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_a this->props.codec = codec; /* - * A2DP: ensure there's a transport with the selected codec (0 means any). + * A2DP/BAP: ensure there's a transport with the selected codec (0 means any). * Don't try to switch codecs when the device is in the A2DP source role, since * devices do not appear to like that. */ - if (profile == DEVICE_PROFILE_A2DP && !(this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE)) { + if ((profile == DEVICE_PROFILE_A2DP || profile == DEVICE_PROFILE_BAP) + && !(this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE)) { int ret; - const struct a2dp_codec *codecs[64]; + const struct media_codec *codecs[64]; - get_a2dp_codecs(this, codec, codecs, SPA_N_ELEMENTS(codecs)); + get_media_codecs(this, codec, codecs, SPA_N_ELEMENTS(codecs)); this->switching_codec = true; - ret = spa_bt_device_ensure_a2dp_codec(this->bt_dev, codecs); + ret = spa_bt_device_ensure_media_codec(this->bt_dev, codecs); if (ret < 0) { if (ret != -ENOTSUP) spa_log_error(this->log, "failed to switch codec (%d), setting basic profile", ret); @@ -873,6 +906,8 @@ static void codec_switched(void *userdata, int status) spa_log_error(this->log, "failed to switch codec (%d), setting fallback profile", status); if (this->profile == DEVICE_PROFILE_A2DP && this->props.codec != 0) { this->props.codec = 0; + } else if (this->profile == DEVICE_PROFILE_BAP && this->props.codec != 0) { + this->props.codec = 0; } else if (this->profile == DEVICE_PROFILE_HSP_HFP && this->props.codec != 0) { this->props.codec = 0; } else { @@ -912,9 +947,9 @@ static void profiles_changed(void *userdata, uint32_t prev_profiles, uint32_t pr if (this->switching_codec) return; - if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) { + if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_MEDIA_SINK) { free(this->supported_codecs); - this->supported_codecs = spa_bt_device_get_supported_a2dp_codecs( + this->supported_codecs = spa_bt_device_get_supported_media_codecs( this->bt_dev, &this->supported_codec_count, true); } @@ -925,16 +960,17 @@ static void profiles_changed(void *userdata, uint32_t prev_profiles, uint32_t pr break; case DEVICE_PROFILE_AG: nodes_changed = (connected_change & (SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY | - SPA_BT_PROFILE_A2DP_SOURCE)); + SPA_BT_PROFILE_MEDIA_SOURCE)); spa_log_debug(this->log, "profiles changed: AG nodes changed: %d", nodes_changed); break; case DEVICE_PROFILE_A2DP: - if (get_supported_a2dp_codec(this, this->props.codec, NULL) == NULL) + case DEVICE_PROFILE_BAP: + if (get_supported_media_codec(this, this->props.codec, NULL) == NULL) this->props.codec = 0; - nodes_changed = (connected_change & (SPA_BT_PROFILE_A2DP_SINK | - SPA_BT_PROFILE_A2DP_SOURCE)); - spa_log_debug(this->log, "profiles changed: A2DP nodes changed: %d", + nodes_changed = (connected_change & (SPA_BT_PROFILE_MEDIA_SINK | + SPA_BT_PROFILE_MEDIA_SOURCE)); + spa_log_debug(this->log, "profiles changed: media nodes changed: %d", nodes_changed); break; case DEVICE_PROFILE_HSP_HFP: @@ -1019,15 +1055,16 @@ static uint32_t profile_direction_mask(struct impl *this, uint32_t index, enum s struct spa_bt_device *device = this->bt_dev; uint32_t mask; bool have_output = false, have_input = false; - const struct a2dp_codec *a2dp_codec; + const struct media_codec *media_codec; switch (index) { case DEVICE_PROFILE_A2DP: - if (device->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) + case DEVICE_PROFILE_BAP: + if (device->connected_profiles & SPA_BT_PROFILE_MEDIA_SINK) have_output = true; - a2dp_codec = get_supported_a2dp_codec(this, codec, NULL); - if (a2dp_codec && a2dp_codec->duplex_codec) + media_codec = get_supported_media_codec(this, codec, NULL); + if (media_codec && media_codec->duplex_codec) have_input = true; break; case DEVICE_PROFILE_HSP_HFP: @@ -1056,19 +1093,27 @@ static uint32_t get_profile_from_index(struct impl *this, uint32_t index, uint32 *codec = 0; *next = index + 1; - if (index <= 3) { + if (index <= DEVICE_PROFILE_LAST) { return index; } else if (index != SPA_ID_INVALID) { const struct spa_type_info *info; + uint32_t profile; - *codec = index - 3; + *codec = index - DEVICE_PROFILE_LAST; *next = SPA_ID_INVALID; for (info = spa_type_bluetooth_audio_codec; info->type; ++info) if (info->type > *codec) - *next = SPA_MIN(info->type + 3, *next); + *next = SPA_MIN(info->type + DEVICE_PROFILE_LAST, *next); - return get_hfp_codec(*codec) ? DEVICE_PROFILE_HSP_HFP : DEVICE_PROFILE_A2DP; + if (get_hfp_codec(*codec)) + profile = DEVICE_PROFILE_HSP_HFP; + else if (*codec == SPA_BLUETOOTH_AUDIO_CODEC_LC3) + profile = DEVICE_PROFILE_BAP; + else + profile = DEVICE_PROFILE_A2DP; + + return profile; } *next = SPA_ID_INVALID; @@ -1080,18 +1125,18 @@ static uint32_t get_index_from_profile(struct impl *this, uint32_t profile, enum if (profile == DEVICE_PROFILE_OFF || profile == DEVICE_PROFILE_AG) return profile; - if (profile == DEVICE_PROFILE_A2DP) { - if (codec == 0 || (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE)) + if (profile == DEVICE_PROFILE_A2DP || profile == DEVICE_PROFILE_BAP) { + if (codec == 0 || (this->bt_dev->connected_profiles & SPA_BT_PROFILE_MEDIA_SOURCE)) return profile; - return codec + 3; + return codec + DEVICE_PROFILE_LAST; } if (profile == DEVICE_PROFILE_HSP_HFP) { if (codec == 0 || (this->bt_dev->connected_profiles & SPA_BT_PROFILE_HFP_AG)) return profile; - return codec + 3; + return codec + DEVICE_PROFILE_LAST; } return SPA_ID_INVALID; @@ -1129,11 +1174,11 @@ static void set_initial_profile(struct impl *this) if (this->supported_codecs) free(this->supported_codecs); - this->supported_codecs = spa_bt_device_get_supported_a2dp_codecs( + this->supported_codecs = spa_bt_device_get_supported_media_codecs( this->bt_dev, &this->supported_codec_count, true); - /* Prefer A2DP, then HFP, then null, but select AG if the device - appears not to have A2DP_SINK or any HEAD_UNIT profile */ + /* Prefer BAP, then A2DP, then HFP, then null, but select AG if the device + appears not to have BAP_SINK, A2DP_SINK or any HEAD_UNIT profile */ /* If default profile is set to HSP/HFP, first try those and exit if found. */ if (this->bt_dev->settings != NULL) { @@ -1144,16 +1189,20 @@ static void set_initial_profile(struct impl *this) return; } - for (i = SPA_BT_PROFILE_A2DP_SINK; i <= SPA_BT_PROFILE_A2DP_SOURCE; i <<= 1) { + for (i = SPA_BT_PROFILE_BAP_SINK; i <= SPA_BT_PROFILE_A2DP_SOURCE; i <<= 1) { if (!(this->bt_dev->connected_profiles & i)) continue; t = find_transport(this, i, 0); if (t) { - this->profile = (i == SPA_BT_PROFILE_A2DP_SOURCE) ? - DEVICE_PROFILE_AG : DEVICE_PROFILE_A2DP; - this->props.codec = t->a2dp_codec->id; - spa_log_debug(this->log, "initial profile A2DP profile:%d codec:%d", + if (i == SPA_BT_PROFILE_A2DP_SOURCE || i == SPA_BT_PROFILE_BAP_SOURCE) + this->profile = DEVICE_PROFILE_AG; + else if (i == SPA_BT_PROFILE_BAP_SINK) + this->profile = DEVICE_PROFILE_BAP; + else + this->profile = DEVICE_PROFILE_A2DP; + this->props.codec = t->media_codec->id; + spa_log_debug(this->log, "initial profile media profile:%d codec:%d", this->profile, this->props.codec); return; } @@ -1213,19 +1262,19 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * n_sink++; if (codec) { size_t idx; - const struct a2dp_codec *a2dp_codec = get_supported_a2dp_codec(this, codec, &idx); - if (a2dp_codec == NULL) { + const struct media_codec *media_codec = get_supported_media_codec(this, codec, &idx); + if (media_codec == NULL) { errno = EINVAL; return NULL; } - name_and_codec = spa_aprintf("%s-%s", name, a2dp_codec->name); + name_and_codec = spa_aprintf("%s-%s", name, media_codec->name); name = name_and_codec; - if (profile == SPA_BT_PROFILE_A2DP_SINK && !a2dp_codec->duplex_codec) { + if (profile == SPA_BT_PROFILE_A2DP_SINK && !media_codec->duplex_codec) { desc_and_codec = spa_aprintf(_("High Fidelity Playback (A2DP Sink, codec %s)"), - a2dp_codec->description); + media_codec->description); } else { desc_and_codec = spa_aprintf(_("High Fidelity Duplex (A2DP Source/Sink, codec %s)"), - a2dp_codec->description); + media_codec->description); } desc = desc_and_codec; @@ -1240,6 +1289,52 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * } break; } + case DEVICE_PROFILE_BAP: + { + uint32_t profile = device->connected_profiles & + (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE); + size_t idx; + const struct media_codec *media_codec; + + if (profile == 0) + return NULL; + + if (!codec) { + errno = EINVAL; + return NULL; + } + + if (profile & (SPA_BT_PROFILE_BAP_SINK)) + n_sink++; + if (profile & (SPA_BT_PROFILE_BAP_SOURCE)) + n_source++; + + name = spa_bt_profile_name(profile); + + media_codec = get_supported_media_codec(this, codec, &idx); + if (media_codec == NULL) { + errno = EINVAL; + return NULL; + } + name_and_codec = spa_aprintf("%s-%s", name, media_codec->name); + name = name_and_codec; + switch (profile) { + case SPA_BT_PROFILE_BAP_SINK: + desc_and_codec = spa_aprintf(_("High Fidelity Playback (BAP Sink, codec %s)"), + media_codec->description); + break; + case SPA_BT_PROFILE_BAP_SOURCE: + desc_and_codec = spa_aprintf(_("High Fidelity Input (BAP Source, codec %s)"), + media_codec->description); + break; + default: + desc_and_codec = spa_aprintf(_("High Fidelity Duplex (BAP Source/Sink, codec %s)"), + media_codec->description); + } + desc = desc_and_codec; + priority = 128 + this->supported_codec_count - idx; /* order as in codec list */ + break; + } case DEVICE_PROFILE_HSP_HFP: { /* make this device profile visible only if there is a head unit */ @@ -1537,7 +1632,8 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, spa_pod_builder_array(b, sizeof(uint32_t), SPA_TYPE_Id, node->n_channels, node->channels); - if (this->profile == DEVICE_PROFILE_A2DP && dev == DEVICE_ID_SINK) { + if ((this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP) && + dev == DEVICE_ID_SINK) { spa_pod_builder_prop(b, SPA_PROP_latencyOffsetNsec, 0); spa_pod_builder_long(b, node->latency_offset); } @@ -1560,7 +1656,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, return spa_pod_builder_pop(b, &f[0]); } -static bool iterate_supported_a2dp_codecs(struct impl *this, int *j, const struct a2dp_codec **codec) +static bool iterate_supported_media_codecs(struct impl *this, int *j, const struct media_codec **codec) { int i; @@ -1582,12 +1678,12 @@ static struct spa_pod *build_prop_info(struct impl *this, struct spa_pod_builder { struct spa_pod_frame f[2]; struct spa_pod_choice *choice; - const struct a2dp_codec *codec; + const struct media_codec *codec; size_t n; int j; -#define FOR_EACH_A2DP_CODEC(j, codec) \ - for (j = -1; iterate_supported_a2dp_codecs(this, &j, &codec);) +#define FOR_EACH_MEDIA_CODEC(j, codec) \ + for (j = -1; iterate_supported_media_codecs(this, &j, &codec);) #define FOR_EACH_HFP_CODEC(j) \ for (j = HFP_AUDIO_CODEC_MSBC; j >= HFP_AUDIO_CODEC_CVSD; --j) \ if (spa_bt_device_supports_hfp_codec(this->bt_dev, j) == 1) @@ -1608,8 +1704,8 @@ static struct spa_pod *build_prop_info(struct impl *this, struct spa_pod_builder spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); choice = (struct spa_pod_choice *)spa_pod_builder_frame(b, &f[1]); n = 0; - if (this->profile == DEVICE_PROFILE_A2DP) { - FOR_EACH_A2DP_CODEC(j, codec) { + if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP) { + FOR_EACH_MEDIA_CODEC(j, codec) { if (n == 0) spa_pod_builder_int(b, codec->id); spa_pod_builder_int(b, codec->id); @@ -1628,8 +1724,8 @@ static struct spa_pod *build_prop_info(struct impl *this, struct spa_pod_builder spa_pod_builder_pop(b, &f[1]); spa_pod_builder_prop(b, SPA_PROP_INFO_labels, 0); spa_pod_builder_push_struct(b, &f[1]); - if (this->profile == DEVICE_PROFILE_A2DP) { - FOR_EACH_A2DP_CODEC(j, codec) { + if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP) { + FOR_EACH_MEDIA_CODEC(j, codec) { spa_pod_builder_int(b, codec->id); spa_pod_builder_string(b, codec->description); } @@ -1642,7 +1738,7 @@ static struct spa_pod *build_prop_info(struct impl *this, struct spa_pod_builder spa_pod_builder_pop(b, &f[1]); return spa_pod_builder_pop(b, &f[0]); -#undef FOR_EACH_A2DP_CODEC +#undef FOR_EACH_MEDIA_CODEC #undef FOR_EACH_HFP_CODEC } @@ -1688,6 +1784,7 @@ static int impl_enum_params(void *object, int seq, case DEVICE_PROFILE_OFF: case DEVICE_PROFILE_AG: case DEVICE_PROFILE_A2DP: + case DEVICE_PROFILE_BAP: case DEVICE_PROFILE_HSP_HFP: param = build_profile(this, &b, id, result.index, profile, codec, false); if (param == NULL) @@ -2024,7 +2121,7 @@ static int impl_set_param(void *object, if (codec_id == SPA_ID_INVALID) return 0; - if (this->profile == DEVICE_PROFILE_A2DP) { + if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP) { size_t j; for (j = 0; j < this->supported_codec_count; ++j) { if (this->supported_codecs[j]->id == codec_id) { diff --git a/spa/plugins/bluez5/codec-loader.c b/spa/plugins/bluez5/codec-loader.c index 172c42309455b473ad22924c1de7de227036d291..f8363f8880a76e6d69589fa40135831146cfca19 100644 --- a/spa/plugins/bluez5/codec-loader.c +++ b/spa/plugins/bluez5/codec-loader.c @@ -29,7 +29,7 @@ #include "defs.h" #include "codec-loader.h" -#define A2DP_CODEC_LIB_BASE "bluez5/libspa-codec-bluez5-" +#define MEDIA_CODEC_LIB_BASE "bluez5/libspa-codec-bluez5-" /* AVDTP allows 0x3E endpoints, can't have more codecs than that */ #define MAX_CODECS 0x3E @@ -40,7 +40,7 @@ static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.codecs"); #define SPA_LOG_TOPIC_DEFAULT &log_topic struct impl { - const struct a2dp_codec *codecs[MAX_CODECS + 1]; + const struct media_codec *codecs[MAX_CODECS + 1]; struct spa_handle *handles[MAX_HANDLES]; size_t n_codecs; size_t n_handles; @@ -48,9 +48,10 @@ struct impl { struct spa_log *log; }; -static int codec_order(const struct a2dp_codec *c) +static int codec_order(const struct media_codec *c) { static const enum spa_bluetooth_audio_codec order[] = { + SPA_BLUETOOTH_AUDIO_CODEC_LC3, SPA_BLUETOOTH_AUDIO_CODEC_LDAC, SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD, SPA_BLUETOOTH_AUDIO_CODEC_APTX, @@ -78,8 +79,8 @@ static int codec_order(const struct a2dp_codec *c) static int codec_order_cmp(const void *a, const void *b) { - const struct a2dp_codec * const *ca = a; - const struct a2dp_codec * const *cb = b; + const struct media_codec * const *ca = a; + const struct media_codec * const *cb = b; int ia = codec_order(*ca); int ib = codec_order(*cb); if (*ca == *cb) @@ -87,7 +88,7 @@ static int codec_order_cmp(const void *a, const void *b) return (ia == ib) ? (*ca < *cb ? -1 : 1) : ia - ib; } -static int load_a2dp_codecs_from(struct impl *impl, const char *factory_name, const char *libname) +static int load_media_codecs_from(struct impl *impl, const char *factory_name, const char *libname) { struct spa_handle *handle = NULL; void *iface; @@ -108,7 +109,7 @@ static int load_a2dp_codecs_from(struct impl *impl, const char *factory_name, co spa_log_debug(impl->log, "loading codecs from %s", factory_name); - if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Bluez5CodecA2DP, &iface)) < 0) { + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Bluez5CodecMedia, &iface)) < 0) { spa_log_warn(impl->log, "Bluetooth codec plugin %s has no codec interface", factory_name); goto fail; @@ -116,17 +117,21 @@ static int load_a2dp_codecs_from(struct impl *impl, const char *factory_name, co bluez5_codec_a2dp = iface; - if (bluez5_codec_a2dp->iface.version != SPA_VERSION_BLUEZ5_CODEC_A2DP) { + if (bluez5_codec_a2dp->iface.version != SPA_VERSION_BLUEZ5_CODEC_MEDIA) { spa_log_warn(impl->log, "codec plugin %s has incompatible ABI version (%d != %d)", - factory_name, bluez5_codec_a2dp->iface.version, SPA_VERSION_BLUEZ5_CODEC_A2DP); + factory_name, bluez5_codec_a2dp->iface.version, SPA_VERSION_BLUEZ5_CODEC_MEDIA); res = -ENOENT; goto fail; } for (i = 0; bluez5_codec_a2dp->codecs[i]; ++i) { - const struct a2dp_codec *c = bluez5_codec_a2dp->codecs[i]; + const struct media_codec *c = bluez5_codec_a2dp->codecs[i]; + const char *ep = c->endpoint_name ? c->endpoint_name : c->name; size_t j; + if (!ep) + goto next_codec; + if (impl->n_codecs >= MAX_CODECS) { spa_log_error(impl->log, "too many A2DP codecs"); break; @@ -134,14 +139,17 @@ static int load_a2dp_codecs_from(struct impl *impl, const char *factory_name, co /* Don't load duplicate endpoints */ for (j = 0; j < impl->n_codecs; ++j) { - const struct a2dp_codec *c2 = impl->codecs[j]; - const char *ep1 = c->endpoint_name ? c->endpoint_name : c->name; + const struct media_codec *c2 = impl->codecs[j]; const char *ep2 = c2->endpoint_name ? c2->endpoint_name : c2->name; - if (spa_streq(ep1, ep2)) + if (spa_streq(ep, ep2) && c->fill_caps && c2->fill_caps) { + spa_log_debug(impl->log, "media codec %s from %s duplicate endpoint %s", + c->name, factory_name, ep); goto next_codec; + } } - spa_log_debug(impl->log, "loaded A2DP codec %s from %s", c->name, factory_name); + spa_log_debug(impl->log, "loaded media codec %s from %s, endpoint:%s", + c->name, factory_name, ep); if (c->set_log) c->set_log(impl->log); @@ -166,22 +174,23 @@ fail: return res; } -const struct a2dp_codec * const *load_a2dp_codecs(struct spa_plugin_loader *loader, struct spa_log *log) +const struct media_codec * const *load_media_codecs(struct spa_plugin_loader *loader, struct spa_log *log) { struct impl *impl; bool has_sbc; size_t i; const struct { const char *factory; const char *lib; } plugins[] = { -#define A2DP_CODEC_FACTORY_LIB(basename) \ - { A2DP_CODEC_FACTORY_NAME(basename), A2DP_CODEC_LIB_BASE basename } - A2DP_CODEC_FACTORY_LIB("aac"), - A2DP_CODEC_FACTORY_LIB("aptx"), - A2DP_CODEC_FACTORY_LIB("faststream"), - A2DP_CODEC_FACTORY_LIB("ldac"), - A2DP_CODEC_FACTORY_LIB("sbc"), - A2DP_CODEC_FACTORY_LIB("lc3plus"), - A2DP_CODEC_FACTORY_LIB("opus") -#undef A2DP_CODEC_FACTORY_LIB +#define MEDIA_CODEC_FACTORY_LIB(basename) \ + { MEDIA_CODEC_FACTORY_NAME(basename), MEDIA_CODEC_LIB_BASE basename } + MEDIA_CODEC_FACTORY_LIB("aac"), + MEDIA_CODEC_FACTORY_LIB("aptx"), + MEDIA_CODEC_FACTORY_LIB("faststream"), + MEDIA_CODEC_FACTORY_LIB("ldac"), + MEDIA_CODEC_FACTORY_LIB("sbc"), + MEDIA_CODEC_FACTORY_LIB("lc3plus"), + MEDIA_CODEC_FACTORY_LIB("opus"), + MEDIA_CODEC_FACTORY_LIB("lc3") +#undef MEDIA_CODEC_FACTORY_LIB }; impl = calloc(sizeof(struct impl), 1); @@ -194,7 +203,7 @@ const struct a2dp_codec * const *load_a2dp_codecs(struct spa_plugin_loader *load spa_log_topic_init(impl->log, &log_topic); for (i = 0; i < SPA_N_ELEMENTS(plugins); ++i) - load_a2dp_codecs_from(impl, plugins[i].factory, plugins[i].lib); + load_media_codecs_from(impl, plugins[i].factory, plugins[i].lib); has_sbc = false; for (i = 0; i < impl->n_codecs; ++i) @@ -203,19 +212,19 @@ const struct a2dp_codec * const *load_a2dp_codecs(struct spa_plugin_loader *load if (!has_sbc) { spa_log_error(impl->log, "failed to load A2DP SBC codec from plugins"); - free_a2dp_codecs(impl->codecs); + free_media_codecs(impl->codecs); errno = ENOENT; return NULL; } - qsort(impl->codecs, impl->n_codecs, sizeof(const struct a2dp_codec *), codec_order_cmp); + qsort(impl->codecs, impl->n_codecs, sizeof(const struct media_codec *), codec_order_cmp); return impl->codecs; } -void free_a2dp_codecs(const struct a2dp_codec * const *a2dp_codecs) +void free_media_codecs(const struct media_codec * const *media_codecs) { - struct impl *impl = SPA_CONTAINER_OF(a2dp_codecs, struct impl, codecs); + struct impl *impl = SPA_CONTAINER_OF(media_codecs, struct impl, codecs); size_t i; for (i = 0; i < impl->n_handles; ++i) diff --git a/spa/plugins/bluez5/codec-loader.h b/spa/plugins/bluez5/codec-loader.h index 5422cc45802cab2b04cd8c4c7786eb6d69c8dad2..b77d9c49f4b0ef696198291e2fd0b5fd07784132 100644 --- a/spa/plugins/bluez5/codec-loader.h +++ b/spa/plugins/bluez5/codec-loader.h @@ -31,9 +31,9 @@ #include <spa/support/plugin-loader.h> #include "a2dp-codec-caps.h" -#include "a2dp-codecs.h" +#include "media-codecs.h" -const struct a2dp_codec * const *load_a2dp_codecs(struct spa_plugin_loader *loader, struct spa_log *log); -void free_a2dp_codecs(const struct a2dp_codec * const *a2dp_codecs); +const struct media_codec * const *load_media_codecs(struct spa_plugin_loader *loader, struct spa_log *log); +void free_media_codecs(const struct media_codec * const *media_codecs); #endif diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index 90348afa05f1805507adb52451241fce75105a46..5d194b3d6293d901147c885f7a3b3015c7f89cec 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -141,6 +141,9 @@ extern "C" { #define SPA_BT_UUID_HSP_AG "00001112-0000-1000-8000-00805f9b34fb" #define SPA_BT_UUID_HFP_HF "0000111e-0000-1000-8000-00805f9b34fb" #define SPA_BT_UUID_HFP_AG "0000111f-0000-1000-8000-00805f9b34fb" +#define SPA_BT_UUID_PACS "00001850-0000-1000-8000-00805f9b34fb" +#define SPA_BT_UUID_BAP_SINK "00002bc9-0000-1000-8000-00805f9b34fb" +#define SPA_BT_UUID_BAP_SOURCE "00002bcb-0000-1000-8000-00805f9b34fb" #define PROFILE_HSP_AG "/Profile/HSPAG" #define PROFILE_HSP_HS "/Profile/HSPHS" @@ -158,9 +161,12 @@ extern "C" { #define HFP_AUDIO_CODEC_CVSD 0x01 #define HFP_AUDIO_CODEC_MSBC 0x02 -#define A2DP_OBJECT_MANAGER_PATH "/MediaEndpoint" -#define A2DP_SINK_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSink" -#define A2DP_SOURCE_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSource" +#define MEDIA_OBJECT_MANAGER_PATH "/MediaEndpoint" +#define A2DP_SINK_ENDPOINT MEDIA_OBJECT_MANAGER_PATH "/A2DPSink" +#define A2DP_SOURCE_ENDPOINT MEDIA_OBJECT_MANAGER_PATH "/A2DPSource" + +#define BAP_SINK_ENDPOINT MEDIA_OBJECT_MANAGER_PATH "/BAPSink" +#define BAP_SOURCE_ENDPOINT MEDIA_OBJECT_MANAGER_PATH "/BAPSource" #define SPA_BT_UNKNOWN_DELAY 0 @@ -172,19 +178,30 @@ extern "C" { #define MSBC_ENCODED_SIZE 60 /* 2 bytes header + 57 mSBC payload + 1 byte padding */ #define MSBC_PAYLOAD_SIZE 57 +enum spa_bt_media_direction { + SPA_BT_MEDIA_SOURCE, + SPA_BT_MEDIA_SINK, +}; + enum spa_bt_profile { SPA_BT_PROFILE_NULL = 0, - SPA_BT_PROFILE_A2DP_SINK = (1 << 0), - SPA_BT_PROFILE_A2DP_SOURCE = (1 << 1), - SPA_BT_PROFILE_HSP_HS = (1 << 2), - SPA_BT_PROFILE_HSP_AG = (1 << 3), - SPA_BT_PROFILE_HFP_HF = (1 << 4), - SPA_BT_PROFILE_HFP_AG = (1 << 5), + SPA_BT_PROFILE_BAP_SINK = (1 << 0), + SPA_BT_PROFILE_BAP_SOURCE = (1 << 1), + SPA_BT_PROFILE_A2DP_SINK = (1 << 2), + SPA_BT_PROFILE_A2DP_SOURCE = (1 << 3), + SPA_BT_PROFILE_HSP_HS = (1 << 4), + SPA_BT_PROFILE_HSP_AG = (1 << 5), + SPA_BT_PROFILE_HFP_HF = (1 << 6), + SPA_BT_PROFILE_HFP_AG = (1 << 7), SPA_BT_PROFILE_A2DP_DUPLEX = (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_A2DP_SOURCE), + SPA_BT_PROFILE_BAP_DUPLEX = (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE), SPA_BT_PROFILE_HEADSET_HEAD_UNIT = (SPA_BT_PROFILE_HSP_HS | SPA_BT_PROFILE_HFP_HF), SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY = (SPA_BT_PROFILE_HSP_AG | SPA_BT_PROFILE_HFP_AG), SPA_BT_PROFILE_HEADSET_AUDIO = (SPA_BT_PROFILE_HEADSET_HEAD_UNIT | SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY), + + SPA_BT_PROFILE_MEDIA_SINK = (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_BAP_SINK), + SPA_BT_PROFILE_MEDIA_SOURCE = (SPA_BT_PROFILE_A2DP_SOURCE | SPA_BT_PROFILE_BAP_SOURCE), }; static inline enum spa_bt_profile spa_bt_profile_from_uuid(const char *uuid) @@ -203,6 +220,10 @@ static inline enum spa_bt_profile spa_bt_profile_from_uuid(const char *uuid) return SPA_BT_PROFILE_HFP_HF; else if (strcasecmp(uuid, SPA_BT_UUID_HFP_AG) == 0) return SPA_BT_PROFILE_HFP_AG; + else if (strcasecmp(uuid, SPA_BT_UUID_BAP_SINK) == 0) + return SPA_BT_PROFILE_BAP_SINK; + else if (strcasecmp(uuid, SPA_BT_UUID_BAP_SOURCE) == 0) + return SPA_BT_PROFILE_BAP_SOURCE; else return 0; } @@ -303,6 +324,12 @@ static inline const char *spa_bt_profile_name (enum spa_bt_profile profile) { return "headset-audio-gateway"; case SPA_BT_PROFILE_HEADSET_AUDIO: return "headset-audio"; + case SPA_BT_PROFILE_BAP_SOURCE: + return "bap-source"; + case SPA_BT_PROFILE_BAP_SINK: + return "bap-sink"; + case SPA_BT_PROFILE_BAP_DUPLEX: + return "bap-duplex"; default: break; } @@ -412,7 +439,7 @@ static inline enum spa_bt_form_factor spa_bt_form_factor_from_class(uint32_t blu return SPA_BT_FORM_FACTOR_UNKNOWN; } -struct spa_bt_a2dp_codec_switch; +struct spa_bt_media_codec_switch; struct spa_bt_transport; struct spa_bt_device_events { @@ -432,6 +459,8 @@ struct spa_bt_device_events { void (*destroy) (void *data); }; +struct media_codec; + struct spa_bt_device { struct spa_list link; struct spa_bt_monitor *monitor; @@ -480,18 +509,18 @@ struct spa_bt_device { const struct spa_dict *settings; DBusPendingCall *battery_pending_call; -}; -struct a2dp_codec; + const struct media_codec *preferred_codec; +}; struct spa_bt_device *spa_bt_device_find(struct spa_bt_monitor *monitor, const char *path); struct spa_bt_device *spa_bt_device_find_by_address(struct spa_bt_monitor *monitor, const char *remote_address, const char *local_address); int spa_bt_device_add_profile(struct spa_bt_device *device, enum spa_bt_profile profile); int spa_bt_device_connect_profile(struct spa_bt_device *device, enum spa_bt_profile profile); int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force); -int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a2dp_codec * const *codecs); -bool spa_bt_device_supports_a2dp_codec(struct spa_bt_device *device, const struct a2dp_codec *codec, bool sink); -const struct a2dp_codec **spa_bt_device_get_supported_a2dp_codecs(struct spa_bt_device *device, size_t *count, bool sink); +int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct media_codec * const *codecs); +bool spa_bt_device_supports_media_codec(struct spa_bt_device *device, const struct media_codec *codec, bool sink); +const struct media_codec **spa_bt_device_get_supported_media_codecs(struct spa_bt_device *device, size_t *count, bool sink); int spa_bt_device_ensure_hfp_codec(struct spa_bt_device *device, unsigned int codec); int spa_bt_device_supports_hfp_codec(struct spa_bt_device *device, unsigned int codec); int spa_bt_device_release_transports(struct spa_bt_device *device); @@ -570,11 +599,13 @@ struct spa_bt_transport { struct spa_list device_link; enum spa_bt_profile profile; enum spa_bt_transport_state state; - const struct a2dp_codec *a2dp_codec; + const struct media_codec *media_codec; unsigned int codec; void *configuration; int configuration_len; char *endpoint_path; + bool bap_initiator; + struct spa_list bap_transport_linked; uint32_t n_channels; uint32_t channels[64]; diff --git a/spa/plugins/bluez5/a2dp-codecs.c b/spa/plugins/bluez5/media-codecs.c similarity index 89% rename from spa/plugins/bluez5/a2dp-codecs.c rename to spa/plugins/bluez5/media-codecs.c index 42c7a193c1a759b6ecd5892928157a5d4b968e68..6f64e82e403ea6b6ddbc24188c3b0d3c8f9abf6d 100644 --- a/spa/plugins/bluez5/a2dp-codecs.c +++ b/spa/plugins/bluez5/media-codecs.c @@ -10,9 +10,9 @@ #include <spa/utils/string.h> -#include "a2dp-codecs.h" +#include "media-codecs.h" -int a2dp_codec_select_config(const struct a2dp_codec_config configs[], size_t n, +int media_codec_select_config(const struct media_codec_config configs[], size_t n, uint32_t cap, int preferred_value) { size_t i; @@ -60,9 +60,9 @@ int a2dp_codec_select_config(const struct a2dp_codec_config configs[], size_t n, return res; } -bool a2dp_codec_check_caps(const struct a2dp_codec *codec, unsigned int codec_id, +bool media_codec_check_caps(const struct media_codec *codec, unsigned int codec_id, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info, + const struct media_codec_audio_info *info, const struct spa_dict *global_settings) { uint8_t config[A2DP_MAX_CAPS_SIZE]; @@ -98,7 +98,7 @@ impl_get_interface(struct spa_handle *handle, const char *type, void **interface this = (struct impl *) handle; - if (spa_streq(type, SPA_TYPE_INTERFACE_Bluez5CodecA2DP)) + if (spa_streq(type, SPA_TYPE_INTERFACE_Bluez5CodecMedia)) *interface = &this->bluez5_codec_a2dp; else return -ENOENT; @@ -136,10 +136,10 @@ impl_init(const struct spa_handle_factory *factory, this = (struct impl *) handle; - this->bluez5_codec_a2dp.codecs = codec_plugin_a2dp_codecs; + this->bluez5_codec_a2dp.codecs = codec_plugin_media_codecs; this->bluez5_codec_a2dp.iface = SPA_INTERFACE_INIT( - SPA_TYPE_INTERFACE_Bluez5CodecA2DP, - SPA_VERSION_BLUEZ5_CODEC_A2DP, + SPA_TYPE_INTERFACE_Bluez5CodecMedia, + SPA_VERSION_BLUEZ5_CODEC_MEDIA, NULL, this); @@ -147,7 +147,7 @@ impl_init(const struct spa_handle_factory *factory, } static const struct spa_interface_info impl_interfaces[] = { - {SPA_TYPE_INTERFACE_Bluez5CodecA2DP,}, + {SPA_TYPE_INTERFACE_Bluez5CodecMedia,}, }; static int diff --git a/spa/plugins/bluez5/a2dp-codecs.h b/spa/plugins/bluez5/media-codecs.h similarity index 64% rename from spa/plugins/bluez5/a2dp-codecs.h rename to spa/plugins/bluez5/media-codecs.h index 7fb5cd510611b6c48591b97d822d58649955633b..d3447db055071a42d07c98a651a991bc0d0090e3 100644 --- a/spa/plugins/bluez5/a2dp-codecs.h +++ b/spa/plugins/bluez5/media-codecs.h @@ -36,34 +36,35 @@ #include <spa/support/log.h> #include "a2dp-codec-caps.h" +#include "bap-codec-caps.h" /* * The codec plugin SPA interface is private. The version should be incremented * when any of the structs or semantics change. */ -#define SPA_TYPE_INTERFACE_Bluez5CodecA2DP SPA_TYPE_INFO_INTERFACE_BASE "Bluez5:Codec:A2DP:Private" +#define SPA_TYPE_INTERFACE_Bluez5CodecMedia SPA_TYPE_INFO_INTERFACE_BASE "Bluez5:Codec:Media:Private" -#define SPA_VERSION_BLUEZ5_CODEC_A2DP 5 +#define SPA_VERSION_BLUEZ5_CODEC_MEDIA 7 struct spa_bluez5_codec_a2dp { struct spa_interface iface; - const struct a2dp_codec * const *codecs; /**< NULL terminated array */ + const struct media_codec * const *codecs; /**< NULL terminated array */ }; -#define A2DP_CODEC_FACTORY_NAME(basename) (SPA_NAME_API_CODEC_BLUEZ5_A2DP "." basename) +#define MEDIA_CODEC_FACTORY_NAME(basename) (SPA_NAME_API_CODEC_BLUEZ5_MEDIA "." basename) #ifdef CODEC_PLUGIN -#define A2DP_CODEC_EXPORT_DEF(basename,...) \ - const char *codec_plugin_factory_name = A2DP_CODEC_FACTORY_NAME(basename); \ - static const struct a2dp_codec * const codec_plugin_a2dp_codec_list[] = { __VA_ARGS__, NULL }; \ - const struct a2dp_codec * const * const codec_plugin_a2dp_codecs = codec_plugin_a2dp_codec_list; +#define MEDIA_CODEC_EXPORT_DEF(basename,...) \ + const char *codec_plugin_factory_name = MEDIA_CODEC_FACTORY_NAME(basename); \ + static const struct media_codec * const codec_plugin_media_codec_list[] = { __VA_ARGS__, NULL }; \ + const struct media_codec * const * const codec_plugin_media_codecs = codec_plugin_media_codec_list; -extern const struct a2dp_codec * const * const codec_plugin_a2dp_codecs; +extern const struct media_codec * const * const codec_plugin_media_codecs; extern const char *codec_plugin_factory_name; #endif -#define A2DP_CODEC_FLAG_SINK (1 << 0) +#define MEDIA_CODEC_FLAG_SINK (1 << 0) #define A2DP_CODEC_DEFAULT_RATE 48000 #define A2DP_CODEC_DEFAULT_CHANNELS 2 @@ -74,16 +75,18 @@ enum { NEED_FLUSH_FRAGMENT = 2, }; -struct a2dp_codec_audio_info { +struct media_codec_audio_info { uint32_t rate; uint32_t channels; }; -struct a2dp_codec { +struct media_codec { enum spa_bluetooth_audio_codec id; uint8_t codec_id; a2dp_vendor_codec_t vendor; + bool bap; + const char *name; const char *description; const char *endpoint_name; /**< Endpoint name. If NULL, same as name */ @@ -91,37 +94,45 @@ struct a2dp_codec { const size_t send_buf_size; - const struct a2dp_codec *duplex_codec; /**< Codec for non-standard A2DP duplex channel */ + const struct media_codec *duplex_codec; /**< Codec for non-standard A2DP duplex channel */ + + struct spa_log *log; - int (*fill_caps) (const struct a2dp_codec *codec, uint32_t flags, + /** If fill_caps is NULL, no endpoint is registered (for sharing with another codec). */ + int (*fill_caps) (const struct media_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]); - int (*select_config) (const struct a2dp_codec *codec, uint32_t flags, + + int (*select_config) (const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info, + const struct media_codec_audio_info *info, const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE]); - int (*enum_config) (const struct a2dp_codec *codec, uint32_t flags, + int (*enum_config) (const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *builder, struct spa_pod **param); - int (*validate_config) (const struct a2dp_codec *codec, uint32_t flags, + int (*validate_config) (const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info); + int (*get_qos)(const struct media_codec *codec, + const void *config, size_t config_size, + const struct bap_endpoint_qos *endpoint_qos, + struct bap_codec_qos *qos); /** qsort comparison sorting caps in order of preference for the codec. * Used in codec switching to select best remote endpoints. * The caps handed in correspond to this codec_id, but are * otherwise not checked beforehand. */ - int (*caps_preference_cmp) (const struct a2dp_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, - const void *caps2, size_t caps2_size, const struct a2dp_codec_audio_info *info, + int (*caps_preference_cmp) (const struct media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, + const void *caps2, size_t caps2_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings); - void *(*init_props) (const struct a2dp_codec *codec, uint32_t flags, const struct spa_dict *settings); + void *(*init_props) (const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings); void (*clear_props) (void *); int (*enum_props) (void *props, const struct spa_dict *settings, uint32_t id, uint32_t idx, struct spa_pod_builder *builder, struct spa_pod **param); int (*set_props) (void *props, const struct spa_pod *param); - void *(*init) (const struct a2dp_codec *codec, uint32_t flags, void *config, size_t config_size, + void *(*init) (const struct media_codec *codec, uint32_t flags, void *config, size_t config_size, const struct spa_audio_info *info, void *props, size_t mtu); void (*deinit) (void *data); @@ -151,17 +162,17 @@ struct a2dp_codec { void (*set_log) (struct spa_log *global_log); }; -struct a2dp_codec_config { +struct media_codec_config { uint32_t config; int value; unsigned int priority; }; -int a2dp_codec_select_config(const struct a2dp_codec_config configs[], size_t n, +int media_codec_select_config(const struct media_codec_config configs[], size_t n, uint32_t cap, int preferred_value); -bool a2dp_codec_check_caps(const struct a2dp_codec *codec, unsigned int codec_id, - const void *caps, size_t caps_size, const struct a2dp_codec_audio_info *info, +bool media_codec_check_caps(const struct media_codec *codec, unsigned int codec_id, + const void *caps, size_t caps_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings); #endif diff --git a/spa/plugins/bluez5/a2dp-sink.c b/spa/plugins/bluez5/media-sink.c similarity index 85% rename from spa/plugins/bluez5/a2dp-sink.c rename to spa/plugins/bluez5/media-sink.c index 9d712d81551bd5a5ff7085eb9e1ed17a0e7547e4..a5d72fcfca7b0ff4ab67b538fccdf84b2f75d86a 100644 --- a/spa/plugins/bluez5/a2dp-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -1,4 +1,4 @@ -/* Spa A2DP Sink +/* Spa Media Sink * * Copyright © 2018 Wim Taymans * @@ -53,26 +53,23 @@ #include "defs.h" #include "rtp.h" -#include "a2dp-codecs.h" +#include "media-codecs.h" -static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.sink.a2dp"); +static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.sink.media"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic #define DEFAULT_CLOCK_NAME "clock.system.monotonic" struct props { - uint32_t min_latency; - uint32_t max_latency; int64_t latency_offset; char clock_name[64]; }; -#define FILL_FRAMES 2 +#define FILL_FRAMES 4 +#define MIN_BUFFERS 2 #define MAX_BUFFERS 32 -#define MIN_LATENCY 128 -#define MAX_LATENCY 8192 -#define BUFFER_SIZE (MAX_LATENCY*8) +#define BUFFER_SIZE (8192*8) struct buffer { uint32_t id; @@ -121,6 +118,8 @@ struct impl { struct spa_hook_list hooks; struct spa_callbacks callbacks; + uint32_t quantum_limit; + uint64_t info_all; struct spa_node_info info; #define IDX_PropInfo 0 @@ -136,6 +135,8 @@ struct impl { unsigned int started:1; unsigned int following:1; + unsigned int is_output:1; + unsigned int flush_pending:1; unsigned int is_duplex:1; @@ -151,8 +152,12 @@ struct impl { uint64_t current_time; uint64_t next_time; uint64_t last_error; + uint64_t process_time; + + uint64_t prev_flush_time; + uint64_t next_flush_time; - const struct a2dp_codec *codec; + const struct media_codec *codec; bool codec_props_changed; void *codec_props; void *codec_data; @@ -160,12 +165,11 @@ struct impl { int need_flush; bool fragment; - uint64_t fragment_timeout; uint32_t block_size; uint8_t buffer[BUFFER_SIZE]; uint32_t buffer_used; uint32_t header_size; - uint32_t frame_count; + uint32_t block_count; uint16_t seqnum; uint32_t timestamp; uint64_t sample_count; @@ -174,12 +178,10 @@ struct impl { uint32_t fd_buffer_size; }; -#define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0) +#define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0) static void reset_props(struct impl *this, struct props *props) { - props->min_latency = MIN_LATENCY; - props->max_latency = MAX_LATENCY; props->latency_offset = 0; strncpy(props->clock_name, DEFAULT_CLOCK_NAME, sizeof(props->clock_name)); } @@ -209,24 +211,8 @@ static int impl_node_enum_params(void *object, int seq, switch (id) { case SPA_PARAM_PropInfo: { - struct props *p = &this->props; - switch (result.index) { case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_minLatency), - SPA_PROP_INFO_description, SPA_POD_String("The minimum latency"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->min_latency, 1, INT32_MAX)); - break; - case 1: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_maxLatency), - SPA_PROP_INFO_description, SPA_POD_String("The maximum latency"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->max_latency, 1, INT32_MAX)); - break; - case 2: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_latencyOffsetNsec), @@ -235,7 +221,7 @@ static int impl_node_enum_params(void *object, int seq, break; default: enum_codec = true; - index_offset = 3; + index_offset = 1; } break; } @@ -247,8 +233,6 @@ static int impl_node_enum_params(void *object, int seq, case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, - SPA_PROP_minLatency, SPA_POD_Int(p->min_latency), - SPA_PROP_maxLatency, SPA_POD_Int(p->max_latency), SPA_PROP_latencyOffsetNsec, SPA_POD_Long(p->latency_offset)); break; default: @@ -386,8 +370,6 @@ static int apply_props(struct impl *this, const struct spa_pod *param) } else { spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, - SPA_PROP_minLatency, SPA_POD_OPT_Int(&new_props.min_latency), - SPA_PROP_maxLatency, SPA_POD_OPT_Int(&new_props.max_latency), SPA_PROP_latencyOffsetNsec, SPA_POD_OPT_Long(&new_props.latency_offset)); } @@ -439,7 +421,7 @@ static int reset_buffer(struct impl *this) this->codec_props_changed = false; } this->need_flush = 0; - this->frame_count = 0; + this->block_count = 0; this->fragment = false; this->buffer_used = this->codec->start_encode(this->codec_data, this->buffer, sizeof(this->buffer), @@ -464,20 +446,32 @@ static int get_transport_unused_size(struct impl *this) static int send_buffer(struct impl *this) { int written, unsent; + unsent = get_transport_unused_size(this); if (unsent >= 0) { unsent = this->fd_buffer_size - unsent; this->codec->abr_process(this->codec_data, unsent); } - spa_log_trace(this->log, "%p: send %d %u %u %u %u", - this, this->frame_count, this->block_size, this->seqnum, - this->timestamp, this->buffer_used); - written = send(this->flush_source.fd, this->buffer, this->buffer_used, MSG_DONTWAIT | MSG_NOSIGNAL); - spa_log_trace(this->log, "%p: send %d", this, written); + if (SPA_UNLIKELY(spa_log_level_topic_enabled(this->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) { + struct timespec ts; + uint64_t now; + uint64_t dt; + + spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &ts); + now = SPA_TIMESPEC_TO_NSEC(&ts); + dt = now - this->prev_flush_time; + this->prev_flush_time = now; + + spa_log_trace(this->log, + "%p: send blocks:%d block:%u seq:%u ts:%u size:%u " + "wrote:%d dt:%"PRIu64, + this, this->block_count, this->block_size, this->seqnum, + this->timestamp, this->buffer_used, written, dt); + } if (written < 0) { spa_log_debug(this->log, "%p: %m", this); @@ -497,7 +491,7 @@ static int encode_buffer(struct impl *this, const void *data, uint32_t size) spa_log_trace(this->log, "%p: encode %d used %d, %d %d %d", this, size, this->buffer_used, port->frame_size, this->block_size, - this->frame_count); + this->block_count); if (this->need_flush) return 0; @@ -525,7 +519,7 @@ static int encode_buffer(struct impl *this, const void *data, uint32_t size) return processed; this->sample_count += processed / port->frame_size; - this->frame_count += processed / this->block_size; + this->block_count += processed / this->block_size; this->buffer_used += out_encoded; spa_log_trace(this->log, "%p: processed %d %zd used %d", @@ -546,7 +540,7 @@ static int encode_fragment(struct impl *this) spa_log_trace(this->log, "%p: encode fragment used %d, %d %d %d", this, this->buffer_used, port->frame_size, this->block_size, - this->frame_count); + this->block_count); if (this->need_flush) return 0; @@ -597,25 +591,41 @@ static int add_data(struct impl *this, const void *data, uint32_t size) return total; } -static void enable_flush(struct impl *this, bool enabled, uint64_t timeout) +static void enable_flush_timer(struct impl *this, bool enabled) { - bool flush_enabled = enabled && (timeout == 0); struct itimerspec ts; - if (SPA_FLAG_IS_SET(this->flush_source.mask, SPA_IO_OUT) != flush_enabled) { - SPA_FLAG_UPDATE(this->flush_source.mask, SPA_IO_OUT, flush_enabled); - spa_loop_update_source(this->data_loop, &this->flush_source); - } - if (!enabled) - timeout = 0; + this->next_flush_time = 0; - ts.it_value.tv_sec = timeout / SPA_NSEC_PER_SEC; - ts.it_value.tv_nsec = timeout % SPA_NSEC_PER_SEC; + ts.it_value.tv_sec = this->next_flush_time / SPA_NSEC_PER_SEC; + ts.it_value.tv_nsec = this->next_flush_time % SPA_NSEC_PER_SEC; 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); + this->flush_timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); + + 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 += d[0].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) @@ -623,6 +633,7 @@ static int flush_data(struct impl *this, uint64_t now_time) int written; uint32_t total_frames; struct port *port = &this->port; + int unused_buffer; if (!this->flush_source.loop) { /* I/O in error state */ @@ -699,17 +710,30 @@ again: } if (written > 0 && this->buffer_used == this->header_size) { - enable_flush(this, false, 0); + enable_flush_timer(this, false); + return 0; + } + + if (this->flush_pending) { + spa_log_trace(this->log, "%p: wait for flush timer", this); return 0; } + /* + * Get socket queue size before writing to it. + * This should be the same as buffer size to increase bitpool + * Bitpool shouldn't be increased when data is left over in the buffer + */ + unused_buffer = get_transport_unused_size(this); + written = flush_buffer(this); if (written == -EAGAIN) { spa_log_trace(this->log, "%p: fail flush", this); if (now_time - this->last_error > SPA_NSEC_PER_SEC / 2) { - spa_log_trace(this->log, "%p: reduce bitpool", this); - this->codec->reduce_bitpool(this->codec_data); + int res = this->codec->reduce_bitpool(this->codec_data); + + spa_log_debug(this->log, "%p: reduce bitpool: %i", this, res); this->last_error = now_time; } @@ -719,97 +743,99 @@ again: * glitch in any case. */ written = this->buffer_used; - reset_buffer(this); } if (written < 0) { spa_log_trace(this->log, "%p: error flushing %s", this, spa_strerror(written)); reset_buffer(this); - enable_flush(this, false, 0); + enable_flush_timer(this, false); return written; } else if (written > 0) { /* - * We cannot write all data we have at once, since this can exceed - * device buffers. We'll want a limited number of "excess" - * samples. This is an issue for the "low-latency" A2DP codecs. - * - * Flushing the rest of the data (if any) is delayed after a timeout, - * selected on an average-rate basis: - * - * npackets = quantum / packet_samples - * write_end_time = npackets * timeout - * max_excess = quantum - sample_rate * write_end_time - * packet_time = packet_samples / sample_rate - * => timeout = (quantum - max_excess)/quantum * packet_time + * We cannot write all data we have at once, since this can exceed device + * buffers (esp. for the A2DP low-latency codecs) and socket buffers, so + * flush needs to be delayed. */ - uint64_t max_excess = 2*256; - uint64_t packet_samples = (uint64_t)this->frame_count * this->block_size / port->frame_size; - uint64_t packet_time = packet_samples * SPA_NSEC_PER_SEC / port->current_format.info.raw.rate; - uint64_t quantum = SPA_LIKELY(this->clock) ? this->clock->duration : 0; - uint64_t timeout = (quantum > max_excess) ? - (packet_time * (quantum - max_excess) / quantum) : 0; + uint32_t packet_samples = this->block_count * this->block_size + / port->frame_size; + uint64_t packet_time = (uint64_t)packet_samples * SPA_NSEC_PER_SEC + / 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); + + /* + * We could delay the output by one packet to avoid waiting + * for the next buffer and so make send intervals exactly regular. + * However, this is not needed for A2DP or BAP. The controller + * will do the scheduling for us, and there's also the socket buffer + * in between. + */ +#if 0 + this->next_flush_time += SPA_MIN(packet_time, + duration_ns * (port->n_buffers - 1)); +#endif + } else { + if (this->next_flush_time == 0) + this->next_flush_time = this->process_time; + this->next_flush_time += packet_time; + } if (this->need_flush == NEED_FLUSH_FRAGMENT) { reset_buffer(this); this->fragment = true; - this->fragment_timeout = (packet_samples > 0) ? timeout : this->fragment_timeout; goto again; } - if (this->fragment_timeout > 0) { - timeout = this->fragment_timeout; - this->fragment_timeout = 0; - } - reset_buffer(this); if (now_time - this->last_error > SPA_NSEC_PER_SEC) { - if (get_transport_unused_size(this) == (int)this->fd_buffer_size) { - spa_log_trace(this->log, "%p: increase bitpool", this); - this->codec->increase_bitpool(this->codec_data); + if (unused_buffer == (int)this->fd_buffer_size) { + int res = this->codec->increase_bitpool(this->codec_data); + + spa_log_debug(this->log, "%p: increase bitpool: %i", this, res); } this->last_error = now_time; } - if (!spa_list_is_empty(&port->ready)) { - spa_log_trace(this->log, "%p: flush after %d ns", this, (int)timeout); - if (timeout == 0) - goto again; - else - enable_flush(this, true, timeout); - } else { - enable_flush(this, false, 0); - } + + spa_log_trace(this->log, "%p: flush at:%"PRIu64" process:%"PRIu64, this, + this->next_flush_time, this->process_time); + reset_buffer(this); + enable_flush_timer(this, true); } else { /* Don't want to flush yet, or failed to write anything */ spa_log_trace(this->log, "%p: skip flush", this); - enable_flush(this, false, 0); + enable_flush_timer(this, false); } return 0; } -static void a2dp_on_flush(struct spa_source *source) +static void media_on_flush_error(struct spa_source *source) { struct impl *this = source->data; - spa_log_trace(this->log, "%p: flushing", this); + spa_log_trace(this->log, "%p: flush event", this); - if (!SPA_FLAG_IS_SET(source->rmask, SPA_IO_OUT)) { + if (source->rmask & (SPA_IO_ERR | SPA_IO_HUP)) { 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); return; } - - if (this->transport == NULL) { - enable_flush(this, false, 0); - return; - } - - flush_data(this, this->current_time); } -static void a2dp_on_flush_timeout(struct spa_source *source) +static void media_on_flush_timeout(struct spa_source *source) { struct impl *this = source->data; uint64_t exp; @@ -820,14 +846,17 @@ static void a2dp_on_flush_timeout(struct spa_source *source) spa_log_warn(this->log, "error reading timerfd: %s", strerror(errno)); if (this->transport == NULL) { - enable_flush(this, false, 0); + enable_flush_timer(this, false); return; } - flush_data(this, this->current_time); + while (exp-- > 0) { + this->flush_pending = false; + flush_data(this, this->current_time); + } } -static void a2dp_on_timeout(struct spa_source *source) +static void media_on_timeout(struct spa_source *source) { struct impl *this = source->data; struct port *port = &this->port; @@ -889,6 +918,7 @@ static int do_start(struct impl *this) struct port *port; socklen_t len; uint8_t *conf; + uint32_t flags; if (this->started) return 0; @@ -897,7 +927,7 @@ static int do_start(struct impl *this) this->following = is_following(this); - spa_log_debug(this->log, "%p: start following:%d", this, this->following); + spa_log_debug(this->log, "%p: start following:%d", this, this->following); if ((res = spa_bt_transport_acquire(this->transport, false)) < 0) return res; @@ -910,8 +940,10 @@ static int do_start(struct impl *this) spa_log_debug(this->log, "Transport configuration:"); spa_log_hexdump(this->log, SPA_LOG_LEVEL_DEBUG, 2, conf, (size_t)size); + flags = this->is_duplex ? MEDIA_CODEC_FLAG_SINK : 0; + this->codec_data = this->codec->init(this->codec, - this->is_duplex ? A2DP_CODEC_FLAG_SINK : 0, + flags, this->transport->configuration, this->transport->configuration_len, &port->current_format, @@ -920,8 +952,9 @@ static int do_start(struct impl *this) if (this->codec_data == NULL) return -EIO; - spa_log_info(this->log, "%p: using A2DP codec %s, delay:%"PRIi64" ms", this, this->codec->description, - (int64_t)(spa_bt_transport_get_delay_nsec(this->transport) / SPA_NSEC_PER_MSEC)); + spa_log_info(this->log, "%p: using %s codec %s, delay:%"PRIi64" ms", this, + 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; @@ -932,8 +965,7 @@ static int do_start(struct impl *this) return -EIO; } - spa_log_debug(this->log, "%p: block_size %d", this, - this->block_size); + spa_log_debug(this->log, "%p: block_size %d", this, this->block_size); val = this->codec->send_buf_size > 0 /* The kernel doubles the SO_SNDBUF option value set by setsockopt(). */ @@ -963,25 +995,27 @@ static int do_start(struct impl *this) this->source.data = this; this->source.fd = this->timerfd; - this->source.func = a2dp_on_timeout; + 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); this->flush_timer_source.data = this; this->flush_timer_source.fd = this->flush_timerfd; - this->flush_timer_source.func = a2dp_on_flush_timeout; + 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; - this->flush_source.func = a2dp_on_flush; - this->flush_source.mask = 0; + this->flush_source.func = media_on_flush_error; + this->flush_source.mask = SPA_IO_ERR | SPA_IO_HUP; this->flush_source.rmask = 0; spa_loop_add_source(this->data_loop, &this->flush_source); + this->flush_pending = false; + set_timers(this); this->started = true; @@ -1027,7 +1061,7 @@ static int do_stop(struct impl *this) if (!this->started) return 0; - spa_log_trace(this->log, "%p: stop", this); + spa_log_trace(this->log, "%p: stop", this); spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, this); @@ -1079,8 +1113,10 @@ static void emit_node_info(struct impl *this, bool full) { struct spa_dict_item node_info_items[] = { { SPA_KEY_DEVICE_API, "bluez5" }, - { SPA_KEY_MEDIA_CLASS, "Audio/Sink" }, - { SPA_KEY_NODE_DRIVER, "true" }, + { SPA_KEY_MEDIA_CLASS, this->is_output ? "Audio/Sink" : "Stream/Input/Audio" }, + { "media.name", ((this->transport && this->transport->device->name) ? + this->transport->device->name : this->codec->bap ? "BAP" : "A2DP" ) }, + { SPA_KEY_NODE_DRIVER, this->is_output ? "true" : "false" }, }; uint64_t old = full ? this->info.change_mask : 0; if (full) @@ -1198,7 +1234,7 @@ impl_node_port_enum_params(void *object, int seq, return -EIO; if ((res = this->codec->enum_config(this->codec, - this->is_duplex ? A2DP_CODEC_FLAG_SINK : 0, + this->is_duplex ? MEDIA_CODEC_FLAG_SINK : 0, this->transport->configuration, this->transport->configuration_len, id, result.index, &b, ¶m)) != 1) @@ -1222,11 +1258,14 @@ impl_node_port_enum_params(void *object, int seq, param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 2, MAX_BUFFERS), + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int( + MIN_BUFFERS, + MIN_BUFFERS, + MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), - SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( - this->props.min_latency * port->frame_size, - this->props.min_latency * port->frame_size, + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( + this->quantum_limit * port->frame_size, + 16 * port->frame_size, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->frame_size)); break; @@ -1315,6 +1354,11 @@ static int port_set_format(struct impl *this, struct port *port, if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; + if (info.info.raw.rate == 0 || + info.info.raw.channels == 0 || + info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) + return -EINVAL; + port->frame_size = info.info.raw.channels; switch (info.info.raw.format) { case SPA_AUDIO_FORMAT_S16: @@ -1486,18 +1530,21 @@ static int impl_node_process(void *object) io->buffer_id = SPA_ID_INVALID; io->status = SPA_STATUS_OK; } - if (!spa_list_is_empty(&port->ready)) { - if (this->following) { - if (this->position) { - this->current_time = this->position->clock.nsec; - } else { - struct timespec now; - spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); - this->current_time = SPA_TIMESPEC_TO_NSEC(&now); - } + + if (this->following) { + if (this->position) { + this->current_time = this->position->clock.nsec; + } else { + struct timespec now; + spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); + this->current_time = SPA_TIMESPEC_TO_NSEC(&now); } - if (this->need_flush) - reset_buffer(this); + } + + this->process_time = this->current_time; + + if (!spa_list_is_empty(&port->ready)) { + spa_log_trace(this->log, "%p: flush on process", this); flush_data(this, this->current_time); } @@ -1610,8 +1657,7 @@ static int impl_clear(struct spa_handle *handle) { struct impl *this = (struct impl *) handle; - if (this->codec_data) - this->codec->deinit(this->codec_data); + do_stop(this); if (this->codec_props && this->codec->clear_props) this->codec->clear_props(this->codec_props); if (this->transport) @@ -1701,6 +1747,11 @@ impl_init(const struct spa_handle_factory *factory, spa_list_init(&port->ready); + this->quantum_limit = 8192; + + if (info && (str = spa_dict_lookup(info, "clock.quantum-limit"))) + spa_atou32(str, &this->quantum_limit, 0); + if (info && (str = spa_dict_lookup(info, "api.bluez5.a2dp-duplex")) != NULL) this->is_duplex = spa_atob(str); @@ -1711,12 +1762,12 @@ impl_init(const struct spa_handle_factory *factory, spa_log_error(this->log, "a transport is needed"); return -EINVAL; } - if (this->transport->a2dp_codec == NULL) { + if (this->transport->media_codec == NULL) { spa_log_error(this->log, "a transport codec is needed"); return -EINVAL; } - this->codec = this->transport->a2dp_codec; + this->codec = this->transport->media_codec; if (this->is_duplex) { if (!this->codec->duplex_codec) { @@ -1728,9 +1779,14 @@ impl_init(const struct spa_handle_factory *factory, if (this->codec->init_props != NULL) this->codec_props = this->codec->init_props(this->codec, - this->is_duplex ? A2DP_CODEC_FLAG_SINK : 0, + this->is_duplex ? MEDIA_CODEC_FLAG_SINK : 0, this->transport->device->settings); + if (this->codec->bap) + this->is_output = this->transport->bap_initiator; + else + this->is_output = true; + reset_props(this, &this->props); spa_bt_transport_add_listener(this->transport, @@ -1770,12 +1826,22 @@ impl_enum_interface_info(const struct spa_handle_factory *factory, static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, - { SPA_KEY_FACTORY_DESCRIPTION, "Play audio with the a2dp" }, + { SPA_KEY_FACTORY_DESCRIPTION, "Play audio with the media" }, { SPA_KEY_FACTORY_USAGE, SPA_KEY_API_BLUEZ5_TRANSPORT"=<transport>" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); +const struct spa_handle_factory spa_media_sink_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_API_BLUEZ5_MEDIA_SINK, + &info, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; + +/* Retained for backward compatibility: */ const struct spa_handle_factory spa_a2dp_sink_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_BLUEZ5_A2DP_SINK, diff --git a/spa/plugins/bluez5/a2dp-source.c b/spa/plugins/bluez5/media-source.c similarity index 94% rename from spa/plugins/bluez5/a2dp-source.c rename to spa/plugins/bluez5/media-source.c index 0082b9d1e3ee25a4fa60160b49885b7a6667598d..58ff14a52955a73d16bc019a14d391f417cd4d5b 100644 --- a/spa/plugins/bluez5/a2dp-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -1,4 +1,4 @@ -/* Spa A2DP Source +/* Spa Media Source * * Copyright © 2018 Wim Taymans * Copyright © 2019 Collabora Ltd. @@ -54,9 +54,9 @@ #include "defs.h" #include "rtp.h" -#include "a2dp-codecs.h" +#include "media-codecs.h" -static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.source.a2dp"); +static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.source.media"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic @@ -156,7 +156,7 @@ struct impl { uint64_t current_time; uint64_t next_time; - const struct a2dp_codec *codec; + const struct media_codec *codec; bool codec_props_changed; void *codec_props; void *codec_data; @@ -276,6 +276,7 @@ static int do_reassign_follower(struct spa_loop *loop, struct impl *this = user_data; struct port *port = &this->port; + set_timers(this); spa_bt_decode_buffer_recover(&port->buffer); return 0; } @@ -448,7 +449,7 @@ static int32_t decode_data(struct impl *this, uint8_t *src, uint32_t src_size, return dst_size - avail; } -static void a2dp_on_ready_read(struct spa_source *source) +static void media_on_ready_read(struct spa_source *source) { struct impl *this = source->data; struct port *port = &this->port; @@ -533,7 +534,7 @@ static int set_duplex_timeout(struct impl *this, uint64_t timeout) this->duplex_timerfd, 0, &ts, NULL); } -static void a2dp_on_duplex_timeout(struct spa_source *source) +static void media_on_duplex_timeout(struct spa_source *source) { struct impl *this = source->data; uint64_t exp; @@ -543,7 +544,7 @@ static void a2dp_on_duplex_timeout(struct spa_source *source) set_duplex_timeout(this, this->duplex_timeout); - a2dp_on_ready_read(source); + media_on_ready_read(source); } static int setup_matching(struct impl *this) @@ -567,13 +568,14 @@ static int setup_matching(struct impl *this) return 0; } -static void a2dp_on_timeout(struct spa_source *source) +static int produce_buffer(struct impl *this); + +static void media_on_timeout(struct spa_source *source) { struct impl *this = source->data; struct port *port = &this->port; uint64_t exp, duration; uint32_t rate; - struct spa_io_buffers *io = port->io; uint64_t prev_time, now_time; if (this->transport == NULL) @@ -608,8 +610,11 @@ static void a2dp_on_timeout(struct spa_source *source) this->clock->next_nsec = this->next_time; } - spa_log_trace(this->log, "%p: %d", this, io->status); - io->status = SPA_STATUS_HAVE_DATA; + if (port->io) { + int status = produce_buffer(this); + spa_log_trace(this->log, "%p: io:%d status:%d", this, port->io->status, status); + } + spa_node_call_ready(&this->callbacks, SPA_STATUS_HAVE_DATA); set_timeout(this, this->next_time); @@ -619,6 +624,7 @@ static int transport_start(struct impl *this) { int res, val; struct port *port = &this->port; + uint32_t flags; if (this->transport_acquired) return 0; @@ -630,8 +636,10 @@ static int transport_start(struct impl *this) this->transport_acquired = true; + flags = this->is_duplex ? 0 : MEDIA_CODEC_FLAG_SINK; + this->codec_data = this->codec->init(this->codec, - this->is_duplex ? 0 : A2DP_CODEC_FLAG_SINK, + flags, this->transport->configuration, this->transport->configuration_len, &port->current_format, @@ -640,7 +648,8 @@ static int transport_start(struct impl *this) if (this->codec_data == NULL) return -EIO; - spa_log_info(this->log, "%p: using A2DP codec %s", this, this->codec->description); + spa_log_info(this->log, "%p: using %s codec %s", this, + this->codec->bap ? "BAP" : "A2DP", this->codec->description); val = fcntl(this->transport->fd, F_GETFL); if (fcntl(this->transport->fd, F_SETFL, val | O_NONBLOCK) < 0) @@ -672,7 +681,7 @@ static int transport_start(struct impl *this) if (!this->use_duplex_source) { this->source.fd = this->transport->fd; - this->source.func = a2dp_on_ready_read; + this->source.func = media_on_ready_read; this->source.mask = SPA_IO_IN; this->source.rmask = 0; spa_loop_add_source(this->data_loop, &this->source); @@ -689,7 +698,7 @@ static int transport_start(struct impl *this) * XXX: forward stream. */ this->source.fd = this->duplex_timerfd; - this->source.func = a2dp_on_duplex_timeout; + this->source.func = media_on_duplex_timeout; this->source.mask = SPA_IO_IN; this->source.rmask = 0; spa_loop_add_source(this->data_loop, &this->source); @@ -700,7 +709,7 @@ static int transport_start(struct impl *this) this->timer_source.data = this; this->timer_source.fd = this->timerfd; - this->timer_source.func = a2dp_on_timeout; + 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); @@ -729,7 +738,7 @@ static int do_start(struct impl *this) this, this->transport->state, this->following); if (this->transport->state >= SPA_BT_TRANSPORT_STATE_PENDING || - this->is_duplex) + this->is_duplex || this->codec->bap) res = transport_start(this); this->started = true; @@ -847,7 +856,7 @@ static void emit_node_info(struct impl *this, bool full) { SPA_KEY_MEDIA_CLASS, this->is_input ? "Audio/Source" : "Stream/Output/Audio" }, { SPA_KEY_NODE_LATENCY, this->is_input ? "" : "512/48000" }, { "media.name", ((this->transport && this->transport->device->name) ? - this->transport->device->name : "A2DP") }, + this->transport->device->name : this->codec->bap ? "BAP" : "A2DP") }, { SPA_KEY_NODE_DRIVER, this->is_input ? "true" : "false" }, }; @@ -968,7 +977,7 @@ impl_node_port_enum_params(void *object, int seq, return -EIO; if ((res = this->codec->enum_config(this->codec, - this->is_duplex ? 0 : A2DP_CODEC_FLAG_SINK, + this->is_duplex ? 0 : MEDIA_CODEC_FLAG_SINK, this->transport->configuration, this->transport->configuration_len, id, result.index, &b, ¶m)) != 1) @@ -1092,6 +1101,11 @@ static int port_set_format(struct impl *this, struct port *port, if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; + if (info.info.raw.rate == 0 || + info.info.raw.channels == 0 || + info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) + return -EINVAL; + port->frame_size = info.info.raw.channels; switch (info.info.raw.format) { @@ -1335,21 +1349,15 @@ static void process_buffering(struct impl *this) } } -static int impl_node_process(void *object) +static int produce_buffer(struct impl *this) { - struct impl *this = object; - struct port *port; - struct spa_io_buffers *io; struct buffer *buffer; + struct port *port = &this->port; + struct spa_io_buffers *io = port->io; - spa_return_val_if_fail(this != NULL, -EINVAL); - - port = &this->port; - if ((io = port->io) == NULL) + if (io == NULL) return -EIO; - spa_log_trace(this->log, "%p status:%d", this, io->status); - /* Return if we already have a buffer */ if (io->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; @@ -1360,7 +1368,7 @@ static int impl_node_process(void *object) io->buffer_id = SPA_ID_INVALID; } - /* Handle buffering delay */ + /* Handle buffering */ process_buffering(this); /* Return if there are no buffers ready to be processed */ @@ -1380,6 +1388,37 @@ static int impl_node_process(void *object) return SPA_STATUS_HAVE_DATA; } +static int impl_node_process(void *object) +{ + struct impl *this = object; + struct port *port; + struct spa_io_buffers *io; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + port = &this->port; + if ((io = port->io) == NULL) + return -EIO; + + spa_log_trace(this->log, "%p status:%d", this, io->status); + + /* Return if we already have a buffer */ + if (io->status == SPA_STATUS_HAVE_DATA) + return SPA_STATUS_HAVE_DATA; + + /* Recycle */ + if (io->buffer_id < port->n_buffers) { + recycle_buffer(this, port, io->buffer_id); + io->buffer_id = SPA_ID_INVALID; + } + + /* Follower produces buffers here, driver in timeout */ + if (this->following) + return produce_buffer(this); + else + return SPA_STATUS_OK; +} + static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, @@ -1445,8 +1484,8 @@ static int impl_clear(struct spa_handle *handle) { struct impl *this = (struct impl *) handle; struct port *port = &this->port; - if (this->codec_data) - this->codec->deinit(this->codec_data); + + do_stop(this); if (this->codec_props && this->codec->clear_props) this->codec->clear_props(this->codec_props); if (this->transport) @@ -1555,7 +1594,7 @@ impl_init(const struct spa_handle_factory *factory, spa_atou32(str, &this->quantum_limit, 0); if ((str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_TRANSPORT)) != NULL) sscanf(str, "pointer:%p", &this->transport); - if ((str = spa_dict_lookup(info, "bluez5.a2dp-source-role")) != NULL) + if ((str = spa_dict_lookup(info, "bluez5.media-source-role")) != NULL) this->is_input = spa_streq(str, "input"); if ((str = spa_dict_lookup(info, "api.bluez5.a2dp-duplex")) != NULL) this->is_duplex = spa_atob(str); @@ -1565,11 +1604,11 @@ impl_init(const struct spa_handle_factory *factory, spa_log_error(this->log, "a transport is needed"); return -EINVAL; } - if (this->transport->a2dp_codec == NULL) { + if (this->transport->media_codec == NULL) { spa_log_error(this->log, "a transport codec is needed"); return -EINVAL; } - this->codec = this->transport->a2dp_codec; + this->codec = this->transport->media_codec; if (this->is_duplex) { if (!this->codec->duplex_codec) { @@ -1581,9 +1620,12 @@ impl_init(const struct spa_handle_factory *factory, } this->use_duplex_source = this->is_duplex || (this->codec->duplex_codec != NULL); + if (this->codec->bap) + this->is_input = this->transport->bap_initiator; + if (this->codec->init_props != NULL) this->codec_props = this->codec->init_props(this->codec, - this->is_duplex ? 0 : A2DP_CODEC_FLAG_SINK, + this->is_duplex ? 0 : MEDIA_CODEC_FLAG_SINK, this->transport->device->settings); spa_bt_transport_add_listener(this->transport, @@ -1627,12 +1669,22 @@ impl_enum_interface_info(const struct spa_handle_factory *factory, static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Collabora Ltd. <contact@collabora.com>" }, - { SPA_KEY_FACTORY_DESCRIPTION, "Capture bluetooth audio with a2dp" }, + { SPA_KEY_FACTORY_DESCRIPTION, "Capture bluetooth audio with media" }, { SPA_KEY_FACTORY_USAGE, SPA_KEY_API_BLUEZ5_TRANSPORT"=<transport>" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); +const struct spa_handle_factory spa_media_source_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_API_BLUEZ5_MEDIA_SOURCE, + &info, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; + +/* Retained for backward compatibility */ const struct spa_handle_factory spa_a2dp_source_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_BLUEZ5_A2DP_SOURCE, diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build index 2a17c78ee7cdd74b391d6f35d0622a604b755f8b..db010697fdd199f6701d3196ab0545edb9eb0155 100644 --- a/spa/plugins/bluez5/meson.build +++ b/spa/plugins/bluez5/meson.build @@ -10,6 +10,7 @@ cdata.set('HAVE_BLUEZ_5_BACKEND_NATIVE', get_option('bluez5-backend-hfp-native').allowed()) cdata.set('HAVE_BLUEZ_5_BACKEND_HSP_NATIVE', get_option('bluez5-backend-hsp-native').allowed()) cdata.set('HAVE_BLUEZ_5_BACKEND_HFP_NATIVE', get_option('bluez5-backend-hfp-native').allowed()) +cdata.set('HAVE_BLUEZ_5_BACKEND_NATIVE_MM', get_option('bluez5-backend-native-mm').allowed()) cdata.set('HAVE_BLUEZ_5_BACKEND_OFONO', get_option('bluez5-backend-ofono').allowed()) cdata.set('HAVE_BLUEZ_5_BACKEND_HSPHFPD', get_option('bluez5-backend-hsphfpd').allowed()) cdata.set('HAVE_BLUEZ_5_HCI', dependency('bluez', version: '< 6', required: false).found()) @@ -17,9 +18,9 @@ cdata.set('HAVE_BLUEZ_5_HCI', dependency('bluez', version: '< 6', required: fals bluez5_sources = [ 'plugin.c', 'codec-loader.c', - 'a2dp-codecs.c', - 'a2dp-sink.c', - 'a2dp-source.c', + 'media-codecs.c', + 'media-sink.c', + 'media-source.c', 'sco-sink.c', 'sco-source.c', 'sco-io.c', @@ -38,7 +39,11 @@ if get_option('bluez5-backend-hsp-native').allowed() or get_option('bluez5-backe if libusb_dep.found() bluez5_deps += libusb_dep endif - bluez5_sources += ['backend-native.c'] + if mm_dep.found() + bluez5_deps += mm_dep + bluez5_sources += ['modemmanager.c'] + endif + bluez5_sources += ['backend-native.c', 'upower.c'] endif if get_option('bluez5-backend-ofono').allowed() @@ -59,7 +64,7 @@ bluez5lib = shared_library('spa-bluez5', codec_args = [ '-DCODEC_PLUGIN' ] bluez_codec_sbc = shared_library('spa-codec-bluez5-sbc', - [ 'a2dp-codec-sbc.c', 'a2dp-codecs.c' ], + [ 'a2dp-codec-sbc.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep, sbc_dep ], @@ -67,7 +72,7 @@ bluez_codec_sbc = shared_library('spa-codec-bluez5-sbc', install_dir : spa_plugindir / 'bluez5') bluez_codec_faststream = shared_library('spa-codec-bluez5-faststream', - [ 'a2dp-codec-faststream.c', 'a2dp-codecs.c' ], + [ 'a2dp-codec-faststream.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep, sbc_dep ], @@ -76,7 +81,7 @@ bluez_codec_faststream = shared_library('spa-codec-bluez5-faststream', if fdk_aac_dep.found() bluez_codec_aac = shared_library('spa-codec-bluez5-aac', - [ 'a2dp-codec-aac.c', 'a2dp-codecs.c' ], + [ 'a2dp-codec-aac.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep, fdk_aac_dep ], @@ -86,7 +91,7 @@ endif if aptx_dep.found() bluez_codec_aptx = shared_library('spa-codec-bluez5-aptx', - [ 'a2dp-codec-aptx.c', 'a2dp-codecs.c' ], + [ 'a2dp-codec-aptx.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep, aptx_dep, sbc_dep ], @@ -102,7 +107,7 @@ if ldac_dep.found() ldac_dep += ldac_abr_dep endif bluez_codec_ldac = shared_library('spa-codec-bluez5-ldac', - [ 'a2dp-codec-ldac.c', 'a2dp-codecs.c' ], + [ 'a2dp-codec-ldac.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : ldac_args, dependencies : [ spa_dep, ldac_dep ], @@ -112,7 +117,7 @@ endif if get_option('bluez5-codec-lc3plus').allowed() and lc3plus_dep.found() bluez_codec_lc3plus = shared_library('spa-codec-bluez5-lc3plus', - [ 'a2dp-codec-lc3plus.c', 'a2dp-codecs.c' ], + [ 'a2dp-codec-lc3plus.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep, lc3plus_dep, mathlib ], @@ -124,10 +129,20 @@ if get_option('bluez5-codec-opus').allowed() and opus_dep.found() opus_args = codec_args opus_dep = [ opus_dep ] bluez_codec_opus = shared_library('spa-codec-bluez5-opus', - [ 'a2dp-codec-opus.c', 'a2dp-codecs.c' ], + [ 'a2dp-codec-opus.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : opus_args, dependencies : [ spa_dep, opus_dep, mathlib ], install : true, install_dir : spa_plugindir / 'bluez5') endif + +if get_option('bluez5-codec-lc3').allowed() and lc3_dep.found() + bluez_codec_lc3 = shared_library('spa-codec-bluez5-lc3', + [ 'bap-codec-lc3.c', 'media-codecs.c' ], + include_directories : [ configinc ], + c_args : codec_args, + dependencies : [ spa_dep, lc3_dep, mathlib ], + install : true, + install_dir : spa_plugindir / 'bluez5') +endif diff --git a/spa/plugins/bluez5/modemmanager.c b/spa/plugins/bluez5/modemmanager.c new file mode 100644 index 0000000000000000000000000000000000000000..d9df95ac87a69a1338c63328b0aec9d54836f8af --- /dev/null +++ b/spa/plugins/bluez5/modemmanager.c @@ -0,0 +1,1249 @@ +/* Spa Bluez5 ModemManager proxy + * + * Copyright © 2022 Collabora + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <errno.h> +#include <spa/utils/string.h> + +#include <ModemManager.h> + +#include "modemmanager.h" + +#define DBUS_INTERFACE_OBJECTMANAGER "org.freedesktop.DBus.ObjectManager" + +struct modem { + char *path; + bool network_has_service; + unsigned int signal_strength; +}; + +struct impl { + struct spa_bt_monitor *monitor; + + struct spa_log *log; + DBusConnection *conn; + + char *allowed_modem_device; + bool filters_added; + DBusPendingCall *pending; + DBusPendingCall *voice_pending; + + const struct mm_ops *ops; + void *user_data; + + struct modem modem; + struct spa_list call_list; +}; + +struct dbus_cmd_data { + struct impl *this; + struct call *call; + void *user_data; +}; + +static bool mm_dbus_connection_send_with_reply(struct impl *this, DBusMessage *m, DBusPendingCall **pending_return, + DBusPendingCallNotifyFunction function, void *user_data) +{ + dbus_bool_t dbus_ret; + + spa_assert(*pending_return == NULL); + + dbus_ret = dbus_connection_send_with_reply(this->conn, m, pending_return, -1); + if (!dbus_ret || *pending_return == NULL) { + spa_log_debug(this->log, "dbus call failure"); + return false; + } + + dbus_ret = dbus_pending_call_set_notify(*pending_return, function, user_data, NULL); + if (!dbus_ret) { + spa_log_debug(this->log, "dbus set notify failure"); + dbus_pending_call_cancel(*pending_return); + dbus_pending_call_unref(*pending_return); + *pending_return = NULL; + return false; + } + + return true; +} + +static int mm_state_to_clcc(struct impl *this, MMCallState state) +{ + switch (state) { + case MM_CALL_STATE_DIALING: + return CLCC_DIALING; + case MM_CALL_STATE_RINGING_OUT: + return CLCC_ALERTING; + case MM_CALL_STATE_RINGING_IN: + return CLCC_INCOMING; + case MM_CALL_STATE_ACTIVE: + return CLCC_ACTIVE; + case MM_CALL_STATE_HELD: + return CLCC_HELD; + case MM_CALL_STATE_WAITING: + return CLCC_WAITING; + case MM_CALL_STATE_TERMINATED: + case MM_CALL_STATE_UNKNOWN: + default: + return -1; + } +} + +static void mm_call_state_changed(struct impl *this) +{ + struct call *call; + bool call_indicator = false; + enum call_setup call_setup_indicator = CIND_CALLSETUP_NONE; + + spa_list_for_each(call, &this->call_list, link) { + call_indicator |= (call->state == CLCC_ACTIVE); + + if (call->state == CLCC_INCOMING && call_setup_indicator < CIND_CALLSETUP_INCOMING) + call_setup_indicator = CIND_CALLSETUP_INCOMING; + else if (call->state == CLCC_DIALING && call_setup_indicator < CIND_CALLSETUP_DIALING) + call_setup_indicator = CIND_CALLSETUP_DIALING; + else if (call->state == CLCC_ALERTING && call_setup_indicator < CIND_CALLSETUP_ALERTING) + call_setup_indicator = CIND_CALLSETUP_ALERTING; + } + + if (this->ops->set_call_active) + this->ops->set_call_active(call_indicator, this->user_data); + + if (this->ops->set_call_setup) + this->ops->set_call_setup(call_setup_indicator, this->user_data); +} + +static void mm_get_call_properties_reply(DBusPendingCall *pending, void *user_data) +{ + struct call *call = user_data; + struct impl *this = call->this; + DBusMessage *r; + DBusMessageIter arg_i, element_i; + MMCallDirection direction; + MMCallState state; + + spa_assert(call->pending == pending); + dbus_pending_call_unref(pending); + call->pending = NULL; + + r = dbus_pending_call_steal_reply(pending); + if (r == NULL) + return; + + if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { + spa_log_warn(this->log, "ModemManager D-Bus Call not available"); + goto finish; + } + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { + spa_log_error(this->log, "GetAll() failed: %s", dbus_message_get_error_name(r)); + goto finish; + } + + if (!dbus_message_iter_init(r, &arg_i) || !spa_streq(dbus_message_get_signature(r), "a{sv}")) { + spa_log_error(this->log, "Invalid arguments in GetAll() reply"); + goto finish; + } + + spa_log_debug(this->log, "Call path: %s", call->path); + + dbus_message_iter_recurse(&arg_i, &element_i); + while (dbus_message_iter_get_arg_type(&element_i) != DBUS_TYPE_INVALID) { + DBusMessageIter i, value_i; + const char *key; + + dbus_message_iter_recurse(&element_i, &i); + + dbus_message_iter_get_basic(&i, &key); + dbus_message_iter_next(&i); + dbus_message_iter_recurse(&i, &value_i); + + if (spa_streq(key, MM_CALL_PROPERTY_DIRECTION)) { + dbus_message_iter_get_basic(&value_i, &direction); + spa_log_debug(this->log, "Call direction: %u", direction); + call->direction = (direction == MM_CALL_DIRECTION_INCOMING) ? CALL_INCOMING : CALL_OUTGOING; + } else if (spa_streq(key, MM_CALL_PROPERTY_NUMBER)) { + char *number; + + dbus_message_iter_get_basic(&value_i, &number); + spa_log_debug(this->log, "Call number: %s", number); + if (call->number) + free(call->number); + call->number = strdup(number); + } else if (spa_streq(key, MM_CALL_PROPERTY_STATE)) { + int clcc_state; + + dbus_message_iter_get_basic(&value_i, &state); + spa_log_debug(this->log, "Call state: %u", state); + clcc_state = mm_state_to_clcc(this, state); + if (clcc_state < 0) { + spa_log_debug(this->log, "Unsupported modem state: %s, state=%d", call->path, call->state); + } else { + call->state = clcc_state; + mm_call_state_changed(this); + } + } + + dbus_message_iter_next(&element_i); + } + +finish: + dbus_message_unref(r); +} + +static DBusHandlerResult mm_parse_voice_properties(struct impl *this, DBusMessageIter *props_i) +{ + while (dbus_message_iter_get_arg_type(props_i) != DBUS_TYPE_INVALID) { + DBusMessageIter i, value_i, element_i; + const char *key; + + dbus_message_iter_recurse(props_i, &i); + + dbus_message_iter_get_basic(&i, &key); + dbus_message_iter_next(&i); + dbus_message_iter_recurse(&i, &value_i); + + if (spa_streq(key, MM_MODEM_VOICE_PROPERTY_CALLS)) { + spa_log_debug(this->log, "Voice properties"); + dbus_message_iter_recurse(&value_i, &element_i); + + while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_OBJECT_PATH) { + const char *call_object; + + dbus_message_iter_get_basic(&element_i, &call_object); + spa_log_debug(this->log, " Call: %s", call_object); + + dbus_message_iter_next(&element_i); + } + } + + dbus_message_iter_next(props_i); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult mm_parse_modem3gpp_properties(struct impl *this, DBusMessageIter *props_i) +{ + while (dbus_message_iter_get_arg_type(props_i) != DBUS_TYPE_INVALID) { + DBusMessageIter i, value_i; + const char *key; + + dbus_message_iter_recurse(props_i, &i); + + dbus_message_iter_get_basic(&i, &key); + dbus_message_iter_next(&i); + dbus_message_iter_recurse(&i, &value_i); + + if (spa_streq(key, MM_MODEM_MODEM3GPP_PROPERTY_OPERATORNAME)) { + char *operator_name; + + dbus_message_iter_get_basic(&value_i, &operator_name); + spa_log_debug(this->log, "Network operator code: %s", operator_name); + if (this->ops->set_modem_operator_name) + this->ops->set_modem_operator_name(operator_name, this->user_data); + } else if (spa_streq(key, MM_MODEM_MODEM3GPP_PROPERTY_REGISTRATIONSTATE)) { + MMModem3gppRegistrationState state; + bool is_roaming; + + dbus_message_iter_get_basic(&value_i, &state); + spa_log_debug(this->log, "Registration state: %d", state); + + if (state == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING || + state == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_CSFB_NOT_PREFERRED || + state == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_SMS_ONLY) + is_roaming = true; + else + is_roaming = false; + + if (this->ops->set_modem_roaming) + this->ops->set_modem_roaming(is_roaming, this->user_data); + } + + dbus_message_iter_next(props_i); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult mm_parse_modem_properties(struct impl *this, DBusMessageIter *props_i) +{ + while (dbus_message_iter_get_arg_type(props_i) != DBUS_TYPE_INVALID) { + DBusMessageIter i, value_i; + const char *key; + + dbus_message_iter_recurse(props_i, &i); + + dbus_message_iter_get_basic(&i, &key); + dbus_message_iter_next(&i); + dbus_message_iter_recurse(&i, &value_i); + + if(spa_streq(key, MM_MODEM_PROPERTY_EQUIPMENTIDENTIFIER)) { + char *imei; + + dbus_message_iter_get_basic(&value_i, &imei); + spa_log_debug(this->log, "Modem IMEI: %s", imei); + } else if(spa_streq(key, MM_MODEM_PROPERTY_MANUFACTURER)) { + char *manufacturer; + + dbus_message_iter_get_basic(&value_i, &manufacturer); + spa_log_debug(this->log, "Modem manufacturer: %s", manufacturer); + } else if(spa_streq(key, MM_MODEM_PROPERTY_MODEL)) { + char *model; + + dbus_message_iter_get_basic(&value_i, &model); + spa_log_debug(this->log, "Modem model: %s", model); + } else if (spa_streq(key, MM_MODEM_PROPERTY_OWNNUMBERS)) { + char *number; + DBusMessageIter array_i; + + dbus_message_iter_recurse(&value_i, &array_i); + if (dbus_message_iter_get_arg_type(&array_i) == DBUS_TYPE_STRING) { + dbus_message_iter_get_basic(&array_i, &number); + spa_log_debug(this->log, "Modem own number: %s", number); + if (this->ops->set_modem_own_number) + this->ops->set_modem_own_number(number, this->user_data); + } + } else if(spa_streq(key, MM_MODEM_PROPERTY_REVISION)) { + char *revision; + + dbus_message_iter_get_basic(&value_i, &revision); + spa_log_debug(this->log, "Modem revision: %s", revision); + } else if(spa_streq(key, MM_MODEM_PROPERTY_SIGNALQUALITY)) { + unsigned int percentage, signal_strength; + DBusMessageIter struct_i; + + dbus_message_iter_recurse(&value_i, &struct_i); + if (dbus_message_iter_get_arg_type(&struct_i) == DBUS_TYPE_UINT32) { + dbus_message_iter_get_basic(&struct_i, &percentage); + signal_strength = (unsigned int) round(percentage / 20.0); + spa_log_debug(this->log, "Network signal strength: %d (%d)", percentage, signal_strength); + if(this->ops->set_modem_signal_strength) + this->ops->set_modem_signal_strength(signal_strength, this->user_data); + } + } else if(spa_streq(key, MM_MODEM_PROPERTY_STATE)) { + MMModemState state; + bool has_service; + + dbus_message_iter_get_basic(&value_i, &state); + spa_log_debug(this->log, "Network state: %d", state); + + has_service = (state >= MM_MODEM_STATE_REGISTERED); + if (this->ops->set_modem_service) + this->ops->set_modem_service(has_service, this->user_data); + } + + dbus_message_iter_next(props_i); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult mm_parse_interfaces(struct impl *this, DBusMessageIter *dict_i) +{ + DBusMessageIter element_i, props_i; + const char *path; + + spa_assert(this); + spa_assert(dict_i); + + dbus_message_iter_get_basic(dict_i, &path); + dbus_message_iter_next(dict_i); + dbus_message_iter_recurse(dict_i, &element_i); + + while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter iface_i; + const char *interface; + + dbus_message_iter_recurse(&element_i, &iface_i); + dbus_message_iter_get_basic(&iface_i, &interface); + dbus_message_iter_next(&iface_i); + spa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_ARRAY); + + dbus_message_iter_recurse(&iface_i, &props_i); + + if (spa_streq(interface, MM_DBUS_INTERFACE_MODEM)) { + spa_log_debug(this->log, "Found Modem interface %s, path %s", interface, path); + if (this->modem.path == NULL) { + if (this->allowed_modem_device) { + DBusMessageIter i; + + dbus_message_iter_recurse(&iface_i, &i); + while (dbus_message_iter_get_arg_type(&i) != DBUS_TYPE_INVALID) { + DBusMessageIter key_i, value_i; + const char *key; + + dbus_message_iter_recurse(&i, &key_i); + + dbus_message_iter_get_basic(&key_i, &key); + dbus_message_iter_next(&key_i); + dbus_message_iter_recurse(&key_i, &value_i); + + if (spa_streq(key, MM_MODEM_PROPERTY_DEVICE)) { + char *device; + + dbus_message_iter_get_basic(&value_i, &device); + if (!spa_streq(this->allowed_modem_device, device)) { + spa_log_debug(this->log, "Modem not allowed: %s", device); + goto next; + } + } + dbus_message_iter_next(&i); + } + } + this->modem.path = strdup(path); + } else if (!spa_streq(this->modem.path, path)) { + spa_log_debug(this->log, "A modem is already registered"); + goto next; + } + mm_parse_modem_properties(this, &props_i); + } else if (spa_streq(interface, MM_DBUS_INTERFACE_MODEM_MODEM3GPP)) { + if (spa_streq(this->modem.path, path)) { + spa_log_debug(this->log, "Found Modem3GPP interface %s, path %s", interface, path); + mm_parse_modem3gpp_properties(this, &props_i); + } + } else if (spa_streq(interface, MM_DBUS_INTERFACE_MODEM_VOICE)) { + if (spa_streq(this->modem.path, path)) { + spa_log_debug(this->log, "Found Voice interface %s, path %s", interface, path); + mm_parse_voice_properties(this, &props_i); + } + } + +next: + dbus_message_iter_next(&element_i); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static void mm_get_managed_objects_reply(DBusPendingCall *pending, void *user_data) +{ + struct impl *this = user_data; + DBusMessage *r; + DBusMessageIter i, array_i; + + spa_assert(this->pending == pending); + dbus_pending_call_unref(pending); + this->pending = NULL; + + r = dbus_pending_call_steal_reply(pending); + if (r == NULL) + return; + + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { + spa_log_error(this->log, "Failed to get a list of endpoints from ModemManager: %s", + dbus_message_get_error_name(r)); + goto finish; + } + + if (!dbus_message_iter_init(r, &i) || !spa_streq(dbus_message_get_signature(r), "a{oa{sa{sv}}}")) { + spa_log_error(this->log, "Invalid arguments in GetManagedObjects() reply"); + goto finish; + } + + dbus_message_iter_recurse(&i, &array_i); + while (dbus_message_iter_get_arg_type(&array_i) != DBUS_TYPE_INVALID) { + DBusMessageIter dict_i; + + dbus_message_iter_recurse(&array_i, &dict_i); + mm_parse_interfaces(this, &dict_i); + dbus_message_iter_next(&array_i); + } + +finish: + dbus_message_unref(r); +} + +static void call_free(struct call *call) { + spa_list_remove(&call->link); + + if (call->pending != NULL) { + dbus_pending_call_cancel(call->pending); + dbus_pending_call_unref(call->pending); + } + + if (call->number) + free(call->number); + if (call->path) + free(call->path); + free(call); +} + +static void mm_clean_voice(struct impl *this) +{ + struct call *call; + + spa_list_consume(call, &this->call_list, link) + call_free(call); + + if (this->voice_pending != NULL) { + dbus_pending_call_cancel(this->voice_pending); + dbus_pending_call_unref(this->voice_pending); + } + + if (this->ops->set_call_setup) + this->ops->set_call_setup(CIND_CALLSETUP_NONE, this->user_data); + if (this->ops->set_call_active) + this->ops->set_call_active(false, this->user_data); +} + +static void mm_clean_modem3gpp(struct impl *this) +{ + if (this->ops->set_modem_operator_name) + this->ops->set_modem_operator_name(NULL, this->user_data); + if (this->ops->set_modem_roaming) + this->ops->set_modem_roaming(false, this->user_data); +} + +static void mm_clean_modem(struct impl *this) +{ + if (this->modem.path) { + free(this->modem.path); + this->modem.path = NULL; + } + if(this->ops->set_modem_signal_strength) + this->ops->set_modem_signal_strength(0, this->user_data); + if (this->ops->set_modem_service) + this->ops->set_modem_service(false, this->user_data); + this->modem.network_has_service = false; +} + +static DBusHandlerResult mm_filter_cb(DBusConnection *bus, DBusMessage *m, void *user_data) +{ + struct impl *this = user_data; + DBusError err; + + dbus_error_init(&err); + + if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) { + const char *name, *old_owner, *new_owner; + + spa_log_debug(this->log, "Name owner changed %s", dbus_message_get_path(m)); + + if (!dbus_message_get_args(m, &err, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old_owner, + DBUS_TYPE_STRING, &new_owner, + DBUS_TYPE_INVALID)) { + spa_log_error(this->log, "Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message); + goto finish; + } + + if (spa_streq(name, MM_DBUS_SERVICE)) { + if (old_owner && *old_owner) { + spa_log_debug(this->log, "ModemManager daemon disappeared (%s)", old_owner); + mm_clean_voice(this); + mm_clean_modem3gpp(this); + mm_clean_modem(this); + } + + if (new_owner && *new_owner) + spa_log_debug(this->log, "ModemManager daemon appeared (%s)", new_owner); + } + } else if (dbus_message_is_signal(m, DBUS_INTERFACE_OBJECTMANAGER, DBUS_SIGNAL_INTERFACES_ADDED)) { + DBusMessageIter arg_i; + + spa_log_warn(this->log, "sender: %s", dbus_message_get_sender(m)); + + if (!dbus_message_iter_init(m, &arg_i) || !spa_streq(dbus_message_get_signature(m), "oa{sa{sv}}")) { + spa_log_error(this->log, "Invalid signature found in InterfacesAdded"); + goto finish; + } + + mm_parse_interfaces(this, &arg_i); + } else if (dbus_message_is_signal(m, DBUS_INTERFACE_OBJECTMANAGER, DBUS_SIGNAL_INTERFACES_REMOVED)) { + const char *path; + DBusMessageIter arg_i, element_i; + + if (!dbus_message_iter_init(m, &arg_i) || !spa_streq(dbus_message_get_signature(m), "oas")) { + spa_log_error(this->log, "Invalid signature found in InterfacesRemoved"); + goto finish; + } + + dbus_message_iter_get_basic(&arg_i, &path); + if (!spa_streq(this->modem.path, path)) + goto finish; + + dbus_message_iter_next(&arg_i); + dbus_message_iter_recurse(&arg_i, &element_i); + + while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_STRING) { + const char *iface; + + dbus_message_iter_get_basic(&element_i, &iface); + + spa_log_debug(this->log, "Interface removed %s", path); + if (spa_streq(iface, MM_DBUS_INTERFACE_MODEM)) { + spa_log_debug(this->log, "Modem interface %s removed, path %s", iface, path); + mm_clean_modem(this); + } else if (spa_streq(iface, MM_DBUS_INTERFACE_MODEM_MODEM3GPP)) { + spa_log_debug(this->log, "Modem3GPP interface %s removed, path %s", iface, path); + mm_clean_modem3gpp(this); + } else if (spa_streq(iface, MM_DBUS_INTERFACE_MODEM_VOICE)) { + spa_log_debug(this->log, "Voice interface %s removed, path %s", iface, path); + mm_clean_voice(this); + } + + dbus_message_iter_next(&element_i); + } + } else if (dbus_message_is_signal(m, DBUS_INTERFACE_PROPERTIES, DBUS_SIGNAL_PROPERTIES_CHANGED)) { + const char *path; + DBusMessageIter iface_i, props_i; + const char *interface; + + path = dbus_message_get_path(m); + if (!spa_streq(this->modem.path, path)) + goto finish; + + if (!dbus_message_iter_init(m, &iface_i) || !spa_streq(dbus_message_get_signature(m), "sa{sv}as")) { + spa_log_error(this->log, "Invalid signature found in PropertiesChanged"); + goto finish; + } + + dbus_message_iter_get_basic(&iface_i, &interface); + dbus_message_iter_next(&iface_i); + spa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_ARRAY); + + dbus_message_iter_recurse(&iface_i, &props_i); + + if (spa_streq(interface, MM_DBUS_INTERFACE_MODEM)) { + spa_log_debug(this->log, "Properties changed on %s", path); + mm_parse_modem_properties(this, &props_i); + } else if (spa_streq(interface, MM_DBUS_INTERFACE_MODEM_MODEM3GPP)) { + spa_log_debug(this->log, "Properties changed on %s", path); + mm_parse_modem3gpp_properties(this, &props_i); + } else if (spa_streq(interface, MM_DBUS_INTERFACE_MODEM_VOICE)) { + spa_log_debug(this->log, "Properties changed on %s", path); + mm_parse_voice_properties(this, &props_i); + } + } else if (dbus_message_is_signal(m, MM_DBUS_INTERFACE_MODEM_VOICE, MM_MODEM_VOICE_SIGNAL_CALLADDED)) { + DBusMessageIter iface_i; + const char *path; + struct call *call_object; + const char *mm_call_interface = MM_DBUS_INTERFACE_CALL; + + if (!spa_streq(this->modem.path, dbus_message_get_path(m))) + goto finish; + + if (!dbus_message_iter_init(m, &iface_i) || !spa_streq(dbus_message_get_signature(m), "o")) { + spa_log_error(this->log, "Invalid signature found in %s", MM_MODEM_VOICE_SIGNAL_CALLADDED); + goto finish; + } + + dbus_message_iter_get_basic(&iface_i, &path); + spa_log_debug(this->log, "New call: %s", path); + + call_object = calloc(1, sizeof(struct call)); + if (call_object == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + call_object->this = this; + call_object->path = strdup(path); + spa_list_append(&this->call_list, &call_object->link); + + m = dbus_message_new_method_call(MM_DBUS_SERVICE, path, DBUS_INTERFACE_PROPERTIES, "GetAll"); + if (m == NULL) + goto finish; + dbus_message_append_args(m, DBUS_TYPE_STRING, &mm_call_interface, DBUS_TYPE_INVALID); + if (!mm_dbus_connection_send_with_reply(this, m, &call_object->pending, mm_get_call_properties_reply, call_object)) { + spa_log_error(this->log, "dbus call failure"); + dbus_message_unref(m); + goto finish; + } + } else if (dbus_message_is_signal(m, MM_DBUS_INTERFACE_MODEM_VOICE, MM_MODEM_VOICE_SIGNAL_CALLDELETED)) { + const char *path; + DBusMessageIter iface_i; + struct call *call, *call_tmp; + + if (!spa_streq(this->modem.path, dbus_message_get_path(m))) + goto finish; + + if (!dbus_message_iter_init(m, &iface_i) || !spa_streq(dbus_message_get_signature(m), "o")) { + spa_log_error(this->log, "Invalid signature found in %s", MM_MODEM_VOICE_SIGNAL_CALLDELETED); + goto finish; + } + + dbus_message_iter_get_basic(&iface_i, &path); + spa_log_debug(this->log, "Call ended: %s", path); + + spa_list_for_each_safe(call, call_tmp, &this->call_list, link) { + if (spa_streq(call->path, path)) + call_free(call); + } + mm_call_state_changed(this); + } else if (dbus_message_is_signal(m, MM_DBUS_INTERFACE_CALL, MM_CALL_SIGNAL_STATECHANGED)) { + const char *path; + DBusMessageIter iface_i; + MMCallState old, new; + MMCallStateReason reason; + struct call *call = NULL, *call_tmp; + int clcc_state; + + if (!dbus_message_iter_init(m, &iface_i) || !spa_streq(dbus_message_get_signature(m), "iiu")) { + spa_log_error(this->log, "Invalid signature found in %s", MM_CALL_SIGNAL_STATECHANGED); + goto finish; + } + + path = dbus_message_get_path(m); + + dbus_message_iter_get_basic(&iface_i, &old); + dbus_message_iter_next(&iface_i); + dbus_message_iter_get_basic(&iface_i, &new); + dbus_message_iter_next(&iface_i); + dbus_message_iter_get_basic(&iface_i, &reason); + + spa_log_debug(this->log, "Call state %s changed to %d (old = %d, reason = %u)", path, new, old, reason); + + spa_list_for_each(call_tmp, &this->call_list, link) { + if (spa_streq(call_tmp->path, path)) { + call = call_tmp; + break; + } + } + + if (call == NULL) { + spa_log_warn(this->log, "No call reference for %s", path); + goto finish; + } + + clcc_state = mm_state_to_clcc(this, new); + if (clcc_state < 0) { + spa_log_debug(this->log, "Unsupported modem state: %s, state=%d", call->path, call->state); + } else { + call->state = clcc_state; + mm_call_state_changed(this); + } + } + +finish: + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static int add_filters(struct impl *this) +{ + DBusError err; + + if (this->filters_added) + return 0; + + dbus_error_init(&err); + + if (!dbus_connection_add_filter(this->conn, mm_filter_cb, this, NULL)) { + spa_log_error(this->log, "failed to add filter function"); + goto fail; + } + + dbus_bus_add_match(this->conn, + "type='signal',sender='org.freedesktop.DBus'," + "interface='org.freedesktop.DBus',member='NameOwnerChanged'," "arg0='" MM_DBUS_SERVICE "'", &err); + dbus_bus_add_match(this->conn, + "type='signal',sender='" MM_DBUS_SERVICE "'," + "interface='" DBUS_INTERFACE_OBJECTMANAGER "',member='" DBUS_SIGNAL_INTERFACES_ADDED "'", &err); + dbus_bus_add_match(this->conn, + "type='signal',sender='" MM_DBUS_SERVICE "'," + "interface='" DBUS_INTERFACE_OBJECTMANAGER "',member='" DBUS_SIGNAL_INTERFACES_REMOVED "'", &err); + dbus_bus_add_match(this->conn, + "type='signal',sender='" MM_DBUS_SERVICE "'," + "interface='" DBUS_INTERFACE_PROPERTIES "',member='" DBUS_SIGNAL_PROPERTIES_CHANGED "'", &err); + dbus_bus_add_match(this->conn, + "type='signal',sender='" MM_DBUS_SERVICE "'," + "interface='" MM_DBUS_INTERFACE_MODEM_VOICE "',member='" MM_MODEM_VOICE_SIGNAL_CALLADDED "'", &err); + dbus_bus_add_match(this->conn, + "type='signal',sender='" MM_DBUS_SERVICE "'," + "interface='" MM_DBUS_INTERFACE_MODEM_VOICE "',member='" MM_MODEM_VOICE_SIGNAL_CALLDELETED "'", &err); + dbus_bus_add_match(this->conn, + "type='signal',sender='" MM_DBUS_SERVICE "'," + "interface='" MM_DBUS_INTERFACE_CALL "',member='" MM_CALL_SIGNAL_STATECHANGED "'", &err); + + this->filters_added = true; + + return 0; + +fail: + dbus_error_free(&err); + return -EIO; +} + +static bool is_dbus_service_available(struct impl *this, const char *service) +{ + DBusMessage *m, *r; + DBusError err; + bool success = false; + + m = dbus_message_new_method_call("org.freedesktop.DBus", "/org/freedesktop/DBus", + "org.freedesktop.DBus", "NameHasOwner"); + if (m == NULL) + return false; + dbus_message_append_args(m, DBUS_TYPE_STRING, &service, DBUS_TYPE_INVALID); + + dbus_error_init(&err); + r = dbus_connection_send_with_reply_and_block(this->conn, m, -1, &err); + dbus_message_unref(m); + m = NULL; + + if (r == NULL) { + spa_log_info(this->log, "NameHasOwner failed for %s", service); + dbus_error_free(&err); + goto finish; + } + + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { + spa_log_error(this->log, "NameHasOwner() returned error: %s", dbus_message_get_error_name(r)); + goto finish; + } + + if (!dbus_message_get_args(r, &err, + DBUS_TYPE_BOOLEAN, &success, + DBUS_TYPE_INVALID)) { + spa_log_error(this->log, "Failed to parse NameHasOwner() reply: %s", err.message); + dbus_error_free(&err); + goto finish; + } + +finish: + if (r) + dbus_message_unref(r); + + return success; +} + +bool mm_is_available(void *modemmanager) +{ + struct impl *this = modemmanager; + + if (this == NULL) + return false; + + return this->modem.path != NULL; +} + +unsigned int mm_supported_features() +{ + return SPA_BT_HFP_AG_FEATURE_REJECT_CALL | SPA_BT_HFP_AG_FEATURE_ENHANCED_CALL_STATUS; +} + +static void mm_get_call_simple_reply(DBusPendingCall *pending, void *data) +{ + struct dbus_cmd_data *dbus_cmd_data = data; + struct impl *this = dbus_cmd_data->this; + struct call *call = dbus_cmd_data->call; + void *user_data = dbus_cmd_data->user_data; + DBusMessage *r; + + free(data); + + spa_assert(call->pending == pending); + dbus_pending_call_unref(pending); + call->pending = NULL; + + r = dbus_pending_call_steal_reply(pending); + if (r == NULL) + return; + + if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { + spa_log_warn(this->log, "ModemManager D-Bus method not available"); + goto finish; + } + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { + spa_log_error(this->log, "ModemManager method failed: %s", dbus_message_get_error_name(r)); + goto finish; + } + + this->ops->send_cmd_result(true, 0, user_data); + return; + +finish: + this->ops->send_cmd_result(false, CMEE_AG_FAILURE, user_data); +} + +static void mm_get_call_create_reply(DBusPendingCall *pending, void *data) +{ + struct dbus_cmd_data *dbus_cmd_data = data; + struct impl *this = dbus_cmd_data->this; + void *user_data = dbus_cmd_data->user_data; + DBusMessage *r; + + free(data); + + spa_assert(this->voice_pending == pending); + dbus_pending_call_unref(pending); + this->voice_pending = NULL; + + r = dbus_pending_call_steal_reply(pending); + if (r == NULL) + return; + + if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { + spa_log_warn(this->log, "ModemManager D-Bus method not available"); + goto finish; + } + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { + spa_log_error(this->log, "ModemManager method failed: %s", dbus_message_get_error_name(r)); + goto finish; + } + + this->ops->send_cmd_result(true, 0, user_data); + return; + +finish: + this->ops->send_cmd_result(false, CMEE_AG_FAILURE, user_data); +} + +bool mm_answer_call(void *modemmanager, void *user_data, enum cmee_error *error) +{ + struct impl *this = modemmanager; + struct call *call_object, *call_tmp; + struct dbus_cmd_data *data; + DBusMessage *m; + + call_object = NULL; + spa_list_for_each(call_tmp, &this->call_list, link) { + if (call_tmp->state == CLCC_INCOMING) { + call_object = call_tmp; + break; + } + } + if (!call_object) { + spa_log_debug(this->log, "No ringing in call"); + if (error) + *error = CMEE_OPERATION_NOT_ALLOWED; + return false; + } + + data = malloc(sizeof(struct dbus_cmd_data)); + if (!data) { + if (error) + *error = CMEE_AG_FAILURE; + return false; + } + data->this = this; + data->call = call_object; + data->user_data = user_data; + + m = dbus_message_new_method_call(MM_DBUS_SERVICE, call_object->path, MM_DBUS_INTERFACE_CALL, MM_CALL_METHOD_ACCEPT); + if (m == NULL) { + if (error) + *error = CMEE_AG_FAILURE; + return false; + } + if (!mm_dbus_connection_send_with_reply(this, m, &call_object->pending, mm_get_call_simple_reply, data)) { + spa_log_error(this->log, "dbus call failure"); + dbus_message_unref(m); + if (error) + *error = CMEE_AG_FAILURE; + return false; + } + + return true; +} + +bool mm_hangup_call(void *modemmanager, void *user_data, enum cmee_error *error) +{ + struct impl *this = modemmanager; + struct call *call_object, *call_tmp; + struct dbus_cmd_data *data; + DBusMessage *m; + + call_object = NULL; + spa_list_for_each(call_tmp, &this->call_list, link) { + if (call_tmp->state == CLCC_ACTIVE) { + call_object = call_tmp; + break; + } + } + if (!call_object) { + spa_list_for_each(call_tmp, &this->call_list, link) { + if (call_tmp->state == CLCC_DIALING || + call_tmp->state == CLCC_ALERTING || + call_tmp->state == CLCC_INCOMING) { + call_object = call_tmp; + break; + } + } + } + if (!call_object) { + spa_log_debug(this->log, "No call to reject or hang up"); + if (error) + *error = CMEE_OPERATION_NOT_ALLOWED; + return false; + } + + data = malloc(sizeof(struct dbus_cmd_data)); + if (!data) { + if (error) + *error = CMEE_AG_FAILURE; + return false; + } + data->this = this; + data->call = call_object; + data->user_data = user_data; + + m = dbus_message_new_method_call(MM_DBUS_SERVICE, call_object->path, MM_DBUS_INTERFACE_CALL, MM_CALL_METHOD_HANGUP); + if (m == NULL) { + if (error) + *error = CMEE_AG_FAILURE; + return false; + } + if (!mm_dbus_connection_send_with_reply(this, m, &call_object->pending, mm_get_call_simple_reply, data)) { + spa_log_error(this->log, "dbus call failure"); + dbus_message_unref(m); + if (error) + *error = CMEE_AG_FAILURE; + return false; + } + + return true; +} + +static void append_basic_variant_dict_entry(DBusMessageIter *dict, const char* key, int variant_type_int, const char* variant_type_str, void* variant) { + DBusMessageIter dict_entry_it, variant_it; + dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_it); + dbus_message_iter_append_basic(&dict_entry_it, DBUS_TYPE_STRING, &key); + + dbus_message_iter_open_container(&dict_entry_it, DBUS_TYPE_VARIANT, variant_type_str, &variant_it); + dbus_message_iter_append_basic(&variant_it, variant_type_int, variant); + dbus_message_iter_close_container(&dict_entry_it, &variant_it); + dbus_message_iter_close_container(dict, &dict_entry_it); +} + +static inline bool is_valid_dial_string_char(char c) +{ + return ('0' <= c && c <= '9') + || ('A' <= c && c <= 'C') + || c == '*' + || c == '#' + || c == '+'; +} + +bool mm_do_call(void *modemmanager, const char* number, void *user_data, enum cmee_error *error) +{ + struct impl *this = modemmanager; + struct dbus_cmd_data *data; + DBusMessage *m; + DBusMessageIter iter, dict; + + for (size_t i = 0; number[i]; i++) { + if (!is_valid_dial_string_char(number[i])) { + spa_log_warn(this->log, "Call creation canceled, invalid character found in dial string: %c", number[i]); + if (error) + *error = CMEE_INVALID_CHARACTERS_DIAL_STRING; + return false; + } + } + + data = malloc(sizeof(struct dbus_cmd_data)); + if (!data) { + if (error) + *error = CMEE_AG_FAILURE; + return false; + } + data->this = this; + data->user_data = user_data; + + m = dbus_message_new_method_call(MM_DBUS_SERVICE, this->modem.path, MM_DBUS_INTERFACE_MODEM_VOICE, MM_MODEM_VOICE_METHOD_CREATECALL); + if (m == NULL) { + if (error) + *error = CMEE_AG_FAILURE; + return false; + } + dbus_message_iter_init_append(m, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &dict); + append_basic_variant_dict_entry(&dict, "number", DBUS_TYPE_STRING, "s", &number); + dbus_message_iter_close_container(&iter, &dict); + if (!mm_dbus_connection_send_with_reply(this, m, &this->voice_pending, mm_get_call_create_reply, data)) { + spa_log_error(this->log, "dbus call failure"); + dbus_message_unref(m); + if (error) + *error = CMEE_AG_FAILURE; + return false; + } + + return true; +} + +bool mm_send_dtmf(void *modemmanager, const char *dtmf, void *user_data, enum cmee_error *error) +{ + struct impl *this = modemmanager; + struct call *call_object, *call_tmp; + struct dbus_cmd_data *data; + DBusMessage *m; + + call_object = NULL; + spa_list_for_each(call_tmp, &this->call_list, link) { + if (call_tmp->state == CLCC_ACTIVE) { + call_object = call_tmp; + break; + } + } + if (!call_object) { + spa_log_debug(this->log, "No active call"); + if (error) + *error = CMEE_OPERATION_NOT_ALLOWED; + return false; + } + + /* Allowed dtmf characters: 0-9, *, #, A-D */ + if (!((dtmf[0] >= '0' && dtmf[0] <= '9') + || (dtmf[0] == '*') + || (dtmf[0] == '#') + || (dtmf[0] >= 'A' && dtmf[0] <= 'D'))) { + spa_log_debug(this->log, "Invalid DTMF character: %s", dtmf); + if (error) + *error = CMEE_INVALID_CHARACTERS_TEXT_STRING; + return false; + } + + data = malloc(sizeof(struct dbus_cmd_data)); + if (!data) { + if (error) + *error = CMEE_AG_FAILURE; + return false; + } + data->this = this; + data->call = call_object; + data->user_data = user_data; + + m = dbus_message_new_method_call(MM_DBUS_SERVICE, call_object->path, MM_DBUS_INTERFACE_CALL, MM_CALL_METHOD_SENDDTMF); + if (m == NULL) { + if (error) + *error = CMEE_AG_FAILURE; + return false; + } + dbus_message_append_args(m, DBUS_TYPE_STRING, &dtmf, DBUS_TYPE_INVALID); + if (!mm_dbus_connection_send_with_reply(this, m, &call_object->pending, mm_get_call_simple_reply, data)) { + spa_log_error(this->log, "dbus call failure"); + dbus_message_unref(m); + if (error) + *error = CMEE_AG_FAILURE; + return false; + } + + return true; +} + +const char *mm_get_incoming_call_number(void *modemmanager) +{ + struct impl *this = modemmanager; + struct call *call_object, *call_tmp; + + call_object = NULL; + spa_list_for_each(call_tmp, &this->call_list, link) { + if (call_tmp->state == CLCC_INCOMING) { + call_object = call_tmp; + break; + } + } + if (!call_object) { + spa_log_debug(this->log, "No ringing in call"); + return NULL; + } + + return call_object->number; +} + +struct spa_list *mm_get_calls(void *modemmanager) +{ + struct impl *this = modemmanager; + + return &this->call_list; +} + +void *mm_register(struct spa_log *log, void *dbus_connection, const struct spa_dict *info, + const struct mm_ops *ops, void *user_data) +{ + struct impl *this; + const char *modem_device_str = NULL; + bool modem_device_found = false; + + spa_assert(log); + spa_assert(dbus_connection); + + if (info) { + if ((modem_device_str = spa_dict_lookup(info, "bluez5.hfphsp-backend-native-modem")) != NULL) { + if (!spa_streq(modem_device_str, "none")) + modem_device_found = true; + } + } + if (!modem_device_found) { + spa_log_info(log, "No modem allowed, doesn't link to ModemManager"); + return NULL; + } + + this = calloc(1, sizeof(struct impl)); + if (this == NULL) + return NULL; + + this->log = log; + this->conn = dbus_connection; + this->ops = ops; + this->user_data = user_data; + if (modem_device_str && !spa_streq(modem_device_str, "any")) + this->allowed_modem_device = strdup(modem_device_str); + spa_list_init(&this->call_list); + + if (add_filters(this) < 0) { + goto fail; + } + + if (is_dbus_service_available(this, MM_DBUS_SERVICE)) { + DBusMessage *m; + + m = dbus_message_new_method_call(MM_DBUS_SERVICE, "/org/freedesktop/ModemManager1", + DBUS_INTERFACE_OBJECTMANAGER, "GetManagedObjects"); + if (m == NULL) + goto fail; + + if (!mm_dbus_connection_send_with_reply(this, m, &this->pending, mm_get_managed_objects_reply, this)) { + spa_log_error(this->log, "dbus call failure"); + dbus_message_unref(m); + goto fail; + } + } + + return this; + +fail: + free(this); + return NULL; +} + +void mm_unregister(void *data) +{ + struct impl *this = data; + + if (this->pending != NULL) { + dbus_pending_call_cancel(this->pending); + dbus_pending_call_unref(this->pending); + } + + mm_clean_voice(this); + mm_clean_modem3gpp(this); + mm_clean_modem(this); + + if (this->filters_added) { + dbus_connection_remove_filter(this->conn, mm_filter_cb, this); + this->filters_added = false; + } + + if (this->allowed_modem_device) + free(this->allowed_modem_device); + + free(this); +} diff --git a/spa/plugins/bluez5/modemmanager.h b/spa/plugins/bluez5/modemmanager.h new file mode 100644 index 0000000000000000000000000000000000000000..a239b2ace2a0b14fbe80dba80f506e3ee8c5e8c8 --- /dev/null +++ b/spa/plugins/bluez5/modemmanager.h @@ -0,0 +1,161 @@ +/* Spa Bluez5 ModemManager proxy + * + * Copyright © 2022 Collabora + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef SPA_BLUEZ5_MODEMMANAGER_H_ +#define SPA_BLUEZ5_MODEMMANAGER_H_ + +#include <spa/utils/list.h> + +#include "defs.h" + +enum cmee_error { + CMEE_AG_FAILURE = 0, + CMEE_NO_CONNECTION_TO_PHONE = 1, + CMEE_OPERATION_NOT_ALLOWED = 3, + CMEE_OPERATION_NOT_SUPPORTED = 4, + CMEE_INVALID_CHARACTERS_TEXT_STRING = 25, + CMEE_INVALID_CHARACTERS_DIAL_STRING = 27, + CMEE_NO_NETWORK_SERVICE = 30 +}; + +enum call_setup { + CIND_CALLSETUP_NONE = 0, + CIND_CALLSETUP_INCOMING, + CIND_CALLSETUP_DIALING, + CIND_CALLSETUP_ALERTING +}; + +enum call_direction { + CALL_OUTGOING, + CALL_INCOMING +}; + +enum call_state { + CLCC_ACTIVE, + CLCC_HELD, + CLCC_DIALING, + CLCC_ALERTING, + CLCC_INCOMING, + CLCC_WAITING, + CLCC_RESPONSE_AND_HOLD +}; + +struct call { + struct spa_list link; + unsigned int index; + struct impl *this; + DBusPendingCall *pending; + + char *path; + char *number; + bool call_indicator; + enum call_direction direction; + enum call_state state; + bool multiparty; +}; + +struct mm_ops { + void (*send_cmd_result)(bool success, enum cmee_error error, void *user_data); + void (*set_modem_service)(bool available, void *user_data); + void (*set_modem_signal_strength)(unsigned int strength, void *user_data); + void (*set_modem_operator_name)(const char *name, void *user_data); + void (*set_modem_own_number)(const char *number, void *user_data); + void (*set_modem_roaming)(bool is_roaming, void *user_data); + void (*set_call_active)(bool active, void *user_data); + void (*set_call_setup)(enum call_setup value, void *user_data); +}; + +#ifdef HAVE_BLUEZ_5_BACKEND_NATIVE_MM +void *mm_register(struct spa_log *log, void *dbus_connection, const struct spa_dict *info, + const struct mm_ops *ops, void *user_data); +void mm_unregister(void *data); +bool mm_is_available(void *modemmanager); +unsigned int mm_supported_features(); +bool mm_answer_call(void *modemmanager, void *user_data, enum cmee_error *error); +bool mm_hangup_call(void *modemmanager, void *user_data, enum cmee_error *error); +bool mm_do_call(void *modemmanager, const char* number, void *user_data, enum cmee_error *error); +bool mm_send_dtmf(void *modemmanager, const char *dtmf, void *user_data, enum cmee_error *error); +const char *mm_get_incoming_call_number(void *modemmanager); +struct spa_list *mm_get_calls(void *modemmanager); +#else +void *mm_register(struct spa_log *log, void *dbus_connection, const struct spa_dict *info, + const struct mm_ops *ops, void *user_data) +{ + return NULL; +} + +void mm_unregister(void *data) +{ +} + +bool mm_is_available(void *modemmanager) +{ + return false; +} + +unsigned int mm_supported_features(void) +{ + return 0; +} + +bool mm_answer_call(void *modemmanager, void *user_data, enum cmee_error *error) +{ + if (error) + *error = CMEE_OPERATION_NOT_SUPPORTED; + return false; +} + +bool mm_hangup_call(void *modemmanager, void *user_data, enum cmee_error *error) +{ + if (error) + *error = CMEE_OPERATION_NOT_SUPPORTED; + return false; +} + +bool mm_do_call(void *modemmanager, const char* number, void *user_data, enum cmee_error *error) +{ + if (error) + *error = CMEE_OPERATION_NOT_SUPPORTED; + return false; +} + +bool mm_send_dtmf(void *modemmanager, const char *dtmf, void *user_data, enum cmee_error *error) +{ + if (error) + *error = CMEE_OPERATION_NOT_SUPPORTED; + return false; +} + +const char *mm_get_incoming_call_number(void *modemmanager) +{ + return NULL; +} + +struct spa_list *mm_get_calls(void *modemmanager) +{ + return NULL; +} +#endif + +#endif diff --git a/spa/plugins/bluez5/plugin.c b/spa/plugins/bluez5/plugin.c index bb09a22a280617bb3be4e0b8a07c0b50170b9a12..4a06f81d344e3ae9398ec9fbb52cd8e905ee7b4c 100644 --- a/spa/plugins/bluez5/plugin.c +++ b/spa/plugins/bluez5/plugin.c @@ -29,10 +29,12 @@ extern const struct spa_handle_factory spa_bluez5_dbus_factory; extern const struct spa_handle_factory spa_bluez5_device_factory; -extern const struct spa_handle_factory spa_a2dp_sink_factory; -extern const struct spa_handle_factory spa_a2dp_source_factory; +extern const struct spa_handle_factory spa_media_sink_factory; +extern const struct spa_handle_factory spa_media_source_factory; extern const struct spa_handle_factory spa_sco_sink_factory; extern const struct spa_handle_factory spa_sco_source_factory; +extern const struct spa_handle_factory spa_a2dp_sink_factory; +extern const struct spa_handle_factory spa_a2dp_source_factory; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) @@ -48,10 +50,10 @@ int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *factory = &spa_bluez5_device_factory; break; case 2: - *factory = &spa_a2dp_sink_factory; + *factory = &spa_media_sink_factory; break; case 3: - *factory = &spa_a2dp_source_factory; + *factory = &spa_media_source_factory; break; case 4: *factory = &spa_sco_sink_factory; @@ -59,6 +61,12 @@ int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t case 5: *factory = &spa_sco_source_factory; break; + case 6: + *factory = &spa_a2dp_sink_factory; + break; + case 7: + *factory = &spa_a2dp_source_factory; + break; default: return 0; } diff --git a/spa/plugins/bluez5/quirks.c b/spa/plugins/bluez5/quirks.c index 7612e9ae6d784aae14f4eef590593cc2cfb27fae..8a7f92643a5197a4d411082dbd916e31b9a7a030 100644 --- a/spa/plugins/bluez5/quirks.c +++ b/spa/plugins/bluez5/quirks.c @@ -56,7 +56,6 @@ #include <spa/utils/json.h> #include <spa/utils/string.h> -#include "a2dp-codecs.h" #include "defs.h" static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.quirks"); @@ -89,10 +88,9 @@ static enum spa_bt_feature parse_feature(const char *str) { "faststream", SPA_BT_FEATURE_FASTSTREAM }, { "a2dp-duplex", SPA_BT_FEATURE_A2DP_DUPLEX }, }; - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(feature_keys); ++i) { - if (spa_streq(str, feature_keys[i].key)) - return feature_keys[i].value; + SPA_FOR_EACH_ELEMENT_VAR(feature_keys, f) { + if (spa_streq(str, f->key)) + return f->value; } return 0; } diff --git a/spa/plugins/bluez5/sco-sink.c b/spa/plugins/bluez5/sco-sink.c index d92bada2af852540f132759f84a24c27cd1302f0..f8db7eaf8fcf031610288dd0aab9d253dede3671 100644 --- a/spa/plugins/bluez5/sco-sink.c +++ b/spa/plugins/bluez5/sco-sink.c @@ -60,14 +60,10 @@ static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.sink.sco"); #define DEFAULT_CLOCK_NAME "clock.system.monotonic" struct props { - uint32_t min_latency; - uint32_t max_latency; char clock_name[64]; }; #define MAX_BUFFERS 32 -#define MIN_LATENCY 512 -#define MAX_LATENCY 1024 struct buffer { uint32_t id; @@ -130,6 +126,8 @@ struct impl { struct spa_param_info params[N_NODE_PARAMS]; struct props props; + uint32_t quantum_limit; + /* Transport */ struct spa_bt_transport *transport; struct spa_hook transport_listener; @@ -140,38 +138,39 @@ struct impl { /* Flags */ unsigned int started:1; unsigned int following:1; + unsigned int flush_pending:1; /* Sources */ struct spa_source source; + struct spa_source flush_timer_source; /* Timer */ int timerfd; - struct timespec now; + int flush_timerfd; struct spa_io_clock *clock; struct spa_io_position *position; + uint64_t current_time; + uint64_t next_time; + uint64_t process_time; + uint64_t prev_flush_time; + uint64_t next_flush_time; + /* mSBC */ sbc_t msbc; uint8_t *buffer; uint8_t *buffer_head; uint8_t *buffer_next; int buffer_size; - - /* Times */ - uint64_t start_time; - uint64_t total_samples; + int msbc_seq; }; #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0) -static const uint32_t default_min_latency = MIN_LATENCY; -static const uint32_t default_max_latency = MAX_LATENCY; static const char sntable[4] = { 0x08, 0x38, 0xC8, 0xF8 }; static void reset_props(struct props *props) { - props->min_latency = default_min_latency; - props->max_latency = default_max_latency; strncpy(props->clock_name, DEFAULT_CLOCK_NAME, sizeof(props->clock_name)); } @@ -199,23 +198,7 @@ static int impl_node_enum_params(void *object, int seq, switch (id) { case SPA_PARAM_PropInfo: { - struct props *p = &this->props; - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_minLatency), - SPA_PROP_INFO_description, SPA_POD_String("The minimum latency"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->min_latency, 1, INT32_MAX)); - break; - case 1: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_maxLatency), - SPA_PROP_INFO_description, SPA_POD_String("The maximum latency"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->max_latency, 1, INT32_MAX)); - break; default: return 0; } @@ -223,14 +206,10 @@ static int impl_node_enum_params(void *object, int seq, } case SPA_PARAM_Props: { - struct props *p = &this->props; - switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_Props, id, - SPA_PROP_minLatency, SPA_POD_Int(p->min_latency), - SPA_PROP_maxLatency, SPA_POD_Int(p->max_latency)); + SPA_TYPE_OBJECT_Props, id); break; default: return 0; @@ -260,28 +239,17 @@ static int set_timeout(struct impl *this, uint64_t time) ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; return spa_system_timerfd_settime(this->data_system, - this->timerfd, 0, &ts, NULL); + this->timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); } static int set_timers(struct impl *this) { - return set_timeout(this, this->following ? 0 : 1); -} - -static uint64_t get_next_timeout(struct impl *this, uint64_t now_time, uint64_t processed_samples) -{ - struct port *port = &this->port; - uint64_t playback_time = 0, elapsed_time = 0, next_time = 0; - - this->total_samples += processed_samples; + struct timespec now; - playback_time = (this->total_samples * SPA_NSEC_PER_SEC) / port->current_format.info.raw.rate; - if (now_time > this->start_time) - elapsed_time = now_time - this->start_time; - if (elapsed_time < playback_time) - next_time = playback_time - elapsed_time; + spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); + this->next_time = SPA_TIMESPEC_TO_NSEC(&now); - return next_time; + return set_timeout(this, this->following ? 0 : this->next_time); } static int do_reassign_follower(struct spa_loop *loop, @@ -344,9 +312,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param) reset_props(&new_props); } else { spa_pod_parse_object(param, - SPA_TYPE_OBJECT_Props, NULL, - SPA_PROP_minLatency, SPA_POD_OPT_Int(&new_props.min_latency), - SPA_PROP_maxLatency, SPA_POD_OPT_Int(&new_props.max_latency)); + SPA_TYPE_OBJECT_Props, NULL); } changed = (memcmp(&new_props, &this->props, sizeof(struct props)) != 0); @@ -378,22 +344,64 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, return 0; } +static void enable_flush_timer(struct impl *this, bool enabled) +{ + struct itimerspec ts; + + if (!enabled) + this->next_flush_time = 0; + + ts.it_value.tv_sec = this->next_flush_time / SPA_NSEC_PER_SEC; + ts.it_value.tv_nsec = this->next_flush_time % SPA_NSEC_PER_SEC; + ts.it_interval.tv_sec = 0; + ts.it_interval.tv_nsec = 0; + spa_system_timerfd_settime(this->data_system, + this->flush_timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); + + 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 += d[0].chunk->size; + } + + if (bytes > port->ready_offset) + bytes -= port->ready_offset; + else + bytes = 0; + + return bytes / port->frame_size; +} + static void flush_data(struct impl *this) { struct port *port = &this->port; - struct spa_data *datas; const uint32_t min_in_size = (this->transport->codec == HFP_AUDIO_CODEC_MSBC) ? MSBC_DECODED_SIZE : this->transport->write_mtu; uint8_t * const packet = (this->transport->codec == HFP_AUDIO_CODEC_MSBC) ? this->buffer_head : port->write_buffer; + 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; -again: 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)); @@ -429,147 +437,212 @@ again: } } - /* send the data if the write buffer is full */ - if (port->write_buffer_size >= min_in_size) { - uint64_t now_time; - static int sn = 0; - int processed = 0; + if (this->flush_pending) { + spa_log_trace(this->log, "%p: wait for flush timer", this); + return; + } + + 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; + } + + if (this->transport->codec == HFP_AUDIO_CODEC_MSBC) { ssize_t out_encoded; - int written; - uint64_t next_timeout; - - spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &this->now); - now_time = SPA_TIMESPEC_TO_NSEC(&this->now); - if (this->start_time == 0) - this->start_time = now_time; - - if (this->transport->codec == HFP_AUDIO_CODEC_MSBC) { - /* Encode */ - if (this->buffer_next + MSBC_ENCODED_SIZE > this->buffer + this->buffer_size) { - /* Buffer overrun; shouldn't usually happen. Drop data and reset. */ - this->buffer_head = this->buffer_next = this->buffer; - spa_log_warn(this->log, "sco-sink: mSBC buffer overrun, dropping data"); - } - this->buffer_next[0] = 0x01; - this->buffer_next[1] = sntable[sn]; - this->buffer_next[59] = 0x00; - sn = (sn + 1) % 4; - processed = sbc_encode(&this->msbc, port->write_buffer, port->write_buffer_size, - this->buffer_next + 2, MSBC_ENCODED_SIZE - 3, &out_encoded); - if (processed < 0) { - spa_log_warn(this->log, "sbc_encode failed: %d", processed); - return; - } - this->buffer_next += out_encoded + 3; - port->write_buffer_size = 0; - - /* Write */ - written = spa_bt_sco_io_write(this->transport->sco_io, packet, - this->buffer_next - this->buffer_head); - if (written < 0) { - spa_log_warn(this->log, "failed to write data: %d (%s)", - written, spa_strerror(written)); - goto stop; - } - - this->buffer_head += written; - - if (this->buffer_head == this->buffer_next) - this->buffer_head = this->buffer_next = this->buffer; - else if (this->buffer_next + MSBC_ENCODED_SIZE > this->buffer + this->buffer_size) { - /* Written bytes is not necessarily commensurate - * with MSBC_ENCODED_SIZE. If this occurs, copy data. - */ - int size = this->buffer_next - this->buffer_head; - spa_memmove(this->buffer, this->buffer_head, size); - this->buffer_next = this->buffer + size; - this->buffer_head = this->buffer; - } - } else { - written = spa_bt_sco_io_write(this->transport->sco_io, packet, - port->write_buffer_size); - if (written < 0) { - spa_log_warn(this->log, "sco-sink: write failure: %d (%s)", - written, spa_strerror(written)); - goto stop; - } else if (written == 0) { - /* EAGAIN or similar, just skip ahead */ - written = SPA_MIN(port->write_buffer_size, (uint32_t)48); - } - - processed = written; - port->write_buffer_size -= written; - - if (port->write_buffer_size > 0 && written > 0) { - spa_memmove(port->write_buffer, port->write_buffer + written, port->write_buffer_size); - } + + /* Encode */ + if (this->buffer_next + MSBC_ENCODED_SIZE > this->buffer + this->buffer_size) { + /* Buffer overrun; shouldn't usually happen. Drop data and reset. */ + this->buffer_head = this->buffer_next = this->buffer; + spa_log_warn(this->log, "sco-sink: mSBC buffer overrun, dropping data"); + } + this->buffer_next[0] = 0x01; + this->buffer_next[1] = sntable[this->msbc_seq % 4]; + this->buffer_next[59] = 0x00; + this->msbc_seq = (this->msbc_seq + 1) % 4; + processed = sbc_encode(&this->msbc, port->write_buffer, port->write_buffer_size, + this->buffer_next + 2, MSBC_ENCODED_SIZE - 3, &out_encoded); + if (processed < 0) { + spa_log_warn(this->log, "sbc_encode failed: %d", processed); + return; + } + this->buffer_next += out_encoded + 3; + port->write_buffer_size = 0; + + /* Write */ + written = spa_bt_sco_io_write(this->transport->sco_io, packet, + this->buffer_next - this->buffer_head); + if (written < 0) { + spa_log_warn(this->log, "failed to write data: %d (%s)", + written, spa_strerror(written)); + goto stop; } - spa_log_trace(this->log, "write socket data %d", written); + this->buffer_head += written; + + if (this->buffer_head == this->buffer_next) + this->buffer_head = this->buffer_next = this->buffer; + else if (this->buffer_next + MSBC_ENCODED_SIZE > this->buffer + this->buffer_size) { + /* Written bytes is not necessarily commensurate + * with MSBC_ENCODED_SIZE. If this occurs, copy data. + */ + int size = this->buffer_next - this->buffer_head; + spa_memmove(this->buffer, this->buffer_head, size); + this->buffer_next = this->buffer + size; + this->buffer_head = this->buffer; + } + } else { + written = spa_bt_sco_io_write(this->transport->sco_io, packet, + port->write_buffer_size); + if (written < 0) { + spa_log_warn(this->log, "sco-sink: write failure: %d (%s)", + written, spa_strerror(written)); + goto stop; + } else if (written == 0) { + /* EAGAIN or similar, just skip ahead */ + written = SPA_MIN(port->write_buffer_size, (uint32_t)48); + } - next_timeout = get_next_timeout(this, now_time, processed / port->frame_size); + processed = written; + port->write_buffer_size -= written; - if (!this->following && this->clock) { - this->clock->nsec = now_time; - this->clock->position = this->total_samples; - this->clock->delay = processed / port->frame_size; - this->clock->rate_diff = 1.0f; - this->clock->next_nsec = now_time + next_timeout; + if (port->write_buffer_size > 0 && written > 0) { + spa_memmove(port->write_buffer, port->write_buffer + written, port->write_buffer_size); } + } - if (next_timeout == 0) - goto again; + if (SPA_UNLIKELY(spa_log_level_topic_enabled(this->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) { + struct timespec ts; + uint64_t now; + uint64_t dt; - spa_log_trace(this->log, "timeout %"PRIu64" ns", next_timeout); - set_timeout(this, next_timeout); - } else { - /* As follower, driver will wake us up when there is data */ - if (this->following) - return; + spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &ts); + now = SPA_TIMESPEC_TO_NSEC(&ts); + dt = now - this->prev_flush_time; + this->prev_flush_time = now; + + spa_log_trace(this->log, + "%p: send wrote:%d dt:%"PRIu64, + this, written, dt); + } + + spa_log_trace(this->log, "write socket data %d", written); - /* As driver, run timeout now to schedule data */ - spa_log_trace(this->log, "timeout 1 ns (driver: schedule now)"); - set_timeout(this, 1); + 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); + + /* + * We could delay the output by one packet to avoid waiting + * for the next buffer and so make send intervals more regular. + * However, this appears not needed in practice, and it's better + * to not add latency if not needed. + */ +#if 0 + this->next_flush_time += SPA_MIN(packet_time, + duration_ns * (port->n_buffers - 1)); +#endif + } else { + if (this->next_flush_time == 0) + this->next_flush_time = this->process_time; + this->next_flush_time += packet_time; } + enable_flush_timer(this, true); return; stop: if (this->source.loop) spa_loop_remove_source(this->data_loop, &this->source); + enable_flush_timer(this, false); +} + + +static void sco_on_flush_timeout(struct spa_source *source) +{ + struct impl *this = source->data; + uint64_t exp; + + spa_log_trace(this->log, "%p: flush on timeout", this); + + if (spa_system_timerfd_read(this->data_system, this->flush_timerfd, &exp) < 0) + spa_log_warn(this->log, "error reading timerfd: %s", strerror(errno)); + + if (this->transport == NULL) { + enable_flush_timer(this, false); + return; + } + + while (exp-- > 0) { + this->flush_pending = false; + flush_data(this); + } } static void sco_on_timeout(struct spa_source *source) { struct impl *this = source->data; struct port *port = &this->port; - uint64_t exp; + uint64_t exp, duration; + uint32_t rate; + struct spa_io_buffers *io = port->io; + uint64_t prev_time, now_time; if (this->transport == NULL) return; - /* Read the timerfd */ if (this->started && spa_system_timerfd_read(this->data_system, this->timerfd, &exp) < 0) spa_log_warn(this->log, "error reading timerfd: %s", strerror(errno)); - /* delay if no buffers available */ - if (!this->following && spa_list_is_empty(&port->ready)) { - set_timeout(this, this->transport->write_mtu / port->frame_size * SPA_NSEC_PER_SEC / port->current_format.info.raw.rate); - port->io->status = SPA_STATUS_NEED_DATA; - spa_node_call_ready(&this->callbacks, SPA_STATUS_NEED_DATA); - return; + prev_time = this->current_time; + now_time = this->current_time = this->next_time; + + spa_log_debug(this->log, "%p: timer %"PRIu64" %"PRIu64"", this, + now_time, now_time - prev_time); + + if (SPA_LIKELY(this->position)) { + duration = this->position->clock.duration; + rate = this->position->clock.rate.denom; + } else { + duration = 1024; + rate = 48000; + } + + this->next_time = now_time + duration * SPA_NSEC_PER_SEC / rate; + + if (SPA_LIKELY(this->clock)) { + this->clock->nsec = now_time; + this->clock->position += duration; + this->clock->duration = duration; + this->clock->rate_diff = 1.0f; + this->clock->next_nsec = this->next_time; + this->clock->delay = 0; } - /* Flush data */ - flush_data(this); + spa_log_trace(this->log, "%p: %d", this, io->status); + io->status = SPA_STATUS_NEED_DATA; + spa_node_call_ready(&this->callbacks, SPA_STATUS_NEED_DATA); + + set_timeout(this, this->next_time); } /* greater common divider */ static int gcd(int a, int b) { while(b) { - int c = b; - b = a % b; - a = c; + int c = b; + b = a % b; + a = c; } return a; } @@ -616,6 +689,10 @@ static int do_start(struct impl *this) this->buffer_size = lcm(24, lcm(60, lcm(this->transport->write_mtu, 2 * MSBC_ENCODED_SIZE))); this->buffer = calloc(this->buffer_size, sizeof(uint8_t)); this->buffer_head = this->buffer_next = this->buffer; + if (this->buffer == NULL) { + res = -errno; + goto fail; + } } spa_return_val_if_fail(this->transport->write_mtu <= sizeof(this->port.write_buffer), -EINVAL); @@ -632,7 +709,15 @@ static int do_start(struct impl *this) 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; + 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); + /* start processing */ + this->flush_pending = false; set_timers(this); /* Set the started flag */ @@ -675,13 +760,20 @@ static int do_remove_source(struct spa_loop *loop, void *user_data) { struct impl *this = user_data; + struct itimerspec ts; - this->start_time = 0; - this->total_samples = 0; set_timeout(this, 0); if (this->source.loop) spa_loop_remove_source(this->data_loop, &this->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); + /* Drop buffered data in the ready queue. Ideally there shouldn't be any. */ drop_port_output(this); @@ -758,7 +850,7 @@ static void emit_node_info(struct impl *this, bool full) { SPA_KEY_DEVICE_API, "bluez5" }, { SPA_KEY_MEDIA_CLASS, "Stream/Input/Audio" }, { "media.name", ((this->transport && this->transport->device->name) ? - this->transport->device->name : "HSP/HFP") }, + this->transport->device->name : "HSP/HFP") }, { SPA_KEY_MEDIA_ROLE, "Communication" }, }; bool is_ag = this->transport && @@ -917,9 +1009,9 @@ impl_node_port_enum_params(void *object, int seq, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), - SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( - this->props.max_latency * port->frame_size, - this->props.min_latency * port->frame_size, + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( + this->quantum_limit * port->frame_size, + 16 * port->frame_size, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->frame_size)); break; @@ -1014,6 +1106,11 @@ static int port_set_format(struct impl *this, struct port *port, if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; + if (info.info.raw.format != SPA_AUDIO_FORMAT_S16_LE || + info.info.raw.rate == 0 || + info.info.raw.channels != 1) + return -EINVAL; + port->frame_size = info.info.raw.channels * 2; port->current_format = info; port->have_format = true; @@ -1150,6 +1247,11 @@ static int impl_node_process(void *object) if ((io = port->io) == NULL) return -EIO; + if (this->position && this->position->clock.flags & SPA_IO_CLOCK_FLAG_FREEWHEEL) { + io->status = SPA_STATUS_NEED_DATA; + return SPA_STATUS_HAVE_DATA; + } + if (io->status == SPA_STATUS_HAVE_DATA && io->buffer_id < port->n_buffers) { struct buffer *b = &port->buffers[io->buffer_id]; @@ -1167,8 +1269,22 @@ static int impl_node_process(void *object) io->status = SPA_STATUS_OK; } - if (!spa_list_is_empty(&port->ready)) + if (this->following) { + if (this->position) { + this->current_time = this->position->clock.nsec; + } else { + struct timespec now; + spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); + this->current_time = SPA_TIMESPEC_TO_NSEC(&now); + } + } + + this->process_time = this->current_time; + + if (!spa_list_is_empty(&port->ready)) { + spa_log_trace(this->log, "%p: flush on process", this); flush_data(this); + } return SPA_STATUS_HAVE_DATA; } @@ -1236,9 +1352,12 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void static int impl_clear(struct spa_handle *handle) { struct impl *this = (struct impl *) handle; + + do_stop(this); if (this->transport) spa_hook_remove(&this->transport_listener); spa_system_close(this->data_system, this->timerfd); + spa_system_close(this->data_system, this->flush_timerfd); return 0; } @@ -1323,6 +1442,11 @@ impl_init(const struct spa_handle_factory *factory, spa_list_init(&port->ready); + this->quantum_limit = 8192; + + if (info && (str = spa_dict_lookup(info, "clock.quantum-limit"))) + spa_atou32(str, &this->quantum_limit, 0); + if (info && (str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_TRANSPORT))) sscanf(str, "pointer:%p", &this->transport); @@ -1336,6 +1460,9 @@ impl_init(const struct spa_handle_factory *factory, this->timerfd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); + this->flush_timerfd = spa_system_timerfd_create(this->data_system, + CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); + return 0; } diff --git a/spa/plugins/bluez5/sco-source.c b/spa/plugins/bluez5/sco-source.c index 52a1d27cce5eac79b64dd67214a93b52b0424033..17855065f0f4634ecfebc6a58a49f672ec3038b8 100644 --- a/spa/plugins/bluez5/sco-source.c +++ b/spa/plugins/bluez5/sco-source.c @@ -248,6 +248,7 @@ static int do_reassign_follower(struct spa_loop *loop, struct impl *this = user_data; struct port *port = &this->port; + set_timers(this); spa_bt_decode_buffer_recover(&port->buffer); return 0; } @@ -590,13 +591,14 @@ static int setup_matching(struct impl *this) return 0; } +static int produce_buffer(struct impl *this); + static void sco_on_timeout(struct spa_source *source) { struct impl *this = source->data; struct port *port = &this->port; uint64_t exp, duration; uint32_t rate; - struct spa_io_buffers *io = port->io; uint64_t prev_time, now_time; if (this->transport == NULL) @@ -631,8 +633,11 @@ static void sco_on_timeout(struct spa_source *source) this->clock->next_nsec = this->next_time; } - spa_log_trace(this->log, "%p: %d", this, io->status); - io->status = SPA_STATUS_HAVE_DATA; + if (port->io) { + int status = produce_buffer(this); + spa_log_trace(this->log, "%p: io:%d status:%d", this, port->io->status, status); + } + spa_node_call_ready(&this->callbacks, SPA_STATUS_HAVE_DATA); set_timeout(this, this->next_time); @@ -1068,6 +1073,11 @@ static int port_set_format(struct impl *this, struct port *port, if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; + if (info.info.raw.format != SPA_AUDIO_FORMAT_S16_LE || + info.info.raw.rate == 0 || + info.info.raw.channels != 1) + return -EINVAL; + port->frame_size = info.info.raw.channels * 2; port->current_format = info; port->have_format = true; @@ -1285,17 +1295,13 @@ static void process_buffering(struct impl *this) } } -static int impl_node_process(void *object) +static int produce_buffer(struct impl *this) { - struct impl *this = object; - struct port *port; - struct spa_io_buffers *io; struct buffer *buffer; + struct port *port = &this->port; + struct spa_io_buffers *io = port->io; - spa_return_val_if_fail(this != NULL, -EINVAL); - - port = &this->port; - if ((io = port->io) == NULL) + if (io == NULL) return -EIO; /* Return if we already have a buffer */ @@ -1308,7 +1314,7 @@ static int impl_node_process(void *object) io->buffer_id = SPA_ID_INVALID; } - /* Produce data */ + /* Handle buffering */ process_buffering(this); /* Return if there are no buffers ready to be processed */ @@ -1328,6 +1334,35 @@ static int impl_node_process(void *object) return SPA_STATUS_HAVE_DATA; } +static int impl_node_process(void *object) +{ + struct impl *this = object; + struct port *port; + struct spa_io_buffers *io; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + port = &this->port; + if ((io = port->io) == NULL) + return -EIO; + + /* Return if we already have a buffer */ + if (io->status == SPA_STATUS_HAVE_DATA) + return SPA_STATUS_HAVE_DATA; + + /* Recycle */ + if (io->buffer_id < port->n_buffers) { + recycle_buffer(this, port, io->buffer_id); + io->buffer_id = SPA_ID_INVALID; + } + + /* Follower produces buffers here, driver in timeout */ + if (this->following) + return produce_buffer(this); + else + return SPA_STATUS_OK; +} + static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, @@ -1391,6 +1426,8 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void static int impl_clear(struct spa_handle *handle) { struct impl *this = (struct impl *) handle; + + do_stop(this); if (this->transport) spa_hook_remove(&this->transport_listener); spa_system_close(this->data_system, this->timerfd); diff --git a/spa/plugins/bluez5/upower.c b/spa/plugins/bluez5/upower.c new file mode 100644 index 0000000000000000000000000000000000000000..23a637a22fe47e134959d2b9f936ad6dbd071932 --- /dev/null +++ b/spa/plugins/bluez5/upower.c @@ -0,0 +1,311 @@ +/* Spa Bluez5 UPower proxy + * + * Copyright © 2022 Collabora + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <errno.h> +#include <spa/utils/string.h> + +#include "upower.h" + +#define UPOWER_SERVICE "org.freedesktop.UPower" +#define UPOWER_DEVICE_INTERFACE UPOWER_SERVICE ".Device" +#define UPOWER_DISPLAY_DEVICE_OBJECT "/org/freedesktop/UPower/devices/DisplayDevice" + +struct impl { + struct spa_bt_monitor *monitor; + + struct spa_log *log; + DBusConnection *conn; + + bool filters_added; + + void *user_data; + void (*set_battery_level)(unsigned int level, void *user_data); +}; + +static DBusHandlerResult upower_parse_percentage(struct impl *this, DBusMessageIter *variant_i) +{ + double percentage; + unsigned int battery_level; + + dbus_message_iter_get_basic(variant_i, &percentage); + spa_log_debug(this->log, "Battery level: %f %%", percentage); + + battery_level = (unsigned int) round(percentage / 20.0); + this->set_battery_level(battery_level, this->user_data); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static void upower_get_percentage_properties_reply(DBusPendingCall *pending, void *user_data) +{ + struct impl *backend = user_data; + DBusMessage *r; + DBusMessageIter i, variant_i; + + r = dbus_pending_call_steal_reply(pending); + if (r == NULL) + return; + + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { + spa_log_error(backend->log, "Failed to get percentage from UPower: %s", + dbus_message_get_error_name(r)); + goto finish; + } + + if (!dbus_message_iter_init(r, &i) || !spa_streq(dbus_message_get_signature(r), "v")) { + spa_log_error(backend->log, "Invalid arguments in Get() reply"); + goto finish; + } + + dbus_message_iter_recurse(&i, &variant_i); + upower_parse_percentage(backend, &variant_i); + +finish: + dbus_message_unref(r); +} + +static void upower_clean(struct impl *this) +{ + this->set_battery_level(0, this->user_data); +} + +static DBusHandlerResult upower_filter_cb(DBusConnection *bus, DBusMessage *m, void *user_data) +{ + struct impl *this = user_data; + DBusError err; + + dbus_error_init(&err); + + if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) { + const char *name, *old_owner, *new_owner; + + spa_log_debug(this->log, "Name owner changed %s", dbus_message_get_path(m)); + + if (!dbus_message_get_args(m, &err, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old_owner, + DBUS_TYPE_STRING, &new_owner, + DBUS_TYPE_INVALID)) { + spa_log_error(this->log, "Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message); + goto finish; + } + + if (spa_streq(name, UPOWER_SERVICE)) { + if (old_owner && *old_owner) { + spa_log_debug(this->log, "UPower daemon disappeared (%s)", old_owner); + upower_clean(this); + } + + if (new_owner && *new_owner) { + DBusPendingCall *call; + static const char* upower_device_interface = UPOWER_DEVICE_INTERFACE; + static const char* percentage_property = "Percentage"; + + spa_log_debug(this->log, "UPower daemon appeared (%s)", new_owner); + + m = dbus_message_new_method_call(UPOWER_SERVICE, UPOWER_DISPLAY_DEVICE_OBJECT, DBUS_INTERFACE_PROPERTIES, "Get"); + if (m == NULL) + goto finish; + dbus_message_append_args(m, DBUS_TYPE_STRING, &upower_device_interface, + DBUS_TYPE_STRING, &percentage_property, DBUS_TYPE_INVALID); + dbus_connection_send_with_reply(this->conn, m, &call, -1); + dbus_pending_call_set_notify(call, upower_get_percentage_properties_reply, this, NULL); + dbus_message_unref(m); + } + } + } else if (dbus_message_is_signal(m, DBUS_INTERFACE_PROPERTIES, DBUS_SIGNAL_PROPERTIES_CHANGED)) { + const char *path; + DBusMessageIter iface_i, props_i; + const char *interface; + + if (!dbus_message_iter_init(m, &iface_i) || !spa_streq(dbus_message_get_signature(m), "sa{sv}as")) { + spa_log_error(this->log, "Invalid signature found in PropertiesChanged"); + goto finish; + } + + dbus_message_iter_get_basic(&iface_i, &interface); + dbus_message_iter_next(&iface_i); + spa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_ARRAY); + + dbus_message_iter_recurse(&iface_i, &props_i); + + path = dbus_message_get_path(m); + + if (spa_streq(interface, UPOWER_DEVICE_INTERFACE)) { + spa_log_debug(this->log, "Properties changed on %s", path); + + while (dbus_message_iter_get_arg_type(&props_i) != DBUS_TYPE_INVALID) { + DBusMessageIter i, value_i; + const char *key; + + dbus_message_iter_recurse(&props_i, &i); + + dbus_message_iter_get_basic(&i, &key); + dbus_message_iter_next(&i); + dbus_message_iter_recurse(&i, &value_i); + + if(spa_streq(key, "Percentage")) + upower_parse_percentage(this, &value_i); + + dbus_message_iter_next(&props_i); + } + } + } + +finish: + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static int add_filters(struct impl *this) +{ + DBusError err; + + if (this->filters_added) + return 0; + + dbus_error_init(&err); + + if (!dbus_connection_add_filter(this->conn, upower_filter_cb, this, NULL)) { + spa_log_error(this->log, "failed to add filter function"); + goto fail; + } + + dbus_bus_add_match(this->conn, + "type='signal',sender='org.freedesktop.DBus'," + "interface='org.freedesktop.DBus',member='NameOwnerChanged'," "arg0='" UPOWER_SERVICE "'", &err); + dbus_bus_add_match(this->conn, + "type='signal',sender='" UPOWER_SERVICE "'," + "interface='" DBUS_INTERFACE_PROPERTIES "',member='" DBUS_SIGNAL_PROPERTIES_CHANGED "'," + "path='" UPOWER_DISPLAY_DEVICE_OBJECT "',arg0='" UPOWER_DEVICE_INTERFACE "'", &err); + + this->filters_added = true; + + return 0; + +fail: + dbus_error_free(&err); + return -EIO; +} + +static bool is_dbus_service_available(struct impl *this, const char *service) +{ + DBusMessage *m, *r; + DBusError err; + bool success = false; + + m = dbus_message_new_method_call("org.freedesktop.DBus", "/org/freedesktop/DBus", + "org.freedesktop.DBus", "NameHasOwner"); + if (m == NULL) + return false; + dbus_message_append_args(m, DBUS_TYPE_STRING, &service, DBUS_TYPE_INVALID); + + dbus_error_init(&err); + r = dbus_connection_send_with_reply_and_block(this->conn, m, -1, &err); + dbus_message_unref(m); + m = NULL; + + if (r == NULL) { + spa_log_info(this->log, "NameHasOwner failed for %s", service); + dbus_error_free(&err); + goto finish; + } + + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { + spa_log_error(this->log, "NameHasOwner() returned error: %s", dbus_message_get_error_name(r)); + goto finish; + } + + if (!dbus_message_get_args(r, &err, + DBUS_TYPE_BOOLEAN, &success, + DBUS_TYPE_INVALID)) { + spa_log_error(this->log, "Failed to parse NameHasOwner() reply: %s", err.message); + dbus_error_free(&err); + goto finish; + } + +finish: + if (r) + dbus_message_unref(r); + + return success; +} + +void *upower_register(struct spa_log *log, + void *dbus_connection, + void (*set_battery_level)(unsigned int level, void *user_data), + void *user_data) +{ + struct impl *this; + + spa_assert(log); + spa_assert(dbus_connection); + spa_assert(set_battery_level); + spa_assert(user_data); + + this = calloc(1, sizeof(struct impl)); + if (this == NULL) + return NULL; + + this->log = log; + this->conn = dbus_connection; + this->set_battery_level = set_battery_level; + this->user_data = user_data; + + if (add_filters(this) < 0) { + goto fail4; + } + + if (is_dbus_service_available(this, UPOWER_SERVICE)) { + DBusMessage *m; + DBusPendingCall *call; + static const char* upower_device_interface = UPOWER_DEVICE_INTERFACE; + static const char* percentage_property = "Percentage"; + + m = dbus_message_new_method_call(UPOWER_SERVICE, UPOWER_DISPLAY_DEVICE_OBJECT, DBUS_INTERFACE_PROPERTIES, "Get"); + if (m == NULL) + goto fail4; + dbus_message_append_args(m, DBUS_TYPE_STRING, &upower_device_interface, + DBUS_TYPE_STRING, &percentage_property, DBUS_TYPE_INVALID); + dbus_connection_send_with_reply(this->conn, m, &call, -1); + dbus_pending_call_set_notify(call, upower_get_percentage_properties_reply, this, NULL); + dbus_message_unref(m); + } + + return this; + +fail4: + free(this); + return NULL; +} + +void upower_unregister(void *data) +{ + struct impl *this = data; + + if (this->filters_added) { + dbus_connection_remove_filter(this->conn, upower_filter_cb, this); + this->filters_added = false; + } + free(this); +} diff --git a/spa/plugins/audioconvert/resample-peaks-c.c b/spa/plugins/bluez5/upower.h similarity index 74% rename from spa/plugins/audioconvert/resample-peaks-c.c rename to spa/plugins/bluez5/upower.h index 161e06fe00eadb9d7cc214d8dcf20a55584b41af..9ebd7510d0cfb046240443328e2e212674ec05a7 100644 --- a/spa/plugins/audioconvert/resample-peaks-c.c +++ b/spa/plugins/bluez5/upower.h @@ -1,6 +1,6 @@ -/* Spa +/* Spa Bluez5 UPower proxy * - * Copyright © 2018 Wim Taymans + * Copyright © 2022 Collabora * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -22,16 +22,15 @@ * DEALINGS IN THE SOFTWARE. */ -#include <math.h> +#ifndef SPA_BLUEZ5_UPOWER_H_ +#define SPA_BLUEZ5_UPOWER_H_ -#include "resample-peaks-impl.h" +#include "defs.h" -static inline float find_abs_max_c(const float *s, uint32_t n_samples, float m) -{ - uint32_t n; - for (n = 0; n < n_samples; n++) - m = fmaxf(fabsf(s[n]), m); - return m; -} +void *upower_register(struct spa_log *log, + void *dbus_connection, + void (*set_battery_level)(unsigned int level, void *user_data), + void *user_data); +void upower_unregister(void *data); -MAKE_PEAKS(c); +#endif \ No newline at end of file diff --git a/spa/plugins/libcamera/libcamera-client.c b/spa/plugins/libcamera/libcamera-client.c index 24170e7b67dd6a70e903d4bffad04a0272348dfd..a31a41fd9a8836a9fef4c73eb4bfaa07168a9d80 100644 --- a/spa/plugins/libcamera/libcamera-client.c +++ b/spa/plugins/libcamera/libcamera-client.c @@ -77,8 +77,8 @@ static int emit_object_info(struct impl *this, uint32_t id) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "libcamera"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Video/Device"); - info.props = &SPA_DICT_INIT(items, n_items); - spa_device_emit_object_info(&this->hooks, id, &info); + info.props = &SPA_DICT_INIT(items, n_items); + spa_device_emit_object_info(&this->hooks, id, &info); return 1; } @@ -112,18 +112,18 @@ impl_device_add_listener(void *object, struct spa_hook *listener, const struct spa_device_events *events, void *data) { struct impl *this = object; - struct spa_hook_list save; + struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(events != NULL, -EINVAL); - spa_hook_list_isolate(&this->hooks, &save, listener, events, data); + spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_device_info(this, true); emit_object_info(this, 0); - spa_hook_list_join(&this->hooks, &save); + spa_hook_list_join(&this->hooks, &save); listener->removed = impl_hook_removed; listener->priv = this; diff --git a/spa/plugins/libcamera/libcamera-device.cpp b/spa/plugins/libcamera/libcamera-device.cpp index f2f10c01785fb4b0267c2a4065392d218dae86a5..0edd1a33cb089480542709a4a1fb0ba258d50ba0 100644 --- a/spa/plugins/libcamera/libcamera-device.cpp +++ b/spa/plugins/libcamera/libcamera-device.cpp @@ -54,30 +54,29 @@ using namespace libcamera; -struct props { - char device[128]; - char device_name[128]; -}; - -static void reset_props(struct props *props) -{ - spa_zero(*props); -} +namespace { struct impl { struct spa_handle handle; - struct spa_device device; + struct spa_device device = {}; struct spa_log *log; - struct props props; + std::string device_id; struct spa_hook_list hooks; - CameraManager *manager; + std::shared_ptr<CameraManager> manager; std::shared_ptr<Camera> camera; + + impl(spa_log *log, + std::shared_ptr<CameraManager> manager, + std::shared_ptr<Camera> camera, + std::string device_id); }; +} + static std::string cameraModel(const Camera *camera) { const ControlList &props = camera->properties(); @@ -120,11 +119,11 @@ static int emit_info(struct impl *impl, bool full) info.change_mask = SPA_DEVICE_CHANGE_MASK_PROPS; #define ADD_ITEM(key, value) items[n_items++] = SPA_DICT_ITEM_INIT(key, value) - snprintf(path, sizeof(path), "libcamera:%s", impl->props.device); + snprintf(path, sizeof(path), "libcamera:%s", impl->device_id.c_str()); ADD_ITEM(SPA_KEY_OBJECT_PATH, path); ADD_ITEM(SPA_KEY_DEVICE_API, "libcamera"); ADD_ITEM(SPA_KEY_MEDIA_CLASS, "Video/Device"); - ADD_ITEM(SPA_KEY_API_LIBCAMERA_PATH, impl->props.device); + ADD_ITEM(SPA_KEY_API_LIBCAMERA_PATH, impl->device_id.c_str()); if (auto location = cameraLoc(impl->camera.get())) ADD_ITEM(SPA_KEY_API_LIBCAMERA_LOCATION, location); @@ -132,7 +131,7 @@ static int emit_info(struct impl *impl, bool full) snprintf(model, sizeof(model), "%s", cameraModel(impl->camera.get()).c_str()); ADD_ITEM(SPA_KEY_DEVICE_PRODUCT_NAME, model); ADD_ITEM(SPA_KEY_DEVICE_DESCRIPTION, model); - snprintf(name, sizeof(name), "libcamera_device.%s", impl->props.device); + snprintf(name, sizeof(name), "libcamera_device.%s", impl->device_id.c_str()); ADD_ITEM(SPA_KEY_DEVICE_NAME, name); #undef ADD_ITEM @@ -235,13 +234,30 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void static int impl_clear(struct spa_handle *handle) { - struct impl *impl = (struct impl *) handle; - if (impl->manager) - libcamera_manager_release(impl->manager); - impl->manager = NULL; + std::destroy_at(reinterpret_cast<impl *>(handle)); return 0; } +impl::impl(spa_log *log, + std::shared_ptr<CameraManager> manager, + std::shared_ptr<Camera> camera, + std::string device_id) + : handle({ SPA_VERSION_HANDLE, impl_get_interface, impl_clear }), + log(log), + device_id(std::move(device_id)), + manager(std::move(manager)), + camera(std::move(camera)) +{ + libcamera_log_topic_init(log); + + spa_hook_list_init(&hooks); + + device.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Device, + SPA_VERSION_DEVICE, + &impl_device, this); +} + static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) @@ -256,44 +272,32 @@ impl_init(const struct spa_handle_factory *factory, const struct spa_support *support, uint32_t n_support) { - struct impl *impl; const char *str; int res; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); - handle->get_interface = impl_get_interface; - handle->clear = impl_clear, impl = (struct impl *) handle; - - impl->log = (struct spa_log*) spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); - libcamera_log_topic_init(impl->log); - - spa_hook_list_init(&impl->hooks); - - impl->device.iface = SPA_INTERFACE_INIT( - SPA_TYPE_INTERFACE_Device, - SPA_VERSION_DEVICE, - &impl_device, impl); + auto log = static_cast<spa_log *>(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log)); - reset_props(&impl->props); + auto manager = libcamera_manager_acquire(res); + if (!manager) { + spa_log_error(log, "can't start camera manager: %s", spa_strerror(res)); + return res; + } + std::string device_id; if (info && (str = spa_dict_lookup(info, SPA_KEY_API_LIBCAMERA_PATH))) - strncpy(impl->props.device, str, sizeof(impl->props.device)); - - impl->manager = libcamera_manager_acquire(); - if (impl->manager == NULL) { - res = -errno; - spa_log_error(impl->log, "can't start camera manager: %s", spa_strerror(res)); - return res; - } + device_id = str; - impl->camera = impl->manager->get(impl->props.device); - if (impl->camera == NULL) { - spa_log_error(impl->log, "unknown camera id %s", impl->props.device); - libcamera_manager_release(impl->manager); + auto camera = manager->get(device_id); + if (!camera) { + spa_log_error(log, "unknown camera id %s", device_id.c_str()); return -ENOENT; } + + new (handle) impl(log, std::move(manager), std::move(camera), std::move(device_id)); + return 0; } diff --git a/spa/plugins/libcamera/libcamera-manager.cpp b/spa/plugins/libcamera/libcamera-manager.cpp index b9243ad86982711eaee8f90067ed841757f0e63d..afba4032a8e6d9288e6e76c0292a9b9a1d9d2a18 100644 --- a/spa/plugins/libcamera/libcamera-manager.cpp +++ b/spa/plugins/libcamera/libcamera-manager.cpp @@ -29,7 +29,10 @@ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> - +#include <utility> +#include <mutex> +#include <optional> +#include <queue> #include <libcamera/camera.h> #include <libcamera/camera_manager.h> @@ -54,67 +57,72 @@ using namespace libcamera; #define MAX_DEVICES 64 -struct global { - int ref; - CameraManager *manager; -}; - -static struct global global; +namespace { struct device { uint32_t id; std::shared_ptr<Camera> camera; }; -typedef struct impl { +struct impl { struct spa_handle handle; - struct spa_device device; + struct spa_device device = {}; struct spa_log *log; - struct spa_loop *main_loop; + struct spa_loop_utils *loop_utils; struct spa_hook_list hooks; - uint64_t info_all; - struct spa_device_info info; + static constexpr uint64_t info_all = SPA_DEVICE_CHANGE_MASK_FLAGS | SPA_DEVICE_CHANGE_MASK_PROPS; + struct spa_device_info info = SPA_DEVICE_INFO_INIT(); - CameraManager *manager; + std::shared_ptr<CameraManager> manager; void addCamera(std::shared_ptr<libcamera::Camera> camera); - void removeCamera(std::shared_ptr<libcamera::Camera> camera); + void removeCamera(std::shared_ptr<libcamera::Camera> camera); struct device devices[MAX_DEVICES]; - uint32_t n_devices; -} Impl; + uint32_t n_devices = 0; -int libcamera_manager_release(CameraManager *manager) -{ - if (global.manager != manager) - return -EINVAL; + struct hotplug_event { + enum class type { add, remove } type; + std::shared_ptr<Camera> camera; + }; - if (--global.ref == 0) { - global.manager->stop(); - delete global.manager; - global.manager = NULL; + std::mutex hotplug_events_lock; + std::queue<hotplug_event> hotplug_events; + struct spa_source *hotplug_event_source; + + impl(spa_log *log, spa_loop_utils *loop_utils, spa_source *hotplug_event_source); + + ~impl() + { + spa_loop_utils_destroy_source(loop_utils, hotplug_event_source); } - return 0; +}; + } -CameraManager *libcamera_manager_acquire(void) +static std::weak_ptr<CameraManager> global_manager; + +std::shared_ptr<CameraManager> libcamera_manager_acquire(int& res) { - int res; + if (auto manager = global_manager.lock()) + return manager; - if (global.ref++ == 0) { - global.manager = new CameraManager(); - if (global.manager == NULL) - return NULL; + auto manager = std::make_shared<CameraManager>(); + if ((res = manager->start()) < 0) + return {}; - if ((res = global.manager->start()) < 0) { - libcamera_manager_release(global.manager); - errno = -res; - return NULL; - } - } - return global.manager; + global_manager = manager; + + return manager; +} +static uint32_t get_free_id(struct impl *impl) +{ + for (std::size_t i = 0; i < MAX_DEVICES; i++) + if (impl->devices[i].camera == nullptr) + return i; + return 0; } static struct device *add_device(struct impl *impl, std::shared_ptr<Camera> camera) @@ -124,18 +132,19 @@ static struct device *add_device(struct impl *impl, std::shared_ptr<Camera> came if (impl->n_devices >= MAX_DEVICES) return NULL; - id = impl->n_devices++; + id = get_free_id(impl);; device = &impl->devices[id]; - device->id = id; - device->camera = camera; + device->id = get_free_id(impl);; + device->camera = std::move(camera); + impl->n_devices++; return device; } -static struct device *find_device(struct impl *impl, std::shared_ptr<Camera> camera) +static struct device *find_device(struct impl *impl, const Camera *camera) { uint32_t i; for (i = 0; i < impl->n_devices; i++) { - if (impl->devices[i].camera == camera) + if (impl->devices[i].camera.get() == camera) return &impl->devices[i]; } return NULL; @@ -143,12 +152,16 @@ static struct device *find_device(struct impl *impl, std::shared_ptr<Camera> cam static void remove_device(struct impl *impl, struct device *device) { - *device = impl->devices[--impl->n_devices]; + uint32_t old = --impl->n_devices; + device->camera.reset(); + *device = std::move(impl->devices[old]); + impl->devices[old].camera = nullptr; } static void clear_devices(struct impl *impl) { - impl->n_devices = 0; + while (impl->n_devices > 0) + impl->devices[--impl->n_devices].camera.reset(); } static int emit_object_info(struct impl *impl, struct device *device) @@ -176,66 +189,122 @@ static int emit_object_info(struct impl *impl, struct device *device) ADD_ITEM(SPA_KEY_API_LIBCAMERA_PATH, path); #undef ADD_ITEM - dict = SPA_DICT_INIT(items, n_items); - info.props = &dict; - spa_device_emit_object_info(&impl->hooks, id, &info); + dict = SPA_DICT_INIT(items, n_items); + info.props = &dict; + spa_device_emit_object_info(&impl->hooks, id, &info); return 1; } -void Impl::addCamera(std::shared_ptr<Camera> camera) +static void try_add_camera(struct impl *impl, std::shared_ptr<Camera> camera) { - struct impl *impl = this; struct device *device; - spa_log_info(impl->log, "new camera"); - - if ((device = find_device(impl, camera)) != NULL) + if ((device = find_device(impl, camera.get())) != NULL) return; - if ((device = add_device(impl, camera)) == NULL) + if ((device = add_device(impl, std::move(camera))) == NULL) return; + spa_log_info(impl->log, "camera added: id:%d %s", device->id, + device->camera->id().c_str()); emit_object_info(impl, device); } -void Impl::removeCamera(std::shared_ptr<Camera> camera) +static void try_remove_camera(struct impl *impl, const Camera *camera) { - struct impl *impl = this; struct device *device; - spa_log_info(impl->log, "camera removed"); if ((device = find_device(impl, camera)) == NULL) return; + spa_log_info(impl->log, "camera removed: id:%d %s", device->id, + device->camera->id().c_str()); + spa_device_emit_object_info(&impl->hooks, device->id, NULL); remove_device(impl, device); } -static int start_monitor(struct impl *impl) +static void consume_hotplug_event(struct impl *impl, impl::hotplug_event& event) { - impl->manager->cameraAdded.connect(impl, &Impl::addCamera); - impl->manager->cameraRemoved.connect(impl, &Impl::removeCamera); - return 0; + auto& [ type, camera ] = event; + + switch (type) { + case impl::hotplug_event::type::add: + spa_log_info(impl->log, "camera appeared: %s", camera->id().c_str()); + try_add_camera(impl, std::move(camera)); + break; + case impl::hotplug_event::type::remove: + spa_log_info(impl->log, "camera disappeared: %s", camera->id().c_str()); + try_remove_camera(impl, camera.get()); + break; + } +} + +static void on_hotplug_event(void *data, std::uint64_t) +{ + auto impl = static_cast<struct impl *>(data); + + for (;;) { + std::optional<impl::hotplug_event> event; + + { + std::unique_lock guard(impl->hotplug_events_lock); + + if (!impl->hotplug_events.empty()) { + event = std::move(impl->hotplug_events.front()); + impl->hotplug_events.pop(); + } + } + + if (!event) + break; + + consume_hotplug_event(impl, *event); + } +} + +void impl::addCamera(std::shared_ptr<Camera> camera) +{ + { + std::unique_lock guard(hotplug_events_lock); + hotplug_events.push({ hotplug_event::type::add, std::move(camera) }); + } + + spa_loop_utils_signal_event(loop_utils, hotplug_event_source); +} + +void impl::removeCamera(std::shared_ptr<Camera> camera) +{ + { + std::unique_lock guard(hotplug_events_lock); + hotplug_events.push({ hotplug_event::type::remove, std::move(camera) }); + } + + spa_loop_utils_signal_event(loop_utils, hotplug_event_source); +} + +static void start_monitor(struct impl *impl) +{ + impl->manager->cameraAdded.connect(impl, &impl::addCamera); + impl->manager->cameraRemoved.connect(impl, &impl::removeCamera); } static int stop_monitor(struct impl *impl) { - if (impl->manager != NULL) { - impl->manager->cameraAdded.disconnect(impl, &Impl::addCamera); - impl->manager->cameraRemoved.disconnect(impl, &Impl::removeCamera); + if (impl->manager) { + impl->manager->cameraAdded.disconnect(impl, &impl::addCamera); + impl->manager->cameraRemoved.disconnect(impl, &impl::removeCamera); } clear_devices (impl); return 0; } -static int enum_devices(struct impl *impl) +static void collect_existing_devices(struct impl *impl) { auto cameras = impl->manager->cameras(); - for (const std::shared_ptr<Camera> &cam : cameras) { - impl->addCamera(cam); - } - return 0; + for (std::shared_ptr<Camera>& camera : cameras) + try_add_camera(impl, std::move(camera)); } static const struct spa_dict_item device_info_items[] = { @@ -262,9 +331,7 @@ static void impl_hook_removed(struct spa_hook *hook) struct impl *impl = (struct impl*)hook->priv; if (spa_hook_list_is_empty(&impl->hooks)) { stop_monitor(impl); - if (impl->manager) - libcamera_manager_release(impl->manager); - impl->manager = NULL; + impl->manager.reset(); } } @@ -274,26 +341,29 @@ impl_device_add_listener(void *object, struct spa_hook *listener, { int res; struct impl *impl = (struct impl*) object; - struct spa_hook_list save; + struct spa_hook_list save; + bool had_manager = !!impl->manager; spa_return_val_if_fail(impl != NULL, -EINVAL); spa_return_val_if_fail(events != NULL, -EINVAL); - impl->manager = libcamera_manager_acquire(); - if (impl->manager == NULL) - return -errno; + if (!impl->manager && !(impl->manager = libcamera_manager_acquire(res))) + return res; - spa_hook_list_isolate(&impl->hooks, &save, listener, events, data); + spa_hook_list_isolate(&impl->hooks, &save, listener, events, data); emit_device_info(impl, true); - if ((res = enum_devices(impl)) < 0) - return res; - - if ((res = start_monitor(impl)) < 0) - return res; + if (had_manager) { + for (std::size_t i = 0; i < impl->n_devices; i++) + emit_object_info(impl, &impl->devices[i]); + } + else { + collect_existing_devices(impl); + start_monitor(impl); + } - spa_hook_list_join(&impl->hooks, &save); + spa_hook_list_join(&impl->hooks, &save); listener->removed = impl_hook_removed; listener->priv = impl; @@ -325,14 +395,30 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void static int impl_clear(struct spa_handle *handle) { - struct impl *impl = (struct impl *) handle; + auto impl = reinterpret_cast<struct impl *>(handle); + stop_monitor(impl); - if (impl->manager) - libcamera_manager_release(impl->manager); - impl->manager = NULL; + std::destroy_at(impl); + return 0; } +impl::impl(spa_log *log, spa_loop_utils *loop_utils, spa_source *hotplug_event_source) + : handle({ SPA_VERSION_HANDLE, impl_get_interface, impl_clear }), + log(log), + loop_utils(loop_utils), + hotplug_event_source(hotplug_event_source) +{ + libcamera_log_topic_init(log); + + spa_hook_list_init(&hooks); + + device.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Device, + SPA_VERSION_DEVICE, + &impl_device, this); +} + static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) @@ -347,35 +433,25 @@ impl_init(const struct spa_handle_factory *factory, const struct spa_support *support, uint32_t n_support) { - struct impl *impl; - spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); - handle->get_interface = impl_get_interface; - handle->clear = impl_clear; - - impl = (struct impl *) handle; - - impl->log = (struct spa_log*)spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); - libcamera_log_topic_init(impl->log); + auto log = static_cast<spa_log *>(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log)); - impl->main_loop = (struct spa_loop*)spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); - if (impl->main_loop == NULL) { - spa_log_error(impl->log, "a main-loop is needed"); + auto loop_utils = static_cast<spa_loop_utils *>(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_LoopUtils)); + if (!loop_utils) { + spa_log_error(log, "a " SPA_TYPE_INTERFACE_LoopUtils " is needed"); return -EINVAL; } - spa_hook_list_init(&impl->hooks); - impl->device.iface = SPA_INTERFACE_INIT( - SPA_TYPE_INTERFACE_Device, - SPA_VERSION_DEVICE, - &impl_device, impl); + auto hotplug_event_source = spa_loop_utils_add_event(loop_utils, on_hotplug_event, handle); + if (!hotplug_event_source) { + int res = -errno; + spa_log_error(log, "failed to create hotplug event: %m"); + return res; + } - impl->info = SPA_DEVICE_INFO_INIT(); - impl->info_all = SPA_DEVICE_CHANGE_MASK_FLAGS | - SPA_DEVICE_CHANGE_MASK_PROPS; - impl->info.flags = 0; + new (handle) impl(log, loop_utils, hotplug_event_source); return 0; } diff --git a/spa/plugins/libcamera/libcamera-manager.hpp b/spa/plugins/libcamera/libcamera-manager.hpp index 53e488bc52f7386641bcc10780198cd576928cc0..4336a392ec2ae6b55cb8bc6d8739babc4e385a31 100644 --- a/spa/plugins/libcamera/libcamera-manager.hpp +++ b/spa/plugins/libcamera/libcamera-manager.hpp @@ -22,11 +22,8 @@ * DEALINGS IN THE SOFTWARE. */ -#include <libcamera/camera_manager.h> - -#include <linux/media.h> +#include <memory> -using namespace libcamera; +#include <libcamera/camera_manager.h> -CameraManager *libcamera_manager_acquire(void); -int libcamera_manager_release(CameraManager *manager); +std::shared_ptr<libcamera::CameraManager> libcamera_manager_acquire(int& res); diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 0d32b745b72c399c423169e8af553847d1cfcbd7..565325369cd4b499b896a635660d20c9506e58f4 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -31,6 +31,7 @@ #include <sys/stat.h> #include <fcntl.h> #include <deque> +#include <optional> #include <spa/support/plugin.h> #include <spa/support/log.h> @@ -60,15 +61,9 @@ #include "libcamera.h" #include "libcamera-manager.hpp" -struct props { - char device[128]; - char device_name[128]; -}; +using namespace libcamera; -static void reset_props(struct props *props) -{ - spa_zero(*props); -} +namespace { #define MAX_BUFFERS 32 #define MASK_BUFFERS 31 @@ -97,75 +92,110 @@ struct control { struct port { struct impl *impl; - bool have_format; - struct spa_video_info current_format; - struct spa_fraction rate; + std::optional<spa_video_info> current_format; + + struct spa_fraction rate = {}; StreamConfiguration streamConfig; - uint32_t memtype; + uint32_t memtype = 0; struct control controls[MAX_CONTROLS]; - uint32_t n_controls; + uint32_t n_controls = 0; struct buffer buffers[MAX_BUFFERS]; - uint32_t n_buffers; + uint32_t n_buffers = 0; struct spa_list queue; - struct spa_ringbuffer ring; + struct spa_ringbuffer ring = SPA_RINGBUFFER_INIT(); uint32_t ring_ids[MAX_BUFFERS]; - uint64_t info_all; - struct spa_port_info info; - struct spa_io_buffers *io; - struct spa_io_sequence *control; - struct spa_param_info params[8]; - - uint32_t fmt_index; - bool next_fmt; + static constexpr uint64_t info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; + struct spa_port_info info = SPA_PORT_INFO_INIT(); + struct spa_io_buffers *io = nullptr; + struct spa_io_sequence *control = nullptr; +#define PORT_PropInfo 0 +#define PORT_EnumFormat 1 +#define PORT_Meta 2 +#define PORT_IO 3 +#define PORT_Format 4 +#define PORT_Buffers 5 +#define N_PORT_PARAMS 6 + struct spa_param_info params[N_PORT_PARAMS]; + + uint32_t fmt_index = 0; PixelFormat enum_fmt; - uint32_t size_index; - bool next_size; + uint32_t size_index = 0; + + port(struct impl *impl) + : impl(impl) + { + spa_list_init(&queue); + + params[PORT_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); + params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); + params[PORT_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); + params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); + params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + + info.flags = SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL; + info.params = params; + info.n_params = N_PORT_PARAMS; + } }; struct impl { struct spa_handle handle; - struct spa_node node; + struct spa_node node = {}; struct spa_log *log; struct spa_loop *data_loop; struct spa_system *system; - uint64_t info_all; - struct spa_node_info info; - struct spa_param_info params[8]; - struct props props; + static constexpr uint64_t info_all = + SPA_NODE_CHANGE_MASK_FLAGS | + SPA_NODE_CHANGE_MASK_PROPS | + SPA_NODE_CHANGE_MASK_PARAMS; + struct spa_node_info info = SPA_NODE_INFO_INIT(); +#define NODE_PropInfo 0 +#define NODE_Props 1 +#define NODE_EnumFormat 2 +#define NODE_Format 3 +#define N_NODE_PARAMS 4 + struct spa_param_info params[N_NODE_PARAMS]; + + std::string device_id; + std::string device_name; struct spa_hook_list hooks; - struct spa_callbacks callbacks; + struct spa_callbacks callbacks = {}; - struct port out_ports[1]; + std::array<port, 1> out_ports; - struct spa_io_position *position; - struct spa_io_clock *clock; + struct spa_io_position *position = nullptr; + struct spa_io_clock *clock = nullptr; - CameraManager *manager; + std::shared_ptr<CameraManager> manager; std::shared_ptr<Camera> camera; - FrameBufferAllocator *allocator; + FrameBufferAllocator *allocator = nullptr; std::vector<std::unique_ptr<libcamera::Request>> requestPool; std::deque<libcamera::Request *> pendingRequests; void requestComplete(libcamera::Request *request); - unsigned int have_config; std::unique_ptr<CameraConfiguration> config; - struct spa_source source; + struct spa_source source = {}; + + ControlList ctrls; + bool active = false; + bool acquired = false; - unsigned int active:1; - unsigned int acquired:1; + impl(spa_log *log, spa_loop *data_loop, spa_system *system, + std::shared_ptr<CameraManager> manager, std::shared_ptr<Camera> camera, std::string device_id); }; -typedef struct impl Impl; +} #define CHECK_PORT(impl,direction,port_id) ((direction) == SPA_DIRECTION_OUTPUT && (port_id) == 0) @@ -174,6 +204,55 @@ typedef struct impl Impl; #include "libcamera-utils.cpp" +static int port_get_format(struct impl *impl, struct port *port, + uint32_t index, + const struct spa_pod *filter, + struct spa_pod **param, + struct spa_pod_builder *builder) +{ + struct spa_pod_frame f; + + if (!port->current_format) + return -EIO; + if (index > 0) + return 0; + + spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, SPA_PARAM_Format); + spa_pod_builder_add(builder, + SPA_FORMAT_mediaType, SPA_POD_Id(port->current_format->media_type), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(port->current_format->media_subtype), + 0); + + switch (port->current_format->media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + spa_pod_builder_add(builder, + SPA_FORMAT_VIDEO_format, SPA_POD_Id(port->current_format->info.raw.format), + SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format->info.raw.size), + SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format->info.raw.framerate), + 0); + break; + case SPA_MEDIA_SUBTYPE_mjpg: + case SPA_MEDIA_SUBTYPE_jpeg: + spa_pod_builder_add(builder, + SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format->info.mjpg.size), + SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format->info.mjpg.framerate), + 0); + break; + case SPA_MEDIA_SUBTYPE_h264: + spa_pod_builder_add(builder, + SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format->info.h264.size), + SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format->info.h264.framerate), + 0); + break; + default: + return -EIO; + } + + *param = (struct spa_pod*)spa_pod_builder_pop(builder, &f); + + return 1; +} + static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) @@ -184,6 +263,7 @@ static int impl_node_enum_params(void *object, int seq, uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; + int res; spa_return_val_if_fail(impl != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); @@ -198,44 +278,49 @@ next: switch (id) { case SPA_PARAM_PropInfo: { - struct props *p = &impl->props; - switch (result.index) { case 0: param = (struct spa_pod*)spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_device), SPA_PROP_INFO_description, SPA_POD_String("The libcamera device"), - SPA_PROP_INFO_type, SPA_POD_String(p->device)); + SPA_PROP_INFO_type, SPA_POD_String(impl->device_id.c_str())); break; case 1: param = (struct spa_pod*)spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_deviceName), SPA_PROP_INFO_description, SPA_POD_String("The libcamera device name"), - SPA_PROP_INFO_type, SPA_POD_String(p->device_name)); + SPA_PROP_INFO_type, SPA_POD_String(impl->device_name.c_str())); break; default: - return 0; + return spa_libcamera_enum_controls(impl, + GET_OUT_PORT(impl, 0), + seq, result.index - 2, num, filter); } break; } case SPA_PARAM_Props: { - struct props *p = &impl->props; - switch (result.index) { case 0: param = (struct spa_pod*)spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, - SPA_PROP_device, SPA_POD_String(p->device), - SPA_PROP_deviceName, SPA_POD_String(p->device_name)); + SPA_PROP_device, SPA_POD_String(impl->device_id.c_str()), + SPA_PROP_deviceName, SPA_POD_String(impl->device_name.c_str())); break; default: return 0; } break; } + case SPA_PARAM_EnumFormat: + return spa_libcamera_enum_format(impl, GET_OUT_PORT(impl, 0), + seq, start, num, filter); + case SPA_PARAM_Format: + if ((res = port_get_format(impl, GET_OUT_PORT(impl, 0), result.index, filter, ¶m, &b)) <= 0) + return res; + break; default: return -ENOENT; } @@ -262,15 +347,28 @@ static int impl_node_set_param(void *object, switch (id) { case SPA_PARAM_Props: { - struct props *p = &impl->props; + struct spa_pod_object *obj = (struct spa_pod_object *) param; + struct spa_pod_prop *prop; if (param == NULL) { - reset_props(p); + impl->device_id.clear(); + impl->device_name.clear(); return 0; } - spa_pod_parse_object(param, - SPA_TYPE_OBJECT_Props, NULL, - SPA_PROP_device, SPA_POD_OPT_Stringn(p->device, sizeof(p->device))); + SPA_POD_OBJECT_FOREACH(obj, prop) { + char device[128]; + + switch (prop->key) { + case SPA_PROP_device: + strncpy(device, (char *)SPA_POD_CONTENTS(struct spa_pod_string, &prop->value), + sizeof(device)-1); + impl->device_id = device; + break; + default: + spa_libcamera_set_control(impl, prop); + break; + } + } break; } default: @@ -311,7 +409,7 @@ static int impl_node_send_command(void *object, const struct spa_command *comman { struct port *port = GET_OUT_PORT(impl, 0); - if (!port->have_format) + if (!port->current_format) return -EIO; if (port->n_buffers == 0) return -EIO; @@ -336,7 +434,6 @@ static const struct spa_dict_item info_items[] = { { SPA_KEY_DEVICE_API, "libcamera" }, { SPA_KEY_MEDIA_CLASS, "Video/Source" }, { SPA_KEY_MEDIA_ROLE, "Camera" }, - { SPA_KEY_NODE_PAUSE_ON_IDLE, "false" }, { SPA_KEY_NODE_DRIVER, "true" }, }; @@ -424,55 +521,6 @@ static int impl_node_remove_port(void *object, return -ENOTSUP; } -static int port_get_format(struct impl *impl, struct port *port, - uint32_t index, - const struct spa_pod *filter, - struct spa_pod **param, - struct spa_pod_builder *builder) -{ - struct spa_pod_frame f; - - if (!port->have_format) - return -EIO; - if (index > 0) - return 0; - - spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, SPA_PARAM_Format); - spa_pod_builder_add(builder, - SPA_FORMAT_mediaType, SPA_POD_Id(port->current_format.media_type), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(port->current_format.media_subtype), - 0); - - switch (port->current_format.media_subtype) { - case SPA_MEDIA_SUBTYPE_raw: - spa_pod_builder_add(builder, - SPA_FORMAT_VIDEO_format, SPA_POD_Id(port->current_format.info.raw.format), - SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.raw.size), - SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.raw.framerate), - 0); - break; - case SPA_MEDIA_SUBTYPE_mjpg: - case SPA_MEDIA_SUBTYPE_jpeg: - spa_pod_builder_add(builder, - SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.mjpg.size), - SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.mjpg.framerate), - 0); - break; - case SPA_MEDIA_SUBTYPE_h264: - spa_pod_builder_add(builder, - SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.h264.size), - SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.h264.framerate), - 0); - break; - default: - return -EIO; - } - - *param = (struct spa_pod*)spa_pod_builder_pop(builder, &f); - - return 1; -} - static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, @@ -515,7 +563,7 @@ next: break; case SPA_PARAM_Buffers: { - if (!port->have_format) + if (!port->current_format) return -EIO; if (result.index > 0) return 0; @@ -591,16 +639,17 @@ static int port_set_format(struct impl *impl, struct port *port, int res; if (format == NULL) { - if (!port->have_format) + if (!port->current_format) return 0; spa_libcamera_stream_off(impl); spa_libcamera_clear_buffers(impl, port); - port->have_format = false; + port->current_format.reset(); spa_libcamera_close(impl); goto done; } else { + spa_zero(info); if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; @@ -616,31 +665,31 @@ static int port_set_format(struct impl *impl, struct port *port, return -EINVAL; } - if (port->have_format && info.media_type == port->current_format.media_type && - info.media_subtype == port->current_format.media_subtype && - info.info.raw.format == port->current_format.info.raw.format && - info.info.raw.size.width == port->current_format.info.raw.size.width && - info.info.raw.size.height == port->current_format.info.raw.size.height) + if (port->current_format && info.media_type == port->current_format->media_type && + info.media_subtype == port->current_format->media_subtype && + info.info.raw.format == port->current_format->info.raw.format && + info.info.raw.size.width == port->current_format->info.raw.size.width && + info.info.raw.size.height == port->current_format->info.raw.size.height) return 0; break; case SPA_MEDIA_SUBTYPE_mjpg: if (spa_format_video_mjpg_parse(format, &info.info.mjpg) < 0) return -EINVAL; - if (port->have_format && info.media_type == port->current_format.media_type && - info.media_subtype == port->current_format.media_subtype && - info.info.mjpg.size.width == port->current_format.info.mjpg.size.width && - info.info.mjpg.size.height == port->current_format.info.mjpg.size.height) + if (port->current_format && info.media_type == port->current_format->media_type && + info.media_subtype == port->current_format->media_subtype && + info.info.mjpg.size.width == port->current_format->info.mjpg.size.width && + info.info.mjpg.size.height == port->current_format->info.mjpg.size.height) return 0; break; case SPA_MEDIA_SUBTYPE_h264: if (spa_format_video_h264_parse(format, &info.info.h264) < 0) return -EINVAL; - if (port->have_format && info.media_type == port->current_format.media_type && - info.media_subtype == port->current_format.media_subtype && - info.info.h264.size.width == port->current_format.info.h264.size.width && - info.info.h264.size.height == port->current_format.info.h264.size.height) + if (port->current_format && info.media_type == port->current_format->media_type && + info.media_subtype == port->current_format->media_subtype && + info.info.h264.size.width == port->current_format->info.h264.size.width && + info.info.h264.size.height == port->current_format->info.h264.size.height) return 0; break; default: @@ -648,9 +697,9 @@ static int port_set_format(struct impl *impl, struct port *port, } } - if (port->have_format && !(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { + if (port->current_format && !(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { spa_libcamera_use_buffers(impl, port, NULL, 0); - port->have_format = false; + port->current_format.reset(); } if (spa_libcamera_set_format(impl, port, &info, flags & SPA_NODE_PARAM_FLAG_TEST_ONLY) < 0) @@ -658,19 +707,22 @@ static int port_set_format(struct impl *impl, struct port *port, if (!(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { port->current_format = info; - port->have_format = true; } done: + impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - if (port->have_format) { - port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); - port->params[5] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); + if (port->current_format) { + impl->params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); + port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); + port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { - port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[5] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + impl->params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(impl, port, false); + emit_node_info(impl, false); return 0; } @@ -715,7 +767,7 @@ static int impl_node_port_use_buffers(void *object, port = GET_PORT(impl, direction, port_id); - if (!port->have_format) + if (!port->current_format) return -EIO; if (port->n_buffers) { @@ -781,15 +833,9 @@ static int impl_node_port_reuse_buffer(void *object, return res; } -static void set_control(struct impl *impl, struct port *port, uint32_t control_id, float value) -{ - spa_log_error(impl->log, "Failed to set control"); -} - static int process_control(struct impl *impl, struct spa_pod_sequence *control) { struct spa_pod_control *c; - struct port *port; SPA_POD_SEQUENCE_FOREACH(control, c) { switch (c->type) { @@ -799,9 +845,7 @@ static int process_control(struct impl *impl, struct spa_pod_sequence *control) struct spa_pod_object *obj = (struct spa_pod_object *) &c->value; SPA_POD_OBJECT_FOREACH(obj, prop) { - port = GET_OUT_PORT(impl, 0); - set_control(impl, port, prop->key, - SPA_POD_VALUE(struct spa_pod_float, &prop->value)); + spa_libcamera_set_control(impl, prop); } break; } @@ -896,13 +940,41 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void static int impl_clear(struct spa_handle *handle) { - struct impl *impl; - - impl = (struct impl *) handle; - impl->~Impl(); + std::destroy_at(reinterpret_cast<impl *>(handle)); return 0; } +impl::impl(spa_log *log, spa_loop *data_loop, spa_system *system, + std::shared_ptr<CameraManager> manager, std::shared_ptr<Camera> camera, std::string device_id) + : handle({ SPA_VERSION_HANDLE, impl_get_interface, impl_clear }), + log(log), + data_loop(data_loop), + system(system), + device_id(std::move(device_id)), + out_ports{{this}}, + manager(std::move(manager)), + camera(std::move(camera)) +{ + libcamera_log_topic_init(log); + + spa_hook_list_init(&hooks); + + node.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Node, + SPA_VERSION_NODE, + &impl_node, this); + + params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); + params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); + params[NODE_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); + params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + + info.max_output_ports = 1; + info.flags = SPA_NODE_FLAG_RT; + info.params = params; + info.n_params = N_NODE_PARAMS; +} + static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) @@ -917,87 +989,45 @@ impl_init(const struct spa_handle_factory *factory, const struct spa_support *support, uint32_t n_support) { - struct impl *impl; const char *str; - struct port *port; int res; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); - impl = new(handle) Impl(); - - handle->get_interface = impl_get_interface; - handle->clear = impl_clear; + auto log = static_cast<spa_log *>(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log)); + auto data_loop = static_cast<spa_loop *>(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop)); + auto system = static_cast<spa_system *>(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System)); - impl->log = (struct spa_log*)spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); - libcamera_log_topic_init(impl->log); - - impl->data_loop = (struct spa_loop*)spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); - impl->system = (struct spa_system*)spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System); - - if (impl->data_loop == NULL) { - spa_log_error(impl->log, "a data_loop is needed"); + if (!data_loop) { + spa_log_error(log, "a data_loop is needed"); return -EINVAL; } - if (impl->system == NULL) { - spa_log_error(impl->log, "a system is needed"); + if (!system) { + spa_log_error(log, "a system is needed"); return -EINVAL; } - impl->node.iface = SPA_INTERFACE_INIT( - SPA_TYPE_INTERFACE_Node, - SPA_VERSION_NODE, - &impl_node, impl); - spa_hook_list_init(&impl->hooks); - - impl->info_all = SPA_NODE_CHANGE_MASK_FLAGS | - SPA_NODE_CHANGE_MASK_PROPS | - SPA_NODE_CHANGE_MASK_PARAMS; - impl->info = SPA_NODE_INFO_INIT(); - impl->info.max_output_ports = 1; - impl->info.flags = SPA_NODE_FLAG_RT; - impl->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); - impl->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); - impl->info.params = impl->params; - impl->info.n_params = 2; - reset_props(&impl->props); - - port = GET_OUT_PORT(impl, 0); - port->impl = impl; - spa_list_init(&port->queue); - port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | - SPA_PORT_CHANGE_MASK_PARAMS; - port->info = SPA_PORT_INFO_INIT(); - port->info.flags = SPA_PORT_FLAG_LIVE | - SPA_PORT_FLAG_PHYSICAL | - SPA_PORT_FLAG_TERMINAL; - port->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); - port->params[1] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); - port->params[2] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); - port->params[3] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); - port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[5] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - port->info.params = port->params; - port->info.n_params = 6; + auto manager = libcamera_manager_acquire(res); + if (!manager) { + spa_log_error(log, "can't start camera manager: %s", spa_strerror(res)); + return res; + } + std::string device_id; if (info && (str = spa_dict_lookup(info, SPA_KEY_API_LIBCAMERA_PATH))) - strncpy(impl->props.device, str, sizeof(impl->props.device)); + device_id = str; - impl->manager = libcamera_manager_acquire(); - if (impl->manager == NULL) { - res = -errno; - spa_log_error(impl->log, "can't start camera manager: %s", spa_strerror(res)); - return res; - } - - impl->camera = impl->manager->get(impl->props.device); - if (impl->camera == NULL) { - spa_log_error(impl->log, "unknown camera id %s", impl->props.device); - libcamera_manager_release(impl->manager); + auto camera = manager->get(device_id); + if (!camera) { + spa_log_error(log, "unknown camera id %s", device_id.c_str()); return -ENOENT; } + + new (handle) impl(log, data_loop, system, + std::move(manager), std::move(camera), std::move(device_id)); + return 0; } diff --git a/spa/plugins/libcamera/libcamera-utils.cpp b/spa/plugins/libcamera/libcamera-utils.cpp index 99d0672541f1605ad05696e90c193beb4dee7ae4..446220bd10500cb38035cd9bddc1e3f120f39010 100644 --- a/spa/plugins/libcamera/libcamera-utils.cpp +++ b/spa/plugins/libcamera/libcamera-utils.cpp @@ -32,6 +32,7 @@ #include <errno.h> #include <sys/mman.h> #include <poll.h> +#include <limits.h> #include <linux/media.h> @@ -40,7 +41,7 @@ int spa_libcamera_open(struct impl *impl) if (impl->acquired) return 0; - spa_log_info(impl->log, "open camera %s", impl->props.device); + spa_log_info(impl->log, "open camera %s", impl->device_id.c_str()); impl->camera->acquire(); impl->allocator = new FrameBufferAllocator(impl->camera); @@ -54,10 +55,10 @@ int spa_libcamera_close(struct impl *impl) struct port *port = &impl->out_ports[0]; if (!impl->acquired) return 0; - if (impl->active || port->have_format) + if (impl->active || port->current_format) return 0; - spa_log_info(impl->log, "close camera %s", impl->props.device); + spa_log_info(impl->log, "close camera %s", impl->device_id.c_str()); delete impl->allocator; impl->allocator = nullptr; @@ -69,13 +70,12 @@ int spa_libcamera_close(struct impl *impl) static void spa_libcamera_get_config(struct impl *impl) { - if (impl->have_config) + if (impl->config) return; StreamRoles roles; roles.push_back(VideoRecording); impl->config = impl->camera->generateConfiguration(roles); - impl->have_config = true; } static int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, uint32_t buffer_id) @@ -91,10 +91,10 @@ static int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, ui if (buffer_id >= impl->requestPool.size()) { spa_log_warn(impl->log, "invalid buffer_id %u >= %zu", buffer_id, impl->requestPool.size()); - return -EINVAL; - } + return -EINVAL; + } Request *request = impl->requestPool[buffer_id].get(); - Stream *stream = port->streamConfig.stream(); + Stream *stream = port->streamConfig.stream(); FrameBuffer *buffer = impl->allocator->buffers(stream)[buffer_id].get(); if ((res = request->addBuffer(stream, buffer)) < 0) { spa_log_warn(impl->log, "can't add buffer %u for request: %s", @@ -104,7 +104,9 @@ static int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, ui if (!impl->active) { impl->pendingRequests.push_back(request); return 0; - } else { + } else { + request->controls().merge(impl->ctrls); + impl->ctrls.clear(); if ((res = impl->camera->queueRequest(request)) < 0) { spa_log_warn(impl->log, "can't queue buffer %u: %s", buffer_id, spa_strerror(res)); @@ -119,7 +121,7 @@ static int allocBuffers(struct impl *impl, struct port *port, unsigned int count int res; if ((res = impl->allocator->allocate(port->streamConfig.stream())) < 0) - return res; + return res; for (unsigned int i = 0; i < count; i++) { std::unique_ptr<Request> request = impl->camera->createRequest(i); @@ -129,7 +131,7 @@ static int allocBuffers(struct impl *impl, struct port *port, unsigned int count } impl->requestPool.push_back(std::move(request)); } - return res; + return res; } static void freeBuffers(struct impl *impl, struct port *port) @@ -239,6 +241,14 @@ static const struct format_info *find_format_info_by_media_type(uint32_t type, return NULL; } +static int score_size(Size &a, Size &b) +{ + int x, y; + x = (int)a.width - (int)b.width; + y = (int)a.height - (int)b.height; + return x * x + y * y; +} + static int spa_libcamera_enum_format(struct impl *impl, struct port *port, int seq, uint32_t start, uint32_t num, const struct spa_pod *filter) @@ -250,7 +260,7 @@ spa_libcamera_enum_format(struct impl *impl, struct port *port, int seq, struct spa_pod_frame f[2]; struct spa_result_node_params result; struct spa_pod *fmt; - uint32_t count = 0; + uint32_t i, count = 0, num_sizes; PixelFormat format; Size frameSize; SizeRange sizeRange = SizeRange(); @@ -285,8 +295,23 @@ next_fmt: goto next_fmt; } - if (port->size_index < formats.sizes(format).size()) { - frameSize = formats.sizes(format)[port->size_index]; + num_sizes = formats.sizes(format).size(); + if (num_sizes > 0 && port->size_index <= num_sizes) { + if (port->size_index == 0) { + Size wanted = Size(640, 480), test; + int score, best = INT_MAX; + for (i = 0; i < num_sizes; i++) { + test = formats.sizes(format)[i]; + score = score_size(wanted, test); + if (score < best) { + best = score; + frameSize = test; + } + } + } + else { + frameSize = formats.sizes(format)[port->size_index - 1]; + } } else if (port->size_index < 1) { sizeRange = formats.range(format); if (sizeRange.hStep == 0 || sizeRange.vStep == 0) { @@ -419,8 +444,6 @@ static int spa_libcamera_set_format(struct impl *impl, struct port *port, if ((res = allocBuffers(impl, port, port->streamConfig.bufferCount)) < 0) goto error; - port->have_format = true; - port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_RATE; port->info.flags = SPA_PORT_FLAG_CAN_ALLOC_BUFFERS | SPA_PORT_FLAG_LIVE | @@ -440,9 +463,158 @@ spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq, uint32_t start, uint32_t num, const struct spa_pod *filter) { + const ControlInfoMap &info = impl->camera->controls(); + uint8_t buffer[1024]; + struct spa_pod_builder b = { 0 }; + struct spa_pod_frame f[2]; + struct spa_result_node_params result; + struct spa_pod *ctrl; + uint32_t count = 0, skip; + int res; + const ControlId *ctrl_id; + ControlInfo ctrl_info; + + result.id = SPA_PARAM_PropInfo; + result.next = start; + + auto it = info.begin(); + for (skip = result.next; skip; skip--) + it++; + + if (false) { +next: + it++; + } + result.index = result.next++; + if (it == info.end()) + goto enum_end; + + ctrl_id = it->first; + ctrl_info = it->second; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo); + spa_pod_builder_add(&b, + SPA_PROP_INFO_id, SPA_POD_Id(ctrl_id->id()), + SPA_PROP_INFO_description, SPA_POD_String(ctrl_id->name().c_str()), + 0); + + switch (ctrl_id->type()) { + case ControlTypeBool: + spa_pod_builder_add(&b, + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( + (bool)ctrl_info.def().get<bool>()), + 0); + break; + case ControlTypeFloat: + spa_pod_builder_add(&b, + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + (float)ctrl_info.def().get<float>(), + (float)ctrl_info.min().get<float>(), + (float)ctrl_info.max().get<float>()), + 0); + break; + case ControlTypeInteger32: + spa_pod_builder_add(&b, + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int( + (int32_t)ctrl_info.def().get<int32_t>(), + (int32_t)ctrl_info.min().get<int32_t>(), + (int32_t)ctrl_info.max().get<int32_t>()), + 0); + break; + default: + goto next; + } + + ctrl = (struct spa_pod*) spa_pod_builder_pop(&b, &f[0]); + + if (spa_pod_filter(&b, &result.param, ctrl, filter) < 0) + goto next; + + spa_node_emit_result(&impl->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + +enum_end: + res = 0; + return res; +} + +struct val { + uint32_t type; + float f_val; + int32_t i_val; + bool b_val; + uint32_t id; +}; + +static int do_update_ctrls(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) +{ + struct impl *impl = (struct impl *)user_data; + const struct val *d = (const struct val *)data; + switch (d->type) { + case ControlTypeBool: + impl->ctrls.set(d->id, d->b_val); + break; + case ControlTypeFloat: + impl->ctrls.set(d->id, d->f_val); + break; + case ControlTypeInteger32: + //impl->ctrls.set(d->id, (int32_t)d->i_val); + break; + default: + break; + } return 0; } +static int +spa_libcamera_set_control(struct impl *impl, const struct spa_pod_prop *prop) +{ + const ControlInfoMap &info = impl->camera->controls(); + const ControlId *ctrl_id; + int res; + struct val d; + + auto v = info.idmap().find(prop->key); + if (v == info.idmap().end()) + return -ENOENT; + + ctrl_id = v->second; + + d.type = ctrl_id->type(); + d.id = ctrl_id->id(); + + switch (d.type) { + case ControlTypeBool: + if ((res = spa_pod_get_bool(&prop->value, &d.b_val)) < 0) + goto done; + break; + case ControlTypeFloat: + if ((res = spa_pod_get_float(&prop->value, &d.f_val)) < 0) + goto done; + break; + case ControlTypeInteger32: + if ((res = spa_pod_get_int(&prop->value, &d.i_val)) < 0) + goto done; + break; + default: + res = -EINVAL; + goto done; + } + spa_loop_invoke(impl->data_loop, do_update_ctrls, 0, &d, sizeof(d), true, impl); + res = 0; +done: + return res; +} + + static void libcamera_on_fd_events(struct spa_source *source) { struct impl *impl = (struct impl*) source->data; @@ -480,7 +652,12 @@ static void libcamera_on_fd_events(struct spa_source *source) spa_list_append(&port->queue, &b->link); io = port->io; - if (io != NULL && io->status != SPA_STATUS_HAVE_DATA) { + if (io == NULL) { + b = spa_list_first(&port->queue, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); + spa_libcamera_buffer_recycle(impl, port, b->id); + } else if (io->status != SPA_STATUS_HAVE_DATA) { if (io->buffer_id < port->n_buffers) spa_libcamera_buffer_recycle(impl, port, io->buffer_id); @@ -607,7 +784,7 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, } -void Impl::requestComplete(libcamera::Request *request) +void impl::requestComplete(libcamera::Request *request) { struct impl *impl = this; struct port *port = &impl->out_ports[0]; @@ -617,20 +794,24 @@ void Impl::requestComplete(libcamera::Request *request) spa_log_debug(impl->log, "request complete"); + buffer_id = request->cookie(); + b = &port->buffers[buffer_id]; + if ((request->status() == Request::RequestCancelled)) { - spa_log_debug(impl->log, "Request was cancelled"); - return; - } + spa_log_debug(impl->log, "Request was cancelled"); + request->reuse(); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); + spa_libcamera_buffer_recycle(impl, port, b->id); + return; + } FrameBuffer *buffer = request->findBuffer(stream); if (buffer == nullptr) { - spa_log_warn(impl->log, "unknown buffer"); + spa_log_warn(impl->log, "unknown buffer"); return; } const FrameMetadata &fmd = buffer->metadata(); - buffer_id = request->cookie(); - b = &port->buffers[buffer_id]; if (impl->clock) { impl->clock->nsec = fmd.timestamp; @@ -664,7 +845,7 @@ static int spa_libcamera_stream_on(struct impl *impl) struct port *port = &impl->out_ports[0]; int res; - if (!port->have_format) { + if (!port->current_format) { spa_log_error(impl->log, "Exting %s with -EIO", __FUNCTION__); return -EIO; } @@ -674,15 +855,15 @@ static int spa_libcamera_stream_on(struct impl *impl) impl->camera->requestCompleted.connect(impl, &impl::requestComplete); - spa_log_info(impl->log, "starting camera %s", impl->props.device); + spa_log_info(impl->log, "starting camera %s", impl->device_id.c_str()); if ((res = impl->camera->start()) < 0) - return res == -EACCES ? -EBUSY : res; + goto error; for (Request *req : impl->pendingRequests) { - if ((res = impl->camera->queueRequest(req)) < 0) - return res == -EACCES ? -EBUSY : res; - } - impl->pendingRequests.clear(); + if ((res = impl->camera->queueRequest(req)) < 0) + goto error_stop; + } + impl->pendingRequests.clear(); impl->source.func = libcamera_on_fd_events; impl->source.data = impl; @@ -691,13 +872,20 @@ static int spa_libcamera_stream_on(struct impl *impl) impl->source.rmask = 0; if (impl->source.fd < 0) { spa_log_error(impl->log, "Failed to create eventfd: %s", spa_strerror(impl->source.fd)); - return impl->source.fd; + res = impl->source.fd; + goto error_stop; } spa_loop_add_source(impl->data_loop, &impl->source); impl->active = true; return 0; + +error_stop: + impl->camera->stop(); +error: + impl->camera->requestCompleted.disconnect(impl, &impl::requestComplete); + return res == -EACCES ? -EBUSY : res; } static int do_remove_source(struct spa_loop *loop, @@ -724,11 +912,14 @@ static int spa_libcamera_stream_off(struct impl *impl) return 0; } - spa_log_info(impl->log, "stopping camera %s", impl->props.device); + impl->active = false; + spa_log_info(impl->log, "stopping camera %s", impl->device_id.c_str()); impl->pendingRequests.clear(); - if ((res = impl->camera->stop()) < 0) - return res == -EACCES ? -EBUSY : res; + if ((res = impl->camera->stop()) < 0) { + spa_log_warn(impl->log, "error stopping camera %s: %s", + impl->device_id.c_str(), spa_strerror(res)); + } impl->camera->requestCompleted.disconnect(impl, &impl::requestComplete); @@ -739,7 +930,6 @@ static int spa_libcamera_stream_off(struct impl *impl) } spa_list_init(&port->queue); - impl->active = false; return 0; } diff --git a/spa/plugins/support/cpu-x86.c b/spa/plugins/support/cpu-x86.c index ded9bfaa79d2097e5e73af3fb9217aeabfb02c5a..722f7c906df47afa445a3e760c2a8f42eef142ab 100644 --- a/spa/plugins/support/cpu-x86.c +++ b/spa/plugins/support/cpu-x86.c @@ -56,6 +56,7 @@ x86_init(struct impl *impl) } else if (family == 0x06) model += extended_model; } + (void)model; flags = 0; if (ecx & bit_SSE3) diff --git a/spa/plugins/support/cpu.c b/spa/plugins/support/cpu.c index 67440af16f7d0868f7732bb950b22a25b3c6fa1e..3e6bb85dd7d76a5272b18c190a4de2691bac3efa 100644 --- a/spa/plugins/support/cpu.c +++ b/spa/plugins/support/cpu.c @@ -171,19 +171,18 @@ impl_cpu_get_vm_type(void *object) /* https://wiki.freebsd.org/bhyve */ { "BHYVE", SPA_CPU_VM_BHYVE }, }; - uint32_t i, j; - for (i = 0; i < SPA_N_ELEMENTS(dmi_vendors); i++) { + SPA_FOR_EACH_ELEMENT_VAR(dmi_vendors, dv) { char buffer[256], *s; - if ((s = read_file(dmi_vendors[i], buffer, sizeof(buffer))) == NULL) + if ((s = read_file(*dv, buffer, sizeof(buffer))) == NULL) continue; - for (j = 0; j < SPA_N_ELEMENTS(dmi_vendor_table); j++) { - if (spa_strstartswith(s, dmi_vendor_table[j].vendor)) { + SPA_FOR_EACH_ELEMENT_VAR(dmi_vendor_table, t) { + if (spa_strstartswith(s, t->vendor)) { spa_log_debug(impl->log, "Virtualization %s found in DMI (%s)", - s, dmi_vendors[i]); - impl->vm_type = dmi_vendor_table[j].id; + s, *dv); + impl->vm_type = t->id; goto done; } } diff --git a/spa/plugins/support/logger.c b/spa/plugins/support/logger.c index 3b854c65eff17f1da02401bf2ae78d8afedf3c2a..615a49bee364d25752759cf1ab0590c0f23b43d3 100644 --- a/spa/plugins/support/logger.c +++ b/spa/plugins/support/logger.c @@ -357,7 +357,7 @@ impl_init(const struct spa_handle_factory *factory, if ((str = spa_dict_lookup(info, SPA_KEY_LOG_LEVEL)) != NULL) this->log.level = atoi(str); if ((str = spa_dict_lookup(info, SPA_KEY_LOG_FILE)) != NULL) { - this->file = fopen(str, "w"); + this->file = fopen(str, "we"); if (this->file == NULL) fprintf(stderr, "Warning: failed to open file %s: (%m)", str); else diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c index 758ce59db192332bf7d7a475f3f5fd38e265b166..dd308cfda99a3445c0eeba1c9f358fbf1595a34e 100644 --- a/spa/plugins/support/loop.c +++ b/spa/plugins/support/loop.c @@ -186,8 +186,8 @@ static void flush_items(struct impl *impl) if (block) { if ((res = spa_system_eventfd_write(impl->system, impl->ack_fd, 1)) < 0) - spa_log_warn(impl->log, "%p: failed to write event fd: %s", - impl, spa_strerror(res)); + spa_log_warn(impl->log, "%p: failed to write event fd:%d: %s", + impl, impl->ack_fd, spa_strerror(res)); } } impl->flushing = false; @@ -283,8 +283,8 @@ loop_invoke(void *object, spa_loop_control_hook_before(&impl->hooks_list); if ((res = spa_system_eventfd_read(impl->system, impl->ack_fd, &count)) < 0) - spa_log_warn(impl->log, "%p: failed to read event fd: %s", - impl, spa_strerror(res)); + spa_log_warn(impl->log, "%p: failed to read event fd:%d: %s", + impl, impl->ack_fd, spa_strerror(res)); spa_loop_control_hook_after(&impl->hooks_list); @@ -526,12 +526,12 @@ static int loop_enable_idle(void *object, struct spa_source *source, bool enable if (enabled && !s->enabled) { if ((res = spa_system_eventfd_write(s->impl->system, source->fd, 1)) < 0) - spa_log_warn(s->impl->log, "%p: failed to write idle fd %d: %s", + spa_log_warn(s->impl->log, "%p: failed to write idle fd:%d: %s", source, source->fd, spa_strerror(res)); } else if (!enabled && s->enabled) { uint64_t count; if ((res = spa_system_eventfd_read(s->impl->system, source->fd, &count)) < 0) - spa_log_warn(s->impl->log, "%p: failed to read idle fd %d: %s", + spa_log_warn(s->impl->log, "%p: failed to read idle fd:%d: %s", source, source->fd, spa_strerror(res)); } s->enabled = enabled; @@ -586,7 +586,7 @@ static void source_event_func(struct spa_source *source) int res; if ((res = spa_system_eventfd_read(s->impl->system, source->fd, &count)) < 0) - spa_log_warn(s->impl->log, "%p: failed to read event fd %d: %s", + spa_log_warn(s->impl->log, "%p: failed to read event fd:%d: %s", source, source->fd, spa_strerror(res)); s->func.event(source->data, count); @@ -639,7 +639,7 @@ static int loop_signal_event(void *object, struct spa_source *source) spa_assert(source->func == source_event_func); if (SPA_UNLIKELY((res = spa_system_eventfd_write(s->impl->system, source->fd, 1)) < 0)) - spa_log_warn(s->impl->log, "%p: failed to write event fd %d: %s", + spa_log_warn(s->impl->log, "%p: failed to write event fd:%d: %s", source, source->fd, spa_strerror(res)); return res; } @@ -652,7 +652,7 @@ static void source_timer_func(struct spa_source *source) if (SPA_UNLIKELY((res = spa_system_timerfd_read(s->impl->system, source->fd, &expirations)) < 0)) - spa_log_warn(s->impl->log, "%p: failed to read timer fd %d: %s", + spa_log_warn(s->impl->log, "%p: failed to read timer fd:%d: %s", source, source->fd, spa_strerror(res)); s->func.timer(source->data, expirations); @@ -732,7 +732,7 @@ static void source_signal_func(struct spa_source *source) int res, signal_number = 0; if ((res = spa_system_signalfd_read(s->impl->system, source->fd, &signal_number)) < 0) - spa_log_warn(s->impl->log, "%p: failed to read signal fd %d: %s", + spa_log_warn(s->impl->log, "%p: failed to read signal fd:%d: %s", source, source->fd, spa_strerror(res)); s->func.signal(source->data, signal_number); @@ -865,11 +865,9 @@ static int impl_clear(struct spa_handle *handle) impl = (struct impl *) handle; - if (impl->enter_count != 0) - spa_log_warn(impl->log, "%p: loop is entered %d times", - impl, impl->enter_count); - - spa_assert(!impl->polling); + if (impl->enter_count != 0 || impl->polling) + spa_log_warn(impl->log, "%p: loop is entered %d times polling:%d", + impl, impl->enter_count, impl->polling); spa_list_consume(source, &impl->source_list, link) loop_destroy_source(impl, &source->source); diff --git a/spa/plugins/support/null-audio-sink.c b/spa/plugins/support/null-audio-sink.c index 4c000f3eb1d0eea5babd92ce70b1cf2699e0e3c1..e42c3c1c8ecf7891c746f4b7311f0e9344ee2098 100644 --- a/spa/plugins/support/null-audio-sink.c +++ b/spa/plugins/support/null-audio-sink.c @@ -610,6 +610,11 @@ port_set_format(struct impl *this, if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; + if (info.info.raw.rate == 0 || + info.info.raw.channels == 0 || + info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) + return -EINVAL; + if (info.info.raw.format == SPA_AUDIO_FORMAT_F32) { port->bpf = 4 * info.info.raw.channels; port->blocks = 1; diff --git a/spa/plugins/v4l2/v4l2-source.c b/spa/plugins/v4l2/v4l2-source.c index 802ead7ee2dbfca254aaf93fe301f916e55e18c8..3d967f2552a1a654e291d862b8b83438aec624f1 100644 --- a/spa/plugins/v4l2/v4l2-source.c +++ b/spa/plugins/v4l2/v4l2-source.c @@ -145,7 +145,8 @@ struct impl { #define NODE_PropInfo 0 #define NODE_Props 1 #define NODE_EnumFormat 2 -#define N_NODE_PARAMS 3 +#define NODE_Format 3 +#define N_NODE_PARAMS 4 struct spa_param_info params[N_NODE_PARAMS]; struct props props; @@ -167,6 +168,56 @@ struct impl { #include "v4l2-utils.c" +static int port_get_format(struct port *port, + uint32_t index, + const struct spa_pod *filter, + struct spa_pod **param, + struct spa_pod_builder *builder) +{ + struct spa_pod_frame f; + + if (!port->have_format) + return -EIO; + if (index > 0) + return 0; + + spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, SPA_PARAM_Format); + spa_pod_builder_add(builder, + SPA_FORMAT_mediaType, SPA_POD_Id(port->current_format.media_type), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(port->current_format.media_subtype), + 0); + + switch (port->current_format.media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + spa_pod_builder_add(builder, + SPA_FORMAT_VIDEO_format, SPA_POD_Id(port->current_format.info.raw.format), + SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.raw.size), + SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.raw.framerate), + 0); + break; + case SPA_MEDIA_SUBTYPE_mjpg: + case SPA_MEDIA_SUBTYPE_jpeg: + spa_pod_builder_add(builder, + SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.mjpg.size), + SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.mjpg.framerate), + 0); + break; + case SPA_MEDIA_SUBTYPE_h264: + spa_pod_builder_add(builder, + SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.h264.size), + SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.h264.framerate), + 0); + break; + default: + return -EIO; + } + + *param = spa_pod_builder_pop(builder, &f); + + return 1; +} + + static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) @@ -177,6 +228,7 @@ static int impl_node_enum_params(void *object, int seq, uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; + int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); @@ -216,9 +268,9 @@ static int impl_node_enum_params(void *object, int seq, SPA_PROP_INFO_type, SPA_POD_Int(p->device_fd)); break; default: - return 0; + return spa_v4l2_enum_controls(this, seq, result.index - 3, num, filter); } - return spa_v4l2_enum_controls(this, seq, start, num, filter); + break; } case SPA_PARAM_Props: { @@ -239,6 +291,11 @@ static int impl_node_enum_params(void *object, int seq, } case SPA_PARAM_EnumFormat: return spa_v4l2_enum_format(this, seq, start, num, filter); + case SPA_PARAM_Format: + if((res = port_get_format(GET_OUT_PORT(this, 0), + result.index, filter, ¶m, &b)) <= 0) + return res; + break; default: return -ENOENT; } @@ -266,14 +323,26 @@ static int impl_node_set_param(void *object, case SPA_PARAM_Props: { struct props *p = &this->props; + struct spa_pod_object *obj = (struct spa_pod_object *) param; + struct spa_pod_prop *prop; if (param == NULL) { reset_props(p); return 0; } - spa_pod_parse_object(param, - SPA_TYPE_OBJECT_Props, NULL, - SPA_PROP_device, SPA_POD_OPT_Stringn(p->device, sizeof(p->device))); + SPA_POD_OBJECT_FOREACH(obj, prop) { + switch (prop->key) { + case SPA_PROP_device: + strncpy(p->device, + (char *)SPA_POD_CONTENTS(struct spa_pod_string, &prop->value), + sizeof(p->device)-1); + break; + default: + spa_v4l2_set_control(this, prop->key, prop); + break; + } + } + break; } default: @@ -440,58 +509,6 @@ static int impl_node_remove_port(void *object, return -ENOTSUP; } -static int port_get_format(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t index, - const struct spa_pod *filter, - struct spa_pod **param, - struct spa_pod_builder *builder) -{ - struct impl *this = object; - struct port *port = GET_PORT(this, direction, port_id); - struct spa_pod_frame f; - - if (!port->have_format) - return -EIO; - if (index > 0) - return 0; - - spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, SPA_PARAM_Format); - spa_pod_builder_add(builder, - SPA_FORMAT_mediaType, SPA_POD_Id(port->current_format.media_type), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(port->current_format.media_subtype), - 0); - - switch (port->current_format.media_subtype) { - case SPA_MEDIA_SUBTYPE_raw: - spa_pod_builder_add(builder, - SPA_FORMAT_VIDEO_format, SPA_POD_Id(port->current_format.info.raw.format), - SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.raw.size), - SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.raw.framerate), - 0); - break; - case SPA_MEDIA_SUBTYPE_mjpg: - case SPA_MEDIA_SUBTYPE_jpeg: - spa_pod_builder_add(builder, - SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.mjpg.size), - SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.mjpg.framerate), - 0); - break; - case SPA_MEDIA_SUBTYPE_h264: - spa_pod_builder_add(builder, - SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.h264.size), - SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.h264.framerate), - 0); - break; - default: - return -EIO; - } - - *param = spa_pod_builder_pop(builder, &f); - - return 1; -} - static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, @@ -529,8 +546,7 @@ static int impl_node_port_enum_params(void *object, int seq, return spa_v4l2_enum_format(this, seq, start, num, filter); case SPA_PARAM_Format: - if((res = port_get_format(this, direction, port_id, - result.index, filter, ¶m, &b)) <= 0) + if((res = port_get_format(port, result.index, filter, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Buffers: @@ -541,7 +557,7 @@ static int impl_node_port_enum_params(void *object, int seq, param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(MAX_BUFFERS, 2, MAX_BUFFERS), + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(4, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(port->fmt.fmt.pix.sizeimage), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->fmt.fmt.pix.bytesperline)); @@ -668,15 +684,19 @@ static int port_set_format(struct impl *this, struct port *port, } done: + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); + this->params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READ); } else { port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + this->params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, 0); } emit_port_info(this, port, false); + emit_node_info(this, false); return 0; } @@ -802,45 +822,9 @@ static int impl_node_port_reuse_buffer(void *object, return res; } -static uint32_t prop_to_control_id(uint32_t prop) -{ - switch (prop) { - case SPA_PROP_brightness: - return V4L2_CID_BRIGHTNESS; - case SPA_PROP_contrast: - return V4L2_CID_CONTRAST; - case SPA_PROP_saturation: - return V4L2_CID_SATURATION; - case SPA_PROP_hue: - return V4L2_CID_HUE; - case SPA_PROP_gamma: - return V4L2_CID_GAMMA; - case SPA_PROP_exposure: - return V4L2_CID_EXPOSURE; - case SPA_PROP_gain: - return V4L2_CID_GAIN; - case SPA_PROP_sharpness: - return V4L2_CID_SHARPNESS; - default: - return 0; - } -} - -static void set_control(struct impl *this, struct port *port, uint32_t control_id, float value) -{ - struct v4l2_control c; - - spa_zero(c); - c.id = control_id; - c.value = value; - if (ioctl(port->dev.fd, VIDIOC_S_CTRL, &c) < 0) - spa_log_error(this->log, "VIDIOC_S_CTRL %m"); -} - static int process_control(struct impl *this, struct spa_pod_sequence *control) { struct spa_pod_control *c; - struct port *port; SPA_POD_SEQUENCE_FOREACH(control, c) { switch (c->type) { @@ -850,14 +834,7 @@ static int process_control(struct impl *this, struct spa_pod_sequence *control) struct spa_pod_object *obj = (struct spa_pod_object *) &c->value; SPA_POD_OBJECT_FOREACH(obj, prop) { - uint32_t control_id; - - if ((control_id = prop_to_control_id(prop->key)) == 0) - continue; - - port = GET_OUT_PORT(this, 0); - set_control(this, port, control_id, - SPA_POD_VALUE(struct spa_pod_float, &prop->value)); + spa_v4l2_set_control(this, prop->key, prop); } break; } @@ -1005,8 +982,9 @@ impl_init(const struct spa_handle_factory *factory, this->info.max_output_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); - this->params[NODE_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); this->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); + this->params[NODE_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); + this->params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, 0); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; reset_props(&this->props); diff --git a/spa/plugins/v4l2/v4l2-udev.c b/spa/plugins/v4l2/v4l2-udev.c index df1b1f8906b85a9a5a8b2ef976a7ff9e4016058a..e7f926585c131215a67d604f79163f94e010ea58 100644 --- a/spa/plugins/v4l2/v4l2-udev.c +++ b/spa/plugins/v4l2/v4l2-udev.c @@ -266,7 +266,7 @@ static int emit_object_info(struct impl *this, struct device *device) if (str && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS_PATH, str); } - if ((str = udev_device_get_syspath(dev)) && *str) { + if ((str = udev_device_get_devpath(dev)) && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_SYSFS_PATH, str); } if ((str = udev_device_get_property_value(dev, "ID_ID")) && *str) { diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c index 14e967f731b63fd9de6341da0bceb3c4be8ed0e1..8ffda138e216ea8ef29d31dc16a55e468eca3b36 100644 --- a/spa/plugins/v4l2/v4l2-utils.c +++ b/spa/plugins/v4l2/v4l2-utils.c @@ -30,7 +30,7 @@ #include <sys/mman.h> #include <poll.h> -static void v4l2_on_fd_events(struct spa_source *source); +#include <spa/utils/result.h> static int xioctl(int fd, int request, void *arg) { @@ -43,7 +43,6 @@ static int xioctl(int fd, int request, void *arg) return err; } - int spa_v4l2_open(struct spa_v4l2_device *dev, const char *path) { struct stat st; @@ -57,7 +56,7 @@ int spa_v4l2_open(struct spa_v4l2_device *dev, const char *path) return -EIO; } - spa_log_info(dev->log, "Playback device is '%s'", path); + spa_log_info(dev->log, "device is '%s'", path); dev->fd = open(path, O_RDWR | O_NONBLOCK, 0); if (dev->fd == -1) { @@ -85,6 +84,7 @@ int spa_v4l2_open(struct spa_v4l2_device *dev, const char *path) spa_log_error(dev->log, "'%s' QUERYCAP: %m", path); goto error_close; } + snprintf(dev->path, sizeof(dev->path), "%s", path); return 0; error_close: @@ -110,7 +110,7 @@ int spa_v4l2_close(struct spa_v4l2_device *dev) if (dev->active || dev->have_format) return 0; - spa_log_info(dev->log, "close"); + spa_log_info(dev->log, "close '%s'", dev->path); if (close(dev->fd)) spa_log_warn(dev->log, "close: %m"); @@ -351,11 +351,9 @@ static const struct format_info format_info[] = { static const struct format_info *fourcc_to_format_info(uint32_t fourcc) { - size_t i; - - for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) { - if (format_info[i].fourcc == fourcc) - return &format_info[i]; + SPA_FOR_EACH_ELEMENT_VAR(format_info, i) { + if (i->fourcc == fourcc) + return i; } return NULL; } @@ -363,11 +361,9 @@ static const struct format_info *fourcc_to_format_info(uint32_t fourcc) #if 0 static const struct format_info *video_format_to_format_info(uint32_t format) { - int i; - - for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) { - if (format_info[i].format == format) - return &format_info[i]; + SPA_FOR_EACH_ELEMENT_VAR(format_info, i) { + if (i->format == format) + return i; } return NULL; } @@ -381,10 +377,11 @@ static const struct format_info *find_format_info_by_media_type(uint32_t type, size_t i; for (i = startidx; i < SPA_N_ELEMENTS(format_info); i++) { - if ((format_info[i].media_type == type) && - (format_info[i].media_subtype == subtype) && - (format == 0 || format_info[i].format == format)) - return &format_info[i]; + const struct format_info *fi = &format_info[i]; + if ((fi->media_type == type) && + (fi->media_subtype == subtype) && + (format == 0 || fi->format == format)) + return fi; } return NULL; } @@ -895,8 +892,9 @@ static int spa_v4l2_set_format(struct impl *this, struct spa_video_info *format, bool match; spa_zero(fmt); - spa_zero(streamparm); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + spa_zero(streamparm); streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; switch (format->media_subtype) { @@ -929,7 +927,6 @@ static int spa_v4l2_set_format(struct impl *this, struct spa_video_info *format, return -EINVAL; } - fmt.fmt.pix.pixelformat = info->fourcc; fmt.fmt.pix.field = V4L2_FIELD_ANY; fmt.fmt.pix.width = size->width; @@ -976,7 +973,7 @@ static int spa_v4l2_set_format(struct impl *this, struct spa_video_info *format, return match ? 0 : 1; spa_log_info(this->log, "'%s' got %.4s %dx%d %d/%d", - this->props.device, (char *)&fmt.fmt.pix.pixelformat, + dev->path, (char *)&fmt.fmt.pix.pixelformat, fmt.fmt.pix.width, fmt.fmt.pix.height, streamparm.parm.capture.timeperframe.denominator, streamparm.parm.capture.timeperframe.numerator); @@ -1046,28 +1043,38 @@ static int query_ext_ctrl_ioctl(struct port *port, struct v4l2_query_ext_ctrl *q return res; } +static struct { + uint32_t v4l2_id; + uint32_t spa_id; +} control_map[] = { + { V4L2_CID_BRIGHTNESS, SPA_PROP_brightness }, + { V4L2_CID_CONTRAST, SPA_PROP_contrast }, + { V4L2_CID_SATURATION, SPA_PROP_saturation }, + { V4L2_CID_HUE, SPA_PROP_hue }, + { V4L2_CID_GAMMA, SPA_PROP_gamma }, + { V4L2_CID_EXPOSURE, SPA_PROP_exposure }, + { V4L2_CID_GAIN, SPA_PROP_gain }, + { V4L2_CID_SHARPNESS, SPA_PROP_sharpness }, +}; + static uint32_t control_to_prop_id(struct impl *impl, uint32_t control_id) { - switch (control_id) { - case V4L2_CID_BRIGHTNESS: - return SPA_PROP_brightness; - case V4L2_CID_CONTRAST: - return SPA_PROP_contrast; - case V4L2_CID_SATURATION: - return SPA_PROP_saturation; - case V4L2_CID_HUE: - return SPA_PROP_hue; - case V4L2_CID_GAMMA: - return SPA_PROP_gamma; - case V4L2_CID_EXPOSURE: - return SPA_PROP_exposure; - case V4L2_CID_GAIN: - return SPA_PROP_gain; - case V4L2_CID_SHARPNESS: - return SPA_PROP_sharpness; - default: - return SPA_PROP_START_CUSTOM + control_id; + SPA_FOR_EACH_ELEMENT_VAR(control_map, c) { + if (c->v4l2_id == control_id) + return c->spa_id; + } + return SPA_PROP_START_CUSTOM + control_id; +} + +static uint32_t prop_id_to_control(struct impl *impl, uint32_t prop_id) +{ + SPA_FOR_EACH_ELEMENT_VAR(control_map, c) { + if (c->spa_id == prop_id) + return c->v4l2_id; } + if (prop_id >= SPA_PROP_START_CUSTOM) + return prop_id - SPA_PROP_START_CUSTOM; + return SPA_ID_INVALID; } static int @@ -1231,6 +1238,56 @@ spa_v4l2_enum_controls(struct impl *this, int seq, return res; } +static int +spa_v4l2_set_control(struct impl *this, uint32_t id, + const struct spa_pod_prop *prop) +{ + struct port *port = &this->out_ports[0]; + struct spa_v4l2_device *dev = &port->dev; + struct v4l2_control control; + int res; + + spa_zero(control); + control.id = prop_id_to_control(this, prop->key); + if (control.id == SPA_ID_INVALID) + return -ENOENT; + + if ((res = spa_v4l2_open(dev, this->props.device)) < 0) + return res; + + switch (SPA_POD_TYPE(&prop->value)) { + case SPA_TYPE_Bool: + { + bool val; + if ((res = spa_pod_get_bool(&prop->value, &val)) < 0) + goto done; + control.value = val; + break; + } + case SPA_TYPE_Int: + { + int32_t val; + if ((res = spa_pod_get_int(&prop->value, &val)) < 0) + goto done; + control.value = val; + break; + } + default: + res = -EINVAL; + goto done; + } + if (xioctl(dev->fd, VIDIOC_S_CTRL, &control) < 0) { + res = -errno; + goto done; + } + + res = 0; + +done: + spa_v4l2_close(dev); + return res; +} + static int mmap_read(struct impl *this) { struct port *port = &this->out_ports[0]; @@ -1310,7 +1367,13 @@ static void v4l2_on_fd_events(struct spa_source *source) return; io = port->io; - if (io != NULL && io->status != SPA_STATUS_HAVE_DATA) { + if (io == NULL) { + b = spa_list_first(&port->queue, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); + spa_v4l2_buffer_recycle(this, b->id); + } + else if (io->status != SPA_STATUS_HAVE_DATA) { if (io->buffer_id < port->n_buffers) spa_v4l2_buffer_recycle(this, io->buffer_id); @@ -1445,11 +1508,10 @@ mmap_init(struct impl *this, } spa_log_debug(this->log, "got %d buffers", reqbuf.count); - n_buffers = reqbuf.count; - if (n_buffers < 2) { - spa_log_error(this->log, "'%s' can't allocate enough buffers (%d)", - this->props.device, n_buffers); + if (reqbuf.count < n_buffers) { + spa_log_error(this->log, "'%s' can't allocate enough buffers (%d < %d)", + this->props.device, reqbuf.count, n_buffers); return -ENOMEM; } @@ -1549,7 +1611,7 @@ fallback: } spa_v4l2_buffer_recycle(this, i); } - spa_log_info(this->log, "have %u buffers using %s", n_buffers, + spa_log_info(this->log, "%s: have %u buffers using %s", dev->path, n_buffers, use_expbuf ? "EXPBUF" : "MMAP"); port->n_buffers = n_buffers; diff --git a/spa/plugins/v4l2/v4l2.h b/spa/plugins/v4l2/v4l2.h index 892728127f5e6a60d89b23b16c8c6945964bc41b..e2293c7e28c8a3c3efba422081dca23540ffcc8f 100644 --- a/spa/plugins/v4l2/v4l2.h +++ b/spa/plugins/v4l2/v4l2.h @@ -41,6 +41,7 @@ struct spa_v4l2_device { struct v4l2_capability cap; unsigned int active:1; unsigned int have_format:1; + char path[64]; }; int spa_v4l2_open(struct spa_v4l2_device *dev, const char *path); diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 694e6af65abe48d9485b5be2bef7a2fffc537d1b..0a2a1422b2b5c42e02f7b1c120c530d9b344803b 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -562,24 +562,6 @@ static int reconfigure_mode(struct impl *this, bool passthrough, return 0; } -static int format_video_raw_parse_opt(const struct spa_pod *format, struct spa_video_info_raw *info) -{ - uint32_t media_type, media_subtype; - int res; - if ((res = spa_format_parse(format, &media_type, &media_subtype)) < 0) - return res; - if (media_type != SPA_MEDIA_TYPE_video || - media_subtype != SPA_MEDIA_SUBTYPE_raw) - return -ENOTSUP; - - spa_zero(*info); - res = spa_pod_parse_object(format, - SPA_TYPE_OBJECT_Format, NULL, - SPA_FORMAT_VIDEO_format, SPA_POD_OPT_Id(&info->format), - SPA_FORMAT_VIDEO_size, SPA_POD_OPT_Int(&info->size)); - return res; -} - static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { @@ -599,8 +581,9 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, if ((res = spa_format_parse(param, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_video || - info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - return -EINVAL; + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -EINVAL; + if (spa_format_video_raw_parse(param, &info.info.raw) < 0) return -EINVAL; @@ -627,7 +610,15 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, if (format) { struct spa_video_info info; - if (format_video_raw_parse_opt(format, &info.info.raw) >= 0) + + spa_zero(info); + if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) + return res; + if (info.media_type != SPA_MEDIA_TYPE_video || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -ENOTSUP; + + if (spa_format_video_raw_parse(format, &info.info.raw) >= 0) this->default_format = info; } @@ -823,12 +814,12 @@ static int impl_node_send_command(void *object, const struct spa_command *comman case SPA_NODE_COMMAND_Suspend: configure_format(this, 0, NULL); SPA_FALLTHROUGH - case SPA_NODE_COMMAND_Flush: - this->io_buffers.status = SPA_STATUS_OK; - SPA_FALLTHROUGH case SPA_NODE_COMMAND_Pause: this->started = false; break; + case SPA_NODE_COMMAND_Flush: + this->io_buffers.status = SPA_STATUS_OK; + break; default: break; } diff --git a/spa/plugins/videotestsrc/videotestsrc.c b/spa/plugins/videotestsrc/videotestsrc.c index 97742975963b66e7d1a24e074d9e06cf17396b76..f2f42c0609689ccf9ae931e48d1b8b88aff31a27 100644 --- a/spa/plugins/videotestsrc/videotestsrc.c +++ b/spa/plugins/videotestsrc/videotestsrc.c @@ -643,6 +643,12 @@ static int port_set_format(struct impl *this, struct port *port, else return -EINVAL; + if (info.info.raw.size.width == 0 || + info.info.raw.size.height == 0 || + info.info.raw.framerate.num == 0 || + info.info.raw.framerate.denom == 0) + return -EINVAL; + port->current_format = info; port->have_format = true; } diff --git a/spa/plugins/volume/volume.c b/spa/plugins/volume/volume.c index fe6fcc4f4f9e09787baaacd4ab4d9df616cec8ed..08102a428587f8acd38c36ce96bcd2de358ce853 100644 --- a/spa/plugins/volume/volume.c +++ b/spa/plugins/volume/volume.c @@ -321,10 +321,9 @@ static int port_enum_formats(void *object, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(3, + SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(2, SPA_AUDIO_FORMAT_S16, - SPA_AUDIO_FORMAT_S16, - SPA_AUDIO_FORMAT_S32), + SPA_AUDIO_FORMAT_S16), SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int( DEFAULT_RATE, 1, INT32_MAX), SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( @@ -472,6 +471,11 @@ static int port_set_format(void *object, if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; + if (info.info.raw.format != SPA_AUDIO_FORMAT_S16 || + info.info.raw.channels == 0 || + info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) + return -EINVAL; + this->bpf = 2 * info.info.raw.channels; this->current_format = info; port->have_format = true; diff --git a/spa/tests/benchmark-dict.c b/spa/tests/benchmark-dict.c index 9d189b73f5e7a442fdf5a4cbc89113a650b413fc..c47845e7bf0e74b69fee62d884b721cbe2cb0cfb 100644 --- a/spa/tests/benchmark-dict.c +++ b/spa/tests/benchmark-dict.c @@ -39,7 +39,7 @@ static struct spa_dict_item items[MAX_ITEMS]; static char values[MAX_ITEMS][32]; -static void gen_values() +static void gen_values(void) { uint32_t i, j, idx; static const char chars[] = "abcdefghijklmnopqrstuvwxyz.:*ABCDEFGHIJKLMNOPQRSTUVWXYZ"; diff --git a/spa/tests/benchmark-pod.c b/spa/tests/benchmark-pod.c index 43ac20b3b77315c01f54a4718c0c0c1e39d4d64d..8af1a5f960f62ea789de9db4363d922f52d70919 100644 --- a/spa/tests/benchmark-pod.c +++ b/spa/tests/benchmark-pod.c @@ -37,7 +37,7 @@ #define MAX_COUNT 10000000 -static void test_builder() +static void test_builder(void) { uint8_t buffer[1024]; struct spa_pod_builder b = { NULL, }; @@ -90,7 +90,7 @@ static void test_builder() t2 - t1, count, count * (uint64_t)SPA_NSEC_PER_SEC / (t2 - t1)); } -static void test_builder2() +static void test_builder2(void) { uint8_t buffer[1024]; struct spa_pod_builder b = { NULL, }; @@ -131,7 +131,7 @@ static void test_builder2() t2 - t1, count, count * (uint64_t)SPA_NSEC_PER_SEC / (t2 - t1)); } -static void test_parse() +static void test_parse(void) { uint8_t buffer[1024]; struct spa_pod_builder b = { NULL, }; @@ -216,7 +216,7 @@ static void test_parse() t2 - t1, count, count * (uint64_t)SPA_NSEC_PER_SEC / (t2 - t1)); } -static void test_parser() +static void test_parser(void) { uint8_t buffer[1024]; struct spa_pod_builder b = { NULL, }; diff --git a/src/daemon/jack.conf.in b/src/daemon/jack.conf.in index e3e2dc790ed60171610ca82528022e75d1457aeb..f1fa6bc6b7773242b65e65c9ee64d5fe6c499c5f 100644 --- a/src/daemon/jack.conf.in +++ b/src/daemon/jack.conf.in @@ -73,6 +73,7 @@ jack.properties = { #jack.merge-monitor = false #jack.short-name = false #jack.filter-name = false + #jack.filter-char = " " # # allow: Don't restrict self connect requests # fail-external: Fail self connect requests to external ports only @@ -83,6 +84,7 @@ jack.properties = { #jack.locked-process = true #jack.default-as-system = false #jack.fix-midi-events = true + #jack.global-buffer-size = false } # client specific properties @@ -102,4 +104,23 @@ jack.rules = [ } } } + { matches = [ + { application.process.binary = "jack_bufsize" } + ] + actions = { + update-props = { + jack.global-buffer-size = true # quantum set globally using metadata + } + } + } + { matches = [ + { application.process.binary = "qsynth" } + ] + actions = { + update-props = { + node.pause-on-idle = false # makes audio fade out when idle + node.passive = true # makes the sink and qsynth suspend + } + } + } ] diff --git a/src/daemon/pipewire-pulse.conf.in b/src/daemon/pipewire-pulse.conf.in index 1ee3494a084b9f5f792330043c240c5e0f038ffb..2d381a14542007bd6ee6679cec974247e0914318 100644 --- a/src/daemon/pipewire-pulse.conf.in +++ b/src/daemon/pipewire-pulse.conf.in @@ -90,6 +90,7 @@ pulse.properties = { #pulse.default.frag = 96000/48000 # 2 seconds #pulse.default.tlength = 96000/48000 # 2 seconds #pulse.min.quantum = 256/48000 # 5ms + #pulse.idle.timeout = 5 # pause after 5s of underruns #pulse.default.format = F32 #pulse.default.position = [ FL FR ] # These overrides are only applied when running in a vm. @@ -141,6 +142,7 @@ pulse.rules = [ update-props = { pulse.min.req = 1024/48000 # 21ms pulse.min.quantum = 1024/48000 # 21ms + #pulse.idle.timeout = 0 } } } diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in index 748c6173f036f2b4a1eaec4644ffd494b46b8bd5..4aa30a4f91034c34210db2641937239c4dd3cfba 100644 --- a/src/daemon/pipewire.conf.in +++ b/src/daemon/pipewire.conf.in @@ -159,14 +159,15 @@ context.modules = [ { name = libpipewire-module-session-manager } # Use libcanberra to play X11 Bell - #{ name = libpipewire-module-x11-bell - # args = { - # #sink.name = "@DEFAULT_SINK@" - # #sample.name = "bell-window-system" - # #x11.display = null - # #x11.xauthority = null - # } - #} + { name = libpipewire-module-x11-bell + args = { + #sink.name = "@DEFAULT_SINK@" + #sample.name = "bell-window-system" + #x11.display = null + #x11.xauthority = null + } + flags = [ ifexists nofail ] + } ] context.objects = [ diff --git a/src/examples/audio-capture.c b/src/examples/audio-capture.c new file mode 100644 index 0000000000000000000000000000000000000000..4c1afbbec780ce578258b0f0164b947b8f88f968 --- /dev/null +++ b/src/examples/audio-capture.c @@ -0,0 +1,209 @@ +/* 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. + */ + +/* + [title] + Audio capture using \ref pw_stream "pw_stream". + [title] + */ + +#include <stdio.h> +#include <errno.h> +#include <math.h> +#include <signal.h> + +#include <spa/param/audio/format-utils.h> + +#include <pipewire/pipewire.h> + +struct data { + struct pw_main_loop *loop; + struct pw_stream *stream; + + struct spa_audio_info format; + unsigned move:1; +}; + +/* our data processing function is in general: + * + * struct pw_buffer *b; + * b = pw_stream_dequeue_buffer(stream); + * + * .. consume stuff in the buffer ... + * + * pw_stream_queue_buffer(stream, b); + */ +static void on_process(void *userdata) +{ + struct data *data = userdata; + struct pw_buffer *b; + struct spa_buffer *buf; + float *samples, max; + uint32_t c, n, n_channels, n_samples, peak; + + if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { + pw_log_warn("out of buffers: %m"); + return; + } + + buf = b->buffer; + if ((samples = buf->datas[0].data) == NULL) + return; + + n_channels = data->format.info.raw.channels; + n_samples = buf->datas[0].chunk->size / sizeof(float); + + /* move cursor up */ + if (data->move) + fprintf(stdout, "%c[%dA", 0x1b, n_channels + 1); + fprintf(stdout, "captured %d samples\n", n_samples / n_channels); + for (c = 0; c < data->format.info.raw.channels; c++) { + max = 0.0f; + for (n = c; n < n_samples; n += n_channels) + max = fmaxf(max, fabsf(samples[n])); + + peak = SPA_CLAMP(max * 30, 0, 39); + + fprintf(stdout, "channel %d: |%*s%*s| peak:%f\n", + c, peak+1, "*", 40 - peak, "", max); + } + data->move = true; + fflush(stdout); + + pw_stream_queue_buffer(data->stream, b); +} + +/* Be notified when the stream param changes. We're only looking at the + * format changes. + */ +static void +on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) +{ + struct data *data = _data; + + /* NULL means to clear the format */ + if (param == NULL || id != SPA_PARAM_Format) + return; + + if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0) + return; + + /* only accept raw audio */ + if (data->format.media_type != SPA_MEDIA_TYPE_audio || + data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return; + + /* call a helper function to parse the format for us. */ + spa_format_audio_raw_parse(param, &data->format.info.raw); + + fprintf(stdout, "capturing rate:%d channels:%d\n", + data->format.info.raw.rate, data->format.info.raw.channels); + +} + +static const struct pw_stream_events stream_events = { + PW_VERSION_STREAM_EVENTS, + .param_changed = on_stream_param_changed, + .process = on_process, +}; + +static void do_quit(void *userdata, int signal_number) +{ + struct data *data = userdata; + pw_main_loop_quit(data->loop); +} + +int main(int argc, char *argv[]) +{ + struct data data = { 0, }; + const struct spa_pod *params[1]; + uint8_t buffer[1024]; + struct pw_properties *props; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + + pw_init(&argc, &argv); + + /* make a main loop. If you already have another main loop, you can add + * the fd of this pipewire mainloop to it. */ + data.loop = pw_main_loop_new(NULL); + + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); + + /* Create a simple stream, the simple stream manages the core and remote + * objects for you if you don't need to deal with them. + * + * If you plan to autoconnect your stream, you need to provide at least + * media, category and role properties. + * + * Pass your events and a user_data pointer as the last arguments. This + * will inform you about the stream state. The most important event + * you need to listen to is the process event where you need to produce + * the data. + */ + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Music", + NULL); + if (argc > 1) + /* Set stream target if given on command line */ + pw_properties_set(props, PW_KEY_TARGET_OBJECT, argv[1]); + + /* uncomment if you want to capture from the sink monitor ports */ + /* pw_properties_set(props, PW_KEY_STREAM_CAPTURE_SINK, "true"); */ + + data.stream = pw_stream_new_simple( + pw_main_loop_get_loop(data.loop), + "audio-capture", + props, + &stream_events, + &data); + + /* Make one parameter with the supported formats. The SPA_PARAM_EnumFormat + * id means that this is a format enumeration (of 1 value). + * We leave the channels and rate empty to accept the native graph + * rate and channels. */ + params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, + &SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32)); + + /* Now connect this stream. We ask that our process function is + * called in a realtime thread. */ + pw_stream_connect(data.stream, + PW_DIRECTION_INPUT, + PW_ID_ANY, + PW_STREAM_FLAG_AUTOCONNECT | + PW_STREAM_FLAG_MAP_BUFFERS | + PW_STREAM_FLAG_RT_PROCESS, + params, 1); + + /* and wait while we let things run */ + pw_main_loop_run(data.loop); + + pw_stream_destroy(data.stream); + pw_main_loop_destroy(data.loop); + pw_deinit(); + + return 0; +} diff --git a/src/examples/export-sink.c b/src/examples/export-sink.c index 9e0985475ec1591514135b7193be3c285c48c66f..ee8a57c45bb3ca8160b9b58726796f0c702a2f0e 100644 --- a/src/examples/export-sink.c +++ b/src/examples/export-sink.c @@ -303,11 +303,10 @@ static int port_set_format(void *object, Uint32 sdl_format; void *dest; - d->info.change_mask = SPA_PORT_CHANGE_MASK_PARAMS; if (format == NULL) { + spa_zero(d->format); SDL_DestroyTexture(d->texture); - d->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - d->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + d->texture = NULL; } else { spa_debug_format(0, NULL, format); @@ -316,6 +315,9 @@ static int port_set_format(void *object, sdl_format = id_to_sdl_format(d->format.format); if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) return -EINVAL; + if (d->format.size.width == 0 || + d->format.size.height == 0) + return -EINVAL; d->texture = SDL_CreateTexture(d->renderer, sdl_format, @@ -325,9 +327,16 @@ static int port_set_format(void *object, SDL_LockTexture(d->texture, NULL, &dest, &d->stride); SDL_UnlockTexture(d->texture); + } + d->info.change_mask = SPA_PORT_CHANGE_MASK_PARAMS; + if (format) { d->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); d->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); + } else { + d->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + d->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } + spa_node_emit_port_info(&d->hooks, direction, port_id, &d->info); d->info.change_mask = 0; diff --git a/src/examples/export-source.c b/src/examples/export-source.c index f84d01d9262d6e004ed984955707acf74fc0390d..22dfa1426f0106f57122afdce4093cec278c65bc 100644 --- a/src/examples/export-source.c +++ b/src/examples/export-source.c @@ -275,26 +275,31 @@ static int port_set_format(void *object, { struct data *d = object; - d->info.change_mask = SPA_PORT_CHANGE_MASK_PARAMS; if (format == NULL) { - d->format.format = 0; - d->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - d->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - spa_node_emit_port_info(&d->hooks, SPA_DIRECTION_OUTPUT, 0, &d->info); - return 0; - } + spa_zero(d->format); + } else { + spa_debug_format(0, NULL, format); - spa_debug_format(0, NULL, format); - - if (spa_format_audio_raw_parse(format, &d->format) < 0) - return -EINVAL; + if (spa_format_audio_raw_parse(format, &d->format) < 0) + return -EINVAL; - if (d->format.format != SPA_AUDIO_FORMAT_S16 && - d->format.format != SPA_AUDIO_FORMAT_F32) - return -EINVAL; + if (d->format.format != SPA_AUDIO_FORMAT_S16 && + d->format.format != SPA_AUDIO_FORMAT_F32) + return -EINVAL; + if (d->format.rate == 0 || + d->format.channels == 0 || + d->format.channels > SPA_AUDIO_MAX_CHANNELS) + return -EINVAL; + } - d->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); - d->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); + d->info.change_mask = SPA_PORT_CHANGE_MASK_PARAMS; + if (format) { + d->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); + d->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); + } else { + d->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + d->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + } spa_node_emit_port_info(&d->hooks, SPA_DIRECTION_OUTPUT, 0, &d->info); return 0; diff --git a/src/examples/local-v4l2.c b/src/examples/local-v4l2.c index 0e466ba8d6589e952d9d603d8e6142de563fa1a6..5f74767272432eb4144cc81ccdbf611b14973b34 100644 --- a/src/examples/local-v4l2.c +++ b/src/examples/local-v4l2.c @@ -207,28 +207,39 @@ static int port_set_format(void *object, enum spa_direction direction, uint32_t Uint32 sdl_format; void *dest; - if (format == NULL) - return 0; - - spa_debug_format(0, NULL, format); - - spa_format_video_raw_parse(format, &d->format); - - sdl_format = id_to_sdl_format(d->format.format); - if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) - return -EINVAL; - - d->texture = SDL_CreateTexture(d->renderer, - sdl_format, - SDL_TEXTUREACCESS_STREAMING, - d->format.size.width, - d->format.size.height); - SDL_LockTexture(d->texture, NULL, &dest, &d->stride); - SDL_UnlockTexture(d->texture); + if (format == NULL) { + spa_zero(d->format); + SDL_DestroyTexture(d->texture); + d->texture = NULL; + } else { + spa_debug_format(0, NULL, format); + + spa_format_video_raw_parse(format, &d->format); + + sdl_format = id_to_sdl_format(d->format.format); + if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) + return -EINVAL; + if (d->format.size.width == 0 || + d->format.size.height == 0) + return -EINVAL; + + d->texture = SDL_CreateTexture(d->renderer, + sdl_format, + SDL_TEXTUREACCESS_STREAMING, + d->format.size.width, + d->format.size.height); + SDL_LockTexture(d->texture, NULL, &dest, &d->stride); + SDL_UnlockTexture(d->texture); + } d->info.change_mask = SPA_PORT_CHANGE_MASK_PARAMS; - d->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); - d->params[2] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); + if (format) { + d->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); + d->params[2] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); + } else { + d->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + d->params[2] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + } spa_node_emit_port_info(&d->hooks, SPA_DIRECTION_INPUT, 0, &d->info); return 0; diff --git a/src/examples/meson.build b/src/examples/meson.build index 747cb1416233c48977cbd40a4f52f9eec14ef5a5..e2f260085b254530c2d5d40ff8a95198dfa6fcb6 100644 --- a/src/examples/meson.build +++ b/src/examples/meson.build @@ -3,6 +3,7 @@ examples = [ 'audio-src', 'audio-dsp-src', 'audio-dsp-filter', + 'audio-capture', 'video-play', 'video-src', 'video-dsp-play', diff --git a/src/examples/sdl.h b/src/examples/sdl.h index cb7b87a6f1d939fbed8362ab6aeb82c9b0516086..74ea74ab6700022d5b9c6e385c176622b2c7c18a 100644 --- a/src/examples/sdl.h +++ b/src/examples/sdl.h @@ -130,25 +130,22 @@ static struct { static inline uint32_t sdl_format_to_id(Uint32 format) { - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(sdl_video_formats); i++) { - if (sdl_video_formats[i].format == format) - return sdl_video_formats[i].id; + SPA_FOR_EACH_ELEMENT_VAR(sdl_video_formats, f) { + if (f->format == format) + return f->id; } return SPA_VIDEO_FORMAT_UNKNOWN; } static inline Uint32 id_to_sdl_format(uint32_t id) { - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(sdl_video_formats); i++) { - if (sdl_video_formats[i].id == id) - return sdl_video_formats[i].format; + SPA_FOR_EACH_ELEMENT_VAR(sdl_video_formats, f) { + if (f->id == id) + return f->format; } return SDL_PIXELFORMAT_UNKNOWN; } - static inline struct spa_pod *sdl_build_formats(SDL_RendererInfo *info, struct spa_pod_builder *b) { uint32_t i, c; @@ -178,8 +175,8 @@ static inline struct spa_pod *sdl_build_formats(SDL_RendererInfo *info, struct s spa_pod_builder_id(b, id); } /* then all the other ones SDL can convert from/to */ - for (i = 0; i < SPA_N_ELEMENTS(sdl_video_formats); i++) { - uint32_t id = sdl_video_formats[i].id; + SPA_FOR_EACH_ELEMENT_VAR(sdl_video_formats, f) { + uint32_t id = f->id; if (id != SPA_VIDEO_FORMAT_UNKNOWN) spa_pod_builder_id(b, id); } diff --git a/src/examples/video-play-fixate.c b/src/examples/video-play-fixate.c index 1badc3c39a8d60877b642a535419222122c3217a..021eb0df57f0f857cbdbe1f8845f24942c1e1c79 100644 --- a/src/examples/video-play-fixate.c +++ b/src/examples/video-play-fixate.c @@ -340,6 +340,10 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) pw_stream_set_error(stream, -EINVAL, "unknown pixel format"); return; } + if (data->size.width == 0 || data->size.height == 0) { + pw_stream_set_error(stream, -EINVAL, "invalid size"); + return; + } data->texture = SDL_CreateTexture(data->renderer, sdl_format, diff --git a/src/examples/video-play-pull.c b/src/examples/video-play-pull.c index 2b2c34660c1275ef98c1698b9aa4bf8b2be78897..8076779629b4e2c66a56b06f930b04298e570263 100644 --- a/src/examples/video-play-pull.c +++ b/src/examples/video-play-pull.c @@ -388,6 +388,10 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) pw_stream_set_error(stream, -EINVAL, "unknown pixel format"); return; } + if (data->size.width == 0 || data->size.height == 0) { + pw_stream_set_error(stream, -EINVAL, "invalid size"); + return; + } data->texture = SDL_CreateTexture(data->renderer, sdl_format, diff --git a/src/examples/video-play-reneg.c b/src/examples/video-play-reneg.c index aa071850afad34a68860867ba8161a9a891ddd67..f37b66285e6485a488cf93e912a5584f33f7e79c 100644 --- a/src/examples/video-play-reneg.c +++ b/src/examples/video-play-reneg.c @@ -233,6 +233,10 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) pw_stream_set_error(stream, -EINVAL, "unknown pixel format"); return; } + if (data->size.width == 0 || data->size.height == 0) { + pw_stream_set_error(stream, -EINVAL, "invalid size"); + return; + } data->texture = SDL_CreateTexture(data->renderer, sdl_format, diff --git a/src/examples/video-play.c b/src/examples/video-play.c index 68589c90218db9c5a1600893f60fb69f9df95194..61c3114f798d85f4c7da36b9c339c2fa8b6e96d5 100644 --- a/src/examples/video-play.c +++ b/src/examples/video-play.c @@ -336,6 +336,10 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) pw_stream_set_error(stream, -EINVAL, "unknown pixel format"); return; } + if (data->size.width == 0 || data->size.height == 0) { + pw_stream_set_error(stream, -EINVAL, "invalid size"); + return; + } data->texture = SDL_CreateTexture(data->renderer, sdl_format, diff --git a/src/gst/gstpipewiredeviceprovider.c b/src/gst/gstpipewiredeviceprovider.c index 0d2942d77bd7af544be5c9f19482fec06c9aa1cf..5dcce190e8c57c8c152363c1aeb12e315e026c2e 100644 --- a/src/gst/gstpipewiredeviceprovider.c +++ b/src/gst/gstpipewiredeviceprovider.c @@ -491,6 +491,8 @@ static void registry_event_global(void *data, uint32_t id, uint32_t permissions, gst_device_provider_hide_provider (provider, "pulsedeviceprovider"); else if (g_str_has_prefix(str, "v4l2:")) gst_device_provider_hide_provider (provider, "v4l2deviceprovider"); + else if (g_str_has_prefix(str, "libcamera:")) + gst_device_provider_hide_provider (provider, "libcameraprovider"); } } diff --git a/src/modules/meson.build b/src/modules/meson.build index 3e0e3a3fdccd890fcdfc83da5c7db5e33cf3efba..c267e681cd0437852b09b9c56c11bbb57a80501e 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -26,6 +26,8 @@ module_sources = [ 'module-rt.c', 'module-raop-discover.c', 'module-raop-sink.c', + 'module-rtp-source.c', + 'module-rtp-sink.c', 'module-session-manager.c', 'module-zeroconf-discover.c', 'module-roc-source.c', @@ -59,33 +61,36 @@ simd_cargs = [] simd_dependencies = [] if have_sse - pffft_sse = static_library('pffft_sse', - ['module-filter-chain/pffft.c' ], + filter_chain_sse = static_library('filter_chain_sse', + ['module-filter-chain/pffft.c', + 'module-filter-chain/dsp-ops-sse.c' ], c_args : [sse_args, '-O3', '-DHAVE_SSE'], dependencies : [ spa_dep ], install : false ) simd_cargs += ['-DHAVE_SSE'] - simd_dependencies += pffft_sse + simd_dependencies += filter_chain_sse endif if have_neon - pffft_neon = static_library('pffft_neon', + filter_chain_neon = static_library('filter_chain_neon', ['module-filter-chain/pffft.c' ], c_args : [neon_args, '-O3', '-DHAVE_NEON'], dependencies : [ spa_dep ], install : false ) simd_cargs += ['-DHAVE_NEON'] - simd_dependencies += pffft_neon + simd_dependencies += filter_chain_neon endif -pffft_c = static_library('pffft_c', - ['module-filter-chain/pffft.c' ], +filter_chain_c = static_library('filter_chain_c', + ['module-filter-chain/pffft.c', + 'module-filter-chain/dsp-ops.c', + 'module-filter-chain/dsp-ops-c.c' ], c_args : [simd_cargs, '-O3', '-DPFFFT_SIMD_DISABLE'], dependencies : [ spa_dep ], install : false ) -simd_dependencies += pffft_c +simd_dependencies += filter_chain_c filter_chain_sources = [ 'module-filter-chain.c', @@ -258,6 +263,8 @@ pipewire_module_protocol_pulse_sources = [ 'module-protocol-pulse/modules/module-roc-sink.c', 'module-protocol-pulse/modules/module-roc-sink-input.c', 'module-protocol-pulse/modules/module-roc-source.c', + 'module-protocol-pulse/modules/module-rtp-recv.c', + 'module-protocol-pulse/modules/module-rtp-send.c', 'module-protocol-pulse/modules/module-simple-protocol-tcp.c', 'module-protocol-pulse/modules/module-switch-on-connect.c', 'module-protocol-pulse/modules/module-tunnel-sink.c', @@ -482,6 +489,24 @@ summary({'raop-sink (requires OpenSSL)': build_module_raop}, bool_yn: true, sect roc_lib = cc.find_library('roc', has_headers: ['roc/config.h' ], required: get_option('roc')) summary({'ROC': roc_lib.found()}, bool_yn: true, section: 'Streaming between daemons') +pipewire_module_rtp_source = shared_library('pipewire-module-rtp-source', + [ 'module-rtp-source.c' ], + include_directories : [configinc], + install : true, + install_dir : modules_install_dir, + install_rpath: modules_install_dir, + dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep], +) + +pipewire_module_rtp_sink = shared_library('pipewire-module-rtp-sink', + [ 'module-rtp-sink.c' ], + include_directories : [configinc], + install : true, + install_dir : modules_install_dir, + install_rpath: modules_install_dir, + dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep], +) + build_module_roc = roc_lib.found() if build_module_roc pipewire_module_roc_sink = shared_library('pipewire-module-roc-sink', diff --git a/src/modules/module-avb/acmp.c b/src/modules/module-avb/acmp.c index 18ea1ba92c3948a09579f6e75a3a146c56be1d42..a7a409a27988531c50d0e4d0cbecfc1218b39aa5 100644 --- a/src/modules/module-avb/acmp.c +++ b/src/modules/module-avb/acmp.c @@ -345,11 +345,10 @@ static const struct msg_info msg_info[] = { static inline const struct msg_info *find_msg_info(uint16_t type, const char *name) { - uint32_t i; - for (i = 0; i < SPA_N_ELEMENTS(msg_info); i++) { - if ((name == NULL && type == msg_info[i].type) || - (name != NULL && spa_streq(name, msg_info[i].name))) - return &msg_info[i]; + SPA_FOR_EACH_ELEMENT_VAR(msg_info, i) { + if ((name == NULL && type == i->type) || + (name != NULL && spa_streq(name, i->name))) + return i; } return NULL; } diff --git a/src/modules/module-avb/aecp-aem.c b/src/modules/module-avb/aecp-aem.c index 22a2ac2dd7ec4761626034bd563115eddf4ffe0c..d1913301b359c320f428d9f7001aee9813aef53b 100644 --- a/src/modules/module-avb/aecp-aem.c +++ b/src/modules/module-avb/aecp-aem.c @@ -250,11 +250,10 @@ static const struct cmd_info cmd_info[] = { static inline const struct cmd_info *find_cmd_info(uint16_t type, const char *name) { - uint32_t i; - for (i = 0; i < SPA_N_ELEMENTS(cmd_info); i++) { - if ((name == NULL && type == cmd_info[i].type) || - (name != NULL && spa_streq(name, cmd_info[i].name))) - return &cmd_info[i]; + SPA_FOR_EACH_ELEMENT_VAR(cmd_info, i) { + if ((name == NULL && type == i->type) || + (name != NULL && spa_streq(name, i->name))) + return i; } return NULL; } diff --git a/src/modules/module-avb/aecp.c b/src/modules/module-avb/aecp.c index 3c25b0ea31ab81accea0520926869f1ffac01b38..d581f818bae2dca6fb5f96e0b05b49f7dca7d682 100644 --- a/src/modules/module-avb/aecp.c +++ b/src/modules/module-avb/aecp.c @@ -67,11 +67,10 @@ static const struct msg_info msg_info[] = { static inline const struct msg_info *find_msg_info(uint16_t type, const char *name) { - uint32_t i; - for (i = 0; i < SPA_N_ELEMENTS(msg_info); i++) { - if ((name == NULL && type == msg_info[i].type) || - (name != NULL && spa_streq(name, msg_info[i].name))) - return &msg_info[i]; + SPA_FOR_EACH_ELEMENT_VAR(msg_info, i) { + if ((name == NULL && type == i->type) || + (name != NULL && spa_streq(name, i->name))) + return i; } return NULL; } diff --git a/src/modules/module-avb/stream.c b/src/modules/module-avb/stream.c index b86f0814c36452907bd1df197b74a4bdd799852f..0f5b148e8367e83d73b02dca138382d26fbcac2b 100644 --- a/src/modules/module-avb/stream.c +++ b/src/modules/module-avb/stream.c @@ -136,7 +136,7 @@ static int flush_write(struct stream *stream, uint64_t current_time) p->timestamp = ptime; p->dbc = dbc; - n = sendmsg(stream->source->fd, &stream->msg, 0); + n = sendmsg(stream->source->fd, &stream->msg, MSG_NOSIGNAL); if (n < 0 || n != (ssize_t)stream->pdu_size) { pw_log_error("sendmsg() failed %zd != %zd: %m", n, stream->pdu_size); diff --git a/src/modules/module-client-node/client-node.c b/src/modules/module-client-node/client-node.c index 9357d03da9873fcc9e62ced8900cc7552d617bd6..563585553e202d74ae6a43cda1cd3424706bd2b1 100644 --- a/src/modules/module-client-node/client-node.c +++ b/src/modules/module-client-node/client-node.c @@ -744,7 +744,7 @@ do_port_use_buffers(struct impl *impl, struct node *this = &impl->node; struct port *p; struct mix *mix; - uint32_t i, j, peer_id; + uint32_t i, j; struct pw_client_node_buffer *mb; p = GET_PORT(this, direction, port_id); @@ -762,8 +762,6 @@ do_port_use_buffers(struct impl *impl, if ((mix = find_mix(p, mix_id)) == NULL || !mix->valid) return -EINVAL; - peer_id = mix->peer_id; - if (direction == SPA_DIRECTION_OUTPUT) { mix_id = SPA_ID_INVALID; if ((mix = find_mix(p, mix_id)) == NULL || !mix->valid) @@ -881,10 +879,6 @@ do_port_use_buffers(struct impl *impl, } mix->n_buffers = n_buffers; - if (this->resource->version >= 4) - pw_client_node_resource_port_set_mix_info(this->resource, - direction, port_id, mix_id, - peer_id, NULL); return pw_client_node_resource_port_use_buffers(this->resource, direction, port_id, mix_id, flags, n_buffers, mb); @@ -1486,6 +1480,7 @@ static int impl_mix_port_set_io(void *object, struct port *p = object; struct pw_impl_port *port = p->port; struct impl *impl = port->owner_data; + struct node *this = &impl->node; struct pw_impl_port_mix *mix; mix = pw_map_lookup(&port->mix_port_map, mix_id); @@ -1497,6 +1492,11 @@ static int impl_mix_port_set_io(void *object, mix->io = data; else mix->io = NULL; + + if (mix->io != NULL && this->resource && this->resource->version >= 4) + pw_client_node_resource_port_set_mix_info(this->resource, + direction, port->port_id, + mix->port.port_id, mix->peer_id, NULL); } return do_port_set_io(impl, @@ -1558,14 +1558,14 @@ static void node_port_added(void *data, struct pw_impl_port *port) struct impl *impl = data; struct port *p = pw_impl_port_get_user_data(port); - pw_impl_port_set_mix(port, &p->mix_node, - PW_IMPL_PORT_MIX_FLAG_MULTI | - PW_IMPL_PORT_MIX_FLAG_MIX_ONLY); - port->flags |= PW_IMPL_PORT_FLAG_NO_MIXER; port->impl = SPA_CALLBACKS_INIT(&port_impl, p); port->owner_data = impl; + + pw_impl_port_set_mix(port, &p->mix_node, + PW_IMPL_PORT_MIX_FLAG_MULTI | + PW_IMPL_PORT_MIX_FLAG_MIX_ONLY); } static void node_port_removed(void *data, struct pw_impl_port *port) diff --git a/src/modules/module-client-node/protocol-native.c b/src/modules/module-client-node/protocol-native.c index 0b640b2519b187f9f9851967a9444d79a76fc43f..dd516928eebfa820bfab647c70b908d1daf100f8 100644 --- a/src/modules/module-client-node/protocol-native.c +++ b/src/modules/module-client-node/protocol-native.c @@ -556,6 +556,8 @@ static int client_node_demarshal_port_use_buffers(void *data, const struct pw_pr SPA_POD_Id(&m->type), SPA_POD_Int(&m->size), NULL) < 0) return -EINVAL; + + m->data = NULL; } if (spa_pod_parser_get(&prs, SPA_POD_Int(&buf->n_datas), NULL) < 0) @@ -576,7 +578,9 @@ static int client_node_demarshal_port_use_buffers(void *data, const struct pw_pr SPA_POD_Int(&d->maxsize), NULL) < 0) return -EINVAL; + d->fd = -1; d->data = SPA_UINT32_TO_PTR(data_id); + d->chunk = NULL; } } pw_proxy_notify(proxy, struct pw_client_node_events, port_use_buffers, 0, diff --git a/src/modules/module-client-node/v0/client-node.c b/src/modules/module-client-node/v0/client-node.c index aeb5931c6552fd980fb2dab3b4554abf0684ddf1..747a4ad4a2f968268c8e84c90d28304fc21be20d 100644 --- a/src/modules/module-client-node/v0/client-node.c +++ b/src/modules/module-client-node/v0/client-node.c @@ -1333,13 +1333,12 @@ static void convert_properties(struct pw_properties *properties) { "pipewire.target.node", PW_KEY_NODE_TARGET, } }; - uint32_t i; const char *str; - for(i = 0; i < SPA_N_ELEMENTS(props); i++) { - if ((str = pw_properties_get(properties, props[i].from)) != NULL) { - pw_properties_set(properties, props[i].to, str); - pw_properties_set(properties, props[i].from, NULL); + SPA_FOR_EACH_ELEMENT_VAR(props, p) { + if ((str = pw_properties_get(properties, p->from)) != NULL) { + pw_properties_set(properties, p->to, str); + pw_properties_set(properties, p->from, NULL); } } } diff --git a/src/modules/module-client-node/v0/ext-client-node.h b/src/modules/module-client-node/v0/ext-client-node.h index 564df77b65ec40ff10b3e7fa250c44986f512e65..21ba59718bd9aa4d592f21ef21bc0aa50d8e447a 100644 --- a/src/modules/module-client-node/v0/ext-client-node.h +++ b/src/modules/module-client-node/v0/ext-client-node.h @@ -140,9 +140,9 @@ struct pw_client_node0_message_port_reuse_buffer { #define PW_CLIENT_NODE0_MESSAGE_TYPE(message) (((struct pw_client_node0_message*)(message))->body.type.value) -#define PW_CLIENT_NODE0_MESSAGE_INIT(message) (struct pw_client_node0_message) \ +#define PW_CLIENT_NODE0_MESSAGE_INIT(message) ((struct pw_client_node0_message) \ { { { sizeof(struct pw_client_node0_message_body), SPA_TYPE_Struct } }, \ - { SPA_POD_INIT_Int(message) } } + { SPA_POD_INIT_Int(message) } }) #define PW_CLIENT_NODE0_MESSAGE_INIT_FULL(type,size,message,...) (type) \ { { { size, SPA_TYPE_Struct } }, \ diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index 42caf780bcd01fd4a4a2c190f6db9811fcbb2446..7e0d81a79fe9f2c9fa8136032006a5b735f38aa4 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -43,6 +43,7 @@ #include <spa/param/audio/raw.h> #include <spa/param/profiler.h> #include <spa/pod/builder.h> +#include <spa/pod/dynamic.h> #include <spa/support/plugin.h> #include <spa/utils/json.h> #include <spa/utils/names.h> @@ -196,15 +197,18 @@ struct impl { uint32_t max_buffer_size; uint32_t buffer_delay; + uint32_t current_delay; struct spa_handle *spa_handle; struct spa_plugin_loader *loader; + + bool monitor_mode; }; static void process(struct impl *impl) { struct pw_buffer *cout; - struct pw_buffer *pout; + struct pw_buffer *pout = NULL; float rec_buf[impl->info.channels][impl->aec_blocksize / sizeof(float)]; float play_buf[impl->info.channels][impl->aec_blocksize / sizeof(float)]; float play_delayed_buf[impl->info.channels][impl->aec_blocksize / sizeof(float)]; @@ -218,7 +222,7 @@ static void process(struct impl *impl) uint32_t rindex, pindex, oindex, pdindex, avail; int32_t stride = 0; - if ((pout = pw_stream_dequeue_buffer(impl->playback)) == NULL) { + if (impl->playback != NULL && (pout = pw_stream_dequeue_buffer(impl->playback)) == NULL) { pw_log_debug("out of playback buffers: %m"); goto done; } @@ -256,23 +260,50 @@ static void process(struct impl *impl) impl->play_ringsize, pdindex % impl->play_ringsize, (void *)play_delayed[i], size); - /* output to sink, just copy */ - dd = &pout->buffer->datas[i]; - memcpy(dd->data, play[i], size); + if (pout != NULL) { + /* output to sink, just copy */ + dd = &pout->buffer->datas[i]; + memcpy(dd->data, play[i], size); - dd->chunk->offset = 0; - dd->chunk->size = size; - dd->chunk->stride = stride; + dd->chunk->offset = 0; + dd->chunk->size = size; + dd->chunk->stride = stride; + } } spa_ringbuffer_read_update(&impl->rec_ring, rindex + size); spa_ringbuffer_read_update(&impl->play_ring, pindex + size); spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + size); - pw_stream_queue_buffer(impl->playback, pout); + if (impl->playback != NULL) + pw_stream_queue_buffer(impl->playback, pout); - /* Now run the canceller */ - spa_audio_aec_run(impl->aec, rec, play_delayed, out, size / sizeof(float)); + if (SPA_UNLIKELY (impl->current_delay < impl->buffer_delay)) { + uint32_t delay_left = impl->buffer_delay - impl->current_delay; + uint32_t silence_size; + + /* don't run the canceller until play_buffer has been filled, + * copy silence to output in the meantime */ + silence_size = SPA_MIN(size, delay_left * sizeof(float)); + for (i = 0; i < impl->info.channels; i++) + memset(out[i], 0, silence_size); + impl->current_delay += silence_size / sizeof(float); + pw_log_debug("current_delay %d", impl->current_delay); + + if (silence_size != size) { + const float *pd[impl->info.channels]; + float *o[impl->info.channels]; + + for (i = 0; i < impl->info.channels; i++) { + pd[i] = play_delayed[i] + delay_left; + o[i] = out[i] + delay_left; + } + spa_audio_aec_run(impl->aec, 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)); + } /* Next, copy over the output to the output ringbuffer */ avail = spa_ringbuffer_get_write_index(&impl->out_ring, &oindex); @@ -479,14 +510,55 @@ static void input_param_latency_changed(struct impl *impl, const struct spa_pod 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 f[2]; + spa_pod_builder_push_object( + b, &f[0], SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); + spa_pod_builder_prop(b, SPA_PROP_params, 0); + spa_pod_builder_push_struct(b, &f[1]); + + spa_audio_aec_get_params(impl->aec, b); + + spa_pod_builder_pop(b, &f[1]); + return spa_pod_builder_pop(b, &f[0]); + } + return NULL; +} -static void input_param_changed(void *data, uint32_t id, const struct spa_pod *param) +static void input_param_changed(void *data, uint32_t id, const struct spa_pod* param) { - struct impl *impl = data; + 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_Latency: input_param_latency_changed(impl, param); break; + case SPA_PARAM_Props: + if (param != NULL) { + uint8_t buffer[1024]; + struct spa_pod_dynamic_builder b; + const struct spa_pod* params[1]; + SPA_POD_OBJECT_FOREACH(obj, prop) + { + if (prop->key == SPA_PROP_params) { + spa_audio_aec_set_params(impl->aec, &prop->value); + } + } + + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + params[0] = get_props_param(impl, &b.b); + if (params[0]) { + pw_stream_update_params(impl->capture, params, 1); + pw_stream_update_params(impl->playback, params, 1); + } + spa_pod_dynamic_builder_clean(&b); + } else { + pw_log_warn("param is null"); + } + break; } } @@ -519,7 +591,11 @@ static void output_state_changed(void *data, enum pw_stream_state old, switch (state) { case PW_STREAM_STATE_PAUSED: pw_stream_flush(impl->sink, false); - pw_stream_flush(impl->playback, false); + if (impl->playback != NULL) + pw_stream_flush(impl->playback, false); + if (old == PW_STREAM_STATE_STREAMING) { + impl->current_delay = 0; + } break; case PW_STREAM_STATE_UNCONNECTED: pw_log_info("%p: output unconnected", impl); @@ -548,17 +624,40 @@ static void output_param_latency_changed(struct impl *impl, const struct spa_pod if (latency.direction == SPA_DIRECTION_INPUT) pw_stream_update_params(impl->sink, params, 1); - else + else if (impl->playback != NULL) pw_stream_update_params(impl->playback, params, 1); } static void output_param_changed(void *data, uint32_t id, const struct spa_pod *param) { + 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_Latency: output_param_latency_changed(impl, param); break; + case SPA_PARAM_Props: + if (param != NULL) { + uint8_t buffer[1024]; + struct spa_pod_dynamic_builder b; + const struct spa_pod* params[1]; + + SPA_POD_OBJECT_FOREACH(obj, prop) + { + if (prop->key == SPA_PROP_params) { + spa_audio_aec_set_params(impl->aec, &prop->value); + } + } + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + params[0] = get_props_param(impl, &b.b); + if (params[0] != NULL) { + pw_stream_update_params(impl->capture, params, 1); + pw_stream_update_params(impl->playback, params, 1); + } + spa_pod_dynamic_builder_clean(&b); + } + break; } } @@ -635,8 +734,10 @@ static void sink_process(void *data) static void playback_destroy(void *d) { struct impl *impl = d; - spa_hook_remove(&impl->playback_listener); - impl->playback = NULL; + if (impl->playback != NULL) { + spa_hook_remove(&impl->playback_listener); + impl->playback = NULL; + } } static const struct pw_stream_events playback_events = { @@ -657,9 +758,9 @@ static int setup_streams(struct impl *impl) { int res; uint32_t n_params, i; - const struct spa_pod *params[1]; - uint8_t buffer[1024]; - struct spa_pod_builder b; + uint32_t offsets[512]; + const struct spa_pod *params[512]; + struct spa_pod_dynamic_builder b; struct pw_properties *props; const char *str; uint32_t index; @@ -681,6 +782,8 @@ static int setup_streams(struct impl *impl) pw_properties_set(props, SPA_KEY_AUDIO_CHANNELS, str); if ((str = pw_properties_get(impl->source_props, SPA_KEY_AUDIO_POSITION)) != NULL) pw_properties_set(props, SPA_KEY_AUDIO_POSITION, str); + if ((str = pw_properties_get(impl->source_props, "resample.prefill")) != NULL) + pw_properties_set(props, "resample.prefill", str); impl->capture = pw_stream_new(impl->core, "Echo-Cancel Capture", props); @@ -701,32 +804,38 @@ static int setup_streams(struct impl *impl) &impl->source_listener, &source_events, impl); - props = pw_properties_new( + if (impl->monitor_mode) { + impl->playback = NULL; + } else { + props = pw_properties_new( PW_KEY_NODE_NAME, "echo-cancel-playback", PW_KEY_NODE_VIRTUAL, "true", PW_KEY_NODE_PASSIVE, "true", NULL); - if ((str = pw_properties_get(impl->sink_props, PW_KEY_NODE_GROUP)) != NULL) - pw_properties_set(props, PW_KEY_NODE_GROUP, str); - if ((str = pw_properties_get(impl->sink_props, PW_KEY_NODE_LINK_GROUP)) != NULL) - pw_properties_set(props, PW_KEY_NODE_LINK_GROUP, str); - if ((str = pw_properties_get(impl->sink_props, PW_KEY_NODE_LATENCY)) != NULL) - pw_properties_set(props, PW_KEY_NODE_LATENCY, str); - else if (impl->aec->latency) - pw_properties_set(props, PW_KEY_NODE_LATENCY, impl->aec->latency); - if ((str = pw_properties_get(impl->sink_props, SPA_KEY_AUDIO_CHANNELS)) != NULL) - pw_properties_set(props, SPA_KEY_AUDIO_CHANNELS, str); - if ((str = pw_properties_get(impl->sink_props, SPA_KEY_AUDIO_POSITION)) != NULL) - pw_properties_set(props, SPA_KEY_AUDIO_POSITION, str); - - impl->playback = pw_stream_new(impl->core, - "Echo-Cancel Playback", props); - if (impl->playback == NULL) - return -errno; - - pw_stream_add_listener(impl->playback, - &impl->playback_listener, - &playback_events, impl); + if ((str = pw_properties_get(impl->sink_props, PW_KEY_NODE_GROUP)) != NULL) + pw_properties_set(props, PW_KEY_NODE_GROUP, str); + if ((str = pw_properties_get(impl->sink_props, PW_KEY_NODE_LINK_GROUP)) != NULL) + pw_properties_set(props, PW_KEY_NODE_LINK_GROUP, str); + if ((str = pw_properties_get(impl->sink_props, PW_KEY_NODE_LATENCY)) != NULL) + pw_properties_set(props, PW_KEY_NODE_LATENCY, str); + else if (impl->aec->latency) + pw_properties_set(props, PW_KEY_NODE_LATENCY, impl->aec->latency); + if ((str = pw_properties_get(impl->sink_props, SPA_KEY_AUDIO_CHANNELS)) != NULL) + pw_properties_set(props, SPA_KEY_AUDIO_CHANNELS, str); + if ((str = pw_properties_get(impl->sink_props, SPA_KEY_AUDIO_POSITION)) != NULL) + pw_properties_set(props, SPA_KEY_AUDIO_POSITION, str); + if ((str = pw_properties_get(impl->sink_props, "resample.prefill")) != NULL) + pw_properties_set(props, "resample.prefill", str); + + impl->playback = pw_stream_new(impl->core, + "Echo-Cancel Playback", props); + if (impl->playback == NULL) + return -errno; + + pw_stream_add_listener(impl->playback, + &impl->playback_listener, + &playback_events, impl); + } impl->sink = pw_stream_new(impl->core, "Echo-Cancel Sink", impl->sink_props); @@ -739,9 +848,23 @@ static int setup_streams(struct impl *impl) &sink_events, impl); n_params = 0; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, - &impl->info); + spa_pod_dynamic_builder_init(&b, NULL, 0, 4096); + + offsets[n_params++] = b.b.state.offset; + spa_format_audio_raw_build(&b.b, SPA_PARAM_EnumFormat, &impl->info); + + int nbr_of_external_props = spa_audio_aec_enum_props(impl->aec, 0, NULL); + if (nbr_of_external_props > 0) { + for (int i = 0; i < nbr_of_external_props; i++) { + offsets[n_params++] = b.b.state.offset; + spa_audio_aec_enum_props(impl->aec, i, &b.b); + } + get_props_param(impl, &b.b); + } + + for (i = 0; i < n_params; i++) { + params[i] = spa_pod_builder_deref(&b.b, offsets[i]); + } if ((res = pw_stream_connect(impl->capture, PW_DIRECTION_INPUT, @@ -749,33 +872,44 @@ static int setup_streams(struct impl *impl) PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, - params, n_params)) < 0) + params, n_params)) < 0) { + spa_pod_dynamic_builder_clean(&b); return res; + } if ((res = pw_stream_connect(impl->source, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, - params, n_params)) < 0) + params, n_params)) < 0) { + spa_pod_dynamic_builder_clean(&b); return res; + } if ((res = pw_stream_connect(impl->sink, PW_DIRECTION_INPUT, PW_ID_ANY, - PW_STREAM_FLAG_MAP_BUFFERS | - PW_STREAM_FLAG_RT_PROCESS, - params, n_params)) < 0) + impl->playback != NULL ? + PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS : + PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, + params, n_params)) < 0) { + spa_pod_dynamic_builder_clean(&b); return res; + } - if ((res = pw_stream_connect(impl->playback, + if (impl->playback != NULL && (res = pw_stream_connect(impl->playback, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, - params, n_params)) < 0) + params, n_params)) < 0) { + spa_pod_dynamic_builder_clean(&b); return res; + } + + spa_pod_dynamic_builder_clean(&b); impl->rec_ringsize = sizeof(float) * impl->max_buffer_size * impl->info.rate / 1000; impl->play_ringsize = sizeof(float) * ((impl->max_buffer_size * impl->info.rate / 1000) + impl->buffer_delay); @@ -963,6 +1097,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) goto error; } + impl->monitor_mode = false; + if ((str = pw_properties_get(props, "monitor.mode")) != NULL) + impl->monitor_mode = pw_properties_parse_bool(str); + impl->module = module; impl->context = context; @@ -986,13 +1124,23 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_properties_set(impl->source_props, PW_KEY_NODE_DESCRIPTION, "Echo-Cancel Source"); if (pw_properties_get(impl->source_props, PW_KEY_MEDIA_CLASS) == NULL) pw_properties_set(impl->source_props, PW_KEY_MEDIA_CLASS, "Audio/Source"); + if (pw_properties_get(impl->source_props, "resample.prefill") == NULL) + pw_properties_set(impl->source_props, "resample.prefill", "true"); if (pw_properties_get(impl->sink_props, PW_KEY_NODE_NAME) == NULL) pw_properties_set(impl->sink_props, PW_KEY_NODE_NAME, "echo-cancel-sink"); if (pw_properties_get(impl->sink_props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_set(impl->sink_props, PW_KEY_NODE_DESCRIPTION, "Echo-Cancel Sink"); if (pw_properties_get(impl->sink_props, PW_KEY_MEDIA_CLASS) == NULL) - pw_properties_set(impl->sink_props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); + pw_properties_set(impl->sink_props, PW_KEY_MEDIA_CLASS, + impl->monitor_mode ? "Stream/Input/Audio" : "Audio/Sink"); + if (pw_properties_get(impl->sink_props, "resample.prefill") == NULL) + pw_properties_set(impl->sink_props, "resample.prefill", "true"); + if (impl->monitor_mode) { + pw_properties_set(impl->sink_props, PW_KEY_NODE_PASSIVE, "true"); + pw_properties_set(impl->sink_props, PW_KEY_STREAM_MONITOR, "true"); + pw_properties_set(impl->sink_props, PW_KEY_STREAM_CAPTURE_SINK, "true"); + } if ((str = pw_properties_get(props, "aec.method")) != NULL) pw_log_warn("aec.method is not supported anymore use library.name"); diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index bacedf6ef75f199973fb0bd4725cb3746367c217..a8f89e394caa8af107baae178a9bb5a91bb00baf 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -519,13 +519,13 @@ struct link { struct graph_port { const struct fc_descriptor *desc; - void *hndl; + void **hndl; uint32_t port; }; struct graph_hndl { const struct fc_descriptor *desc; - void *hndl; + void **hndl; }; struct graph { @@ -577,6 +577,10 @@ struct impl { struct graph graph; }; +static int graph_instantiate(struct graph *graph); +static void graph_cleanup(struct graph *graph); + + static void capture_destroy(void *d) { struct impl *impl = d; @@ -585,20 +589,26 @@ static void capture_destroy(void *d) } static void capture_process(void *d) +{ + struct impl *impl = d; + pw_stream_trigger_process(impl->playback); +} + +static void playback_process(void *d) { struct impl *impl = d; struct pw_buffer *in, *out; struct graph *graph = &impl->graph; - uint32_t i, outsize = 0, n_hndl = graph->n_hndl; + uint32_t i, insize = 0, outsize = 0, n_hndl = graph->n_hndl; int32_t stride = 0; struct graph_port *port; struct spa_data *bd; if ((in = pw_stream_dequeue_buffer(impl->capture)) == NULL) - pw_log_debug("out of capture buffers: %m"); + pw_log_debug("%p: out of capture buffers: %m", impl); if ((out = pw_stream_dequeue_buffer(impl->playback)) == NULL) - pw_log_debug("out of playback buffers: %m"); + pw_log_debug("%p: out of playback buffers: %m", impl); if (in == NULL || out == NULL) goto done; @@ -614,12 +624,14 @@ static void capture_process(void *d) port = i < graph->n_input ? &graph->input[i] : NULL; if (port && port->desc) - port->desc->connect_port(port->hndl, port->port, + port->desc->connect_port(*port->hndl, port->port, SPA_PTROFF(bd->data, offs, void)); - outsize = i == 0 ? size : SPA_MIN(outsize, size); + insize = i == 0 ? size : SPA_MIN(insize, size); stride = SPA_MAX(stride, bd->chunk->stride); } + outsize = insize; + for (i = 0; i < out->buffer->n_datas; i++) { bd = &out->buffer->datas[i]; @@ -628,7 +640,7 @@ static void capture_process(void *d) port = i < graph->n_output ? &graph->output[i] : NULL; if (port && port->desc) - port->desc->connect_port(port->hndl, port->port, bd->data); + port->desc->connect_port(*port->hndl, port->port, bd->data); else memset(bd->data, 0, outsize); @@ -636,9 +648,13 @@ static void capture_process(void *d) bd->chunk->size = outsize; bd->chunk->stride = stride; } + + pw_log_trace_fp("%p: stride:%d in:%d out:%d requested:%"PRIu64" (%"PRIu64")", impl, + stride, insize, outsize, out->requested, out->requested * stride); + for (i = 0; i < n_hndl; i++) { struct graph_hndl *hndl = &graph->hndl[i]; - hndl->desc->run(hndl->hndl, outsize / sizeof(float)); + hndl->desc->run(*hndl->hndl, outsize / sizeof(float)); } done: @@ -646,8 +662,6 @@ done: pw_stream_queue_buffer(impl->capture, in); if (out != NULL) pw_stream_queue_buffer(impl->playback, out); - - pw_stream_trigger_process(impl->playback); } static float get_default(struct impl *impl, struct descriptor *desc, uint32_t p) @@ -746,11 +760,12 @@ static struct spa_pod *get_prop_info(struct graph *graph, struct spa_pod_builder struct fc_port *p = &d->ports[port->p]; float def, min, max; char name[512]; + uint32_t rate = impl->rate ? impl->rate : 48000; if (p->hint & FC_HINT_SAMPLE_RATE) { - def = p->def * impl->rate; - min = p->min * impl->rate; - max = p->max * impl->rate; + def = p->def * rate; + min = p->min * rate; + max = p->max * rate; } else { def = p->def; min = p->min; @@ -770,10 +785,10 @@ static struct spa_pod *get_prop_info(struct graph *graph, struct spa_pod_builder spa_pod_builder_prop(b, SPA_PROP_INFO_type, 0); if (p->hint & FC_HINT_BOOLEAN) { if (min == max) { - spa_pod_builder_bool(b, def <= 0.0 ? false : true); + spa_pod_builder_bool(b, def <= 0.0f ? false : true); } else { spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); - spa_pod_builder_bool(b, def <= 0.0 ? false : true); + spa_pod_builder_bool(b, def <= 0.0f ? false : true); spa_pod_builder_bool(b, false); spa_pod_builder_bool(b, true); spa_pod_builder_pop(b, &f[1]); @@ -829,7 +844,7 @@ static struct spa_pod *get_props_param(struct graph *graph, struct spa_pod_build spa_pod_builder_string(b, name); if (p->hint & FC_HINT_BOOLEAN) { - spa_pod_builder_bool(b, port->control_data <= 0.0 ? false : true); + spa_pod_builder_bool(b, port->control_data <= 0.0f ? false : true); } else if (p->hint & FC_HINT_INTEGER) { spa_pod_builder_int(b, port->control_data); } else { @@ -908,13 +923,14 @@ static void graph_reset(struct graph *graph) for (i = 0; i < graph->n_hndl; i++) { struct graph_hndl *hndl = &graph->hndl[i]; const struct fc_descriptor *d = hndl->desc; + if (hndl->hndl == NULL || *hndl->hndl == NULL) + continue; if (d->deactivate) - d->deactivate(hndl->hndl); + d->deactivate(*hndl->hndl); if (d->activate) - d->activate(hndl->hndl); + d->activate(*hndl->hndl); } } - static void param_props_changed(struct impl *impl, const struct spa_pod *param) { struct spa_pod_object *obj = (struct spa_pod_object *) param; @@ -986,11 +1002,25 @@ static void param_changed(void *data, uint32_t id, const struct spa_pod *param) { struct impl *impl = data; struct graph *graph = &impl->graph; + int res; switch (id) { case SPA_PARAM_Format: - if (param == NULL) - graph_reset(graph); + if (param == NULL) { + graph_cleanup(graph); + } else { + struct spa_audio_info_raw info; + spa_zero(info); + if ((res = spa_format_audio_raw_parse(param, &info)) < 0) + goto error; + if (info.rate == 0) { + res = -EINVAL; + goto error; + } + impl->rate = info.rate; + if ((res = graph_instantiate(graph)) < 0) + goto error; + } break; case SPA_PARAM_Props: if (param != NULL) @@ -1000,6 +1030,11 @@ static void param_changed(void *data, uint32_t id, const struct spa_pod *param) param_latency_changed(impl, param); break; } + return; + +error: + pw_stream_set_error(impl->capture, res, "can't start graph: %s", + spa_strerror(res)); } static const struct pw_stream_events in_stream_events = { @@ -1020,6 +1055,7 @@ static void playback_destroy(void *d) static const struct pw_stream_events out_stream_events = { PW_VERSION_STREAM_EVENTS, .destroy = playback_destroy, + .process = playback_process, .state_changed = state_changed, .param_changed = param_changed }; @@ -1148,16 +1184,19 @@ static struct plugin *plugin_load(struct impl *impl, const char *type, const cha else if (spa_streq(type, "ladspa")) { pl = load_ladspa_plugin(support, n_support, path, NULL); } -#ifdef HAVE_LILV else if (spa_streq(type, "lv2")) { +#ifdef HAVE_LILV pl = load_lv2_plugin(support, n_support, path, NULL); - } +#else + pw_log_error("filter-chain is compiled without lv2 support"); + pl = NULL; + errno = ENOTSUP; #endif - else { + } else { + pw_log_error("invalid plugin type '%s'", type); pl = NULL; errno = EINVAL; } - if (pl == NULL) goto exit; @@ -1514,10 +1553,8 @@ static int load_node(struct graph *graph, struct spa_json *json) break; } - if (spa_streq(type, "builtin")) { + if (spa_streq(type, "builtin")) snprintf(plugin, sizeof(plugin), "%s", "builtin"); - } else if (!spa_streq(type, "ladspa") && !spa_streq(type, "lv2")) - return -ENOTSUP; pw_log_info("loading type:%s plugin:%s label:%s", type, plugin, label); @@ -1537,6 +1574,10 @@ static int load_node(struct graph *graph, struct spa_json *json) node->control_port = calloc(desc->n_control, sizeof(struct port)); node->notify_port = calloc(desc->n_notify, sizeof(struct port)); + pw_log_info("loaded n_input:%d n_output:%d n_control:%d n_notify:%d", + desc->n_input, desc->n_output, + desc->n_control, desc->n_notify); + for (i = 0; i < desc->n_input; i++) { struct port *port = &node->input_port[i]; port->node = node; @@ -1581,21 +1622,51 @@ static int load_node(struct graph *graph, struct spa_json *json) return 0; } -static void node_free(struct node *node) +static void node_cleanup(struct node *node) { - uint32_t i, j; const struct fc_descriptor *d = node->desc->desc; + uint32_t i; - spa_list_remove(&node->link); for (i = 0; i < node->n_hndl; i++) { - for (j = 0; j < node->desc->n_output; j++) - free(node->output_port[j].audio_data[i]); if (node->hndl[i] == NULL) continue; if (d->deactivate) d->deactivate(node->hndl[i]); d->cleanup(node->hndl[i]); + node->hndl[i] = NULL; + } +} + +static int port_ensure_data(struct port *port, uint32_t i) +{ + float *data; + if ((data = port->audio_data[i]) == NULL) { + data = calloc(1, MAX_SAMPLES * sizeof(float)); + if (data == NULL) { + pw_log_error("cannot create port data: %m"); + return -errno; + } } + port->audio_data[i] = data; + return 0; +} + +static void port_free_data(struct port *port, uint32_t i) +{ + free(port->audio_data[i]); + port->audio_data[i] = NULL; +} + +static void node_free(struct node *node) +{ + uint32_t i, j; + + spa_list_remove(&node->link); + for (i = 0; i < node->n_hndl; i++) { + for (j = 0; j < node->desc->n_output; j++) + port_free_data(&node->output_port[j], i); + } + node_cleanup(node); descriptor_unref(node->desc); free(node->input_port); free(node->output_port); @@ -1604,61 +1675,92 @@ static void node_free(struct node *node) free(node); } -static struct node *find_next_node(struct graph *graph) +static void graph_cleanup(struct graph *graph) { struct node *node; - spa_list_for_each(node, &graph->node_list, link) { - if (node->n_deps == 0 && !node->visited) { - node->visited = true; - return node; - } - } - return NULL; + spa_list_for_each(node, &graph->node_list, link) + node_cleanup(node); } -static int setup_input_port(struct graph *graph, struct port *port) +static int graph_instantiate(struct graph *graph) { - struct descriptor *desc = port->node->desc; - const struct fc_descriptor *d = desc->desc; + struct impl *impl = graph->impl; + struct node *node; + struct port *port; struct link *link; - uint32_t i, n_hndl = port->node->n_hndl; + struct descriptor *desc; + const struct fc_descriptor *d; + uint32_t i, j; + int res; - spa_list_for_each(link, &port->link_list, input_link) { - struct port *peer = link->output; - for (i = 0; i < n_hndl; i++) { - pw_log_info("connect input port %s[%d]:%s %p", - port->node->name, i, d->ports[port->p].name, - peer->audio_data[i]); - d->connect_port(port->node->hndl[i], port->p, peer->audio_data[i]); + spa_list_for_each(node, &graph->node_list, link) { + float *sd = silence_data, *dd = discard_data; + + node_cleanup(node); + + desc = node->desc; + d = desc->desc; + if (d->flags & FC_DESCRIPTOR_SUPPORTS_NULL_DATA) + sd = dd = NULL; + + for (i = 0; i < node->n_hndl; i++) { + pw_log_info("instantiate %s %d rate:%lu", d->name, i, impl->rate); + if ((node->hndl[i] = d->instantiate(d, impl->rate, i, node->config)) == NULL) { + pw_log_error("cannot create plugin instance: %m"); + res = -errno; + goto error; + } + for (j = 0; j < desc->n_input; j++) { + port = &node->input_port[j]; + d->connect_port(node->hndl[i], port->p, sd); + + spa_list_for_each(link, &port->link_list, input_link) { + struct port *peer = link->output; + if ((res = port_ensure_data(peer, i)) < 0) + goto error; + pw_log_info("connect input port %s[%d]:%s %p", + node->name, i, d->ports[port->p].name, + peer->audio_data[i]); + d->connect_port(node->hndl[i], port->p, peer->audio_data[i]); + } + } + for (j = 0; j < desc->n_output; j++) { + port = &node->output_port[j]; + if ((res = port_ensure_data(port, i)) < 0) + goto error; + pw_log_info("connect output port %s[%d]:%s %p", + node->name, i, d->ports[port->p].name, + port->audio_data[i]); + d->connect_port(node->hndl[i], port->p, port->audio_data[i]); + } + for (j = 0; j < desc->n_control; j++) { + port = &node->control_port[j]; + d->connect_port(node->hndl[i], port->p, &port->control_data); + } + for (j = 0; j < desc->n_notify; j++) { + port = &node->notify_port[j]; + d->connect_port(node->hndl[i], port->p, &port->control_data); + } + if (d->activate) + d->activate(node->hndl[i]); } } return 0; +error: + graph_cleanup(graph); + return res; } -static int setup_output_port(struct graph *graph, struct port *port) +static struct node *find_next_node(struct graph *graph) { - struct descriptor *desc = port->node->desc; - const struct fc_descriptor *d = desc->desc; - struct link *link; - uint32_t i, n_hndl = port->node->n_hndl; - - spa_list_for_each(link, &port->link_list, output_link) { - for (i = 0; i < n_hndl; i++) { - float *data; - if ((data = port->audio_data[i]) == NULL) { - data = calloc(1, MAX_SAMPLES * sizeof(float)); - if (data == NULL) - return -errno; - } - port->audio_data[i] = data; - pw_log_info("connect output port %s[%d]:%s %p", - port->node->name, i, d->ports[port->p].name, - port->audio_data[i]); - d->connect_port(port->node->hndl[i], port->p, data); + struct node *node; + spa_list_for_each(node, &graph->node_list, link) { + if (node->n_deps == 0 && !node->visited) { + node->visited = true; + return node; } - link->input->node->n_deps--; } - return 0; + return NULL; } static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_json *outputs) @@ -1666,11 +1768,11 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ struct impl *impl = graph->impl; struct node *node, *first, *last; struct port *port; + struct link *link; struct graph_port *gp; struct graph_hndl *gh; uint32_t i, j, n_nodes, n_input, n_output, n_control, n_hndl = 0; int res; - unsigned long p; struct descriptor *desc; const struct fc_descriptor *d; char v[256]; @@ -1741,52 +1843,11 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ n_control = 0; n_nodes = 0; spa_list_for_each(node, &graph->node_list, link) { - float *sd = silence_data, *dd = discard_data; - + node->n_hndl = n_hndl; desc = node->desc; - d = desc->desc; - if (d->flags & FC_DESCRIPTOR_SUPPORTS_NULL_DATA) - sd = dd = NULL; - - for (i = 0; i < n_hndl; i++) { - pw_log_info("instantiate %s %d", d->name, i); - if ((node->hndl[i] = d->instantiate(d, &impl->rate, i, node->config)) == NULL) { - pw_log_error("cannot create plugin instance: %m"); - res = -errno; - goto error; - } - node->n_hndl = i + 1; - - for (j = 0; j < desc->n_input; j++) { - p = desc->input[j]; - d->connect_port(node->hndl[i], p, sd); - } - for (j = 0; j < desc->n_output; j++) { - p = desc->output[j]; - d->connect_port(node->hndl[i], p, dd); - } - for (j = 0; j < desc->n_control; j++) { - port = &node->control_port[j]; - d->connect_port(node->hndl[i], port->p, &port->control_data); - } - for (j = 0; j < desc->n_notify; j++) { - port = &node->notify_port[j]; - d->connect_port(node->hndl[i], port->p, &port->control_data); - } - if (d->activate) - d->activate(node->hndl[i]); - } n_control += desc->n_control; n_nodes++; } - pw_log_info("suggested rate:%lu capture:%d playback:%d", impl->rate, - impl->capture_info.rate, impl->playback_info.rate); - - if (impl->capture_info.rate == 0) - impl->capture_info.rate = impl->rate; - if (impl->playback_info.rate == 0) - impl->playback_info.rate = impl->rate; - graph->n_input = 0; graph->input = calloc(n_input * n_hndl, sizeof(struct graph_port)); graph->n_output = 0; @@ -1802,7 +1863,7 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ pw_log_info("input port %s[%d]:%s", first->name, i, d->ports[desc->input[j]].name); gp->desc = d; - gp->hndl = first->hndl[i]; + gp->hndl = &first->hndl[i]; gp->port = desc->input[j]; } } else { @@ -1836,7 +1897,7 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ port->node->name, i, d->ports[port->p].name); port->external = graph->n_input; gp->desc = d; - gp->hndl = port->node->hndl[i]; + gp->hndl = &port->node->hndl[i]; gp->port = port->p; } graph->n_input++; @@ -1850,7 +1911,7 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ pw_log_info("output port %s[%d]:%s", last->name, i, d->ports[desc->output[j]].name); gp->desc = d; - gp->hndl = last->hndl[i]; + gp->hndl = &last->hndl[i]; gp->port = desc->output[j]; } } else { @@ -1884,7 +1945,7 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ port->node->name, i, d->ports[port->p].name); port->external = graph->n_output; gp->desc = d; - gp->hndl = port->node->hndl[i]; + gp->hndl = &port->node->hndl[i]; gp->port = port->p; } graph->n_output++; @@ -1904,17 +1965,16 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ desc = node->desc; d = desc->desc; - for (i = 0; i < desc->n_input; i++) - setup_input_port(graph, &node->input_port[i]); - for (i = 0; i < n_hndl; i++) { gh = &graph->hndl[graph->n_hndl++]; - gh->hndl = node->hndl[i]; + gh->hndl = &node->hndl[i]; gh->desc = d; - } - for (i = 0; i < desc->n_output; i++) - setup_output_port(graph, &node->output_port[i]); + } + for (i = 0; i < desc->n_output; i++) { + spa_list_for_each(link, &node->output_port[i].link_list, output_link) + link->input->node->n_deps--; + } /* collect all control ports on the graph */ for (i = 0; i < desc->n_control; i++) { @@ -1922,17 +1982,8 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ graph->n_control++; } } - return 0; - + res = 0; error: - spa_list_for_each(node, &graph->node_list, link) { - for (i = 0; i < node->n_hndl; i++) { - if (node->hndl[i] != NULL) - node->desc->desc->cleanup(node->hndl[i]); - node->hndl[i] = NULL; - } - node->n_hndl = 0; - } return res; } @@ -2063,12 +2114,20 @@ static const struct pw_proxy_events core_proxy_events = { static void impl_destroy(struct impl *impl) { + /* disconnect both streams before destroying any of them */ + if (impl->capture) + pw_stream_disconnect(impl->capture); + if (impl->playback) + pw_stream_disconnect(impl->playback); + if (impl->capture) pw_stream_destroy(impl->capture); if (impl->playback) pw_stream_destroy(impl->playback); + if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); + pw_properties_free(impl->capture_props); pw_properties_free(impl->playback_props); graph_free(&impl->graph); @@ -2178,7 +2237,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->module = module; impl->context = context; - impl->rate = 48000; impl->graph.impl = impl; spa_list_init(&impl->plugin_list); @@ -2188,6 +2246,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_properties_setf(props, PW_KEY_NODE_LINK_GROUP, "filter-chain-%u-%u", pid, id); if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); + if (pw_properties_get(props, "resample.prefill") == NULL) + pw_properties_set(props, "resample.prefill", "true"); if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, "filter-chain-%u-%u", pid, id); @@ -2205,6 +2265,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_LATENCY); copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_MEDIA_NAME); + copy_props(impl, props, "resample.prefill"); parse_audio_info(impl->capture_props, &impl->capture_info); parse_audio_info(impl->playback_props, &impl->playback_info); diff --git a/src/modules/module-filter-chain/biquad.c b/src/modules/module-filter-chain/biquad.c index 296cdd8e4733bfbd086402a9b3cf448a4c9eb3f9..58c8dd0b721eee8921d2210654863c7eda997723 100644 --- a/src/modules/module-filter-chain/biquad.c +++ b/src/modules/module-filter-chain/biquad.c @@ -11,24 +11,6 @@ #include <math.h> #include "biquad.h" -#ifndef max -#define max(a, b) \ - ({ \ - __typeof__(a) _a = (a); \ - __typeof__(b) _b = (b); \ - _a > _b ? _a : _b; \ - }) -#endif - -#ifndef min -#define min(a, b) \ - ({ \ - __typeof__(a) _a = (a); \ - __typeof__(b) _b = (b); \ - _a < _b ? _a : _b; \ - }) -#endif - #ifndef M_PI #define M_PI 3.14159265358979323846 #endif @@ -47,7 +29,7 @@ static void set_coefficient(struct biquad *bq, double b0, double b1, double b2, static void biquad_lowpass(struct biquad *bq, double cutoff, double resonance) { /* Limit cutoff to 0 to 1. */ - cutoff = max(0.0, min(cutoff, 1.0)); + cutoff = fmax(0.0, fmin(cutoff, 1.0)); if (cutoff == 1 || cutoff == 0) { /* When cutoff is 1, the z-transform is 1. @@ -59,7 +41,7 @@ static void biquad_lowpass(struct biquad *bq, double cutoff, double resonance) } /* Compute biquad coefficients for lowpass filter */ - resonance = max(0.0, resonance); /* can't go negative */ + resonance = fmax(0.0, resonance); /* can't go negative */ double g = pow(10.0, 0.05 * resonance); double d = sqrt((4 - sqrt(16 - 16 / (g * g))) / 2); @@ -81,7 +63,7 @@ static void biquad_lowpass(struct biquad *bq, double cutoff, double resonance) static void biquad_highpass(struct biquad *bq, double cutoff, double resonance) { /* Limit cutoff to 0 to 1. */ - cutoff = max(0.0, min(cutoff, 1.0)); + cutoff = fmax(0.0, fmin(cutoff, 1.0)); if (cutoff == 1 || cutoff == 0) { /* When cutoff is one, the z-transform is 0. */ @@ -95,7 +77,7 @@ static void biquad_highpass(struct biquad *bq, double cutoff, double resonance) } /* Compute biquad coefficients for highpass filter */ - resonance = max(0.0, resonance); /* can't go negative */ + resonance = fmax(0.0, resonance); /* can't go negative */ double g = pow(10.0, 0.05 * resonance); double d = sqrt((4 - sqrt(16 - 16 / (g * g))) / 2); @@ -117,10 +99,10 @@ static void biquad_highpass(struct biquad *bq, double cutoff, double resonance) static void biquad_bandpass(struct biquad *bq, double frequency, double Q) { /* No negative frequencies allowed. */ - frequency = max(0.0, frequency); + frequency = fmax(0.0, frequency); /* Don't let Q go negative, which causes an unstable filter. */ - Q = max(0.0, Q); + Q = fmax(0.0, Q); if (frequency <= 0 || frequency >= 1) { /* When the cutoff is zero, the z-transform approaches 0, if Q @@ -158,7 +140,7 @@ static void biquad_bandpass(struct biquad *bq, double frequency, double Q) static void biquad_lowshelf(struct biquad *bq, double frequency, double db_gain) { /* Clip frequencies to between 0 and 1, inclusive. */ - frequency = max(0.0, min(frequency, 1.0)); + frequency = fmax(0.0, fmin(frequency, 1.0)); double A = pow(10.0, db_gain / 40); @@ -195,7 +177,7 @@ static void biquad_highshelf(struct biquad *bq, double frequency, double db_gain) { /* Clip frequencies to between 0 and 1, inclusive. */ - frequency = max(0.0, min(frequency, 1.0)); + frequency = fmax(0.0, fmin(frequency, 1.0)); double A = pow(10.0, db_gain / 40); @@ -232,10 +214,10 @@ static void biquad_peaking(struct biquad *bq, double frequency, double Q, double db_gain) { /* Clip frequencies to between 0 and 1, inclusive. */ - frequency = max(0.0, min(frequency, 1.0)); + frequency = fmax(0.0, fmin(frequency, 1.0)); /* Don't let Q go negative, which causes an unstable filter. */ - Q = max(0.0, Q); + Q = fmax(0.0, Q); double A = pow(10.0, db_gain / 40); @@ -270,10 +252,10 @@ static void biquad_peaking(struct biquad *bq, double frequency, double Q, static void biquad_notch(struct biquad *bq, double frequency, double Q) { /* Clip frequencies to between 0 and 1, inclusive. */ - frequency = max(0.0, min(frequency, 1.0)); + frequency = fmax(0.0, fmin(frequency, 1.0)); /* Don't let Q go negative, which causes an unstable filter. */ - Q = max(0.0, Q); + Q = fmax(0.0, Q); if (frequency <= 0 || frequency >= 1) { /* When frequency is 0 or 1, the z-transform is 1. */ @@ -306,10 +288,10 @@ static void biquad_notch(struct biquad *bq, double frequency, double Q) static void biquad_allpass(struct biquad *bq, double frequency, double Q) { /* Clip frequencies to between 0 and 1, inclusive. */ - frequency = max(0.0, min(frequency, 1.0)); + frequency = fmax(0.0, fmin(frequency, 1.0)); /* Don't let Q go negative, which causes an unstable filter. */ - Q = max(0.0, Q); + Q = fmax(0.0, Q); if (frequency <= 0 || frequency >= 1) { /* When frequency is 0 or 1, the z-transform is 1. */ diff --git a/src/modules/module-filter-chain/builtin_plugin.c b/src/modules/module-filter-chain/builtin_plugin.c index 00982567e0d9c8547f01064e8ea963468364fa4b..6918ad91d08f14a23da2f06a26b7da4f8786111c 100644 --- a/src/modules/module-filter-chain/builtin_plugin.c +++ b/src/modules/module-filter-chain/builtin_plugin.c @@ -40,6 +40,9 @@ #include "biquad.h" #include "pffft.h" #include "convolver.h" +#include "dsp-ops.h" + +static struct dsp_ops dsp_ops; struct builtin { unsigned long rate; @@ -52,7 +55,7 @@ struct builtin { }; static void *builtin_instantiate(const struct fc_descriptor * Descriptor, - unsigned long *SampleRate, int index, const char *config) + unsigned long SampleRate, int index, const char *config) { struct builtin *impl; @@ -60,7 +63,7 @@ static void *builtin_instantiate(const struct fc_descriptor * Descriptor, if (impl == NULL) return NULL; - impl->rate = *SampleRate; + impl->rate = SampleRate; return impl; } @@ -82,7 +85,7 @@ static void copy_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; float *in = impl->port[1], *out = impl->port[0]; - memcpy(out, in, SampleCount * sizeof(float)); + dsp_ops_copy(&dsp_ops, out, in, SampleCount); } static struct fc_port copy_ports[] = { @@ -112,10 +115,10 @@ static const struct fc_descriptor copy_desc = { static void mixer_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; - int i; - unsigned long j; + int i, n_src = 0; float *out = impl->port[0]; - bool first = true; + const void *src[8]; + float gains[8]; if (out == NULL) return; @@ -127,24 +130,10 @@ static void mixer_run(void * Instance, unsigned long SampleCount) if (in == NULL || gain == 0.0f) continue; - if (first) { - if (gain == 1.0f) - memcpy(out, in, SampleCount * sizeof(float)); - else - for (j = 0; j < SampleCount; j++) - out[j] = in[j] * gain; - first = false; - } else { - if (gain == 1.0f) - for (j = 0; j < SampleCount; j++) - out[j] += in[j]; - else - for (j = 0; j < SampleCount; j++) - out[j] += in[j] * gain; - } + src[n_src] = in; + gains[n_src++] = gain; } - if (first) - memset(out, 0, SampleCount * sizeof(float)); + dsp_ops_mix_gain(&dsp_ops, out, src, gains, n_src, SampleCount); } static struct fc_port mixer_ports[] = { @@ -576,7 +565,7 @@ static float *create_dirac(const char *filename, float gain, int delay, int offs } static void * convolver_instantiate(const struct fc_descriptor * Descriptor, - unsigned long *SampleRate, int index, const char *config) + unsigned long SampleRate, int index, const char *config) { struct convolver_impl *impl; float *samples; @@ -588,7 +577,9 @@ static void * convolver_instantiate(const struct fc_descriptor * Descriptor, int blocksize = 0, tailsize = 0; int delay = 0; float gain = 1.0f; + unsigned long rate; + errno = EINVAL; if (config == NULL) return NULL; @@ -598,42 +589,60 @@ static void * convolver_instantiate(const struct fc_descriptor * Descriptor, while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { if (spa_streq(key, "blocksize")) { - if (spa_json_get_int(&it[1], &blocksize) <= 0) + if (spa_json_get_int(&it[1], &blocksize) <= 0) { + pw_log_error("convolver:blocksize requires a number"); return NULL; + } } else if (spa_streq(key, "tailsize")) { - if (spa_json_get_int(&it[1], &tailsize) <= 0) + if (spa_json_get_int(&it[1], &tailsize) <= 0) { + pw_log_error("convolver:tailsize requires a number"); return NULL; + } } else if (spa_streq(key, "gain")) { - if (spa_json_get_float(&it[1], &gain) <= 0) + if (spa_json_get_float(&it[1], &gain) <= 0) { + pw_log_error("convolver:gain requires a number"); return NULL; + } } else if (spa_streq(key, "delay")) { - if (spa_json_get_int(&it[1], &delay) <= 0) + if (spa_json_get_int(&it[1], &delay) <= 0) { + pw_log_error("convolver:delay requires a number"); return NULL; + } } else if (spa_streq(key, "filename")) { - if (spa_json_get_string(&it[1], filename, sizeof(filename)) <= 0) + if (spa_json_get_string(&it[1], filename, sizeof(filename)) <= 0) { + pw_log_error("convolver:filename requires a string"); return NULL; + } } else if (spa_streq(key, "offset")) { - if (spa_json_get_int(&it[1], &offset) <= 0) + if (spa_json_get_int(&it[1], &offset) <= 0) { + pw_log_error("convolver:offset requires a number"); return NULL; + } } else if (spa_streq(key, "length")) { - if (spa_json_get_int(&it[1], &length) <= 0) + if (spa_json_get_int(&it[1], &length) <= 0) { + pw_log_error("convolver:length requires a number"); return NULL; + } } else if (spa_streq(key, "channel")) { - if (spa_json_get_int(&it[1], &channel) <= 0) + if (spa_json_get_int(&it[1], &channel) <= 0) { + pw_log_error("convolver:channel requires a number"); return NULL; + } } else if (spa_json_next(&it[1], &val) < 0) break; } - if (!filename[0]) + if (!filename[0]) { + pw_log_error("convolver:filename was not given"); return NULL; + } if (delay < 0) delay = 0; @@ -647,24 +656,32 @@ static void * convolver_instantiate(const struct fc_descriptor * Descriptor, samples = create_dirac(filename, gain, delay, offset, length, &n_samples); } else { + rate = SampleRate; samples = read_samples(filename, gain, delay, offset, - length, channel, SampleRate, &n_samples); + length, channel, &rate, &n_samples); + if (rate != SampleRate) { + pw_log_warn("Convolver samplerate %lu doesn't match filter rate %lu. " + "Consider forcing a filter rate.", rate, SampleRate); + } } - if (samples == NULL) + if (samples == NULL) { + errno = ENOENT; return NULL; + } if (blocksize <= 0) blocksize = SPA_CLAMP(n_samples, 64, 256); if (tailsize <= 0) - tailsize = SPA_CLAMP(4096, blocksize, 4096); + tailsize = SPA_CLAMP(4096, blocksize, 32768); - pw_log_info("using %d:%d blocksize ir:%s", blocksize, tailsize, filename); + pw_log_info("using n_samples:%u %d:%d blocksize ir:%s", n_samples, + blocksize, tailsize, filename); impl = calloc(1, sizeof(*impl)); if (impl == NULL) goto error; - impl->rate = *SampleRate; + impl->rate = SampleRate; impl->conv = convolver_new(blocksize, tailsize, samples, n_samples); if (impl->conv == NULL) @@ -750,7 +767,7 @@ static void delay_cleanup(void * Instance) } static void *delay_instantiate(const struct fc_descriptor * Descriptor, - unsigned long *SampleRate, int index, const char *config) + unsigned long SampleRate, int index, const char *config) { struct delay_impl *impl; struct spa_json it[2]; @@ -769,8 +786,10 @@ static void *delay_instantiate(const struct fc_descriptor * Descriptor, while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { if (spa_streq(key, "max-delay")) { - if (spa_json_get_float(&it[1], &max_delay) <= 0) + if (spa_json_get_float(&it[1], &max_delay) <= 0) { + pw_log_error("delay:max-delay requires a number"); return NULL; + } } else if (spa_json_next(&it[1], &val) < 0) break; @@ -782,9 +801,9 @@ static void *delay_instantiate(const struct fc_descriptor * Descriptor, if (impl == NULL) return NULL; - impl->rate = *SampleRate; + impl->rate = SampleRate; impl->buffer_samples = max_delay * impl->rate; - pw_log_info("%lu %d", impl->rate, impl->buffer_samples); + pw_log_info("max-delay:%f seconds rate:%lu samples:%d", max_delay, impl->rate, impl->buffer_samples); impl->buffer = calloc(impl->buffer_samples, sizeof(float)); if (impl->buffer == NULL) { @@ -911,7 +930,11 @@ struct fc_plugin *load_builtin_plugin(const struct spa_support *support, uint32_ const char *plugin, const char *config) { struct spa_cpu *cpu_iface; + uint32_t cpu_flags; cpu_iface = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); - pffft_select_cpu(cpu_iface ? spa_cpu_get_flags(cpu_iface) : 0); + cpu_flags = cpu_iface ? spa_cpu_get_flags(cpu_iface) : 0; + dsp_ops.cpu_flags = cpu_flags; + dsp_ops_init(&dsp_ops); + pffft_select_cpu(cpu_flags); return &builtin_plugin; } diff --git a/src/modules/module-filter-chain/convolver.c b/src/modules/module-filter-chain/convolver.c index dcf54d03398bfd92ba3411660b3ff63169c25f36..fff4fbe179098ea755099514f42c302de31ace6c 100644 --- a/src/modules/module-filter-chain/convolver.c +++ b/src/modules/module-filter-chain/convolver.c @@ -251,7 +251,7 @@ static int convolver1_run(struct convolver1 *conv, const float *input, float *ou { int i, processed = 0; - if (conv->segCount == 0) { + if (conv == NULL || conv->segCount == 0) { fft_clear(output, len); return len; } diff --git a/src/modules/module-filter-chain/dsp-ops-c.c b/src/modules/module-filter-chain/dsp-ops-c.c new file mode 100644 index 0000000000000000000000000000000000000000..885c8cd38135995d91cb31eed940c6436263e689 --- /dev/null +++ b/src/modules/module-filter-chain/dsp-ops-c.c @@ -0,0 +1,100 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> +#include <math.h> + +#include <spa/utils/defs.h> + +#include "dsp-ops.h" + +static inline void dsp_clear_c(struct dsp_ops *ops, void * SPA_RESTRICT dst, uint32_t n_samples) +{ + memset(dst, 0, sizeof(float) * n_samples); +} + +static inline void dsp_add_c(struct dsp_ops *ops, void * SPA_RESTRICT dst, + const void * SPA_RESTRICT src, uint32_t n_samples) +{ + uint32_t i; + const float *s = src; + float *d = dst; + for (i = 0; i < n_samples; i++) + d[i] += s[i]; +} + +static inline void dsp_gain_c(struct dsp_ops *ops, void * SPA_RESTRICT dst, + const void * SPA_RESTRICT src, float gain, uint32_t n_samples) +{ + uint32_t i; + const float *s = src; + float *d = dst; + for (i = 0; i < n_samples; i++) + d[i] = s[i] * gain; +} + +static inline void dsp_gain_add_c(struct dsp_ops *ops, void * SPA_RESTRICT dst, + const void * SPA_RESTRICT src, float gain, uint32_t n_samples) +{ + uint32_t i; + const float *s = src; + float *d = dst; + for (i = 0; i < n_samples; i++) + d[i] += s[i] * gain; +} + + +void dsp_copy_c(struct dsp_ops *ops, void * SPA_RESTRICT dst, + const void * SPA_RESTRICT src, uint32_t n_samples) +{ + if (dst != src) + spa_memcpy(dst, src, sizeof(float) * n_samples); +} + +void dsp_mix_gain_c(struct dsp_ops *ops, + void * SPA_RESTRICT dst, + const void * SPA_RESTRICT src[], + float gain[], uint32_t n_src, uint32_t n_samples) +{ + uint32_t i; + if (n_src == 0) { + dsp_clear_c(ops, dst, n_samples); + } else if (n_src == 1) { + if (dst != src[0]) + dsp_copy_c(ops, dst, src[0], n_samples); + } else { + if (gain[0] == 1.0f) + dsp_copy_c(ops, dst, src[0], n_samples); + else + dsp_gain_c(ops, dst, src[0], gain[0], n_samples); + + for (i = 1; i < n_src; i++) { + if (gain[i] == 1.0f) + dsp_add_c(ops, dst, src[i], n_samples); + else + dsp_gain_add_c(ops, dst, src[i], gain[i], n_samples); + } + } +} diff --git a/src/modules/module-filter-chain/dsp-ops-sse.c b/src/modules/module-filter-chain/dsp-ops-sse.c new file mode 100644 index 0000000000000000000000000000000000000000..cabcae3405d7cf2c73cedcfbd0097e4c32df4b8a --- /dev/null +++ b/src/modules/module-filter-chain/dsp-ops-sse.c @@ -0,0 +1,91 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> +#include <math.h> + +#include <spa/utils/defs.h> + +#include "dsp-ops.h" + +#include <xmmintrin.h> + +void dsp_mix_gain_sse(struct dsp_ops *ops, + void * SPA_RESTRICT dst, + const void * SPA_RESTRICT src[], + float gain[], uint32_t n_src, uint32_t n_samples) +{ + if (n_src == 0) { + memset(dst, 0, n_samples * sizeof(float)); + } else if (n_src == 1) { + if (dst != src[0]) + spa_memcpy(dst, src[0], n_samples * sizeof(float)); + } else { + uint32_t n, i, unrolled; + __m128 in[4], g; + const float **s = (const float **)src; + float *d = dst; + + if (SPA_LIKELY(SPA_IS_ALIGNED(dst, 16))) { + unrolled = n_samples & ~15; + for (i = 0; i < n_src; i++) { + if (SPA_UNLIKELY(!SPA_IS_ALIGNED(src[i], 16))) { + unrolled = 0; + break; + } + } + } else + unrolled = 0; + + for (n = 0; n < unrolled; n += 16) { + g = _mm_set1_ps(gain[0]); + in[0] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+ 0])); + in[1] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+ 4])); + in[2] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+ 8])); + in[3] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+12])); + + for (i = 1; i < n_src; i++) { + g = _mm_set1_ps(gain[i]); + in[0] = _mm_add_ps(in[0], _mm_mul_ps(g, _mm_load_ps(&s[i][n+ 0]))); + in[1] = _mm_add_ps(in[1], _mm_mul_ps(g, _mm_load_ps(&s[i][n+ 4]))); + in[2] = _mm_add_ps(in[2], _mm_mul_ps(g, _mm_load_ps(&s[i][n+ 8]))); + in[3] = _mm_add_ps(in[3], _mm_mul_ps(g, _mm_load_ps(&s[i][n+12]))); + } + _mm_store_ps(&d[n+ 0], in[0]); + _mm_store_ps(&d[n+ 4], in[1]); + _mm_store_ps(&d[n+ 8], in[2]); + _mm_store_ps(&d[n+12], in[3]); + } + for (; n < n_samples; n++) { + g = _mm_set_ss(gain[0]); + in[0] = _mm_mul_ss(g, _mm_load_ss(&s[0][n])); + for (i = 1; i < n_src; i++) { + g = _mm_set_ss(gain[i]); + in[0] = _mm_add_ss(in[0], _mm_mul_ss(g, _mm_load_ss(&s[i][n]))); + } + _mm_store_ss(&d[n], in[0]); + } + } +} diff --git a/src/modules/module-filter-chain/dsp-ops.c b/src/modules/module-filter-chain/dsp-ops.c new file mode 100644 index 0000000000000000000000000000000000000000..b40278bf644d27919aed280914407df6ec6c4bff --- /dev/null +++ b/src/modules/module-filter-chain/dsp-ops.c @@ -0,0 +1,92 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> +#include <math.h> + +#include <spa/support/cpu.h> +#include <spa/utils/defs.h> +#include <spa/param/audio/format-utils.h> + +#include "dsp-ops.h" + +struct dsp_info { + uint32_t cpu_flags; + + void (*copy) (struct dsp_ops *ops, + void * SPA_RESTRICT dst, + const void * SPA_RESTRICT src, uint32_t n_samples); + void (*mix_gain) (struct dsp_ops *ops, + void * SPA_RESTRICT dst, + const void * SPA_RESTRICT src[], + float gain[], uint32_t n_src, uint32_t n_samples); +}; + +static struct dsp_info dsp_table[] = +{ +#if defined (HAVE_SSE) + { SPA_CPU_FLAG_SSE, + .copy = dsp_copy_c, + .mix_gain = dsp_mix_gain_sse, + }, +#endif + { 0, + .copy = dsp_copy_c, + .mix_gain = dsp_mix_gain_c, + }, +}; + +#define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) + +static const struct dsp_info *find_dsp_info(uint32_t cpu_flags) +{ + SPA_FOR_EACH_ELEMENT_VAR(dsp_table, t) { + if (MATCH_CPU_FLAGS(t->cpu_flags, cpu_flags)) + return t; + } + return NULL; +} + +static void impl_dsp_ops_free(struct dsp_ops *ops) +{ + spa_zero(*ops); +} + +int dsp_ops_init(struct dsp_ops *ops) +{ + const struct dsp_info *info; + + info = find_dsp_info(ops->cpu_flags); + if (info == NULL) + return -ENOTSUP; + + ops->priv = info; + ops->cpu_flags = info->cpu_flags; + ops->copy = info->copy; + ops->mix_gain = info->mix_gain; + ops->free = impl_dsp_ops_free; + + return 0; +} diff --git a/src/modules/module-filter-chain/dsp-ops.h b/src/modules/module-filter-chain/dsp-ops.h new file mode 100644 index 0000000000000000000000000000000000000000..ffbca6ce7a4d549b1c9d40652a695d8a0fc946c5 --- /dev/null +++ b/src/modules/module-filter-chain/dsp-ops.h @@ -0,0 +1,62 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <spa/utils/defs.h> + +struct dsp_ops { + uint32_t cpu_flags; + + void (*clear) (struct dsp_ops *ops, void * SPA_RESTRICT dst, uint32_t n_samples); + void (*copy) (struct dsp_ops *ops, + void * SPA_RESTRICT dst, + const void * SPA_RESTRICT src, uint32_t n_samples); + void (*mix_gain) (struct dsp_ops *ops, + void * SPA_RESTRICT dst, + const void * SPA_RESTRICT src[], + float gain[], uint32_t n_src, uint32_t n_samples); + void (*free) (struct dsp_ops *ops); + + const void *priv; +}; + +int dsp_ops_init(struct dsp_ops *ops); + +#define dsp_ops_copy(ops,...) (ops)->copy(ops, __VA_ARGS__) +#define dsp_ops_mix_gain(ops,...) (ops)->mix_gain(ops, __VA_ARGS__) +#define dsp_ops_free(ops) (ops)->free(ops) + + +#define MAKE_COPY_FUNC(arch) \ +void dsp_copy_##arch(struct dsp_ops *ops, void * SPA_RESTRICT dst, \ + const void * SPA_RESTRICT src, uint32_t n_samples) +#define MAKE_MIX_GAIN_FUNC(arch) \ +void dsp_mix_gain_##arch(struct dsp_ops *ops, void * SPA_RESTRICT dst, \ + const void * SPA_RESTRICT src[], float gain[], uint32_t n_src, uint32_t n_samples) + + +MAKE_COPY_FUNC(c); +MAKE_MIX_GAIN_FUNC(c); +#if defined (HAVE_SSE) +MAKE_MIX_GAIN_FUNC(sse); +#endif diff --git a/src/modules/module-filter-chain/ladspa_plugin.c b/src/modules/module-filter-chain/ladspa_plugin.c index 591002bea33da82d339b58661775a807b33a8649..172fc2523eab5cc1cfaa83314a7de6ab5fd3e42c 100644 --- a/src/modules/module-filter-chain/ladspa_plugin.c +++ b/src/modules/module-filter-chain/ladspa_plugin.c @@ -22,6 +22,8 @@ * DEALINGS IN THE SOFTWARE. */ +#include "config.h" + #include <dlfcn.h> #include <math.h> @@ -47,10 +49,10 @@ struct descriptor { }; static void *ladspa_instantiate(const struct fc_descriptor *desc, - unsigned long *SampleRate, int index, const char *config) + unsigned long SampleRate, int index, const char *config) { struct descriptor *d = (struct descriptor *)desc; - return d->d->instantiate(d->d, *SampleRate); + return d->d->instantiate(d->d, SampleRate); } static const LADSPA_Descriptor *find_desc(LADSPA_Descriptor_Function desc_func, const char *name) @@ -80,39 +82,39 @@ static float get_default(struct fc_port *port, LADSPA_PortRangeHintDescriptor hi break; case LADSPA_HINT_DEFAULT_LOW: if (LADSPA_IS_HINT_LOGARITHMIC(hint)) - def = (LADSPA_Data) exp(log(lower) * 0.75 + log(upper) * 0.25); + def = (LADSPA_Data) expf(logf(lower) * 0.75f + logf(upper) * 0.25f); else - def = (LADSPA_Data) (lower * 0.75 + upper * 0.25); + def = (LADSPA_Data) (lower * 0.75f + upper * 0.25f); break; case LADSPA_HINT_DEFAULT_MIDDLE: if (LADSPA_IS_HINT_LOGARITHMIC(hint)) - def = (LADSPA_Data) exp(log(lower) * 0.5 + log(upper) * 0.5); + def = (LADSPA_Data) expf(logf(lower) * 0.5f + logf(upper) * 0.5f); else - def = (LADSPA_Data) (lower * 0.5 + upper * 0.5); + def = (LADSPA_Data) (lower * 0.5f + upper * 0.5f); break; case LADSPA_HINT_DEFAULT_HIGH: if (LADSPA_IS_HINT_LOGARITHMIC(hint)) - def = (LADSPA_Data) exp(log(lower) * 0.25 + log(upper) * 0.75); + def = (LADSPA_Data) expf(logf(lower) * 0.25f + logf(upper) * 0.75f); else - def = (LADSPA_Data) (lower * 0.25 + upper * 0.75); + def = (LADSPA_Data) (lower * 0.25f + upper * 0.75f); break; case LADSPA_HINT_DEFAULT_0: - def = 0; + def = 0.0f; break; case LADSPA_HINT_DEFAULT_1: - def = 1; + def = 1.0f; break; case LADSPA_HINT_DEFAULT_100: - def = 100; + def = 100.0f; break; case LADSPA_HINT_DEFAULT_440: - def = 440; + def = 440.0f; break; default: if (upper == lower) def = upper; else - def = SPA_CLAMP(0.5 * upper, lower, upper); + def = SPA_CLAMPF(0.5f * upper, lower, upper); break; } if (LADSPA_IS_HINT_INTEGER(hint)) @@ -238,7 +240,7 @@ struct fc_plugin *load_ladspa_plugin(const struct spa_support *support, uint32_t search_dirs = getenv("LADSPA_PATH"); if (!search_dirs) - search_dirs = "/usr/lib64/ladspa"; + search_dirs = "/usr/lib64/ladspa:/usr/lib/ladspa:" LIBDIR; /* * set the errno for the case when `ladspa_handle_load_by_path()` diff --git a/src/modules/module-filter-chain/lv2_plugin.c b/src/modules/module-filter-chain/lv2_plugin.c index fc4f274bc180ad1b6115364f808e1a6c08a32976..e607e2f1e265291b05e5942cc3c350866ddefc29 100644 --- a/src/modules/module-filter-chain/lv2_plugin.c +++ b/src/modules/module-filter-chain/lv2_plugin.c @@ -298,7 +298,7 @@ work_schedule(LV2_Worker_Schedule_Handle handle, uint32_t size, const void *data } static void *lv2_instantiate(const struct fc_descriptor *desc, - unsigned long *SampleRate, int index, const char *config) + unsigned long SampleRate, int index, const char *config) { struct descriptor *d = (struct descriptor*)desc; struct plugin *p = d->p; @@ -308,7 +308,7 @@ static void *lv2_instantiate(const struct fc_descriptor *desc, static const int32_t min_block_length = 1; static const int32_t max_block_length = 8192; static const int32_t seq_size = 32768; - float fsample_rate = *SampleRate; + float fsample_rate = SampleRate; i = calloc(1, sizeof(*i)); if (i == NULL) @@ -350,7 +350,7 @@ static void *lv2_instantiate(const struct fc_descriptor *desc, i->options_feature.data = i->options; i->features[n_features++] = &i->options_feature; - i->instance = lilv_plugin_instantiate(p->p, *SampleRate, i->features); + i->instance = lilv_plugin_instantiate(p->p, SampleRate, i->features); if (i->instance == NULL) { free(i); return NULL; diff --git a/src/modules/module-filter-chain/pffft.c b/src/modules/module-filter-chain/pffft.c index d89a709495969248a9051b6058511378589359e3..2f65e3fcd1f0925434ca23f9f18a320bf943153d 100644 --- a/src/modules/module-filter-chain/pffft.c +++ b/src/modules/module-filter-chain/pffft.c @@ -25,7 +25,7 @@ Laboratory, the University Corporation for Atmospheric Research, nor the names of its sponsors or contributors may be used to endorse or promote products derived from this Software without - specific prior written permission. + specific prior written permission. - Redistributions of source code must retain the above copyright notices, this list of conditions, and the disclaimer below. @@ -52,7 +52,7 @@ */ /* - ChangeLog: + ChangeLog: - 2011/10/02, version 1: This is the very first release of this file. */ @@ -243,7 +243,7 @@ typedef union v4sf_union { #define assertv4(v,f0,f1,f2,f3) assert(v.f[0] == (f0) && v.f[1] == (f1) && v.f[2] == (f2) && v.f[3] == (f3)) /* detect bugs with the vector support macros */ -static void validate_pffft_simd() +static void validate_pffft_simd(void) { float f[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; v4sf_union a0, a1, a2, a3, t, u; @@ -301,7 +301,7 @@ static void validate_pffft_simd() assertv4(a3, 3, 7, 11, 15); } #else -static void validate_pffft_simd() +static void validate_pffft_simd(void) { } // allow test_pffft.c to call this function even when simd is not available.. #endif //!PFFFT_SIMD_DISABLE @@ -1421,7 +1421,7 @@ static PFFFT_Setup *new_setup_simd(int N, pffft_transform_t transform) { PFFFT_Setup *s = (PFFFT_Setup *) malloc(sizeof(PFFFT_Setup)); int k, m; - /* unfortunately, the fft size must be a multiple of 16 for complex FFTs + /* unfortunately, the fft size must be a multiple of 16 for complex FFTs and 32 for real FFTs -- a lot of stuff would need to be rewritten to handle other cases (or maybe just switch to a scalar fft, I don't know..) */ if (transform == PFFFT_REAL) { @@ -1615,7 +1615,7 @@ static void pffft_cplx_finalize(int Ncvec, const v4sf * in, v4sf * out, const v4 [0 0 0 0 1 1 1 1] * [i0] [0 1 0 -1 1 0 -1 0] [i1] [0 0 0 0 1 -1 1 -1] [i2] - [0 -1 0 1 1 0 -1 0] [i3] + [0 -1 0 1 1 0 -1 0] [i3] */ r0 = VADD(sr0, sr1); @@ -1719,7 +1719,7 @@ static ALWAYS_INLINE(void) pffft_real_finalize_4x4(const v4sf * in0, [0 0 0 0 1 1 1 1] * [i0] [0 -1 0 1 -1 0 1 0] [i1] [0 -1 0 1 1 0 -1 0] [i2] - [0 0 0 0 -1 1 -1 1] [i3] + [0 0 0 0 -1 1 -1 1] [i3] */ //cerr << "matrix initial, before e , REAL:\n 1: " << r0 << "\n 1: " << r1 << "\n 1: " << r2 << "\n 1: " << r3 << "\n"; @@ -1832,7 +1832,7 @@ static ALWAYS_INLINE(void) pffft_real_preprocess_4x4(const v4sf * in, [0 0 0 0 1 -1 1 -1] * [i0] [0 -1 1 0 1 0 0 1] [i1] [0 0 0 0 1 1 -1 -1] [i2] - [0 1 -1 0 1 0 0 1] [i3] + [0 1 -1 0 1 0 0 1] [i3] */ v4sf sr0 = VADD(r0, r3), dr0 = VSUB(r0, r3); diff --git a/src/modules/module-filter-chain/pffft.h b/src/modules/module-filter-chain/pffft.h index 75e0d21cd6199eaa333b3b479d6529ea6f36935f..ca58676fb2a7a093aa0d3169d1f16f728e2ea6d7 100644 --- a/src/modules/module-filter-chain/pffft.h +++ b/src/modules/module-filter-chain/pffft.h @@ -165,13 +165,13 @@ extern "C" { /* the float buffers must have the correct alignment (16-byte boundary on intel and powerpc). This function may be used to obtain such - correctly aligned buffers. + correctly aligned buffers. */ void *pffft_aligned_malloc(size_t nb_bytes); void pffft_aligned_free(void *); /* return 4 or 1 depending on whether support for SSE/Altivec instructions was enabled when building pffft.c */ - int pffft_simd_size(); + int pffft_simd_size(void); void pffft_select_cpu(int flags); diff --git a/src/modules/module-filter-chain/plugin.h b/src/modules/module-filter-chain/plugin.h index e4f2eb5f1d6c1df78d53c68c17ea93dd0a2a3232..ce46b62ecf87bdaf2f442ad499728f2b37fb0b09 100644 --- a/src/modules/module-filter-chain/plugin.h +++ b/src/modules/module-filter-chain/plugin.h @@ -72,7 +72,7 @@ struct fc_descriptor { struct fc_port *ports; void *(*instantiate) (const struct fc_descriptor *desc, - unsigned long *SampleRate, int index, const char *config); + unsigned long SampleRate, int index, const char *config); void (*cleanup) (void *instance); diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index 7cd07ded5bcbb991c4e21a3e0c197038824748c2..505ff1c1381a0352ed375feb968c4dbad69e8b4e 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -35,6 +35,7 @@ #include <spa/utils/result.h> #include <spa/utils/string.h> #include <spa/utils/json.h> +#include <spa/utils/ringbuffer.h> #include <spa/param/profiler.h> #include <spa/debug/pod.h> @@ -54,6 +55,7 @@ * ## Module Options * * - `node.description`: a human readable name for the loopback streams + * - `target.delay.sec`: delay in seconds as float (Since 0.3.60) * - `capture.props = {}`: properties to be passed to the input stream * - `playback.props = {}`: properties to be passed to the output stream * @@ -91,6 +93,7 @@ * { name = libpipewire-module-loopback * args = { * node.description = "CM106 Stereo Pair 2" + * #target.delay.sec = 1.5 * capture.props = { * node.name = "CM106_stereo_pair_2" * media.class = "Audio/Sink" @@ -128,6 +131,7 @@ static const struct spa_dict_item module_props[] = { "[ 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 }, @@ -160,13 +164,21 @@ struct impl { struct pw_stream *capture; struct spa_hook capture_listener; struct spa_audio_info_raw capture_info; + struct spa_latency_info capture_latency; struct pw_properties *playback_props; struct pw_stream *playback; struct spa_hook playback_listener; struct spa_audio_info_raw playback_info; + struct spa_latency_info playback_latency; unsigned int do_disconnect:1; + unsigned int recalc_delay:1; + + float target_delay; + struct spa_ringbuffer buffer; + uint8_t *buffer_data; + uint32_t buffer_size; }; static void capture_destroy(void *d) @@ -176,12 +188,45 @@ static void capture_destroy(void *d) impl->capture = NULL; } +static void recalculate_delay(struct impl *impl) +{ + uint32_t target = impl->capture_info.rate * impl->target_delay, cdelay, pdelay; + uint32_t delay, w; + struct pw_time pwt; + + pw_stream_get_time_n(impl->playback, &pwt, sizeof(pwt)); + pdelay = pwt.delay; + pw_stream_get_time_n(impl->capture, &pwt, sizeof(pwt)); + cdelay = pwt.delay; + + delay = target - SPA_MIN(target, pdelay + cdelay); + delay = SPA_MIN(delay, impl->buffer_size / 4); + + spa_ringbuffer_get_write_index(&impl->buffer, &w); + spa_ringbuffer_read_update(&impl->buffer, w - (delay * 4)); + + pw_log_info("target:%d c:%d + p:%d + delay:%d = (%d)", + target, cdelay, pdelay, delay, + cdelay + pdelay + delay); +} + static void capture_process(void *d) +{ + struct impl *impl = d; + pw_stream_trigger_process(impl->playback); +} + +static void playback_process(void *d) { struct impl *impl = d; struct pw_buffer *in, *out; uint32_t i; + if (impl->recalc_delay) { + recalculate_delay(impl); + impl->recalc_delay = false; + } + if ((in = pw_stream_dequeue_buffer(impl->capture)) == NULL) pw_log_debug("out of capture buffers: %m"); @@ -193,6 +238,7 @@ static void capture_process(void *d) int32_t stride = 0; struct spa_data *d; const void *src[in->buffer->n_datas]; + uint32_t r, w, buffer_size; for (i = 0; i < in->buffer->n_datas; i++) { uint32_t offs, size; @@ -205,13 +251,33 @@ static void capture_process(void *d) outsize = SPA_MIN(outsize, size); stride = SPA_MAX(stride, d->chunk->stride); } + if (impl->buffer_size > 0) { + buffer_size = impl->buffer_size; + spa_ringbuffer_get_write_index(&impl->buffer, &w); + for (i = 0; i < in->buffer->n_datas; i++) { + void *buffer_data = &impl->buffer_data[i * buffer_size]; + spa_ringbuffer_write_data(&impl->buffer, + buffer_data, buffer_size, + w % buffer_size, src[i], outsize); + src[i] = buffer_data; + } + w += outsize; + spa_ringbuffer_write_update(&impl->buffer, w); + spa_ringbuffer_get_read_index(&impl->buffer, &r); + } else { + r = 0; + buffer_size = outsize; + } for (i = 0; i < out->buffer->n_datas; i++) { d = &out->buffer->datas[i]; outsize = SPA_MIN(outsize, d->maxsize); if (i < in->buffer->n_datas) - memcpy(d->data, src[i], outsize); + spa_ringbuffer_read_data(&impl->buffer, + src[i], buffer_size, + r % buffer_size, + d->data, outsize); else memset(d->data, 0, outsize); @@ -219,18 +285,20 @@ static void capture_process(void *d) d->chunk->size = outsize; d->chunk->stride = stride; } + if (impl->buffer_size > 0) { + r += outsize; + spa_ringbuffer_read_update(&impl->buffer, r); + } } if (in != NULL) pw_stream_queue_buffer(impl->capture, in); if (out != NULL) pw_stream_queue_buffer(impl->playback, out); - - pw_stream_trigger_process(impl->playback); } static void param_latency_changed(struct impl *impl, const struct spa_pod *param, - struct pw_stream *other) + struct spa_latency_info *info, struct pw_stream *other) { struct spa_latency_info latency; uint8_t buffer[1024]; @@ -240,9 +308,13 @@ static void param_latency_changed(struct impl *impl, const struct spa_pod *param if (spa_latency_parse(param, &latency) < 0) return; + *info = latency; + spa_pod_builder_init(&b, buffer, sizeof(buffer)); params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); pw_stream_update_params(other, params, 1); + + impl->recalc_delay = true; } static void stream_state_changed(void *data, enum pw_stream_state old, @@ -253,6 +325,7 @@ static void stream_state_changed(void *data, enum pw_stream_state old, case PW_STREAM_STATE_PAUSED: pw_stream_flush(impl->playback, false); pw_stream_flush(impl->capture, false); + impl->recalc_delay = true; break; case PW_STREAM_STATE_UNCONNECTED: pw_log_info("module %p: unconnected", impl); @@ -266,13 +339,53 @@ static void stream_state_changed(void *data, enum pw_stream_state old, } } +static void recalculate_buffer(struct impl *impl) +{ + if (impl->target_delay > 0.0f) { + uint32_t delay = impl->capture_info.rate * impl->target_delay; + void *data; + + impl->buffer_size = (delay + (1u<<15)) * 4; + data = realloc(impl->buffer_data, impl->buffer_size * impl->capture_info.channels); + if (data == NULL) { + pw_log_warn("can't allocate delay buffer, delay disabled: %m"); + impl->buffer_size = 0; + free(impl->buffer_data); + } + impl->buffer_data = data; + spa_ringbuffer_init(&impl->buffer); + } else { + impl->buffer_size = 0; + free(impl->buffer_data); + impl->buffer_data = NULL; + } + pw_log_info("configured delay:%f buffer:%d", impl->target_delay, impl->buffer_size); + impl->recalc_delay = true; +} + static void capture_param_changed(void *data, uint32_t id, const struct spa_pod *param) { struct impl *impl = data; switch (id) { + case SPA_PARAM_Format: + { + struct spa_audio_info_raw info; + if (param == NULL) + return; + if (spa_format_audio_raw_parse(param, &info) < 0) + return; + if (info.rate == 0 || + info.channels == 0 || + info.channels > SPA_AUDIO_MAX_CHANNELS) + return; + + impl->capture_info = info; + recalculate_buffer(impl); + break; + } case SPA_PARAM_Latency: - param_latency_changed(impl, param, impl->playback); + param_latency_changed(impl, param, &impl->capture_latency, impl->playback); break; } } @@ -298,13 +411,14 @@ static void playback_param_changed(void *data, uint32_t id, const struct spa_pod switch (id) { case SPA_PARAM_Latency: - param_latency_changed(impl, param, impl->capture); + param_latency_changed(impl, param, &impl->playback_latency, impl->capture); break; } } static const struct pw_stream_events out_stream_events = { PW_VERSION_STREAM_EVENTS, .destroy = playback_destroy, + .process = playback_process, .state_changed = stream_state_changed, .param_changed = playback_param_changed, }; @@ -399,12 +513,20 @@ static const struct pw_proxy_events core_proxy_events = { static void impl_destroy(struct impl *impl) { + /* disconnect both streams before destroying any of them */ + if (impl->capture) + pw_stream_disconnect(impl->capture); + if (impl->playback) + pw_stream_disconnect(impl->playback); + if (impl->capture) pw_stream_destroy(impl->capture); if (impl->playback) pw_stream_destroy(impl->playback); + if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); + pw_properties_free(impl->capture_props); pw_properties_free(impl->playback_props); free(impl); @@ -518,12 +640,23 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_properties_setf(props, PW_KEY_NODE_LINK_GROUP, "loopback-%u-%u", pid, id); if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); + if (pw_properties_get(props, "resample.prefill") == NULL) + pw_properties_set(props, "resample.prefill", "true"); if ((str = pw_properties_get(props, "capture.props")) != NULL) pw_properties_update_string(impl->capture_props, str, strlen(str)); if ((str = pw_properties_get(props, "playback.props")) != NULL) pw_properties_update_string(impl->playback_props, str, strlen(str)); + if ((str = pw_properties_get(props, "target.delay.sec")) != NULL) + spa_atof(str, &impl->target_delay); + if (impl->target_delay > 0.0f && + pw_properties_get(props, PW_KEY_NODE_LATENCY) == NULL) + /* a source and sink (USB) usually have a 1.5 quantum delay, so we use + * a 2 times smaller quantum to compensate */ + pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", + (unsigned)(impl->target_delay * 48000 / 3), 48000); + copy_props(impl, props, PW_KEY_AUDIO_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); @@ -533,6 +666,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_LATENCY); copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_MEDIA_NAME); + copy_props(impl, props, "resample.prefill"); if ((str = pw_properties_get(props, PW_KEY_NODE_NAME)) == NULL) { pw_properties_setf(props, PW_KEY_NODE_NAME, diff --git a/src/modules/module-pipe-tunnel.c b/src/modules/module-pipe-tunnel.c index ff4a2596867ee45143a7ce217b715d8502379c0f..cc33e2389fa54d3e87c3327478818a6911ce2481 100644 --- a/src/modules/module-pipe-tunnel.c +++ b/src/modules/module-pipe-tunnel.c @@ -55,7 +55,7 @@ /** \page page_module_pipe_tunnel PipeWire Module: Unix Pipe Tunnel * * The pipe-tunnel module provides a source or sink that tunnels all audio to - * a unix pipe. + * or from a unix pipe respectively. * * ## Module Options * @@ -315,7 +315,7 @@ static const struct pw_stream_events capture_stream_events = { .process = capture_stream_process }; -static int create_stream(struct impl *impl) +static int create_stream(struct impl *impl) { int res; uint32_t n_params; @@ -664,6 +664,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_LATENCY); copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_MEDIA_CLASS); + copy_props(impl, props, PW_KEY_TARGET_OBJECT); parse_audio_info(impl->stream_props, &impl->info); diff --git a/src/modules/module-protocol-native.c b/src/modules/module-protocol-native.c index 5ec880e80afdfdf84634d4405c74ca16f04cd980..7604da9015271ad03d8b02c3c76cc1721844609a 100644 --- a/src/modules/module-protocol-native.c +++ b/src/modules/module-protocol-native.c @@ -228,6 +228,9 @@ static void debug_msg(const char *prefix, const struct pw_protocol_native_messag hex = true; if (hex) spa_debug_mem(0, msg->data, msg->size); + + pw_logt_debug(mod_topic_connection, "%s ****", prefix); + } static void pre_demarshal(struct pw_protocol_native_connection *conn, diff --git a/src/modules/module-protocol-native/connection.c b/src/modules/module-protocol-native/connection.c index 6383024bbdfa4a5ac2b74b9e0a7f9dc754bc1abe..1ba256ca3cf85759170468678532267cf8909549 100644 --- a/src/modules/module-protocol-native/connection.c +++ b/src/modules/module-protocol-native/connection.c @@ -221,7 +221,10 @@ static int refill_buffer(struct pw_protocol_native_connection *conn, struct buff struct cmsghdr *cmsg = NULL; struct msghdr msg = { 0 }; struct iovec iov[1]; - char cmsgbuf[CMSG_SPACE(MAX_FDS_MSG * sizeof(int))]; + union { + char cmsgbuf[CMSG_SPACE(MAX_FDS_MSG * sizeof(int))]; + struct cmsghdr align; + } cmsgbuf; int n_fds = 0; size_t avail; @@ -231,7 +234,7 @@ static int refill_buffer(struct pw_protocol_native_connection *conn, struct buff iov[0].iov_len = avail; msg.msg_iov = iov; msg.msg_iovlen = 1; - msg.msg_control = cmsgbuf; + msg.msg_control = &cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); msg.msg_flags = MSG_CMSG_CLOEXEC | MSG_DONTWAIT; @@ -725,9 +728,12 @@ pw_protocol_native_connection_end(struct pw_protocol_native_connection *conn, buf->n_fds = buf->msg.n_fds; if (mod_topic_connection->level >= SPA_LOG_LEVEL_DEBUG) { - pw_log_debug(">>>>>>>>> out: id:%d op:%d size:%d seq:%d", + pw_logt_debug(mod_topic_connection, + ">>>>>>>>> out: id:%d op:%d size:%d seq:%d", buf->msg.id, buf->msg.opcode, size, buf->msg.seq); spa_debug_pod(0, NULL, SPA_PTROFF(p, impl->hdr_size, struct spa_pod)); + pw_logt_debug(mod_topic_connection, + ">>>>>>>>> out: done"); } buf->seq = (buf->seq + 1) & SPA_ASYNC_SEQ_MASK; @@ -755,7 +761,10 @@ int pw_protocol_native_connection_flush(struct pw_protocol_native_connection *co struct msghdr msg = { 0 }; struct iovec iov[1]; struct cmsghdr *cmsg; - char cmsgbuf[CMSG_SPACE(MAX_FDS_MSG * sizeof(int))]; + union { + char cmsgbuf[CMSG_SPACE(MAX_FDS_MSG * sizeof(int))]; + struct cmsghdr align; + } cmsgbuf; int res = 0, *fds; uint32_t fds_len, to_close, n_fds, outfds, i; struct buffer *buf; @@ -786,7 +795,7 @@ int pw_protocol_native_connection_flush(struct pw_protocol_native_connection *co msg.msg_iovlen = 1; if (outfds > 0) { - msg.msg_control = cmsgbuf; + msg.msg_control = &cmsgbuf; msg.msg_controllen = CMSG_SPACE(fds_len); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; diff --git a/src/modules/module-protocol-native/defs.h b/src/modules/module-protocol-native/defs.h index 60adad027ef69a311f15e007cd1bb49e97ffdf0c..dc3c625942f17b045af5475eeb850e550d8e5cd2 100644 --- a/src/modules/module-protocol-native/defs.h +++ b/src/modules/module-protocol-native/defs.h @@ -31,13 +31,19 @@ int pw_protocol_native_connect_portal_screencast(struct pw_protocol_client *clie void (*done_callback) (void *data, int res), void *data); -static inline void *get_first_pod_from_data(void *data, size_t maxsize, off_t offset) +static inline void *get_first_pod_from_data(void *data, uint32_t maxsize, uint64_t offset) { void *pod; - if (offset + sizeof(struct spa_pod) > maxsize) + if (maxsize <= offset) return NULL; + + /* spa_pod_parser_advance() rounds up, so round down here to compensate */ + maxsize = SPA_ROUND_DOWN_N(maxsize - offset, 8); + if (maxsize < sizeof(struct spa_pod)) + return NULL; + pod = SPA_PTROFF(data, offset, void); - if (offset + SPA_POD_SIZE(pod) > maxsize) + if (SPA_POD_BODY_SIZE(pod) > maxsize - sizeof(struct spa_pod)) return NULL; return pod; } diff --git a/src/modules/module-protocol-native/protocol-footer.c b/src/modules/module-protocol-native/protocol-footer.c index bc4caa4a80b5014f61f3f131f1d3218f2248fe76..9b6fe62e60740934acf784043ed00c8c59709de1 100644 --- a/src/modules/module-protocol-native/protocol-footer.c +++ b/src/modules/module-protocol-native/protocol-footer.c @@ -45,7 +45,7 @@ struct footer_builder { unsigned int started:1; }; -#define FOOTER_BUILDER_INIT(builder) (struct footer_builder) { builder } +#define FOOTER_BUILDER_INIT(builder) ((struct footer_builder) { (builder) }) static void start_footer_entry(struct footer_builder *fb, uint32_t opcode) { diff --git a/src/modules/module-protocol-native/protocol-native.c b/src/modules/module-protocol-native/protocol-native.c index b2a1dc56bb15bcf21bb5b18a046320f3a85ef998..1c2d83e4ac400965e7d322da8aaff1f420906818 100644 --- a/src/modules/module-protocol-native/protocol-native.c +++ b/src/modules/module-protocol-native/protocol-native.c @@ -219,13 +219,13 @@ do { \ spa_pod_parser_get(prs, \ SPA_POD_Int(&(n_params)), NULL) < 0) \ return -EINVAL; \ - params = NULL; \ - if (n_params > 0) { \ + (params) = NULL; \ + if ((n_params) > 0) { \ uint32_t i; \ - if (n_params > MAX_PARAM_INFO) \ + if ((n_params) > MAX_PARAM_INFO) \ return -ENOSPC; \ - params = alloca(n_params * sizeof(struct spa_param_info)); \ - for (i = 0; i < n_params; i++) { \ + (params) = alloca((n_params) * sizeof(struct spa_param_info)); \ + for (i = 0; i < (n_params); i++) { \ if (spa_pod_parser_get(prs, \ SPA_POD_Id(&(params)[i].id), \ SPA_POD_Int(&(params)[i].flags), NULL) < 0) \ @@ -240,18 +240,18 @@ do { \ do { \ if (spa_pod_parser_push_struct(prs, f) < 0 || \ spa_pod_parser_get(prs, \ - SPA_POD_Int(&n_permissions), NULL) < 0) \ + SPA_POD_Int(&(n_permissions)), NULL) < 0) \ return -EINVAL; \ - permissions = NULL; \ - if (n_permissions > 0) { \ + (permissions) = NULL; \ + if ((n_permissions) > 0) { \ uint32_t i; \ - if (n_permissions > MAX_PERMISSIONS) \ + if ((n_permissions) > MAX_PERMISSIONS) \ return -ENOSPC; \ - permissions = alloca(n_permissions * sizeof(struct pw_permission)); \ - for (i = 0; i < n_permissions; i++) { \ + (permissions) = alloca((n_permissions) * sizeof(struct pw_permission)); \ + for (i = 0; i < (n_permissions); i++) { \ if (spa_pod_parser_get(prs, \ - SPA_POD_Int(&permissions[i].id), \ - SPA_POD_Int(&permissions[i].permissions), NULL) < 0) \ + SPA_POD_Int(&(permissions)[i].id), \ + SPA_POD_Int(&(permissions)[i].permissions), NULL) < 0) \ return -EINVAL; \ } \ } \ diff --git a/src/modules/module-protocol-native/v0/protocol-native.c b/src/modules/module-protocol-native/v0/protocol-native.c index bc05e9162dbebbee49a6ea49400e950c11e7d66c..65e055c16f1ae62cee4ccfe5c14cd27b9363c59e 100644 --- a/src/modules/module-protocol-native/v0/protocol-native.c +++ b/src/modules/module-protocol-native/v0/protocol-native.c @@ -415,7 +415,7 @@ struct spa_pod_prop_body0 { (iter) <= SPA_PTROFF((body), (_size)-(body)->value.size, __typeof__(*iter)); \ (iter) = SPA_PTROFF((iter), (body)->value.size, __typeof__(*iter))) -#define SPA0_POD_PROP_N_VALUES(b,size) ((size - sizeof(struct spa_pod_prop_body0)) / (b)->value.size) +#define SPA0_POD_PROP_N_VALUES(b,size) (((size) - sizeof(struct spa_pod_prop_body0)) / (b)->value.size) static int remap_from_v2(uint32_t type, void *body, uint32_t size, struct pw_impl_client *client, struct spa_pod_builder *builder) diff --git a/src/modules/module-protocol-pulse/client.c b/src/modules/module-protocol-pulse/client.c index fe7d7885831557d91de73289c19af44f942c3ef6..1e6d202b72478fa55decad0df7664bf10200b029 100644 --- a/src/modules/module-protocol-pulse/client.c +++ b/src/modules/module-protocol-pulse/client.c @@ -266,17 +266,12 @@ static int client_try_flush_messages(struct client *client) int res = -errno; if (res == -EINTR) continue; - if (res != -EAGAIN && res != -EWOULDBLOCK) - pw_log_warn("client %p: send channel:%u %zu, error %d: %m", - client, m->channel, size, res); return res; } - client->out_index += sent; break; } } - return 0; } @@ -296,7 +291,6 @@ int client_flush_messages(struct client *client) if (res != -EAGAIN && res != -EWOULDBLOCK) return res; } - return 0; } diff --git a/src/modules/module-protocol-pulse/extension.c b/src/modules/module-protocol-pulse/extension.c index 00a26a74343224667afc548024b56fb66e428a19..0ffa4c53fb0e1b60d84f323878392f08cd026497 100644 --- a/src/modules/module-protocol-pulse/extension.c +++ b/src/modules/module-protocol-pulse/extension.c @@ -37,12 +37,9 @@ static const struct extension extensions[] = { const struct extension *extension_find(uint32_t index, const char *name) { - const struct extension *ext; - - SPA_FOR_EACH_ELEMENT(extensions, ext) { + SPA_FOR_EACH_ELEMENT_VAR(extensions, ext) { if (index == ext->index || spa_streq(name, ext->name)) return ext; } - return NULL; } diff --git a/src/modules/module-protocol-pulse/format.c b/src/modules/module-protocol-pulse/format.c index fa5a4d4d228b9cf7e0f25049cc7a09e09ceffee7..a3e7a22fd18f480a7c5a32e3496d73e999e4b1cd 100644 --- a/src/modules/module-protocol-pulse/format.c +++ b/src/modules/module-protocol-pulse/format.c @@ -159,32 +159,28 @@ uint32_t format_name2id(const char *name) uint32_t format_paname2id(const char *name, size_t size) { - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(audio_formats); i++) { - if (audio_formats[i].name != NULL && - strncmp(name, audio_formats[i].name, size) == 0) - return audio_formats[i].id; + SPA_FOR_EACH_ELEMENT_VAR(audio_formats, f) { + if (f->name != NULL && + strncmp(name, f->name, size) == 0) + return f->id; } return SPA_AUDIO_FORMAT_UNKNOWN; } enum sample_format format_id2pa(uint32_t id) { - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(audio_formats); i++) { - if (id == audio_formats[i].id) - return audio_formats[i].pa; + SPA_FOR_EACH_ELEMENT_VAR(audio_formats, f) { + if (id == f->id) + return f->pa; } return SAMPLE_INVALID; } const char *format_id2paname(uint32_t id) { - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(audio_formats); i++) { - if (id == audio_formats[i].id && - audio_formats[i].name != NULL) - return audio_formats[i].name; + SPA_FOR_EACH_ELEMENT_VAR(audio_formats, f) { + if (id == f->id && f->name != NULL) + return f->name; } return "invalid"; } @@ -266,21 +262,18 @@ enum channel_position channel_id2pa(uint32_t id, uint32_t *aux) const char *channel_id2paname(uint32_t id, uint32_t *aux) { - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(audio_channels); i++) { - if (id == audio_channels[i].channel && - audio_channels[i].name != NULL) - return audio_channels[i].name; + SPA_FOR_EACH_ELEMENT_VAR(audio_channels, i) { + if (id == i->channel && i->name != NULL) + return i->name; } return audio_channels[CHANNEL_POSITION_AUX0 + ((*aux)++ & 31)].name; } uint32_t channel_paname2id(const char *name, size_t size) { - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(audio_channels); i++) { - if (strncmp(name, audio_channels[i].name, size) == 0) - return audio_channels[i].channel; + SPA_FOR_EACH_ELEMENT_VAR(audio_channels, i) { + if (strncmp(name, i->name, size) == 0) + return i->channel; } return SPA_AUDIO_CHANNEL_UNKNOWN; } @@ -424,25 +417,6 @@ static enum encoding format_encoding_from_id(uint32_t id) return ENCODING_ANY; } -static inline int -audio_raw_parse_opt(const struct spa_pod *format, struct spa_audio_info_raw *info) -{ - struct spa_pod *position = NULL; - int res; - info->flags = 0; - res = spa_pod_parse_object(format, - SPA_TYPE_OBJECT_Format, NULL, - SPA_FORMAT_AUDIO_format, SPA_POD_OPT_Id(&info->format), - SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), - SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), - SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position)); - if (position == NULL || - !spa_pod_copy_array(position, SPA_TYPE_Id, info->position, SPA_AUDIO_MAX_CHANNELS)) - SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); - - return res; -} - int format_parse_param(const struct spa_pod *param, bool collect, struct sample_spec *ss, struct channel_map *map, const struct sample_spec *def_ss, const struct channel_map *def_map) @@ -458,14 +432,17 @@ int format_parse_param(const struct spa_pod *param, bool collect, switch (info.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: + if (spa_format_audio_raw_parse(param, &info.info.raw) < 0) + return -ENOTSUP; if (def_ss != NULL) { if (ss != NULL) *ss = *def_ss; - if (audio_raw_parse_opt(param, &info.info.raw) < 0) - return -ENOTSUP; } else { - if (spa_format_audio_raw_parse(param, &info.info.raw) < 0) - return -ENOTSUP; + if (info.info.raw.format == 0 || + info.info.raw.rate == 0 || + info.info.raw.channels == 0 || + info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) + return -ENOTSUP; } break; case SPA_MEDIA_SUBTYPE_iec958: diff --git a/src/modules/module-protocol-pulse/internal.h b/src/modules/module-protocol-pulse/internal.h index ca6b749264df14bd325bd7b4603ff262fa8773e0..1d5731c999bcb1419197a89167f7c3d1a8c0a3cc 100644 --- a/src/modules/module-protocol-pulse/internal.h +++ b/src/modules/module-protocol-pulse/internal.h @@ -36,6 +36,7 @@ #include <pipewire/private.h> #include "format.h" +#include "server.h" struct pw_loop; struct pw_context; @@ -52,6 +53,7 @@ struct defs { struct sample_spec sample_spec; struct channel_map channel_map; uint32_t quantum_limit; + uint32_t idle_timeout; }; struct stats { @@ -72,6 +74,7 @@ struct impl { struct ratelimit rate_limit; + struct spa_hook_list hooks; struct spa_list servers; struct pw_work_queue *work_queue; @@ -85,6 +88,19 @@ struct impl { struct stats stat; }; +struct impl_events { +#define VERSION_IMPL_EVENTS 0 + uint32_t version; + + void (*server_started) (void *data, struct server *server); + + void (*server_stopped) (void *data, struct server *server); +}; + +void impl_add_listener(struct impl *impl, + struct spa_hook *listener, + const struct impl_events *events, void *data); + extern bool debug_messages; void broadcast_subscribe_event(struct impl *impl, uint32_t mask, uint32_t event, uint32_t id); diff --git a/src/modules/module-protocol-pulse/manager.c b/src/modules/module-protocol-pulse/manager.c index 4841b67c09bf6a00d43a3258742479d713717833..7aa8d70bd03fc648edd3f250d57fce0c5d840988 100644 --- a/src/modules/module-protocol-pulse/manager.c +++ b/src/modules/module-protocol-pulse/manager.c @@ -33,15 +33,13 @@ #include "log.h" #include "module-protocol-pulse/server.h" -#define MAX_PARAMS 32 - -#define manager_emit_sync(m) spa_hook_list_call(&m->hooks, struct pw_manager_events, sync, 0) -#define manager_emit_added(m,o) spa_hook_list_call(&m->hooks, struct pw_manager_events, added, 0, o) -#define manager_emit_updated(m,o) spa_hook_list_call(&m->hooks, struct pw_manager_events, updated, 0, o) -#define manager_emit_removed(m,o) spa_hook_list_call(&m->hooks, struct pw_manager_events, removed, 0, o) -#define manager_emit_metadata(m,o,s,k,t,v) spa_hook_list_call(&m->hooks, struct pw_manager_events, metadata,0,o,s,k,t,v) -#define manager_emit_disconnect(m) spa_hook_list_call(&m->hooks, struct pw_manager_events, disconnect, 0) -#define manager_emit_object_data_timeout(m,o,k) spa_hook_list_call(&m->hooks, struct pw_manager_events, object_data_timeout,0,o,k) +#define manager_emit_sync(m) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, sync, 0) +#define manager_emit_added(m,o) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, added, 0, o) +#define manager_emit_updated(m,o) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, updated, 0, o) +#define manager_emit_removed(m,o) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, removed, 0, o) +#define manager_emit_metadata(m,o,s,k,t,v) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, metadata,0,o,s,k,t,v) +#define manager_emit_disconnect(m) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, disconnect, 0) +#define manager_emit_object_data_timeout(m,o,k) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, object_data_timeout,0,o,k) struct object; @@ -85,8 +83,6 @@ struct object { struct spa_hook proxy_listener; struct spa_hook object_listener; - int param_seq[MAX_PARAMS]; - struct spa_list data_list; }; @@ -113,7 +109,7 @@ static uint32_t clear_params(struct spa_list *param_list, uint32_t id) } static struct pw_manager_param *add_param(struct spa_list *params, - int seq, int *param_seq, uint32_t id, const struct spa_pod *param) + int seq, uint32_t id, const struct spa_pod *param) { struct pw_manager_param *p; @@ -125,24 +121,12 @@ static struct pw_manager_param *add_param(struct spa_list *params, id = SPA_POD_OBJECT_ID(param); } - if (id >= MAX_PARAMS) { - pw_log_error("too big param id %d", id); - errno = EINVAL; - return NULL; - } - - if (seq != param_seq[id]) { - pw_log_debug("ignoring param %d, seq:%d != current_seq:%d", - id, seq, param_seq[id]); - errno = EBUSY; - return NULL; - } - p = malloc(sizeof(*p) + (param != NULL ? SPA_POD_SIZE(param) : 0)); if (p == NULL) return NULL; p->id = id; + p->seq = seq; if (param != NULL) { p->param = SPA_PTROFF(p, sizeof(*p), struct spa_pod); memcpy(p->param, param, SPA_POD_SIZE(param)); @@ -167,7 +151,6 @@ static bool has_param(struct spa_list *param_list, struct pw_manager_param *p) return false; } - static struct object *find_object_by_id(struct manager *m, uint32_t id) { struct object *o; @@ -180,7 +163,19 @@ static struct object *find_object_by_id(struct manager *m, uint32_t id) static void object_update_params(struct object *o) { - struct pw_manager_param *p; + struct pw_manager_param *p, *t; + uint32_t i; + + for (i = 0; i < o->this.n_params; i++) { + spa_list_for_each_safe(p, t, &o->pending_list, link) { + if (p->id == o->this.params[i].id && + p->seq != o->this.params[i].seq && + p->param != NULL) { + spa_list_remove(&p->link); + free(p); + } + } + } spa_list_consume(p, &o->pending_list, link) { spa_list_remove(&p->link); @@ -236,6 +231,8 @@ static void client_event_info(void *data, const struct pw_client_info *info) pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->this.id, info->change_mask); info = o->this.info = pw_client_info_merge(o->this.info, info, o->this.changed == 0); + if (info == NULL) + return; if (info->change_mask & PW_CLIENT_CHANGE_MASK_PROPS) changed++; @@ -275,6 +272,8 @@ static void module_event_info(void *data, const struct pw_module_info *info) pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->this.id, info->change_mask); info = o->this.info = pw_module_info_merge(o->this.info, info, o->this.changed == 0); + if (info == NULL) + return; if (info->change_mask & PW_MODULE_CHANGE_MASK_PROPS) changed++; @@ -314,6 +313,11 @@ static void device_event_info(void *data, const struct pw_device_info *info) pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->this.id, info->change_mask); info = o->this.info = pw_device_info_merge(o->this.info, info, o->this.changed == 0); + if (info == NULL) + return; + + o->this.n_params = info->n_params; + o->this.params = info->params; if (info->change_mask & PW_DEVICE_CHANGE_MASK_PROPS) changed++; @@ -327,11 +331,6 @@ static void device_event_info(void *data, const struct pw_device_info *info) continue; info->params[i].user = 0; - if (id >= MAX_PARAMS) { - pw_log_error("too big param id %d", id); - continue; - } - switch (id) { case SPA_PARAM_EnumProfile: case SPA_PARAM_Profile: @@ -341,14 +340,14 @@ static void device_event_info(void *data, const struct pw_device_info *info) case SPA_PARAM_Route: break; } - add_param(&o->pending_list, o->param_seq[id], o->param_seq, id, NULL); + add_param(&o->pending_list, info->params[i].seq, id, NULL); if (!(info->params[i].flags & SPA_PARAM_INFO_READ)) continue; res = pw_device_enum_params((struct pw_device*)o->this.proxy, - ++o->param_seq[id], id, 0, -1, NULL); + ++info->params[i].seq, id, 0, -1, NULL); if (SPA_RESULT_IS_ASYNC(res)) - o->param_seq[id] = res; + info->params[i].seq = res; } } if (changed) { @@ -385,7 +384,7 @@ static void device_event_param(void *data, int seq, struct manager *m = o->manager; struct pw_manager_param *p; - p = add_param(&o->pending_list, seq, o->param_seq, id, param); + p = add_param(&o->pending_list, seq, id, param); if (p == NULL) return; @@ -434,6 +433,11 @@ static void node_event_info(void *data, const struct pw_node_info *info) pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->this.id, info->change_mask); info = o->this.info = pw_node_info_merge(o->this.info, info, o->this.changed == 0); + if (info == NULL) + return; + + o->this.n_params = info->n_params; + o->this.params = info->params; if (info->change_mask & PW_NODE_CHANGE_MASK_STATE) changed++; @@ -450,20 +454,15 @@ static void node_event_info(void *data, const struct pw_node_info *info) continue; info->params[i].user = 0; - if (id >= MAX_PARAMS) { - pw_log_error("too big param id %d", id); - continue; - } - changed++; - add_param(&o->pending_list, o->param_seq[id], o->param_seq, id, NULL); + add_param(&o->pending_list, info->params[i].seq, id, NULL); if (!(info->params[i].flags & SPA_PARAM_INFO_READ)) continue; res = pw_node_enum_params((struct pw_node*)o->this.proxy, - ++o->param_seq[id], id, 0, -1, NULL); + ++info->params[i].seq, id, 0, -1, NULL); if (SPA_RESULT_IS_ASYNC(res)) - o->param_seq[id] = res; + info->params[i].seq = res; } } if (changed) { @@ -477,7 +476,7 @@ static void node_event_param(void *data, int seq, const struct spa_pod *param) { struct object *o = data; - add_param(&o->pending_list, seq, o->param_seq, id, param); + add_param(&o->pending_list, seq, id, param); } static const struct pw_node_events node_events = { @@ -553,11 +552,10 @@ static const struct object_info *objects[] = static const struct object_info *find_info(const char *type, uint32_t version) { - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(objects); i++) { - if (spa_streq(objects[i]->type, type) && - objects[i]->version <= version) - return objects[i]; + SPA_FOR_EACH_ELEMENT_VAR(objects, i) { + if (spa_streq((*i)->type, type) && + (*i)->version <= version) + return *i; } return NULL; } diff --git a/src/modules/module-protocol-pulse/manager.h b/src/modules/module-protocol-pulse/manager.h index 05ce2e9c5c45b5c15c84c04429867e90b281ce3f..56aea25bb1e5f9d414fa7a5a5f4fd42e40a48c41 100644 --- a/src/modules/module-protocol-pulse/manager.h +++ b/src/modules/module-protocol-pulse/manager.h @@ -72,6 +72,7 @@ struct pw_manager { struct pw_manager_param { uint32_t id; + int32_t seq; struct spa_list link; /**< link in manager_object param_list */ struct spa_pod *param; }; @@ -92,6 +93,9 @@ struct pw_manager_object { int changed; void *info; + struct spa_param_info *params; + uint32_t n_params; + struct spa_list param_list; unsigned int creating:1; unsigned int removing:1; diff --git a/src/modules/module-protocol-pulse/modules/module-loopback.c b/src/modules/module-protocol-pulse/modules/module-loopback.c index df6e47a4bc76b4506742514fe960d676a41d3c11..94d7fc70a5cc06dca1052970eba26b0455f1bb23 100644 --- a/src/modules/module-protocol-pulse/modules/module-loopback.c +++ b/src/modules/module-protocol-pulse/modules/module-loopback.c @@ -47,6 +47,7 @@ struct module_loopback_data { struct pw_properties *playback_props; struct spa_audio_info_raw info; + uint32_t latency_msec; }; static void module_destroy(void *data) @@ -68,6 +69,7 @@ static int module_loopback_load(struct client *client, struct module *module) FILE *f; char *args; size_t size, i; + char val[256]; pw_properties_setf(data->capture_props, PW_KEY_NODE_GROUP, "loopback-%u", module->index); pw_properties_setf(data->playback_props, PW_KEY_NODE_GROUP, "loopback-%u", module->index); @@ -88,6 +90,10 @@ static int module_loopback_load(struct client *client, struct module *module) fprintf(f, " ]"); } } + if (data->latency_msec != 0) + fprintf(f, " target.delay.sec = %s", + spa_json_format_float(val, sizeof(val), + data->latency_msec / 1000.0f)); fprintf(f, " capture.props = {"); pw_properties_serialize_dict(f, &data->capture_props->dict, 0); fprintf(f, " } playback.props = {"); @@ -204,15 +210,8 @@ static int module_loopback_prepare(struct module * const module) pw_properties_set(props, "remix", NULL); } - if ((str = pw_properties_get(props, "latency_msec")) != NULL) { - /* Half the latency on each of the playback and capture streams */ - pw_properties_setf(capture_props, PW_KEY_NODE_LATENCY, "%s/2000", str); - pw_properties_setf(playback_props, PW_KEY_NODE_LATENCY, "%s/2000", str); - pw_properties_set(props, "latency_msec", NULL); - } else { - pw_properties_set(capture_props, PW_KEY_NODE_LATENCY, "100/1000"); - pw_properties_set(playback_props, PW_KEY_NODE_LATENCY, "100/1000"); - } + if ((str = pw_properties_get(props, "latency_msec")) != NULL) + d->latency_msec = atoi(str); if ((str = pw_properties_get(props, "sink_input_properties")) != NULL) { module_args_add_props(playback_props, str); diff --git a/src/modules/module-protocol-pulse/modules/module-rtp-recv.c b/src/modules/module-protocol-pulse/modules/module-rtp-recv.c new file mode 100644 index 0000000000000000000000000000000000000000..29a0d70da38420b3f506530f7c6a948ef3056b97 --- /dev/null +++ b/src/modules/module-protocol-pulse/modules/module-rtp-recv.c @@ -0,0 +1,165 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com> + * + * 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/hook.h> +#include <pipewire/pipewire.h> +#include <pipewire/private.h> + +#include "../defs.h" +#include "../module.h" + +#define NAME "rtp-recv" + +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); +#define PW_LOG_TOPIC_DEFAULT mod_topic + +struct module_rtp_recv_data { + struct module *module; + + struct spa_hook mod_listener; + struct pw_impl_module *mod; + + struct pw_properties *stream_props; + struct pw_properties *global_props; +}; + +static void module_destroy(void *data) +{ + struct module_rtp_recv_data *d = data; + spa_hook_remove(&d->mod_listener); + d->mod = NULL; + module_schedule_unload(d->module); +} + +static const struct pw_impl_module_events module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = module_destroy +}; + +static int module_rtp_recv_load(struct client *client, struct module *module) +{ + struct module_rtp_recv_data *data = module->user_data; + FILE *f; + char *args; + size_t size; + + pw_properties_setf(data->stream_props, "pulse.module.id", + "%u", module->index); + + if ((f = open_memstream(&args, &size)) == NULL) + return -errno; + + fprintf(f, "{"); + pw_properties_serialize_dict(f, &data->global_props->dict, 0); + fprintf(f, " stream.props = {"); + pw_properties_serialize_dict(f, &data->stream_props->dict, 0); + fprintf(f, " } }"); + fclose(f); + + data->mod = pw_context_load_module(module->impl->context, + "libpipewire-module-rtp-source", + args, NULL); + + free(args); + + if (data->mod == NULL) + return -errno; + + pw_impl_module_add_listener(data->mod, + &data->mod_listener, + &module_events, data); + + return 0; +} + +static int module_rtp_recv_unload(struct module *module) +{ + struct module_rtp_recv_data *d = module->user_data; + + if (d->mod) { + spa_hook_remove(&d->mod_listener); + pw_impl_module_destroy(d->mod); + d->mod = NULL; + } + + pw_properties_free(d->global_props); + pw_properties_free(d->stream_props); + + return 0; +} + +static const struct spa_dict_item module_rtp_recv_info[] = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { PW_KEY_MODULE_DESCRIPTION, "Receive data from a network via RTP/SAP/SDP" }, + { PW_KEY_MODULE_USAGE, "sink=<name of the sink> " + "sap_address=<multicast address to listen on> " + "latency_msec=<latency in ms> " }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + +static int module_rtp_recv_prepare(struct module * const module) +{ + struct module_rtp_recv_data * const d = module->user_data; + struct pw_properties * const props = module->props; + struct pw_properties *stream_props = NULL, *global_props = NULL; + const char *str; + int res; + + PW_LOG_TOPIC_INIT(mod_topic); + + stream_props = pw_properties_new(NULL, NULL); + global_props = pw_properties_new(NULL, NULL); + if (!stream_props || !global_props) { + res = -errno; + goto out; + } + if ((str = pw_properties_get(props, "sink")) != NULL) + pw_properties_set(stream_props, PW_KEY_NODE_TARGET, str); + + if ((str = pw_properties_get(props, "sap_address")) != NULL) + pw_properties_set(global_props, "sap.ip", str); + + if ((str = pw_properties_get(props, "latency_msec")) != NULL) + pw_properties_set(global_props, "sess.latency.msec", str); + + d->module = module; + d->stream_props = stream_props; + d->global_props = global_props; + + return 0; +out: + pw_properties_free(stream_props); + pw_properties_free(global_props); + + return res; +} + +DEFINE_MODULE_INFO(module_rtp_recv) = { + .name = "module-rtp-recv", + .prepare = module_rtp_recv_prepare, + .load = module_rtp_recv_load, + .unload = module_rtp_recv_unload, + .properties = &SPA_DICT_INIT_ARRAY(module_rtp_recv_info), + .data_size = sizeof(struct module_rtp_recv_data), +}; diff --git a/src/modules/module-protocol-pulse/modules/module-rtp-send.c b/src/modules/module-protocol-pulse/modules/module-rtp-send.c new file mode 100644 index 0000000000000000000000000000000000000000..1f13e65075dcbcd50cc6e01beaa2a70cd4546128 --- /dev/null +++ b/src/modules/module-protocol-pulse/modules/module-rtp-send.c @@ -0,0 +1,226 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com> + * + * 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/hook.h> +#include <pipewire/pipewire.h> +#include <pipewire/private.h> + +#include "../defs.h" +#include "../module.h" + +#define NAME "rtp-send" + +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); +#define PW_LOG_TOPIC_DEFAULT mod_topic + +struct module_rtp_send_data { + struct module *module; + + struct spa_hook mod_listener; + struct pw_impl_module *mod; + + struct pw_properties *stream_props; + struct pw_properties *global_props; + struct spa_audio_info_raw info; +}; + +static void module_destroy(void *data) +{ + struct module_rtp_send_data *d = data; + spa_hook_remove(&d->mod_listener); + d->mod = NULL; + module_schedule_unload(d->module); +} + +static const struct pw_impl_module_events module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = module_destroy +}; + +static int module_rtp_send_load(struct client *client, struct module *module) +{ + struct module_rtp_send_data *data = module->user_data; + FILE *f; + char *args; + size_t size; + uint32_t i; + + pw_properties_setf(data->stream_props, "pulse.module.id", + "%u", module->index); + + if ((f = open_memstream(&args, &size)) == NULL) + return -errno; + + fprintf(f, "{"); + pw_properties_serialize_dict(f, &data->global_props->dict, 0); + if (data->info.format != 0) + fprintf(f, " \"audio.format\": \"%s\"", format_id2name(data->info.format)); + if (data->info.rate != 0) + fprintf(f, " \"audio.rate\": %u,", data->info.rate); + if (data->info.channels != 0) { + fprintf(f, " \"audio.channels\": %u,", data->info.channels); + if (!(data->info.flags & SPA_AUDIO_FLAG_UNPOSITIONED)) { + fprintf(f, " \"audio.position\": [ "); + for (i = 0; i < data->info.channels; i++) + fprintf(f, "%s\"%s\"", i == 0 ? "" : ",", + channel_id2name(data->info.position[i])); + fprintf(f, " ],"); + } + } + fprintf(f, " stream.props = {"); + pw_properties_serialize_dict(f, &data->stream_props->dict, 0); + fprintf(f, " } }"); + fclose(f); + + data->mod = pw_context_load_module(module->impl->context, + "libpipewire-module-rtp-sink", + args, NULL); + + free(args); + + if (data->mod == NULL) + return -errno; + + pw_impl_module_add_listener(data->mod, + &data->mod_listener, + &module_events, data); + + return 0; +} + +static int module_rtp_send_unload(struct module *module) +{ + struct module_rtp_send_data *d = module->user_data; + + if (d->mod) { + spa_hook_remove(&d->mod_listener); + pw_impl_module_destroy(d->mod); + d->mod = NULL; + } + + pw_properties_free(d->global_props); + pw_properties_free(d->stream_props); + + return 0; +} + +static const struct spa_dict_item module_rtp_send_info[] = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { PW_KEY_MODULE_DESCRIPTION, "Read data from source and send it to the network via RTP/SAP/SDP" }, + { PW_KEY_MODULE_USAGE, "source=<name of the source> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "destination_ip=<destination IP address> " + "source_ip=<source IP address> " + "port=<port number> " + "mtu=<maximum transfer unit> " + "loop=<loopback to local host?> " + "ttl=<ttl value> " + "inhibit_auto_suspend=<always|never|only_with_non_monitor_sources> " + "stream_name=<name of the stream> " + "enable_opus=<enable OPUS codec>" }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + +static int module_rtp_send_prepare(struct module * const module) +{ + 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 spa_audio_info_raw info = { 0 }; + const char *str; + int res; + + PW_LOG_TOPIC_INIT(mod_topic); + + stream_props = pw_properties_new(NULL, NULL); + global_props = pw_properties_new(NULL, NULL); + if (!stream_props || !global_props) { + res = -errno; + goto out; + } + + if ((str = pw_properties_get(props, "source")) != NULL) { + pw_properties_set(stream_props, PW_KEY_NODE_TARGET, str); + if (spa_strendswith(str, ".monitor")) { + pw_properties_setf(stream_props, PW_KEY_NODE_TARGET, + "%.*s", (int)strlen(str)-8, str); + pw_properties_set(stream_props, PW_KEY_STREAM_CAPTURE_SINK, + "true"); + } else { + pw_properties_set(stream_props, PW_KEY_NODE_TARGET, str); + } + } + if (module_args_to_audioinfo(module->impl, props, &info) < 0) { + res = -EINVAL; + goto out; + } + info.format = 0; + if ((str = pw_properties_get(props, "format")) != NULL) { + if ((info.format = format_paname2id(str, strlen(str))) == + SPA_AUDIO_FORMAT_UNKNOWN) { + pw_log_error("unknown format %s", str); + res = -EINVAL; + goto out; + } + } + + 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, "source.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) + pw_properties_set(global_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->info = info; + + return 0; +out: + pw_properties_free(stream_props); + pw_properties_free(global_props); + + return res; +} + +DEFINE_MODULE_INFO(module_rtp_send) = { + .name = "module-rtp-send", + .prepare = module_rtp_send_prepare, + .load = module_rtp_send_load, + .unload = module_rtp_send_unload, + .properties = &SPA_DICT_INIT_ARRAY(module_rtp_send_info), + .data_size = sizeof(struct module_rtp_send_data), +}; diff --git a/src/modules/module-protocol-pulse/modules/module-zeroconf-discover.c b/src/modules/module-protocol-pulse/modules/module-zeroconf-discover.c index 4affa7fae681cf81c4e927a22eb72f245d463b28..79f14e853c2433b2286ad9742e3b7c91e5ec7c81 100644 --- a/src/modules/module-protocol-pulse/modules/module-zeroconf-discover.c +++ b/src/modules/module-protocol-pulse/modules/module-zeroconf-discover.c @@ -40,6 +40,8 @@ struct module_zeroconf_discover_data { struct spa_hook mod_listener; struct pw_impl_module *mod; + + uint32_t latency_msec; }; static void module_destroy(void *data) @@ -58,10 +60,25 @@ static const struct pw_impl_module_events module_events = { static int module_zeroconf_discover_load(struct client *client, struct module *module) { struct module_zeroconf_discover_data *data = module->user_data; + FILE *f; + char *args; + size_t size; + + if ((f = open_memstream(&args, &size)) == NULL) + return -errno; + + fprintf(f, "{"); + if (data->latency_msec > 0) + fprintf(f, " pulse.latency = %u ", data->latency_msec); + fprintf(f, "}"); + fclose(f); data->mod = pw_context_load_module(module->impl->context, "libpipewire-module-zeroconf-discover", - NULL, NULL); + args, NULL); + + free(args); + if (data->mod == NULL) return -errno; @@ -88,7 +105,8 @@ static int module_zeroconf_discover_unload(struct module *module) static const struct spa_dict_item module_zeroconf_discover_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.con>" }, { PW_KEY_MODULE_DESCRIPTION, "mDNS/DNS-SD Service Discovery" }, - { PW_KEY_MODULE_USAGE, "" }, + { PW_KEY_MODULE_USAGE, + "latency_msec=<fixed latency in ms> " }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; @@ -96,9 +114,12 @@ static int module_zeroconf_discover_prepare(struct module * const module) { PW_LOG_TOPIC_INIT(mod_topic); + struct pw_properties * const props = module->props; struct module_zeroconf_discover_data * const data = module->user_data; data->module = module; + pw_properties_fetch_uint32(props, "latency_msec", &data->latency_msec); + return 0; } diff --git a/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c b/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c index e30c91bee3b79d012b5ebb5d275b5d8f34a1883a..8da1b6ae83edf518bc5264fdccae07e554c6995b 100644 --- a/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c +++ b/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c @@ -24,6 +24,7 @@ */ #include <sys/utsname.h> +#include <arpa/inet.h> #include <pipewire/pipewire.h> @@ -32,6 +33,7 @@ #include "../manager.h" #include "../module.h" #include "../pulse-server.h" +#include "../server.h" #include "../../module-zeroconf-discover/avahi-poll.h" #include <avahi-client/client.h> @@ -83,6 +85,7 @@ struct service { struct pw_properties *props; char service_name[AVAHI_LABEL_MAX]; + unsigned published:1; }; struct module_zeroconf_publish_data { @@ -93,8 +96,7 @@ struct module_zeroconf_publish_data { struct spa_hook core_listener; struct spa_hook manager_listener; - - unsigned int port; + struct spa_hook impl_listener; AvahiPoll *avahi_poll; AvahiClient *client; @@ -151,6 +153,7 @@ static void unpublish_service(struct service *s) { spa_list_remove(&s->link); spa_list_append(&s->userdata->pending, &s->link); + s->published = false; } static void unpublish_all_services(struct module_zeroconf_publish_data *d) @@ -315,6 +318,11 @@ static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupStat struct service *s = userdata; spa_assert(s); + if (!s->published) { + pw_log_info("cancel unpublished service: %s", s->service_name); + clear_entry_group(s); + return; + } switch (state) { case AVAHI_ENTRY_GROUP_ESTABLISHED: @@ -380,8 +388,7 @@ static AvahiStringList *get_service_txt(const struct service *s) txt = avahi_string_list_add_pair(txt, "channel_map", channel_map_snprint(cm, sizeof(cm), &s->cm)); txt = avahi_string_list_add_pair(txt, "subtype", subtype_text[s->subtype]); - const struct mapping *m; - SPA_FOR_EACH_ELEMENT(mappings, m) { + SPA_FOR_EACH_ELEMENT_VAR(mappings, m) { const char *value = pw_properties_get(s->props, m->pw_key); if (value != NULL) txt = avahi_string_list_add_pair(txt, m->txt_key, value); @@ -390,17 +397,47 @@ static AvahiStringList *get_service_txt(const struct service *s) return txt; } +static int find_port(struct service *s, int *proto, uint16_t *port) +{ + struct module_zeroconf_publish_data *d = s->userdata; + struct impl *impl = d->module->impl; + struct server *server; + + spa_list_for_each(server, &impl->servers, link) { + if (server->addr.ss_family == AF_INET) { + *proto = AVAHI_PROTO_INET; + *port = ntohs(((struct sockaddr_in*) &server->addr)->sin_port); + return 0; + } else if (server->addr.ss_family == AF_INET6) { + *proto = AVAHI_PROTO_INET6; + *port = ntohs(((struct sockaddr_in6*) &server->addr)->sin6_port); + return 0; + } + } + return -ENODEV; +} + static void publish_service(struct service *s) { - if (!s->userdata->client || avahi_client_get_state(s->userdata->client) != AVAHI_CLIENT_S_RUNNING) + struct module_zeroconf_publish_data *d = s->userdata; + int proto; + uint16_t port; + + if (find_port(s, &proto, &port) < 0) + return; + + pw_log_debug("found proto:%d port:%d", proto, port); + + if (!d->client || avahi_client_get_state(d->client) != AVAHI_CLIENT_S_RUNNING) return; + s->published = true; if (!s->entry_group) { - s->entry_group = avahi_entry_group_new(s->userdata->client, service_entry_group_callback, s); + s->entry_group = avahi_entry_group_new(d->client, service_entry_group_callback, s); if (s->entry_group == NULL) { pw_log_error("avahi_entry_group_new(): %s", - avahi_strerror(avahi_client_errno(s->userdata->client))); - return; + avahi_strerror(avahi_client_errno(d->client))); + goto error; } } else { avahi_entry_group_reset(s->entry_group); @@ -411,22 +448,22 @@ static void publish_service(struct service *s) if (avahi_entry_group_add_service_strlst( s->entry_group, - AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + AVAHI_IF_UNSPEC, proto, 0, s->service_name, s->service_type, NULL, NULL, - s->userdata->port, + port, s->txt) < 0) { pw_log_error("avahi_entry_group_add_service_strlst(): %s", - avahi_strerror(avahi_client_errno(s->userdata->client))); - return; + avahi_strerror(avahi_client_errno(d->client))); + goto error; } if (avahi_entry_group_add_service_subtype( s->entry_group, - AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + AVAHI_IF_UNSPEC, proto, 0, s->service_name, s->service_type, @@ -435,35 +472,40 @@ static void publish_service(struct service *s) (s->subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SOURCE_HARDWARE : (s->subtype == SUBTYPE_VIRTUAL ? SERVICE_SUBTYPE_SOURCE_VIRTUAL : SERVICE_SUBTYPE_SOURCE_MONITOR))) < 0) { pw_log_error("avahi_entry_group_add_service_subtype(): %s", - avahi_strerror(avahi_client_errno(s->userdata->client))); - return; + avahi_strerror(avahi_client_errno(d->client))); + goto error; } if (!s->is_sink && s->subtype != SUBTYPE_MONITOR) { if (avahi_entry_group_add_service_subtype( s->entry_group, - AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + AVAHI_IF_UNSPEC, proto, 0, s->service_name, SERVICE_TYPE_SOURCE, NULL, SERVICE_SUBTYPE_SOURCE_NON_MONITOR) < 0) { pw_log_error("avahi_entry_group_add_service_subtype(): %s", - avahi_strerror(avahi_client_errno(s->userdata->client))); - return; + avahi_strerror(avahi_client_errno(d->client))); + goto error; } } if (avahi_entry_group_commit(s->entry_group) < 0) { pw_log_error("avahi_entry_group_commit(): %s", - avahi_strerror(avahi_client_errno(s->userdata->client))); - return; + avahi_strerror(avahi_client_errno(d->client))); + goto error; } spa_list_remove(&s->link); - spa_list_append(&s->userdata->published, &s->link); + spa_list_append(&d->published, &s->link); pw_log_info("created service: %s", s->service_name); + return; + +error: + s->published = false; + return; } static void publish_pending(struct module_zeroconf_publish_data *data) @@ -572,6 +614,28 @@ static const struct pw_manager_events manager_events = { .removed = manager_removed, }; + +static void impl_server_started(void *data, struct server *server) +{ + struct module_zeroconf_publish_data *d = data; + pw_log_info("a new server is started, try publish"); + publish_pending(d); +} + +static void impl_server_stopped(void *data, struct server *server) +{ + struct module_zeroconf_publish_data *d = data; + pw_log_info("a server stopped, try republish"); + unpublish_all_services(d); + publish_pending(d); +} + +static const struct impl_events impl_events = { + VERSION_IMPL_EVENTS, + .server_started = impl_server_started, + .server_stopped = impl_server_stopped, +}; + static int module_zeroconf_publish_load(struct client *client, struct module *module) { struct module_zeroconf_publish_data *data = module->user_data; @@ -608,6 +672,8 @@ static int module_zeroconf_publish_load(struct client *client, struct module *mo pw_manager_add_listener(data->manager, &data->manager_listener, &manager_events, data); + impl_add_listener(module->impl, &data->impl_listener, &impl_events, data); + return 0; } @@ -616,6 +682,8 @@ static int module_zeroconf_publish_unload(struct module *module) struct module_zeroconf_publish_data *d = module->user_data; struct service *s; + spa_hook_remove(&d->impl_listener); + unpublish_all_services(d); spa_list_consume(s, &d->pending, link) @@ -643,7 +711,6 @@ static int module_zeroconf_publish_unload(struct module *module) static const struct spa_dict_item module_zeroconf_publish_info[] = { { PW_KEY_MODULE_AUTHOR, "Sanchayan Maity <sanchayan@asymptotic.io" }, { PW_KEY_MODULE_DESCRIPTION, "mDNS/DNS-SD Service Publish" }, - { PW_KEY_MODULE_USAGE, "port=<TCP port number>" }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; @@ -653,7 +720,6 @@ static int module_zeroconf_publish_prepare(struct module * const module) struct module_zeroconf_publish_data * const data = module->user_data; data->module = module; - data->port = pw_properties_get_uint32(module->props, "port", PW_PROTOCOL_PULSE_DEFAULT_PORT); spa_list_init(&data->pending); spa_list_init(&data->published); diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index b403aba2140b893be7f3bf7ed9ff86032aa8a943..84bf3b56c5299aed0fc905073f35a9258f6fdfc7 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -85,11 +85,12 @@ #define DEFAULT_MIN_QUANTUM "256/48000" #define DEFAULT_FORMAT "F32" #define DEFAULT_POSITION "[ FL FR ]" +#define DEFAULT_IDLE_TIMEOUT "5" #define MAX_FORMATS 32 /* The max amount of data we send in one block when capturing. In PulseAudio this * size is derived from the mempool PA_MEMPOOL_SLOT_SIZE */ -#define MAX_FRAGSIZE (64*1024) +#define MAX_BLOCK (64*1024) #define TEMPORARY_MOVE_TIMEOUT (SPA_NSEC_PER_SEC) @@ -455,7 +456,7 @@ static void clamp_latency(struct stream *s, struct spa_fraction *lat) static uint64_t fix_playback_buffer_attr(struct stream *s, struct buffer_attr *attr, uint32_t rate, struct spa_fraction *lat) { - uint32_t frame_size, max_prebuf, minreq, latency, max_latency; + uint32_t frame_size, max_prebuf, minreq, latency, max_latency, maxlength; struct defs *defs = &s->impl->defs; if ((frame_size = s->frame_size) == 0) @@ -463,24 +464,26 @@ static uint64_t fix_playback_buffer_attr(struct stream *s, struct buffer_attr *a if (frame_size == 0) frame_size = 4; - pw_log_info("[%s] maxlength:%u tlength:%u minreq:%u prebuf:%u", + maxlength = SPA_ROUND_DOWN(MAXLENGTH, frame_size); + + pw_log_info("[%s] maxlength:%u tlength:%u minreq:%u prebuf:%u max:%u", s->client->name, attr->maxlength, attr->tlength, - attr->minreq, attr->prebuf); + attr->minreq, attr->prebuf, maxlength); minreq = frac_to_bytes_round_up(s->min_req, &s->ss); max_latency = defs->quantum_limit * frame_size; - if (attr->maxlength == (uint32_t) -1 || attr->maxlength > MAXLENGTH) - attr->maxlength = MAXLENGTH; - attr->maxlength = SPA_ROUND_UP(attr->maxlength, frame_size); + if (attr->maxlength == (uint32_t) -1 || attr->maxlength > maxlength) + attr->maxlength = maxlength; + else + attr->maxlength = SPA_ROUND_DOWN(attr->maxlength, frame_size); minreq = SPA_MIN(minreq, attr->maxlength); if (attr->tlength == (uint32_t) -1) attr->tlength = frac_to_bytes_round_up(s->default_tlength, &s->ss); - attr->tlength = SPA_MIN(attr->tlength, attr->maxlength); + attr->tlength = SPA_CLAMP(attr->tlength, minreq, attr->maxlength); attr->tlength = SPA_ROUND_UP(attr->tlength, frame_size); - attr->tlength = SPA_MAX(attr->tlength, minreq); if (attr->minreq == (uint32_t) -1) { uint32_t process = frac_to_bytes_round_up(s->default_req, &s->ss); @@ -655,39 +658,46 @@ static int reply_create_playback_stream(struct stream *stream, struct pw_manager static uint64_t fix_record_buffer_attr(struct stream *s, struct buffer_attr *attr, uint32_t rate, struct spa_fraction *lat) { - uint32_t frame_size, minfrag, latency; + uint32_t frame_size, minfrag, latency, maxlength; if ((frame_size = s->frame_size) == 0) frame_size = sample_spec_frame_size(&s->ss); if (frame_size == 0) frame_size = 4; + maxlength = SPA_ROUND_DOWN(MAXLENGTH, frame_size); + pw_log_info("[%s] maxlength:%u fragsize:%u framesize:%u", s->client->name, attr->maxlength, attr->fragsize, frame_size); - if (attr->maxlength == (uint32_t) -1 || attr->maxlength > MAXLENGTH) - attr->maxlength = MAXLENGTH; - attr->maxlength -= attr->maxlength % frame_size; + if (attr->maxlength == (uint32_t) -1 || attr->maxlength > maxlength) + attr->maxlength = maxlength; + else + attr->maxlength = SPA_ROUND_DOWN(attr->maxlength, frame_size); attr->maxlength = SPA_MAX(attr->maxlength, frame_size); minfrag = frac_to_bytes_round_up(s->min_frag, &s->ss); if (attr->fragsize == (uint32_t) -1 || attr->fragsize == 0) attr->fragsize = frac_to_bytes_round_up(s->default_frag, &s->ss); - attr->fragsize = SPA_MIN(attr->fragsize, attr->maxlength); + attr->fragsize = SPA_CLAMP(attr->fragsize, minfrag, attr->maxlength); attr->fragsize = SPA_ROUND_UP(attr->fragsize, frame_size); - attr->fragsize = SPA_MAX(attr->fragsize, minfrag); attr->tlength = attr->minreq = attr->prebuf = 0; - /* make sure can queue at least to fragsize without overruns */ - if (attr->maxlength < attr->fragsize * 4) + /* make sure we can queue at least to fragsize without overruns */ + if (attr->maxlength < attr->fragsize * 4) { attr->maxlength = attr->fragsize * 4; + if (attr->maxlength > maxlength) { + attr->maxlength = maxlength; + attr->fragsize = SPA_ROUND_DOWN(maxlength / 4, frame_size); + } + } - latency = attr->fragsize / frame_size; + latency = attr->fragsize; - lat->num = latency; + lat->num = latency / frame_size; lat->denom = rate; clamp_latency(s, lat); @@ -1232,7 +1242,7 @@ static void stream_param_changed(void *data, uint32_t id, const struct spa_pod * SPA_PROP_mute, 1, &val, 0); } if (stream->corked) - pw_stream_set_active(stream->stream, false); + stream_set_paused(stream, true, "cork after create"); /* if peer exists, reply immediately, otherwise reply when the link is created */ peer = find_linked(stream->client->manager, stream->id, stream->direction); @@ -1267,6 +1277,7 @@ struct process_data { uint32_t minreq; uint32_t quantum; unsigned int underrun:1; + unsigned int idle:1; }; static int @@ -1306,6 +1317,17 @@ do_process_done(struct spa_loop *loop, else stream_send_started(stream); } + if (pd->idle) { + if (!stream->is_idle) { + stream->idle_time = stream->timestamp; + } else if (!stream->is_paused && + stream->idle_timeout_sec > 0 && + stream->timestamp - stream->idle_time > + (stream->idle_timeout_sec * SPA_NSEC_PER_SEC)) { + stream_set_paused(stream, true, "long underrun"); + } + } + stream->is_idle = pd->idle; stream->playing_for += pd->playing_for; if (stream->underrun_for != (uint64_t)-1) stream->underrun_for += pd->underrun_for; @@ -1341,7 +1363,8 @@ do_process_done(struct spa_loop *loop, pw_log_trace("avail:%d index:%u", avail, index); while ((uint32_t)avail >= stream->attr.fragsize) { - towrite = SPA_MIN(avail, MAX_FRAGSIZE); + towrite = SPA_MIN(avail, MAX_BLOCK); + towrite = SPA_MIN(towrite, stream->attr.fragsize); towrite = SPA_ROUND_DOWN(towrite, stream->frame_size); msg = message_alloc(impl, stream->channel, towrite); @@ -1418,16 +1441,19 @@ static void stream_process(void *data) } if ((stream->attr.prebuf == 0 || do_flush) && !stream->corked) { if (avail > 0) { + avail = SPA_MIN((uint32_t)avail, size); spa_ringbuffer_read_data(&stream->ring, stream->buffer, MAXLENGTH, index % MAXLENGTH, p, avail); - index += avail; } - pd.playing_for = size; + index += size; pd.read_inc = size; spa_ringbuffer_read_update(&stream->ring, index); + + pd.playing_for = size; } + pd.idle = true; pw_log_debug("%p: [%s] underrun read:%u avail:%d max:%u", stream, client->name, index, avail, minreq); } else { @@ -1512,7 +1538,7 @@ static void stream_drained(void *data) reply_simple_ack(stream->client, stream->drain_tag); stream->drain_tag = 0; - pw_stream_set_active(stream->stream, true); + stream_set_paused(stream, false, "complete drain"); } } @@ -2690,7 +2716,7 @@ static int do_cork_stream(struct client *client, uint32_t command, uint32_t tag, return -ENOENT; stream->corked = cork; - pw_stream_set_active(stream->stream, !cork); + stream_set_paused(stream, cork, "cork request"); if (cork) { stream->is_underrun = true; } else { @@ -3450,7 +3476,7 @@ static int do_drain_stream(struct client *client, uint32_t command, uint32_t tag stream->drain_tag = tag; stream->draining = true; - pw_stream_set_active(stream->stream, true); + stream_set_paused(stream, false, "drain start"); return 0; } @@ -5449,6 +5475,8 @@ static void impl_clear(struct impl *impl) pw_map_for_each(&impl->samples, impl_free_sample, impl); pw_map_clear(&impl->samples); + spa_hook_list_clean(&impl->hooks); + #ifdef HAVE_DBUS if (impl->dbus_name) { dbus_release_name(impl->dbus_name); @@ -5488,8 +5516,10 @@ static int parse_frac(struct pw_properties *props, const char *key, const char * if (props == NULL || (str = pw_properties_get(props, key)) == NULL) str = def; - if (sscanf(str, "%u/%u", &res->num, &res->denom) != 2 || res->denom == 0) - return -EINVAL; + if (sscanf(str, "%u/%u", &res->num, &res->denom) != 2 || res->denom == 0) { + pw_log_warn(": invalid fraction %s, default to %s", str, def); + sscanf(def, "%u/%u", &res->num, &res->denom); + } pw_log_info(": defaults: %s = %u/%u", key, res->num, res->denom); return 0; } @@ -5529,7 +5559,21 @@ static int parse_format(struct pw_properties *props, const char *key, const char pw_log_warn(": unknown format %s, default to %s", str, def); res->format = format_name2id(def); } - pw_log_info(": defaults: %s = %s", key, str); + pw_log_info(": defaults: %s = %s", key, format_id2name(res->format)); + return 0; +} +static int parse_uint32(struct pw_properties *props, const char *key, const char *def, + uint32_t *res) +{ + const char *str; + if (props == NULL || + (str = pw_properties_get(props, key)) == NULL) + str = def; + if (!spa_atou32(str, res, 0)) { + pw_log_warn(": invalid uint32_t %s, default to %s", str, def); + spa_atou32(def, res, 0); + } + pw_log_info(": defaults: %s = %u", key, *res); return 0; } @@ -5543,6 +5587,7 @@ static void load_defaults(struct defs *def, struct pw_properties *props) parse_frac(props, "pulse.min.quantum", DEFAULT_MIN_QUANTUM, &def->min_quantum); parse_format(props, "pulse.default.format", DEFAULT_FORMAT, &def->sample_spec); parse_position(props, "pulse.default.position", DEFAULT_POSITION, &def->channel_map); + parse_uint32(props, "pulse.idle.timeout", DEFAULT_IDLE_TIMEOUT, &def->idle_timeout); def->sample_spec.channels = def->channel_map.channels; def->quantum_limit = 8192; } @@ -5587,6 +5632,7 @@ struct pw_protocol_pulse *pw_protocol_pulse_new(struct pw_context *context, impl->work_queue = pw_context_get_work_queue(context); + spa_hook_list_init(&impl->hooks); spa_list_init(&impl->servers); impl->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; impl->rate_limit.burst = 1; @@ -5638,6 +5684,13 @@ error_exit: return NULL; } +void impl_add_listener(struct impl *impl, + struct spa_hook *listener, + const struct impl_events *events, void *data) +{ + spa_hook_list_append(&impl->hooks, listener, events, data); +} + void *pw_protocol_pulse_get_user_data(struct pw_protocol_pulse *pulse) { return SPA_PTROFF(pulse, sizeof(struct impl), void); diff --git a/src/modules/module-protocol-pulse/quirks.c b/src/modules/module-protocol-pulse/quirks.c index e00eafe91739ba6922894bdaeede3de37f1c8bfc..eb2243839afee3966696a62c1965d43182993227 100644 --- a/src/modules/module-protocol-pulse/quirks.c +++ b/src/modules/module-protocol-pulse/quirks.c @@ -38,10 +38,9 @@ static uint64_t parse_quirks(const char *str) { "force-s16-info", QUIRK_FORCE_S16_FORMAT }, { "remove-capture-dont-move", QUIRK_REMOVE_CAPTURE_DONT_MOVE }, }; - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(quirk_keys); ++i) { - if (spa_streq(str, quirk_keys[i].key)) - return quirk_keys[i].value; + SPA_FOR_EACH_ELEMENT_VAR(quirk_keys, i) { + if (spa_streq(str, i->key)) + return i->value; } return 0; } diff --git a/src/modules/module-protocol-pulse/server.c b/src/modules/module-protocol-pulse/server.c index af1913b9518033c35924f3229fcee8e018362743..503746f8f83dbbff1e1e1c5b9cd70720e92ba0f9 100644 --- a/src/modules/module-protocol-pulse/server.c +++ b/src/modules/module-protocol-pulse/server.c @@ -191,6 +191,9 @@ static int handle_memblock(struct client *client, struct message *msg) stream_send_request(stream); + if (stream->is_paused && !stream->corked) + stream_set_paused(stream, false, "new data"); + finish: message_free(msg, false, false); return res; @@ -616,7 +619,6 @@ static int start_unix_server(struct server *server, const struct sockaddr_storag pw_log_warn("server %p: unlink('%s') failed: %m", server, addr_un->sun_path); } - if (bind(fd, (const struct sockaddr *) addr_un, SUN_LEN(addr_un)) < 0) { res = -errno; pw_log_warn("server %p: bind() to '%s' failed: %m", @@ -624,6 +626,10 @@ static int start_unix_server(struct server *server, const struct sockaddr_storag goto error_close; } + if (chmod(addr_un->sun_path, 0777) < 0) + pw_log_warn("server %p: chmod('%s') failed: %m", + server, addr_un->sun_path); + if (listen(fd, server->listen_backlog) < 0) { res = -errno; pw_log_warn("server %p: listen() on '%s' failed: %m", @@ -896,7 +902,7 @@ static struct server *server_new(struct impl *impl) static int server_start(struct server *server, const struct sockaddr_storage *addr) { - const struct impl * const impl = server->impl; + struct impl * const impl = server->impl; int res = 0, fd; switch (addr->ss_family) { @@ -921,6 +927,8 @@ static int server_start(struct server *server, const struct sockaddr_storage *ad res = -errno; pw_log_error("server %p: can't create server source: %m", impl); } + if (res >= 0) + spa_hook_list_call(&impl->hooks, struct impl_events, server_started, 0, server); return res; } @@ -1066,6 +1074,8 @@ void server_free(struct server *server) client_unref(c); } + spa_hook_list_call(&impl->hooks, struct impl_events, server_stopped, 0, server); + if (server->source) pw_loop_destroy_source(impl->loop, server->source); diff --git a/src/modules/module-protocol-pulse/stream.c b/src/modules/module-protocol-pulse/stream.c index 730580da3a6b6709750e612d0161a21ee711376f..fb430908608ba59d2f8f869427ea1c617a6d452f 100644 --- a/src/modules/module-protocol-pulse/stream.c +++ b/src/modules/module-protocol-pulse/stream.c @@ -64,6 +64,7 @@ struct stream *stream_new(struct client *client, enum stream_type type, uint32_t { int res; struct defs *defs = &client->impl->defs; + const char *str; struct stream *stream = calloc(1, sizeof(*stream)); if (stream == NULL) @@ -90,6 +91,9 @@ struct stream *stream_new(struct client *client, enum stream_type type, uint32_t parse_frac(client->props, "pulse.default.req", &defs->default_req, &stream->default_req); parse_frac(client->props, "pulse.default.frag", &defs->default_frag, &stream->default_frag); parse_frac(client->props, "pulse.default.tlength", &defs->default_tlength, &stream->default_tlength); + stream->idle_timeout_sec = defs->idle_timeout; + if ((str = pw_properties_get(client->props, "pulse.idle.timeout")) != NULL) + spa_atou32(str, &stream->idle_timeout_sec, 0); switch (type) { case STREAM_TYPE_RECORD: @@ -207,6 +211,20 @@ uint32_t stream_pop_missing(struct stream *stream) return missing; } +void stream_set_paused(struct stream *stream, bool paused, const char *reason) +{ + if (stream->is_paused == paused) + return; + + if (reason && stream->client) + pw_log_info("%p: [%s] %s because of %s", + stream, stream->client->name, + paused ? "paused" : "resumed", reason); + + stream->is_paused = paused; + pw_stream_set_active(stream->stream, !paused); +} + int stream_send_underflow(struct stream *stream, int64_t offset) { struct client *client = stream->client; diff --git a/src/modules/module-protocol-pulse/stream.h b/src/modules/module-protocol-pulse/stream.h index 7ee6de36b48521f041a663ce11d85f5982af8194..424b2a1e8b4ea2ffdfbbf5578b7183a174ddcf1d 100644 --- a/src/modules/module-protocol-pulse/stream.h +++ b/src/modules/module-protocol-pulse/stream.h @@ -82,6 +82,7 @@ struct stream { uint64_t playing_for; uint64_t ticks_base; uint64_t timestamp; + uint64_t idle_time; int64_t delay; uint32_t last_quantum; @@ -93,6 +94,7 @@ struct stream { struct spa_fraction default_frag; struct spa_fraction default_tlength; struct spa_fraction min_quantum; + uint32_t idle_timeout_sec; struct sample_spec ss; struct channel_map map; @@ -115,6 +117,8 @@ struct stream { unsigned int in_prebuf:1; unsigned int killed:1; unsigned int pending:1; + unsigned int is_idle:1; + unsigned int is_paused:1; }; struct stream *stream_new(struct client *client, enum stream_type type, uint32_t create_tag, @@ -124,6 +128,8 @@ void stream_free(struct stream *stream); void stream_flush(struct stream *stream); uint32_t stream_pop_missing(struct stream *stream); +void stream_set_paused(struct stream *stream, bool paused, const char *reason); + int stream_send_underflow(struct stream *stream, int64_t offset); int stream_send_overflow(struct stream *stream); int stream_send_killed(struct stream *stream); diff --git a/src/modules/module-protocol-pulse/utils.c b/src/modules/module-protocol-pulse/utils.c index 1c9bc1c5f30cb4d4188e6248976bbbbd31478d51..617ca05244ffd3e0fe542d1295887412eaaf14e9 100644 --- a/src/modules/module-protocol-pulse/utils.c +++ b/src/modules/module-protocol-pulse/utils.c @@ -195,7 +195,7 @@ int create_pid_file(void) { strcat(pid_file, "/pid"); - if ((f = fopen(pid_file, "w")) == NULL) { + if ((f = fopen(pid_file, "we")) == NULL) { res = -errno; pw_log_error("failed to open pid file: %m"); return res; diff --git a/src/modules/module-pulse-tunnel.c b/src/modules/module-pulse-tunnel.c index f0231b9f3117e830551a64686ce8fec77337e95a..2c9a97df781fd21130ead6e3754dff392fbd2309 100644 --- a/src/modules/module-pulse-tunnel.c +++ b/src/modules/module-pulse-tunnel.c @@ -72,7 +72,7 @@ * (Default `sink`) * - `pulse.server.address`: the address of the PulseAudio server to tunnel to. * - `pulse.latency`: the latency to end-to-end latency in milliseconds to - * maintain (Default 200ms). + * maintain (Default 200). * - `stream.props`: Extra properties for the local stream. * * ## General options @@ -101,6 +101,7 @@ * tunnel.mode = sink * # Set the remote address to tunnel to * pulse.server.address = "tcp:192.168.1.126" + * #pulse.latency = 200 * #audio.rate=<sample rate> * #audio.channels=<number of channels> * #audio.position=<channel map> @@ -183,6 +184,8 @@ struct impl { pa_context *pa_context; pa_stream *pa_stream; + struct ratelimit rate_limit; + uint32_t target_latency; uint32_t current_latency; uint32_t target_buffer; @@ -524,9 +527,10 @@ static void stream_write_request_cb(pa_stream *s, size_t length, void *userdata) { struct impl *impl = userdata; int32_t avail; - uint32_t index, len, offset, l0, l1; + uint32_t index; + size_t size; pa_usec_t latency; - int negative; + int negative, res; if (impl->resync) { impl->resync = false; @@ -542,43 +546,54 @@ static void stream_write_request_cb(pa_stream *s, size_t length, void *userdata) impl->current_latency += avail / impl->frame_size; while (avail < (int32_t)length) { + uint32_t maxsize = SPA_ROUND_DOWN(sizeof(impl->empty), impl->frame_size); /* send silence for the data we don't have */ - len = SPA_MIN(length - avail, sizeof(impl->empty)); - pa_stream_write(impl->pa_stream, - impl->empty, len, - NULL, 0, PA_SEEK_RELATIVE); - length -= len; + size = SPA_MIN(length - avail, maxsize); + if ((res = pa_stream_write(impl->pa_stream, + impl->empty, size, + NULL, 0, PA_SEEK_RELATIVE)) != 0) + pw_log_warn("error writing stream: %s", pa_strerror(res)); + length -= size; } - if (length > 0 && avail >= (int32_t)length) { - /* always send as much as is requested */ - len = length; - offset = index & RINGBUFFER_MASK; - l0 = SPA_MIN(len, RINGBUFFER_SIZE - offset); - l1 = len - l0; - - pa_stream_write(impl->pa_stream, - SPA_PTROFF(impl->buffer, offset, void), l0, - NULL, 0, PA_SEEK_RELATIVE); - - if (SPA_UNLIKELY(l1 > 0)) { - pa_stream_write(impl->pa_stream, - impl->buffer, l1, - NULL, 0, PA_SEEK_RELATIVE); - } - index += len; + while (length > 0 && avail >= (int32_t)length) { + void *data; + + size = length; + pa_stream_begin_write(impl->pa_stream, &data, &size); + + spa_ringbuffer_read_data(&impl->ring, + impl->buffer, RINGBUFFER_SIZE, + index & RINGBUFFER_MASK, + data, size); + + if ((res = pa_stream_write(impl->pa_stream, + data, size, NULL, 0, PA_SEEK_RELATIVE)) != 0) + pw_log_warn("error writing stream: %zd %s", size, + pa_strerror(res)); + + index += size; + length -= size; + avail -= size; spa_ringbuffer_read_update(&impl->ring, index); } } static void stream_underflow_cb(pa_stream *s, void *userdata) { struct impl *impl = userdata; - pw_log_warn("underflow"); + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + if (ratelimit_test(&impl->rate_limit, SPA_TIMESPEC_TO_NSEC(&ts), SPA_LOG_LEVEL_WARN)) + pw_log_warn("underflow"); impl->resync = true; } static void stream_overflow_cb(pa_stream *s, void *userdata) { struct impl *impl = userdata; - pw_log_warn("overflow"); + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + if (ratelimit_test(&impl->rate_limit, SPA_TIMESPEC_TO_NSEC(&ts), SPA_LOG_LEVEL_WARN)) + pw_log_warn("overflow"); impl->resync = true; } @@ -606,13 +621,14 @@ static pa_proplist* tunnel_new_proplist(struct impl *impl) static int create_pulse_stream(struct impl *impl) { pa_sample_spec ss; + pa_channel_map map; const char *server_address, *remote_node_target; pa_proplist *props = NULL; pa_mainloop_api *api; char stream_name[1024]; pa_buffer_attr bufferattr; int res = -EIO; - uint32_t latency_bytes; + uint32_t latency_bytes, i, aux = 0; if ((impl->pa_mainloop = pa_threaded_mainloop_new()) == NULL) goto error; @@ -659,10 +675,14 @@ static int create_pulse_stream(struct impl *impl) ss.channels = impl->info.channels; ss.rate = impl->info.rate; + map.channels = impl->info.channels; + for (i = 0; i < map.channels; i++) + map.map[i] = (pa_channel_position_t)channel_id2pa(impl->info.position[i], &aux); + snprintf(stream_name, sizeof(stream_name), _("Tunnel for %s@%s"), pw_get_user_name(), pw_get_host_name()); - if (!(impl->pa_stream = pa_stream_new(impl->pa_context, stream_name, &ss, NULL))) { + if (!(impl->pa_stream = pa_stream_new(impl->pa_context, stream_name, &ss, &map))) { res = pa_context_errno(impl->pa_context); goto error_unlock; } @@ -701,6 +721,8 @@ static int create_pulse_stream(struct impl *impl) PA_STREAM_AUTO_TIMING_UPDATE); } else { bufferattr.tlength = latency_bytes / 2; + bufferattr.minreq = bufferattr.tlength / 4; + bufferattr.prebuf = bufferattr.tlength; res = pa_stream_connect_playback(impl->pa_stream, remote_node_target, &bufferattr, @@ -951,6 +973,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) spa_ringbuffer_init(&impl->ring); impl->buffer = calloc(1, RINGBUFFER_SIZE); spa_dll_init(&impl->dll); + impl->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; + impl->rate_limit.burst = 1; if ((str = pw_properties_get(props, "tunnel.mode")) != NULL) { if (spa_streq(str, "source")) { @@ -989,6 +1013,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) 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_NETWORK); copy_props(impl, props, PW_KEY_MEDIA_CLASS); parse_audio_info(impl->stream_props, &impl->info); diff --git a/src/modules/module-raop-discover.c b/src/modules/module-raop-discover.c index dd26bad950630b0c952dce8ffeaf01f7663d0c49..483e7d301bee0942e2371ae82658034fb2ce1b20 100644 --- a/src/modules/module-raop-discover.c +++ b/src/modules/module-raop-discover.c @@ -113,7 +113,7 @@ struct tunnel_info { const char *domain; }; -#define TUNNEL_INFO(...) (struct tunnel_info){ __VA_ARGS__ } +#define TUNNEL_INFO(...) ((struct tunnel_info){ __VA_ARGS__ }) struct tunnel { struct spa_list link; diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index fe6288341bb3d4304d4e1b3d6e43c46a65ee2213..4578411d1f7430fe5698d166ded21eb09006a101 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -108,12 +108,12 @@ * raop.hostname = "my-raop-device" * raop.port = 8190 * #raop.transport = "udp" - * raop.encryption = "RSA" + * raop.encryption.type = "RSA" * #raop.audio.codec = "PCM" * #raop.password = "****" * #audio.format = "S16" * #audio.rate = 44100 - * #audio.channels = 22 + * #audio.channels = 2 * #audio.position = [ FL FR ] * stream.props = { * # extra sink properties @@ -157,7 +157,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define DEFAULT_CHANNELS 2 #define DEFAULT_POSITION "[ FL FR ]" -#define DEFAULT_LATENCY (DEFAULT_RATE*2) +#define DEFAULT_LATENCY 22050 #define MODULE_USAGE "[ raop.hostname=<name of host> ] " \ "[ raop.port=<remote port> ] " \ @@ -421,6 +421,7 @@ static int flush_to_udp_packet(struct impl *impl) switch (impl->codec) { case CODEC_PCM: + case CODEC_ALAC: len = write_codec_pcm(dst, impl->buffer, n_frames); break; default: @@ -428,7 +429,7 @@ static int flush_to_udp_packet(struct impl *impl) memset(dst, 0, len); break; } - if (impl->encryption != CRYPTO_NONE) + if (impl->encryption == CRYPTO_RSA) aes_encrypt(impl, dst, len); impl->rtptime += n_frames; @@ -463,6 +464,7 @@ static int flush_to_tcp_packet(struct impl *impl) switch (impl->codec) { case CODEC_PCM: + case CODEC_ALAC: len = write_codec_pcm(dst, impl->buffer, n_frames); break; default: @@ -470,7 +472,7 @@ static int flush_to_tcp_packet(struct impl *impl) memset(dst, 0, len); break; } - if (impl->encryption != CRYPTO_NONE) + if (impl->encryption == CRYPTO_RSA) aes_encrypt(impl, dst, len); pkt[0] |= htonl((uint32_t) len + 12); @@ -577,10 +579,10 @@ static int create_udp_socket(struct impl *impl, uint16_t *port) if (ip_version == 4) { sa4.sin_port = htons(*port); - ret = bind(fd, &sa4, sizeof(sa4)); + ret = bind(fd, (struct sockaddr*)&sa4, sizeof(sa4)); } else { sa6.sin6_port = htons(*port); - ret = bind(fd, &sa6, sizeof(sa6)); + ret = bind(fd, (struct sockaddr*)&sa6, sizeof(sa6)); } if (ret == 0) break; @@ -722,9 +724,10 @@ on_control_source_io(void *data, int fd, uint32_t mask) } } -static void rtsp_flush_reply(void *data, int status, const struct spa_dict *headers) +static int rtsp_flush_reply(void *data, int status, const struct spa_dict *headers) { pw_log_info("reply %d", status); + return 0; } static int rtsp_do_flush(struct impl *impl) @@ -749,7 +752,7 @@ static int rtsp_do_flush(struct impl *impl) return res; } -static void rtsp_record_reply(void *data, int status, const struct spa_dict *headers) +static int rtsp_record_reply(void *data, int status, const struct spa_dict *headers) { struct impl *impl = data; const char *str; @@ -758,6 +761,7 @@ static void rtsp_record_reply(void *data, int status, const struct spa_dict *hea uint8_t buffer[1024]; struct spa_pod_builder b; struct spa_latency_info latency; + char progress[128]; pw_log_info("reply %d", status); @@ -782,6 +786,10 @@ static void rtsp_record_reply(void *data, int status, const struct spa_dict *hea impl->sync = 0; impl->sync_period = impl->info.rate / (impl->block_size / impl->frame_size); 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); } static int rtsp_do_record(struct impl *impl) @@ -835,7 +843,7 @@ error: pw_loop_update_io(impl->loop, impl->server_source, 0); } -static void rtsp_setup_reply(void *data, int status, const struct spa_dict *headers) +static int rtsp_setup_reply(void *data, int status, const struct spa_dict *headers) { struct impl *impl = data; const char *str, *state = NULL, *s; @@ -847,13 +855,13 @@ static void rtsp_setup_reply(void *data, int status, const struct spa_dict *head if ((str = spa_dict_lookup(headers, "Session")) == NULL) { pw_log_error("missing Session header"); - return; + return 0; } pw_properties_set(impl->headers, "Session", str); if ((str = spa_dict_lookup(headers, "Transport")) == NULL) { pw_log_error("missing Transport header"); - return; + return 0; } impl->server_port = control_port = timing_port = 0; @@ -870,18 +878,21 @@ static void rtsp_setup_reply(void *data, int status, const struct spa_dict *head } if (impl->server_port == 0) { pw_log_error("missing server port in Transport"); - return; + return 0; } - pw_getrandom(&impl->seq, sizeof(impl->seq), 0); - pw_getrandom(&impl->rtptime, sizeof(impl->rtptime), 0); + if (pw_getrandom(&impl->seq, sizeof(impl->seq), 0) < 0 || + pw_getrandom(&impl->rtptime, sizeof(impl->rtptime), 0) < 0) { + pw_log_error("error generating random seq and rtptime: %m"); + return 0; + } pw_log_info("server port:%u", impl->server_port); switch (impl->protocol) { case PROTO_TCP: - if ((impl->server_fd = connect_socket(impl, SOCK_STREAM, -1, impl->server_port)) <= 0) - return; + if ((impl->server_fd = connect_socket(impl, SOCK_STREAM, -1, impl->server_port)) < 0) + return impl->server_fd; impl->server_source = pw_loop_add_io(impl->loop, impl->server_fd, SPA_IO_OUT, false, on_server_source_io, impl); @@ -890,16 +901,16 @@ static void rtsp_setup_reply(void *data, int status, const struct spa_dict *head case PROTO_UDP: if (control_port == 0 || timing_port == 0) { pw_log_error("missing UDP ports in Transport"); - return; + return 0; } pw_log_info("control:%u timing:%u", control_port, timing_port); - if ((impl->server_fd = connect_socket(impl, SOCK_DGRAM, -1, impl->server_port)) <= 0) - return; - if ((impl->control_fd = connect_socket(impl, SOCK_DGRAM, impl->control_fd, control_port)) <= 0) - return; - if ((impl->timing_fd = connect_socket(impl, SOCK_DGRAM, impl->timing_fd, timing_port)) <= 0) - return; + if ((impl->server_fd = connect_socket(impl, SOCK_DGRAM, -1, impl->server_port)) < 0) + 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); @@ -912,8 +923,9 @@ static void rtsp_setup_reply(void *data, int status, const struct spa_dict *head rtsp_do_record(impl); break; default: - return; + return 0; } + return 0; } static int rtsp_do_setup(struct impl *impl) @@ -964,7 +976,7 @@ error: return -EIO; } -static void rtsp_announce_reply(void *data, int status, const struct spa_dict *headers) +static int rtsp_announce_reply(void *data, int status, const struct spa_dict *headers) { struct impl *impl = data; @@ -972,7 +984,7 @@ static void rtsp_announce_reply(void *data, int status, const struct spa_dict *h pw_properties_set(impl->headers, "Apple-Challenge", NULL); - rtsp_do_setup(impl); + return rtsp_do_setup(impl); } static void base64_encode(const uint8_t *data, size_t len, char *enc, char pad) @@ -1059,7 +1071,8 @@ static int rtsp_do_announce(struct impl *impl) int res, frames, i, ip_version; char *sdp; char local_ip[256]; - + int min_latency; + min_latency = DEFAULT_LATENCY; host = pw_properties_get(impl->props, "raop.hostname"); if (impl->protocol == PROTO_TCP) @@ -1086,10 +1099,26 @@ static int rtsp_do_announce(struct impl *impl) ip_version, host, frames); break; + case CRYPTO_AUTH_SETUP: + asprintf(&sdp, "v=0\r\n" + "o=iTunes %s 0 IN IP%d %s\r\n" + "s=iTunes\r\n" + "c=IN IP%d %s\r\n" + "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=min-latency:%d", + impl->session_id, ip_version, local_ip, + ip_version, host, frames, min_latency); + break; + case CRYPTO_RSA: - pw_getrandom(impl->key, sizeof(impl->key), 0); + if (pw_getrandom(impl->key, sizeof(impl->key), 0) < 0 || + pw_getrandom(impl->iv, sizeof(impl->iv), 0) < 0) + return -errno; + AES_set_encrypt_key(impl->key, 128, &impl->aes); - pw_getrandom(impl->iv, sizeof(impl->iv), 0); i = rsa_encrypt(impl->key, 16, rsakey); base64_encode(rsakey, i, key, '='); @@ -1118,15 +1147,13 @@ static int rtsp_do_announce(struct impl *impl) return res; } -static void rtsp_auth_setup_reply(void *data, int status, const struct spa_dict *headers) +static int rtsp_auth_setup_reply(void *data, int status, const struct spa_dict *headers) { struct impl *impl = data; pw_log_info("reply %d", status); - impl->encryption = CRYPTO_NONE; - - rtsp_do_announce(impl); + return rtsp_do_announce(impl); } static int rtsp_do_auth_setup(struct impl *impl) @@ -1159,16 +1186,22 @@ static const char *find_attr(char **tokens, const char *key) return NULL; } -static void rtsp_auth_reply(void *data, int status, const struct spa_dict *headers) +static int rtsp_auth_reply(void *data, int status, const struct spa_dict *headers) { struct impl *impl = data; + int res = 0; + pw_log_info("auth %d", status); switch (status) { case 200: - rtsp_do_announce(impl); + if (impl->encryption == CRYPTO_AUTH_SETUP) + res = rtsp_do_auth_setup(impl); + else + res = rtsp_do_announce(impl); break; } + return res; } SPA_PRINTF_FUNC(2,3) @@ -1217,7 +1250,7 @@ static int rtsp_do_auth(struct impl *impl, const struct spa_dict *headers) spa_scnprintf(auth, sizeof(auth), "Basic %s", enc); } else if (spa_streq(tokens[0], "Digest")) { - const char *realm, *nonce; + const char *realm, *nonce, *url; char h1[MD5_HASH_LENGTH+1]; char h2[MD5_HASH_LENGTH+1]; char resp[MD5_HASH_LENGTH+1]; @@ -1227,13 +1260,15 @@ static int rtsp_do_auth(struct impl *impl, const struct spa_dict *headers) 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:*"); + MD5_hash(h2, "OPTIONS:%s", url); MD5_hash(resp, "%s:%s:%s", h1, nonce, h2); spa_scnprintf(auth, sizeof(auth), - "Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"*\", response=\"%s\"", - DEFAULT_USER_NAME, realm, nonce, resp); + "username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"", + DEFAULT_USER_NAME, realm, nonce, url, resp); } else goto error; @@ -1251,22 +1286,25 @@ error: return -EINVAL; } -static void rtsp_options_reply(void *data, int status, const struct spa_dict *headers) +static int rtsp_options_reply(void *data, int status, const struct spa_dict *headers) { struct impl *impl = data; + int res = 0; + pw_log_info("options %d", status); switch (status) { case 401: - rtsp_do_auth(impl, headers); + res = rtsp_do_auth(impl, headers); break; case 200: if (impl->encryption == CRYPTO_AUTH_SETUP) - rtsp_do_auth_setup(impl); + res = rtsp_do_auth_setup(impl); else - rtsp_do_announce(impl); + res = rtsp_do_announce(impl); break; } + return res; } static void rtsp_connected(void *data) @@ -1280,11 +1318,15 @@ static void rtsp_connected(void *data) impl->connected = true; - pw_getrandom(sci, sizeof(sci), 0); + if (pw_getrandom(sci, sizeof(sci), 0) < 0 || + pw_getrandom(rac, sizeof(rac), 0) < 0) { + pw_log_error("error generating random data: %m"); + return; + } + pw_properties_setf(impl->headers, "Client-Instance", "%08x%08x", sci[0], sci[1]); - pw_getrandom(rac, sizeof(rac), 0); base64_encode(rac, sizeof(rac), sac, '\0'); pw_properties_set(impl->headers, "Apple-Challenge", sac); @@ -1297,29 +1339,29 @@ static void rtsp_connected(void *data) static void connection_cleanup(struct impl *impl) { impl->ready = false; - if (impl->server_fd != -1) { + if (impl->server_source != NULL) { + pw_loop_destroy_source(impl->loop, impl->server_source); + impl->server_source = NULL; + } + if (impl->server_fd >= 0) { close(impl->server_fd); impl->server_fd = -1; } - if (impl->control_fd != -1) { + if (impl->control_source != NULL) { + pw_loop_destroy_source(impl->loop, impl->control_source); + impl->control_source = NULL; + } + if (impl->control_fd >= 0) { close(impl->control_fd); impl->control_fd = -1; } - if (impl->timing_fd != -1) { - close(impl->timing_fd); - impl->timing_fd = -1; - } - if (impl->server_source != NULL) { - pw_loop_destroy_source(impl->loop, impl->server_source); - impl->server_source = NULL; - } if (impl->timing_source != NULL) { pw_loop_destroy_source(impl->loop, impl->timing_source); impl->timing_source = NULL; } - if (impl->control_source != NULL) { - pw_loop_destroy_source(impl->loop, impl->control_source); - impl->control_source = NULL; + if (impl->timing_fd >= 0) { + close(impl->timing_fd); + impl->timing_fd = -1; } } @@ -1390,13 +1432,15 @@ static int rtsp_do_connect(struct impl *impl) if (hostname == NULL || port == NULL) return -EINVAL; - pw_getrandom(&session_id, sizeof(session_id), 0); + if (pw_getrandom(&session_id, sizeof(session_id), 0) < 0) + return -errno; + spa_scnprintf(impl->session_id, sizeof(impl->session_id), "%u", session_id); return pw_rtsp_client_connect(impl->rtsp, hostname, atoi(port), impl->session_id); } -static void rtsp_teardown_reply(void *data, int status, const struct spa_dict *headers) +static int rtsp_teardown_reply(void *data, int status, const struct spa_dict *headers) { struct impl *impl = data; const char *str; @@ -1409,6 +1453,7 @@ static void rtsp_teardown_reply(void *data, int status, const struct spa_dict *h if (spa_streq(str, "close")) pw_rtsp_client_disconnect(impl->rtsp); } + return 0; } static int rtsp_do_teardown(struct impl *impl) @@ -1756,6 +1801,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) str = "PCM"; if (spa_streq(str, "PCM")) impl->codec = CODEC_PCM; + else if (spa_streq(str, "ALAC")) + impl->codec = CODEC_ALAC; else { pw_log_error( "can't handle codec type %s", str); res = -EINVAL; diff --git a/src/modules/module-raop/rtsp-client.c b/src/modules/module-raop/rtsp-client.c index c731a0469e923e35143eedcfe4ffdbb31155ccdf..4ac9a31e497d5797eb340af41e8525f3ac5d5501 100644 --- a/src/modules/module-raop/rtsp-client.c +++ b/src/modules/module-raop/rtsp-client.c @@ -45,7 +45,7 @@ struct message { size_t len; size_t offset; uint32_t cseq; - void (*reply) (void *user_data, int status, const struct spa_dict *headers); + int (*reply) (void *user_data, int status, const struct spa_dict *headers); void *user_data; }; @@ -133,6 +133,11 @@ void *pw_rtsp_client_get_user_data(struct pw_rtsp_client *client) return client->user_data; } +const char *pw_rtsp_client_get_url(struct pw_rtsp_client *client) +{ + return client->url; +} + void pw_rtsp_client_add_listener(struct pw_rtsp_client *client, struct spa_hook *listener, const struct pw_rtsp_client_events *events, void *data) @@ -282,16 +287,23 @@ static int process_status(struct pw_rtsp_client *client, char *buf) static void dispatch_handler(struct pw_rtsp_client *client) { uint32_t cseq; + int res; + struct message *msg; + if (pw_properties_fetch_uint32(client->headers, "CSeq", &cseq) < 0) return; pw_log_info("received reply to request with cseq:%" PRIu32, cseq); - struct message *msg = find_pending(client, cseq); + msg = find_pending(client, cseq); if (msg) { - msg->reply(msg->user_data, client->status, &client->headers->dict); + res = msg->reply(msg->user_data, client->status, &client->headers->dict); spa_list_remove(&msg->link); free(msg); + + if (res < 0) + pw_log_warn("client %p: handle reply cseq:%u error: %s", + client, cseq, spa_strerror(res)); } else { pw_rtsp_client_emit_message(client, client->status, &client->headers->dict); @@ -314,8 +326,8 @@ static int process_header(struct pw_rtsp_client *client, char *buf) return -EPROTO; *value++ = '\0'; - while (*value == ' ') - value++; + + value = pw_strip(value, " "); pw_properties_set(client->headers, key, value); } @@ -554,7 +566,7 @@ int pw_rtsp_client_disconnect(struct pw_rtsp_client *client) int pw_rtsp_client_url_send(struct pw_rtsp_client *client, const char *url, const char *cmd, const struct spa_dict *headers, const char *content_type, const void *content, size_t content_length, - void (*reply) (void *user_data, int status, const struct spa_dict *headers), + int (*reply) (void *user_data, int status, const struct spa_dict *headers), void *user_data) { FILE *f; @@ -608,7 +620,7 @@ int pw_rtsp_client_url_send(struct pw_rtsp_client *client, const char *url, int pw_rtsp_client_send(struct pw_rtsp_client *client, const char *cmd, const struct spa_dict *headers, const char *content_type, const char *content, - void (*reply) (void *user_data, int status, const struct spa_dict *headers), + int (*reply) (void *user_data, int status, const struct spa_dict *headers), void *user_data) { const size_t content_length = content ? strlen(content) : 0; diff --git a/src/modules/module-raop/rtsp-client.h b/src/modules/module-raop/rtsp-client.h index 75b1ce6a15795d3166d3678758739a465187efbe..014468beaa10992f4a4aa113f95245ae60ff9149 100644 --- a/src/modules/module-raop/rtsp-client.h +++ b/src/modules/module-raop/rtsp-client.h @@ -57,6 +57,7 @@ struct pw_rtsp_client * pw_rtsp_client_new(struct pw_loop *main_loop, void pw_rtsp_client_destroy(struct pw_rtsp_client *client); void *pw_rtsp_client_get_user_data(struct pw_rtsp_client *client); +const char *pw_rtsp_client_get_url(struct pw_rtsp_client *client); void pw_rtsp_client_add_listener(struct pw_rtsp_client *client, struct spa_hook *listener, @@ -74,13 +75,13 @@ int pw_rtsp_client_get_local_ip(struct pw_rtsp_client *client, int pw_rtsp_client_url_send(struct pw_rtsp_client *client, const char *url, const char *cmd, const struct spa_dict *headers, const char *content_type, const void *content, size_t content_length, - void (*reply) (void *user_data, int status, const struct spa_dict *headers), + int (*reply) (void *user_data, int status, const struct spa_dict *headers), void *user_data); int pw_rtsp_client_send(struct pw_rtsp_client *client, const char *cmd, const struct spa_dict *headers, const char *content_type, const char *content, - void (*reply) (void *user_data, int status, const struct spa_dict *headers), + int (*reply) (void *user_data, int status, const struct spa_dict *headers), void *user_data); diff --git a/src/modules/module-rtp-sink.c b/src/modules/module-rtp-sink.c new file mode 100644 index 0000000000000000000000000000000000000000..014446d5c057951ef913cac94e84dd1a32e27715 --- /dev/null +++ b/src/modules/module-rtp-sink.c @@ -0,0 +1,945 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include <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/param/audio/format-utils.h> +#include <spa/utils/hook.h> +#include <spa/utils/ringbuffer.h> +#include <spa/utils/json.h> +#include <spa/debug/pod.h> + +#include <pipewire/pipewire.h> +#include <pipewire/private.h> + +#include <module-rtp/sap.h> +#include <module-rtp/rtp.h> + + +/** \page page_module_rtp_sink PipeWire Module: RTP sink + * + * The `rtp-sink` module creates a PipeWire sink that sends audio + * 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 = <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 + * - `local.ifname = <str>`: interface name to use + * - `net.mtu = <int>`: MTU to use, default 1280 + * - `net.ttl = <int>`: TTL to use, default 1 + * - `net.loop = <bool>`: loopback multicast, default false + * - `sess.name = <str>`: a session name + * - `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-sink + * args = { + * #sap.ip = "224.0.0.56" + * #sap.port = 9875 + * #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.name = "PipeWire RTP stream" + * #audio.format = "S16BE" + * #audio.rate = 48000 + * #audio.channels = 2 + * #audio.position = [ FL FR ] + * stream.props = { + * node.name = "rtp-sink" + * } + * } + *} + *] + *\endcode + * + * \since 0.3.60 + */ + +#define NAME "rtp-sink" + +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_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_MIN_PTIME 2 +#define DEFAULT_MAX_PTIME 20 + +#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)"> " \ + "sess.name=<a name for the session> " \ + "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 }, +}; + +static const struct format_info { + uint32_t format; + uint32_t size; + const char *mime; +} format_info[] = { + { SPA_AUDIO_FORMAT_U8, 1, "L8" }, + { SPA_AUDIO_FORMAT_ALAW, 1, "PCMA" }, + { SPA_AUDIO_FORMAT_ULAW, 1, "PCMU" }, + { SPA_AUDIO_FORMAT_S16_BE, 2, "L16" }, + { SPA_AUDIO_FORMAT_S24_BE, 3, "L24" }, +}; + +static const struct format_info *find_format_info(uint32_t format) +{ + SPA_FOR_EACH_ELEMENT_VAR(format_info, f) + if (f->format == format) + return f; + return NULL; +} + +struct impl { + struct pw_impl_module *module; + struct spa_hook module_listener; + struct pw_properties *props; + struct pw_context *module_context; + + struct pw_loop *loop; + + struct pw_core *core; + 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; + + unsigned int do_disconnect:1; + + char *ifname; + char *session_name; + int sess_latency_msec; + int mtu; + bool ttl; + bool mcast_loop; + uint32_t min_ptime; + uint32_t max_ptime; + + struct sockaddr_storage src_addr; + socklen_t src_len; + + uint16_t 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_raw info; + const struct format_info *format_info; + uint32_t frame_size; + int payload; + uint16_t seq; + uint32_t timestamp; + uint32_t ssrc; + + struct spa_ringbuffer ring; + uint8_t buffer[BUFFER_SIZE]; + + int rtp_fd; + int sap_fd; +}; + + +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) +{ + iov[0].iov_len = SPA_MIN(len, size - offset); + iov[0].iov_base = SPA_PTROFF(buffer, offset, void); + iov[1].iov_len = len - iov[0].iov_len; + iov[1].iov_base = buffer; +} + +static void flush_packets(struct impl *impl) +{ + int32_t avail; + uint32_t index; + struct iovec iov[3]; + struct msghdr msg; + ssize_t n; + struct rtp_header header; + int32_t tosend; + + avail = spa_ringbuffer_get_read_index(&impl->ring, &index); + + tosend = SPA_ROUND_DOWN(impl->mtu, impl->frame_size); + + if (avail < tosend) + return; + + spa_zero(header); + header.v = 2; + header.pt = impl->payload; + header.ssrc = htonl(impl->ssrc); + + iov[0].iov_base = &header; + iov[0].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->timestamp); + + set_iovec(&impl->ring, + impl->buffer, BUFFER_SIZE, + index & BUFFER_MASK, + &iov[1], tosend); + + n = sendmsg(impl->rtp_fd, &msg, MSG_NOSIGNAL); + if (n < 0) + pw_log_warn("sendmsg() failed: %m"); + + impl->seq++; + impl->timestamp += tosend / impl->frame_size; + + index += tosend; + avail -= tosend; + } + spa_ringbuffer_read_update(&impl->ring, index); +} + +static void stream_process(void *data) +{ + struct impl *impl = data; + struct pw_buffer *buf; + struct spa_data *d; + uint32_t index; + 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; + + wanted = d[0].chunk->size; + + filled = spa_ringbuffer_get_write_index(&impl->ring, &index); + + if (filled + wanted > (int32_t)BUFFER_SIZE) { + pw_log_warn("overrun %u + %u > %u", filled, wanted, BUFFER_SIZE); + } else { + spa_ringbuffer_write_data(&impl->ring, + impl->buffer, + BUFFER_SIZE, + index & BUFFER_MASK, + d[0].data, wanted); + + index += wanted; + spa_ringbuffer_write_update(&impl->ring, index); + } + pw_stream_queue_buffer(impl->stream, buf); + + flush_packets(impl); +} + +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: + pw_log_error("stream error: %s", error); + break; + default: + break; + } +} + +static const struct pw_stream_events in_stream_events = { + PW_VERSION_STREAM_EVENTS, + .destroy = stream_destroy, + .state_changed = on_stream_state_changed, + .process = stream_process +}; + +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_addr[0] == 0xff; + } + return false; +} + +static int make_socket(struct sockaddr_storage *src, socklen_t src_len, + struct sockaddr_storage *dst, socklen_t dst_len, + bool loop, int ttl) +{ + int af, fd, val, res; + + af = src->ss_family; + if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) { + pw_log_error("socket failed: %m"); + return -errno; + } + if (bind(fd, (struct sockaddr*)src, src_len) < 0) { + res = -errno; + pw_log_error("bind() failed: %m"); + goto error; + } + if (connect(fd, (struct sockaddr*)dst, dst_len) < 0) { + res = -errno; + pw_log_error("connect() failed: %m"); + goto error; + } + if (is_multicast((struct sockaddr*)dst, dst_len)) { + 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"); + } +#ifdef SO_PRIORITY + val = 6; + if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0) + pw_log_warn("setsockopt(SO_PRIORITY) failed: %m"); +#endif + val = IPTOS_LOWDELAY; + if (setsockopt(fd, IPPROTO_IP, IP_TOS, &val, sizeof(val)) < 0) + pw_log_warn("setsockopt(IP_TOS) failed: %m"); + + + return fd; +error: + close(fd); + return res; +} + +static int setup_stream(struct impl *impl) +{ + const struct spa_pod *params[1]; + struct spa_pod_builder b; + uint32_t n_params; + uint8_t buffer[1024]; + 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->mtu / impl->frame_size, + impl->info.rate); + } + pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%d", impl->info.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)); + params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, + &impl->info); + + 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)) < 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) { + 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); + } else + return -EIO; + return 0; +} +static void send_sap(struct impl *impl, bool bye) +{ + char buffer[2048], src_addr[64], dst_addr[64], dst_ttl[8]; + const char *user_name, *af; + struct sockaddr *sa = (struct sockaddr*)&impl->src_addr; + struct sap_header header; + struct iovec iov[4]; + struct msghdr msg; + + spa_zero(header); + header.v = 1; + header.t = bye; + header.msg_id_hash = impl->msg_id_hash; + + iov[0].iov_base = &header; + iov[0].iov_len = sizeof(header); + + if (sa->sa_family == AF_INET) { + iov[1].iov_base = &((struct sockaddr_in*) sa)->sin_addr; + iov[1].iov_len = 4U; + af = "IP4"; + } else { + iov[1].iov_base = &((struct sockaddr_in6*) sa)->sin6_addr; + iov[1].iov_len = 16U; + header.a = 1; + af = "IP6"; + } + iov[2].iov_base = SAP_MIME_TYPE; + iov[2].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); + + snprintf(buffer, sizeof(buffer), + "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" + "m=audio %u RTP/AVP %i\n" + "a=rtpmap:%i %s/%u/%u\n" + "a=type:broadcast\n", + user_name, impl->ntp, af, src_addr, + impl->session_name, + af, dst_addr, dst_ttl, + impl->ntp, + impl->port, impl->payload, + impl->payload, impl->format_info->mime, + impl->info.rate, impl->info.channels); + + iov[3].iov_base = buffer; + iov[3].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); +} + +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) + 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) +{ + 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) +{ + send_sap(impl, 1); + + if (impl->stream) + pw_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); + + free(impl->ifname); + 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 inline uint32_t format_from_name(const char *name, size_t len) +{ + int i; + for (i = 0; spa_type_audio_format[i].name; i++) { + if (strncmp(name, spa_debug_type_short_name(spa_type_audio_format[i].name), len) == 0) + return spa_type_audio_format[i].type; + } + return SPA_AUDIO_FORMAT_UNKNOWN; +} + +static uint32_t channel_from_name(const char *name) +{ + int i; + for (i = 0; spa_type_audio_channel[i].name; i++) { + if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_channel[i].name))) + return spa_type_audio_channel[i].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 it[2]; + char v[256]; + + spa_json_init(&it[0], val, len); + if (spa_json_enter_array(&it[0], &it[1]) <= 0) + spa_json_init(&it[1], val, len); + + info->channels = 0; + while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && + info->channels < SPA_AUDIO_MAX_CHANNELS) { + info->position[info->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; + 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; + uint32_t id = pw_global_get_id(pw_impl_module_get_global(module)); + uint32_t pid = getpid(), port; + char addr[64]; + const char *str; + int res = 0; + + PW_LOG_TOPIC_INIT(mod_topic); + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + return -errno; + + impl->rtp_fd = -1; + impl->sap_fd = -1; + + 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; + + 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->module_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 (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL) + pw_properties_setf(props, PW_KEY_NODE_NAME, "rtp-sink-%u-%u", pid, id); + 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)); + if (pw_properties_get(props, PW_KEY_MEDIA_NAME) == NULL) + pw_properties_set(props, PW_KEY_MEDIA_NAME, "RTP Sender Stream"); + + 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_MEDIA_NAME); + copy_props(impl, props, PW_KEY_MEDIA_CLASS); + + parse_audio_info(impl->stream_props, &impl->info); + + impl->format_info = find_format_info(impl->info.format); + if (impl->format_info == NULL) { + pw_log_error("unsupported audio format:%d channels:%d", + impl->info.format, impl->info.channels); + res = -EINVAL; + goto out; + } + impl->frame_size = impl->format_info->size * impl->info.channels; + impl->msg_id_hash = rand(); + impl->ntp = (uint32_t) time(NULL) + 2208988800U; + + impl->payload = 127; + impl->seq = rand(); + impl->timestamp = rand(); + impl->ssrc = rand(); + + 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) { + pw_log_error("invalid source.ip %s: %s", str, spa_strerror(res)); + goto out; + } + + impl->port = DEFAULT_PORT + ((uint32_t) (rand() % 512) << 1); + impl->port = pw_properties_get_uint32(props, "destination.port", impl->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) { + 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->min_ptime = pw_properties_get_uint32(props, "sess.min-ptime", DEFAULT_MIN_PTIME); + impl->max_ptime = pw_properties_get_uint32(props, "sess.max-ptime", DEFAULT_MAX_PTIME); + + 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.ttl", "%u", impl->ttl); + + impl->core = pw_context_get_object(impl->module_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, + 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 = setup_stream(impl)) < 0) + goto out; + + if ((res = start_sap_announce(impl)) < 0) + 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)); + + pw_log_info("Successfully loaded module-rtp-sink"); + + return 0; +out: + impl_destroy(impl); + return res; +} diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c new file mode 100644 index 0000000000000000000000000000000000000000..503bfc1961b064cafab2fbf78b511fd9ac051a90 --- /dev/null +++ b/src/modules/module-rtp-source.c @@ -0,0 +1,1104 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include <limits.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <net/if.h> +#include <ctype.h> + +#include <spa/param/audio/format-utils.h> +#include <spa/utils/hook.h> +#include <spa/utils/ringbuffer.h> +#include <spa/utils/dll.h> +#include <spa/debug/mem.h> + +#include <pipewire/pipewire.h> +#include <pipewire/private.h> + +#include <module-rtp/sap.h> +#include <module-rtp/rtp.h> + +#ifdef __FreeBSD__ +#define ifr_ifindex ifr_index +#endif + +/** \page page_module_rtp_source PipeWire Module: RTP source + * + * The `rtp-source` module creates a PipeWire source that receives audio + * 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 + * - `sess.latency.msec = <str>`: target network latency in milliseconds, default 100 + * - `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_MEDIA_NAME + * - \ref PW_KEY_MEDIA_CLASS + * + * ## 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 + * stream.props = { + * node.name = "rtp-source" + * #media.class = "Audio/Source" + * } + * } + * } + *] + *\endcode + * + * \since 0.3.60 + */ + +#define NAME "rtp-source" + +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 CLEANUP_INTERVAL_SEC 20 + +#define DEFAULT_SAP_IP "224.0.0.56" +#define DEFAULT_SAP_PORT 9875 +#define DEFAULT_SESS_LATENCY 100 + +#define BUFFER_SIZE (1u<<22) +#define BUFFER_MASK (BUFFER_SIZE-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 ... }" + +static const struct spa_dict_item module_info[] = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { PW_KEY_MODULE_DESCRIPTION, "RTP Source" }, + { PW_KEY_MODULE_USAGE, USAGE }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + +struct impl { + struct pw_impl_module *module; + struct spa_hook module_listener; + struct pw_properties *props; + struct pw_context *module_context; + + struct pw_loop *loop; + struct pw_loop *data_loop; + + 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; + int sap_port; + int sess_latency_msec; + + struct spa_list sessions; + uint32_t n_sessions; +}; + +static const struct format_info { + uint32_t format; + uint32_t size; + const char *mime; +} format_info[] = { + { SPA_AUDIO_FORMAT_U8, 1, "L8" }, + { SPA_AUDIO_FORMAT_ALAW, 1, "PCMA" }, + { SPA_AUDIO_FORMAT_ULAW, 1, "PCMU" }, + { SPA_AUDIO_FORMAT_S16_BE, 2, "L16" }, + { SPA_AUDIO_FORMAT_S24_BE, 3, "L24" }, +}; + +static const struct format_info *find_format_info(const char *mime) +{ + SPA_FOR_EACH_ELEMENT_VAR(format_info, f) + if (spa_streq(f->mime, mime)) + return f; + return NULL; +} + +struct sdp_info { + uint16_t hash; + + char origin[128]; + char session[256]; + + struct sockaddr_storage sa; + socklen_t salen; + + uint16_t port; + uint8_t payload; + + const struct format_info *format_info; + struct spa_audio_info_raw info; + uint32_t stride; +}; + +struct session { + struct impl *impl; + struct spa_list link; + + uint64_t timestamp; + + struct sdp_info info; + + 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 buffer[BUFFER_SIZE]; + + struct spa_dll dll; + uint32_t target_buffer; + float max_error; + unsigned buffering:1; +}; + +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; + struct pw_buffer *buf; + struct spa_data *d; + uint32_t index; + int32_t avail, wanted; + + if ((buf = pw_stream_dequeue_buffer(sess->stream)) == NULL) { + pw_log_debug("Out of stream buffers: %m"); + return; + } + d = buf->buffer->datas; + + wanted = buf->requested ? + SPA_MIN(buf->requested * sess->info.stride, d[0].maxsize) + : d[0].maxsize; + + avail = spa_ringbuffer_get_read_index(&sess->ring, &index); + + if (avail < wanted || sess->buffering) { + memset(d[0].data, 0, wanted); + if (!sess->buffering && sess->have_sync) { + pw_log_info("underrun %u/%u < %u, buffering...", + avail, sess->target_buffer, wanted); + sess->buffering = true; + } + } else { + float error, corr; + if (avail > (int32_t)BUFFER_SIZE) { + index += avail - sess->target_buffer; + avail = sess->target_buffer; + pw_log_warn("overrun %u > %u", avail, BUFFER_SIZE); + } else { + error = (float)sess->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, + sess->target_buffer, error, corr); + + pw_stream_set_control(sess->stream, + SPA_PROP_rate, 1, &corr, NULL); + } + spa_ringbuffer_read_data(&sess->ring, + sess->buffer, + BUFFER_SIZE, + index & BUFFER_MASK, + d[0].data, wanted); + + index += wanted; + spa_ringbuffer_read_update(&sess->ring, index); + } + d[0].chunk->size = wanted; + d[0].chunk->stride = sess->info.stride; + d[0].chunk->offset = 0; + buf->size = wanted / sess->info.stride; + + pw_stream_queue_buffer(sess->stream, buf); +} + +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; + default: + break; + } +} + +static const struct pw_stream_events out_stream_events = { + PW_VERSION_STREAM_EVENTS, + .destroy = stream_destroy, + .state_changed = on_stream_state_changed, + .process = stream_process +}; + +static void +on_rtp_io(void *data, int fd, uint32_t mask) +{ + struct session *sess = data; + struct rtp_header *hdr; + ssize_t len, hlen; + uint8_t buffer[2048], *payload; + + if (mask & SPA_IO_IN) { + uint32_t index, expected_index, timestamp; + uint16_t seq; + int32_t filled; + + pw_log_trace("got rtp"); + 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; + + 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_warn("unexpected seq (%d != %d)", seq, sess->expected_seq); + } + sess->expected_seq = seq + 1; + sess->have_seq = true; + + len = SPA_ROUND_DOWN(len - hlen, sess->info.stride); + payload = &buffer[hlen]; + + filled = spa_ringbuffer_get_write_index(&sess->ring, &index); + + timestamp = ntohl(hdr->timestamp); + expected_index = timestamp * sess->info.stride; + + if (!sess->have_sync) { + sess->ring.readindex = sess->ring.writeindex = + index = expected_index; + filled = 0; + sess->have_sync = true; + sess->buffering = true; + pw_log_info("sync to timestamp %u", index); + + spa_dll_init(&sess->dll); + spa_dll_set_bw(&sess->dll, SPA_DLL_BW_MIN, 128, sess->info.info.rate); + + } else if (expected_index != index) { + pw_log_debug("unexpected timestamp (%u != %u)", + index / sess->info.stride, + expected_index / sess->info.stride); + index = expected_index; + filled = 0; + } + + if (filled + len > BUFFER_SIZE) { + pw_log_warn("capture overrun %u %zd", filled, len); + sess->have_sync = false; + } else { + spa_ringbuffer_write_data(&sess->ring, + sess->buffer, + BUFFER_SIZE, + index & BUFFER_MASK, + payload, len); + index += len; + filled += len; + spa_ringbuffer_write_update(&sess->ring, index); + + if (sess->buffering && (uint32_t)filled > sess->target_buffer) { + sess->buffering = false; + pw_log_info("buffering done %u > %u", + filled, sess->target_buffer); + } + } + } + return; + +receive_error: + pw_log_warn("recv error: %m"); + return; +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 make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname) +{ + int af, fd, val, res; + struct ifreq req; + + af = sa->sa_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*)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_addr[0] == 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, sa, salen) < 0) { + res = -errno; + pw_log_error("bind() failed: %m"); + goto error; + } + return fd; +error: + return res; +} + +static uint32_t msec_to_bytes(struct sdp_info *info, uint32_t msec) +{ + return msec * info->stride * info->info.rate / 1000; +} + +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) +{ + pw_log_info("free session %s %s", sess->info.origin, sess->info.session); + if (sess->impl) { + 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); +} + +static int session_new(struct impl *impl, struct sdp_info *info) +{ + struct session *session; + const struct spa_pod *params[1]; + struct spa_pod_builder b; + uint32_t n_params; + uint8_t buffer[1024]; + struct pw_properties *props; + int res, fd; + + 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->target_buffer = msec_to_bytes(info, impl->sess_latency_msec); + session->max_error = msec_to_bytes(info, ERROR_MSEC); + + spa_dll_init(&session->dll); + spa_dll_set_bw(&session->dll, SPA_DLL_BW_MIN, 128, session->info.info.rate); + + props = pw_properties_copy(impl->stream_props); + if (props == NULL) { + res = -errno; + goto error; + } + + pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%d", info->info.rate); + pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%d/%d", + session->target_buffer / (2 * info->stride), info->info.rate); + 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->info.rate, info->info.channels); + if (info->session[0]) { + pw_properties_set(props, "rtp.session", info->session); + pw_properties_setf(props, PW_KEY_MEDIA_NAME, "RTP Stream (%s)", + info->session); + } else { + pw_properties_set(props, PW_KEY_MEDIA_NAME, "RTP Stream"); + } + + pw_log_info("new session %s %s", info->origin, info->session); + + 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)); + params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, + &info->info); + + 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 ((fd = make_socket((const struct sockaddr *)&info->sa, + info->salen, impl->ifname)) < 0) { + res = fd; + goto error; + } + + session->source = pw_loop_add_io(impl->data_loop, fd, + SPA_IO_IN, true, on_rtp_io, session); + if (session->source == NULL) { + res = -errno; + pw_log_error("can't create io source: %m"); + goto error; + } + + pw_log_info("starting RTP listener"); + 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; + + c[strcspn(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; +} + +static int parse_sdp_a(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; + c[strcspn(c, "/")] = 0; + + info->format_info = find_format_info(c); + if (info->format_info == NULL) + return -EINVAL; + + info->info.format = info->format_info->format; + info->stride = info->format_info->size; + + c += strlen(c) + 1; + if (sscanf(c, "%u/%u", &rate, &channels) == 2) { + info->info.rate = rate; + info->info.channels = channels; + if (channels == 2) { + info->info.position[0] = SPA_AUDIO_CHANNEL_FL; + info->info.position[1] = SPA_AUDIO_CHANNEL_FR; + } + } else if (sscanf(c, "%u", &rate) == 1) { + info->info.rate = rate; + info->info.channels = 1; + } else + return -EINVAL; + + info->stride *= info->info.channels; + + 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; + + s[l] = 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", &s[2]); + else if (spa_strstartswith(s, "s=")) + snprintf(info->session, sizeof(info->session), "%s", &s[2]); + 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=")) + res = parse_sdp_a(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; +} + +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); + } + 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 buffer[2048]; + 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; + + buffer[len] = 0; + parse_sap(impl, buffer, len); + } +} + +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 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 = CLEANUP_INTERVAL_SEC * SPA_NSEC_PER_SEC; + + spa_list_for_each_safe(sess, tmp, &impl->sessions, link) { + if (sess->timestamp + interval < timestamp) + session_free(sess); + } +} + +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->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); + + pw_properties_free(impl->stream_props); + pw_properties_free(impl->props); + + free(impl->ifname); + free(impl->sap_ip); + 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, *stream_props = NULL; + 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 = ""; + + props = pw_properties_new_string(args); + if (props == NULL) { + res = -errno; + pw_log_error( "can't create properties: %m"); + goto out; + } + spa_list_init(&impl->sessions); + 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->module_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(stream_props, PW_KEY_NODE_VIRTUAL) == NULL) + pw_properties_set(stream_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 ((str = pw_properties_get(props, "stream.props")) != NULL) + pw_properties_update_string(stream_props, str, strlen(str)); + + str = pw_properties_get(props, "local.ifname"); + impl->ifname = str ? strdup(str) : NULL; + + str = pw_properties_get(props, "sap.ip"); + impl->sap_ip = strdup(str ? str : DEFAULT_SAP_IP); + impl->sap_port = pw_properties_get_uint32(props, + "sap.port", DEFAULT_SAP_PORT); + impl->sess_latency_msec = pw_properties_get_uint32(props, + "sess.latency.msec", DEFAULT_SESS_LATENCY); + + impl->core = pw_context_get_object(impl->module_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, + 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 = CLEANUP_INTERVAL_SEC; + interval.tv_nsec = 0; + pw_loop_update_timer(impl->loop, impl->timer, &value, &interval, false); + + if ((res = start_sap_listener(impl)) < 0) + 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)); + + pw_log_info("Successfully loaded module-rtp-source"); + + return 0; +out: + impl_destroy(impl); + return res; +} diff --git a/src/modules/module-rtp/rtp.h b/src/modules/module-rtp/rtp.h new file mode 100644 index 0000000000000000000000000000000000000000..92ff3640f4debec89c7b8f373087baff029c860c --- /dev/null +++ b/src/modules/module-rtp/rtp.h @@ -0,0 +1,78 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef PIPEWIRE_RTP_H +#define PIPEWIRE_RTP_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct rtp_header { +#if __BYTE_ORDER == __LITTLE_ENDIAN + unsigned cc:4; + unsigned x:1; + unsigned p:1; + unsigned v:2; + + unsigned pt:7; + unsigned m:1; +#elif __BYTE_ORDER == __BIG_ENDIAN + unsigned v:2; + unsigned p:1; + unsigned x:1; + unsigned cc:4; + + unsigned m:1; + unsigned pt:7; +#else +#error "Unknown byte order" +#endif + uint16_t sequence_number; + uint32_t timestamp; + uint32_t ssrc; + uint32_t csrc[0]; +} __attribute__ ((packed)); + +struct rtp_payload { +#if __BYTE_ORDER == __LITTLE_ENDIAN + unsigned frame_count:4; + unsigned rfa0:1; + unsigned is_last_fragment:1; + unsigned is_first_fragment:1; + unsigned is_fragmented:1; +#elif __BYTE_ORDER == __BIG_ENDIAN + unsigned is_fragmented:1; + unsigned is_first_fragment:1; + unsigned is_last_fragment:1; + unsigned rfa0:1; + unsigned frame_count:4; +#endif +} __attribute__ ((packed)); + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_RTP_H */ diff --git a/src/modules/module-rtp/sap.h b/src/modules/module-rtp/sap.h new file mode 100644 index 0000000000000000000000000000000000000000..b9a0a7a700f46d93b8df68a390e98e506d20db68 --- /dev/null +++ b/src/modules/module-rtp/sap.h @@ -0,0 +1,58 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef PIPEWIRE_SAP_H +#define PIPEWIRE_SAP_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct sap_header { +#if __BYTE_ORDER == __LITTLE_ENDIAN + unsigned c:1; + unsigned e:1; + unsigned t:1; + unsigned r:1; + unsigned a:1; + unsigned v:3; +#elif __BYTE_ORDER == __BIG_ENDIAN + unsigned v:3; + unsigned a:1; + unsigned r:1; + unsigned t:1; + unsigned e:1; + unsigned c:1; +#else +#error "Unknown byte order" +#endif + uint8_t auth_len; + uint16_t msg_id_hash; +} __attribute__ ((packed)); + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_SAP_H */ diff --git a/src/modules/module-session-manager/client-session/endpoint-link.h b/src/modules/module-session-manager/client-session/endpoint-link.h index 936eed13074e31bcc45f04bd2464b95074d2dddb..c75f975bab93263630ee650bda2a2fccde8106ce 100644 --- a/src/modules/module-session-manager/client-session/endpoint-link.h +++ b/src/modules/module-session-manager/client-session/endpoint-link.h @@ -25,6 +25,7 @@ #ifndef MODULE_SESSION_MANAGER_ENDPOINT_LINK_H #define MODULE_SESSION_MANAGER_ENDPOINT_LINK_H +#include <stdint.h> #ifdef __cplusplus extern "C" { diff --git a/src/modules/module-session-manager/client-session/session.h b/src/modules/module-session-manager/client-session/session.h index ec6549e82f59aef7c4eda03b9e27c7e3aaa76833..a94b18fc27a3bb956b10711b2a95d2b7be71e0fc 100644 --- a/src/modules/module-session-manager/client-session/session.h +++ b/src/modules/module-session-manager/client-session/session.h @@ -25,6 +25,7 @@ #ifndef MODULE_SESSION_MANAGER_SESSION_H #define MODULE_SESSION_MANAGER_SESSION_H +#include <stdint.h> #ifdef __cplusplus extern "C" { diff --git a/src/modules/module-x11-bell.c b/src/modules/module-x11-bell.c index b7bc2a9708259677a2c72ec7b2356539ea913dba..cc0c2c8d50005e3c1ec89f7bd5eec6060975a0a4 100644 --- a/src/modules/module-x11-bell.c +++ b/src/modules/module-x11-bell.c @@ -109,7 +109,7 @@ static int play_sample(struct impl *impl) if (sample == NULL) sample = "bell-window-system"; - pw_log_debug("play sample %s", sample); + pw_log_info("play sample %s", sample); if ((res = ca_context_create(&ca)) < 0) { pw_log_error("canberra context create error: %s", ca_strerror(res)); diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c index 317f87ee428e72590d657890b63e5189debb3e55..cc4c042353fdba2a4965c56f9a230d8f3747d4cb 100644 --- a/src/modules/module-zeroconf-discover.c +++ b/src/modules/module-zeroconf-discover.c @@ -54,7 +54,10 @@ * audio to/from remote PulseAudio servers. It also works with * module-protocol-pulse. * - * This module has no options. + * ## Module Options + * + * - `pulse.latency`: the latency to end-to-end latency in milliseconds to + * maintain (Default 200ms). * * ## Example configuration * @@ -72,7 +75,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic -#define MODULE_USAGE " " +#define MODULE_USAGE "pulse.latency=<latency in msec> " static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, @@ -109,7 +112,7 @@ struct tunnel_info { const char *domain; }; -#define TUNNEL_INFO(...) (struct tunnel_info){ __VA_ARGS__ } +#define TUNNEL_INFO(...) ((struct tunnel_info){ __VA_ARGS__ }) struct tunnel { struct spa_list link; @@ -348,6 +351,9 @@ static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiPr _("%s on %s"), desc, fqdn); } + if ((str = pw_properties_get(impl->properties, "pulse.latency")) != NULL) + pw_properties_set(props, "pulse.latency", str); + if ((f = open_memstream(&args, &size)) == NULL) { pw_log_error("Can't open memstream: %m"); goto done; diff --git a/src/pipewire/array.h b/src/pipewire/array.h index 4e2dd72ebbe95589e42ac1cfe97cccf7c495131e..44f31a897786dab9d20c13e2e88779b81567a9ca 100644 --- a/src/pipewire/array.h +++ b/src/pipewire/array.h @@ -52,7 +52,7 @@ struct pw_array { size_t extend; /**< number of bytes to extend with */ }; -#define PW_ARRAY_INIT(extend) (struct pw_array) { NULL, 0, 0, extend } +#define PW_ARRAY_INIT(extend) ((struct pw_array) { NULL, 0, 0, (extend) }) #define pw_array_get_len_s(a,s) ((a)->size / (s)) #define pw_array_get_unchecked_s(a,idx,s,t) SPA_PTROFF((a)->data,(idx)*(s),t) @@ -67,17 +67,17 @@ struct pw_array { #define pw_array_first(a) ((a)->data) #define pw_array_end(a) SPA_PTROFF((a)->data, (a)->size, void) -#define pw_array_check(a,p) (SPA_PTROFF(p,sizeof(*p),void) <= pw_array_end(a)) +#define pw_array_check(a,p) (SPA_PTROFF(p,sizeof(*(p)),void) <= pw_array_end(a)) #define pw_array_for_each(pos, array) \ - for (pos = (__typeof__(pos)) pw_array_first(array); \ + for ((pos) = (__typeof__(pos)) pw_array_first(array); \ pw_array_check(array, pos); \ (pos)++) #define pw_array_consume(pos, array) \ - for (pos = (__typeof__(pos)) pw_array_first(array); \ + for ((pos) = (__typeof__(pos)) pw_array_first(array); \ pw_array_check(array, pos); \ - pos = (__typeof__(pos)) pw_array_first(array)) + (pos) = (__typeof__(pos)) pw_array_first(array)) #define pw_array_remove(a,p) \ ({ \ diff --git a/src/pipewire/buffers.c b/src/pipewire/buffers.c index d27b9ee245378315dc5142b36febb432d35f37f4..3499805ee36328163712d22db966480ed9830808 100644 --- a/src/pipewire/buffers.c +++ b/src/pipewire/buffers.c @@ -255,6 +255,12 @@ int pw_buffers_negotiate(struct pw_context *context, uint32_t flags, struct port input = { innode, SPA_DIRECTION_INPUT, in_port_id }; int res; + if (flags & PW_BUFFERS_FLAG_IN_PRIORITY) { + struct port tmp = output; + output = input; + input = tmp; + } + res = param_filter(result, &input, &output, SPA_PARAM_Buffers, &b); if (res < 0) { pw_context_debug_port_params(context, input.node, input.direction, diff --git a/src/pipewire/buffers.h b/src/pipewire/buffers.h index df72bf3f8e715c6a969734640ea3e6e55df0c9a8..abef3925f8e2b6527095608279d43f5ef38f7fb7 100644 --- a/src/pipewire/buffers.h +++ b/src/pipewire/buffers.h @@ -48,6 +48,7 @@ extern "C" { #define PW_BUFFERS_FLAG_SHARED (1<<1) /**< buffers can be shared */ #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 */ struct pw_buffers { struct pw_memblock *mem; /**< allocated buffer memory */ diff --git a/src/pipewire/context.c b/src/pipewire/context.c index f86179121c640b2572bed839007e21c801b8963d..f5dc54cac00356e5f23cf23b6ca3ad3b21fe70a5 100644 --- a/src/pipewire/context.c +++ b/src/pipewire/context.c @@ -968,13 +968,15 @@ static int fraction_compare(const struct spa_fraction *a, const struct spa_fract return fa < fb ? -1 : (fa > fb ? 1 : 0); } -static bool rates_contains(uint32_t *rates, uint32_t n_rates, uint32_t rate) +static uint32_t find_best_rate(uint32_t *rates, uint32_t n_rates, uint32_t rate, uint32_t best) { uint32_t i; - for (i = 0; i < n_rates; i++) - if (rates[i] == rate) - return true; - return false; + for (i = 0; i < n_rates; i++) { + if (SPA_ABS((int32_t)rate - (int32_t)rates[i]) < + SPA_ABS((int32_t)rate - (int32_t)best)) + best = rates[i]; + } + return best; } /* here we evaluate the complete state of the graph. @@ -1166,6 +1168,10 @@ again: if (s->active) running = !n->passive; + pw_log_debug("%p: follower %p running:%d passive:%d rate:%u/%u latency %u/%u '%s'", + context, s, running, s->passive, rate.num, rate.denom, + latency.num, latency.denom, s->name); + s->moved = false; } @@ -1187,13 +1193,13 @@ again: * Start with the default rate. If the desired rate is * allowed, switch to it */ target_rate = def_rate; - if (rate.denom != 0 && rate.num == 1) { - if (rates_contains(rates, n_rates, rate.denom)) - target_rate = rate.denom; - } + if (rate.denom != 0 && rate.num == 1) + target_rate = find_best_rate(rates, n_rates, + rate.denom, target_rate); } if (target_rate != current_rate) { + bool do_suspend = false; /* we doing a rate switch */ pw_log_info("(%s-%u) state:%s new rate:%u->%u", n->name, n->info.id, @@ -1203,18 +1209,21 @@ again: if (force_rate) { if (settings->clock_rate_update_mode == CLOCK_RATE_UPDATE_MODE_HARD) - suspend_driver(context, n); + do_suspend = true; } else { - if (n->info.state >= PW_NODE_STATE_IDLE) - suspend_driver(context, n); + if (n->info.state >= PW_NODE_STATE_SUSPENDED) + do_suspend = true; } + if (do_suspend) + suspend_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; current_rate = target_rate; /* we might be suspended now and the links need to be prepared again */ - goto again; + if (do_suspend) + goto again; } if (rate_quantum != 0 && current_rate != rate_quantum) { @@ -1263,7 +1272,7 @@ again: n->current_pending = false; } - pw_log_debug("%p: driving %p running:%d passive:%d quantum:%u '%s'", + pw_log_debug("%p: driver %p running:%d passive:%d quantum:%u '%s'", context, n, running, n->passive, quantum, n->name); /* first change the node states of the followers to the new target */ diff --git a/src/pipewire/filter.c b/src/pipewire/filter.c index 0eb368def05462567da909b84fef8c2bcfdd600c..4af39fbbec2acf029643c7936a6e1d3934cf82c0 100644 --- a/src/pipewire/filter.c +++ b/src/pipewire/filter.c @@ -419,16 +419,16 @@ static int enum_params(struct filter *d, struct spa_list *param_list, int seq, spa_list_for_each(p, param_list, link) { struct spa_pod *param; - result.index = result.next++; - if (result.index < start) - continue; - param = p->param; if (param == NULL || p->id != id) continue; found = true; + result.index = result.next++; + if (result.index < start) + continue; + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); if (spa_pod_filter(&b.b, &result.param, param, filter) == 0) { spa_node_emit_result(&d->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); @@ -1179,7 +1179,7 @@ struct match { struct pw_filter *filter; int count; }; -#define MATCH_INIT(f) (struct match){ .filter = f } +#define MATCH_INIT(f) ((struct match){ .filter = (f) }) static int execute_match(void *data, const char *location, const char *action, const char *val, size_t len) diff --git a/src/pipewire/impl-device.c b/src/pipewire/impl-device.c index 188da6f9ded067f83e33c254dbb6bcf4b0eea4c8..855b046de738a737f46150d9b13d21cf4bb3437b 100644 --- a/src/pipewire/impl-device.c +++ b/src/pipewire/impl-device.c @@ -235,7 +235,7 @@ static void remove_busy_resource(struct resource_data *d) if (d->end != -1) { if (d->pi && d->data.cache) { - pw_param_update(&impl->param_list, &impl->pending_list); + pw_param_update(&impl->param_list, &impl->pending_list, 0, NULL); d->pi->user = 1; d->pi = NULL; } @@ -281,8 +281,8 @@ static void result_device_params(void *data, int seq, int res, uint32_t type, co if (d->cache) { pw_log_debug("%p: add param %d", impl, r->id); if (d->count++ == 0) - pw_param_add(&impl->pending_list, r->id, NULL); - pw_param_add(&impl->pending_list, r->id, r->param); + pw_param_add(&impl->pending_list, seq, r->id, NULL); + pw_param_add(&impl->pending_list, seq, r->id, r->param); } break; } @@ -333,10 +333,10 @@ int pw_impl_device_for_each_param(struct pw_impl_device *device, result.next = 0; spa_list_for_each(p, &impl->param_list, link) { - result.index = result.next++; if (p->id != param_id) continue; + result.index = result.next++; if (result.index < index) continue; @@ -364,7 +364,7 @@ int pw_impl_device_for_each_param(struct pw_impl_device *device, spa_hook_remove(&listener); if (!SPA_RESULT_IS_ASYNC(res) && user_data.cache) { - pw_param_update(&impl->param_list, &impl->pending_list); + pw_param_update(&impl->param_list, &impl->pending_list, 0, NULL); pi->user = 1; } } diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index b0293772927d2cc1affb4835a4338d3b6956f3ed..5eeff5b01dcfc799da18dd8cd7d55a8aa6791dbf 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -517,6 +517,9 @@ static int do_allocation(struct pw_impl_link *this) if (output->node->remote || input->node->remote) alloc_flags |= PW_BUFFERS_FLAG_SHARED_MEM; + if (output->node->driver) + alloc_flags |= PW_BUFFERS_FLAG_IN_PRIORITY; + /* if output port can alloc buffers, alloc skeleton buffers */ if (SPA_FLAG_IS_SET(out_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS)) { SPA_FLAG_SET(alloc_flags, PW_BUFFERS_FLAG_NO_MEM); @@ -619,7 +622,8 @@ int pw_impl_link_activate(struct pw_impl_link *this) pw_log_debug("%p: activate activated:%d state:%s", this, impl->activated, pw_link_state_as_string(this->info.state)); - if (impl->activated || !this->prepared || !impl->inode->added || !impl->onode->active) + if (impl->activated || !this->prepared || !impl->inode->active || + !impl->inode->added || !impl->onode->active) return 0; if (!impl->io_set) { diff --git a/src/pipewire/impl-module.c b/src/pipewire/impl-module.c index 0d4df47c09ab6bfc51e705bcf591bfe5e20f8f0a..282ba081b7993f1c6dc82477484ef5711ab48efa 100644 --- a/src/pipewire/impl-module.c +++ b/src/pipewire/impl-module.c @@ -178,6 +178,8 @@ pw_context_load_module(struct pw_context *context, NULL }; + pw_log_info("%p: name:%s args:%s", context, name, args); + module_dir = getenv("PIPEWIRE_MODULE_DIR"); if (module_dir == NULL) { module_dir = MODULEDIR; @@ -276,10 +278,10 @@ pw_context_load_module(struct pw_context *context, error_not_found: res = -ENOENT; - pw_log_error("No module \"%s\" was found", name); + pw_log_info("No module \"%s\" was found", name); goto error_cleanup; error_open_failed: - res = -ENOENT; + res = -EIO; pw_log_error("Failed to open module: \"%s\" %s", filename, dlerror()); goto error_free_filename; error_no_pw_module: diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index 2f3ec571588ab0b649afc87c3b891405f587a744..5480d273e1c4274fd84dd43a7ddb9bae2d013770 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -158,19 +158,6 @@ static void remove_node(struct pw_impl_node *this) this->rt.driver_target.node = NULL; } -static int -do_node_remove(struct spa_loop *loop, - bool async, uint32_t seq, const void *data, size_t size, void *user_data) -{ - struct pw_impl_node *this = user_data; - if (this->source.loop != NULL) { - spa_loop_remove_source(loop, &this->source); - remove_node(this); - } - this->added = false; - return 0; -} - static void node_deactivate(struct pw_impl_node *this) { struct pw_impl_port *port; @@ -185,15 +172,14 @@ static void node_deactivate(struct pw_impl_node *this) spa_list_for_each(link, &port->links, output_link) pw_impl_link_deactivate(link); } - pw_loop_invoke(this->data_loop, do_node_remove, 1, NULL, 0, true, this); } -static int pause_node(struct pw_impl_node *this) +static int idle_node(struct pw_impl_node *this) { struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); int res = 0; - pw_log_debug("%p: pause node state:%s pending:%s pause-on-idle:%d", this, + pw_log_debug("%p: idle node state:%s pending:%s pause-on-idle:%d", this, pw_node_state_as_string(this->info.state), pw_node_state_as_string(impl->pending_state), impl->pause_on_idle); @@ -201,6 +187,9 @@ static int pause_node(struct pw_impl_node *this) if (impl->pending_state <= PW_NODE_STATE_IDLE) return 0; + if (!impl->pause_on_idle) + return 0; + node_deactivate(this); res = spa_node_send_command(this->node, @@ -247,7 +236,8 @@ static int start_node(struct pw_impl_node *this) if (impl->pending_state >= PW_NODE_STATE_RUNNING) return 0; - pw_log_debug("%p: start node", this); + pw_log_debug("%p: start node driving:%d driver:%d added:%d", this, + this->driving, this->driver, this->added); if (!(this->driving && this->driver)) { impl->pending_play = true; @@ -350,6 +340,19 @@ do_node_add(struct spa_loop *loop, return 0; } +static int +do_node_remove(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct pw_impl_node *this = user_data; + if (this->source.loop != NULL) { + spa_loop_remove_source(loop, &this->source); + remove_node(this); + } + this->added = false; + return 0; +} + static void node_update_state(struct pw_impl_node *node, enum pw_node_state state, int res, char *error) { struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); @@ -357,20 +360,32 @@ static void node_update_state(struct pw_impl_node *node, enum pw_node_state stat switch (state) { case PW_NODE_STATE_RUNNING: + pw_log_debug("%p: start node driving:%d driver:%d added:%d", node, + node->driving, node->driver, node->added); + + if (res >= 0) { + pw_loop_invoke(node->data_loop, do_node_add, 1, NULL, 0, true, node); + } if (node->driving && node->driver) { res = spa_node_send_command(node->node, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start)); if (res < 0) { state = PW_NODE_STATE_ERROR; error = spa_aprintf("Start error: %s", spa_strerror(res)); + pw_loop_invoke(node->data_loop, do_node_remove, 1, NULL, 0, true, node); } } if (res >= 0) { - pw_loop_invoke(node->data_loop, do_node_add, 1, NULL, 0, true, node); /* now activate the inputs */ node_activate_inputs(node); } break; + case PW_NODE_STATE_IDLE: + case PW_NODE_STATE_SUSPENDED: + case PW_NODE_STATE_ERROR: + if (state != PW_NODE_STATE_IDLE || impl->pause_on_idle) + pw_loop_invoke(node->data_loop, do_node_remove, 1, NULL, 0, true, node); + break; default: break; } @@ -438,6 +453,9 @@ static int suspend_node(struct pw_impl_node *this) p->state = PW_IMPL_PORT_STATE_CONFIGURE; } + pw_log_debug("%p: suspend node driving:%d driver:%d added:%d", this, + this->driving, this->driver, this->added); + res = spa_node_send_command(this->node, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Suspend)); if (res == -ENOTSUP) @@ -1614,8 +1632,13 @@ static int node_ready(void *data, int status) struct pw_node_target *t; struct pw_impl_port *p; - pw_log_trace_fp("%p: ready driver:%d exported:%d %p status:%d", node, - node->driver, node->exported, driver, status); + pw_log_trace_fp("%p: ready driver:%d exported:%d %p status:%d added:%d", node, + node->driver, node->exported, driver, status, node->added); + + if (!node->added) { + pw_log_warn("%p: ready non-active node", node); + return -EIO; + } if (SPA_UNLIKELY(node == driver)) { struct pw_node_activation *a = node->rt.activation; @@ -1944,8 +1967,8 @@ static void result_node_params(void *data, int seq, int res, uint32_t type, cons d->callback(d->data, seq, r->id, r->index, r->next, r->param); if (d->cache) { if (d->count++ == 0) - pw_param_add(&impl->pending_list, r->id, NULL); - pw_param_add(&impl->pending_list, r->id, r->param); + pw_param_add(&impl->pending_list, seq, r->id, NULL); + pw_param_add(&impl->pending_list, seq, r->id, r->param); } } break; @@ -1997,10 +2020,10 @@ int pw_impl_node_for_each_param(struct pw_impl_node *node, result.next = 0; spa_list_for_each(p, &impl->param_list, link) { - result.index = result.next++; if (p->id != param_id) continue; + result.index = result.next++; if (result.index < index) continue; @@ -2029,7 +2052,7 @@ int pw_impl_node_for_each_param(struct pw_impl_node *node, spa_hook_remove(&listener); if (user_data.cache) { - pw_param_update(&impl->param_list, &impl->pending_list); + pw_param_update(&impl->param_list, &impl->pending_list, 0, NULL); pi->user = 1; } } @@ -2185,8 +2208,7 @@ int pw_impl_node_set_state(struct pw_impl_node *node, enum pw_node_state state) break; case PW_NODE_STATE_IDLE: - if (impl->pause_on_idle) - res = pause_node(node); + res = idle_node(node); break; case PW_NODE_STATE_RUNNING: @@ -2215,8 +2237,7 @@ int pw_impl_node_set_state(struct pw_impl_node *node, enum pw_node_state state) state < PW_NODE_STATE_RUNNING && impl->pending_play) { impl->pending_play = false; - spa_node_send_command(node->node, - &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Pause)); + idle_node(node); } pw_work_queue_cancel(impl->work, node, impl->pending_id); node->info.state = impl->pending_state; diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c index 8b0cb38b02f0df565faae6005a5b5d530b5ed0d0..ad7e191f1307705795ec8df87edacea61a3ac2f4 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -1199,8 +1199,8 @@ static void result_port_params(void *data, int seq, int res, uint32_t type, cons d->callback(d->data, seq, r->id, r->index, r->next, r->param); if (d->cache) { if (d->count++ == 0) - pw_param_add(&impl->pending_list, r->id, NULL); - pw_param_add(&impl->pending_list, r->id, r->param); + pw_param_add(&impl->pending_list, seq, r->id, NULL); + pw_param_add(&impl->pending_list, seq, r->id, r->param); } } break; @@ -1253,10 +1253,10 @@ int pw_impl_port_for_each_param(struct pw_impl_port *port, result.next = 0; spa_list_for_each(p, &impl->param_list, link) { - result.index = result.next++; if (p->id != param_id) continue; + result.index = result.next++; if (result.index < index) continue; @@ -1286,7 +1286,7 @@ int pw_impl_port_for_each_param(struct pw_impl_port *port, spa_hook_remove(&listener); if (user_data.cache) { - pw_param_update(&impl->param_list, &impl->pending_list); + pw_param_update(&impl->param_list, &impl->pending_list, 0, NULL); pi->user = 1; } } diff --git a/src/pipewire/introspect.c b/src/pipewire/introspect.c index 2855009c01e23e07fee3aa0c52dba55a5ccff803..e52ef2851cf072a5ffd540a77006b3daeecae23c 100644 --- a/src/pipewire/introspect.c +++ b/src/pipewire/introspect.c @@ -210,7 +210,7 @@ struct pw_node_info *pw_node_info_merge(struct pw_node_info *info, info->props = pw_spa_dict_copy(update->props); } if (update->change_mask & PW_NODE_CHANGE_MASK_PARAMS) { - uint32_t i, user, n_params = update->n_params; + uint32_t i, n_params = update->n_params; void *np; np = pw_reallocarray(info->params, n_params, sizeof(struct spa_param_info)); @@ -222,15 +222,18 @@ struct pw_node_info *pw_node_info_merge(struct pw_node_info *info, info->params = np; for (i = 0; i < SPA_MIN(info->n_params, n_params); i++) { - user = reset ? 0 : info->params[i].user; - if (info->params[i].flags != update->params[i].flags) - user++; - info->params[i] = update->params[i]; - info->params[i].user = user; + info->params[i].id = update->params[i].id; + if (reset) + info->params[i].user = 0; + if (info->params[i].flags != update->params[i].flags) { + info->params[i].flags = update->params[i].flags; + info->params[i].user++; + } } info->n_params = n_params; for (; i < info->n_params; i++) { - info->params[i] = update->params[i]; + info->params[i].id = update->params[i].id; + info->params[i].flags = update->params[i].flags; info->params[i].user = 1; } } @@ -280,7 +283,7 @@ struct pw_port_info *pw_port_info_merge(struct pw_port_info *info, info->props = pw_spa_dict_copy(update->props); } if (update->change_mask & PW_PORT_CHANGE_MASK_PARAMS) { - uint32_t i, user, n_params = update->n_params; + uint32_t i, n_params = update->n_params; void *np; np = pw_reallocarray(info->params, n_params, sizeof(struct spa_param_info)); @@ -292,15 +295,18 @@ struct pw_port_info *pw_port_info_merge(struct pw_port_info *info, info->params = np; for (i = 0; i < SPA_MIN(info->n_params, n_params); i++) { - user = reset ? 0 : info->params[i].user; - if (info->params[i].flags != update->params[i].flags) - user++; - info->params[i] = update->params[i]; - info->params[i].user = user; + info->params[i].id = update->params[i].id; + if (reset) + info->params[i].user = 0; + if (info->params[i].flags != update->params[i].flags) { + info->params[i].flags = update->params[i].flags; + info->params[i].user++; + } } info->n_params = n_params; for (; i < info->n_params; i++) { - info->params[i] = update->params[i]; + info->params[i].id = update->params[i].id; + info->params[i].flags = update->params[i].flags; info->params[i].user = 1; } } @@ -440,7 +446,7 @@ struct pw_device_info *pw_device_info_merge(struct pw_device_info *info, info->props = pw_spa_dict_copy(update->props); } if (update->change_mask & PW_DEVICE_CHANGE_MASK_PARAMS) { - uint32_t i, user, n_params = update->n_params; + uint32_t i, n_params = update->n_params; void *np; np = pw_reallocarray(info->params, n_params, sizeof(struct spa_param_info)); @@ -452,15 +458,18 @@ struct pw_device_info *pw_device_info_merge(struct pw_device_info *info, info->params = np; for (i = 0; i < SPA_MIN(info->n_params, n_params); i++) { - user = reset ? 0 : info->params[i].user; - if (info->params[i].flags != update->params[i].flags) - user++; - info->params[i] = update->params[i]; - info->params[i].user = user; + info->params[i].id = update->params[i].id; + if (reset) + info->params[i].user = 0; + if (info->params[i].flags != update->params[i].flags) { + info->params[i].flags = update->params[i].flags; + info->params[i].user++; + } } info->n_params = n_params; for (; i < info->n_params; i++) { - info->params[i] = update->params[i]; + info->params[i].id = update->params[i].id; + info->params[i].flags = update->params[i].flags; info->params[i].user = 1; } } diff --git a/src/pipewire/log.h b/src/pipewire/log.h index 26ffc20f9a0d6e5d3690abb6192079bf40c565f3..f91dc1136e96a4a3f5065708ded5a57891a507db 100644 --- a/src/pipewire/log.h +++ b/src/pipewire/log.h @@ -116,7 +116,7 @@ _pw_log_topic_new(struct spa_log_topic *topic); */ #define PW_LOG_TOPIC_STATIC(var, topic) \ static struct spa_log_topic var##__LINE__ = SPA_LOG_TOPIC(0, topic); \ - static struct spa_log_topic *var = &(var##__LINE__) + static struct spa_log_topic *(var) = &(var##__LINE__) /** * Declare a static log topic named \a var. @@ -131,7 +131,7 @@ _pw_log_topic_new(struct spa_log_topic *topic); */ #define PW_LOG_TOPIC(var, topic) \ struct spa_log_topic var##__LINE__ = SPA_LOG_TOPIC(0, topic); \ - struct spa_log_topic *var = &(var##__LINE__) + struct spa_log_topic *(var) = &(var##__LINE__) #define PW_LOG_TOPIC_INIT(var) \ spa_log_topic_init(pw_log_get(), var); diff --git a/src/pipewire/map.h b/src/pipewire/map.h index f2b54fc7cfb9b037f7090854d8d2d023e81c0216..9a46082e235ad315a415cebda1343e3d7b5a6396 100644 --- a/src/pipewire/map.h +++ b/src/pipewire/map.h @@ -85,7 +85,7 @@ struct pw_map { }; /** \param extend the amount of bytes to grow the map with when needed */ -#define PW_MAP_INIT(extend) (struct pw_map) { PW_ARRAY_INIT(extend), SPA_ID_INVALID } +#define PW_MAP_INIT(extend) ((struct pw_map) { PW_ARRAY_INIT(extend), SPA_ID_INVALID }) /** * Get the number of currently allocated elements in the map. diff --git a/src/pipewire/permission.h b/src/pipewire/permission.h index e0274ddcbb2c463678e128d3bce324afca5387cf..1bcebc3aa0a3d115c7681aa8a9b03640208dbb11 100644 --- a/src/pipewire/permission.h +++ b/src/pipewire/permission.h @@ -66,7 +66,7 @@ struct pw_permission { uint32_t permissions; /**< bitmask of above permissions */ }; -#define PW_PERMISSION_INIT(id,p) (struct pw_permission){ (id), (p) } +#define PW_PERMISSION_INIT(id,p) ((struct pw_permission){ (id), (p) }) #define PW_PERMISSION_FORMAT "%c%c%c%c" #define PW_PERMISSION_ARGS(permission) \ diff --git a/src/pipewire/private.h b/src/pipewire/private.h index 50080079afc5f87be846bbb1fd5735408a31455a..659f4ab8f65e41bc56f8368baa9ccfd344ab3bcb 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -49,7 +49,7 @@ struct ucred { #define spa_debug(...) pw_log_trace(__VA_ARGS__) #endif -#define MAX_RATES 16u +#define MAX_RATES 32u #define CLOCK_MIN_QUANTUM 4u #define CLOCK_MAX_QUANTUM 65536u @@ -104,6 +104,7 @@ static inline bool ratelimit_test(struct ratelimit *r, uint64_t now, enum spa_lo struct pw_param { uint32_t id; + int32_t seq; struct spa_list link; struct spa_pod *param; }; @@ -123,7 +124,7 @@ static inline uint32_t pw_param_clear(struct spa_list *param_list, uint32_t id) return count; } -static inline struct pw_param *pw_param_add(struct spa_list *params, +static inline struct pw_param *pw_param_add(struct spa_list *params, int32_t seq, uint32_t id, const struct spa_pod *param) { struct pw_param *p; @@ -140,6 +141,7 @@ static inline struct pw_param *pw_param_add(struct spa_list *params, return NULL; p->id = id; + p->seq = seq; if (param != NULL) { p->param = SPA_PTROFF(p, sizeof(*p), struct spa_pod); memcpy(p->param, param, SPA_POD_SIZE(param)); @@ -151,10 +153,22 @@ static inline struct pw_param *pw_param_add(struct spa_list *params, return p; } -static inline void pw_param_update(struct spa_list *param_list, struct spa_list *pending_list) +static inline void pw_param_update(struct spa_list *param_list, struct spa_list *pending_list, + uint32_t n_params, struct spa_param_info *params) { - struct pw_param *p; + struct pw_param *p, *t; + uint32_t i; + for (i = 0; i < n_params; i++) { + spa_list_for_each_safe(p, t, pending_list, link) { + if (p->id == params[i].id && + p->seq != params[i].seq && + p->param != NULL) { + spa_list_remove(&p->link); + free(p); + } + } + } spa_list_consume(p, pending_list, link) { spa_list_remove(&p->link); if (p->param == NULL) { @@ -177,7 +191,7 @@ static inline struct spa_param_info *pw_param_info_find(struct spa_param_info in return NULL; } -#define pw_protocol_emit_destroy(p) spa_hook_list_call(&p->listener_list, struct pw_protocol_events, destroy, 0) +#define pw_protocol_emit_destroy(p) spa_hook_list_call(&(p)->listener_list, struct pw_protocol_events, destroy, 0) struct pw_protocol { struct spa_list link; /**< link in context protocol_list */ @@ -805,7 +819,7 @@ struct pw_impl_port_implementation { #define pw_impl_port_emit_param_changed(p,i) pw_impl_port_emit(p, param_changed, 1, i) #define pw_impl_port_emit_latency_changed(p) pw_impl_port_emit(p, latency_changed, 2) -#define PW_IMPL_PORT_IS_CONTROL(port) SPA_FLAG_MASK(port->flags, \ +#define PW_IMPL_PORT_IS_CONTROL(port) SPA_FLAG_MASK((port)->flags, \ PW_IMPL_PORT_FLAG_BUFFERS|PW_IMPL_PORT_FLAG_CONTROL,\ PW_IMPL_PORT_FLAG_CONTROL) struct pw_impl_port { diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index ff0e0f84068769766ad7b7ea48d80519798432c4..808eb47473c460fd659949cf8fda5dc6a5b26415 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -400,6 +400,28 @@ static struct buffer *get_buffer(struct pw_stream *stream, uint32_t id) return NULL; } +static inline uint32_t update_requested(struct stream *impl) +{ + uint32_t index, id, res = 0; + struct buffer *buffer; + struct spa_io_rate_match *r = impl->rate_match; + + if (spa_ringbuffer_get_read_index(&impl->dequeued.ring, &index) < 1) + return 0; + + id = impl->dequeued.ids[index & MASK_BUFFERS]; + buffer = &impl->buffers[id]; + if (r) { + buffer->this.requested = r->size; + res = r->size > 0 ? 1 : 0; + } else { + buffer->this.requested = impl->quantum; + res = 1; + } + pw_log_trace_fp("%p: update buffer:%u size:%"PRIu64, impl, id, buffer->this.requested); + return res; +} + static int do_call_process(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) @@ -411,9 +433,11 @@ do_call_process(struct spa_loop *loop, return 0; } -static void call_process(struct stream *impl) +static inline void call_process(struct stream *impl) { pw_log_trace_fp("%p: call process rt:%u", impl, impl->process_rt); + if (impl->direction == SPA_DIRECTION_OUTPUT && update_requested(impl) <= 0) + return; if (impl->process_rt) spa_callbacks_call(&impl->rt_callbacks, struct pw_stream_events, process, 0); else @@ -520,16 +544,16 @@ static int enum_params(void *object, bool is_port, int seq, uint32_t id, uint32_ spa_list_for_each(p, &d->param_list, link) { struct spa_pod *param; - result.index = result.next++; - if (result.index < start) - continue; - param = p->param; if (param == NULL || p->id != id) continue; found = true; + result.index = result.next++; + if (result.index < start) + continue; + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); if (spa_pod_filter(&b.b, &result.param, param, filter) == 0) { spa_node_emit_result(&d->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); @@ -563,26 +587,26 @@ static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struc return 0; } -static inline uint32_t update_requested(struct stream *impl) +static inline void copy_position(struct stream *impl, int64_t queued) { - uint32_t index, id, res = 0; - struct buffer *buffer; - struct spa_io_rate_match *r = impl->rate_match; - - if (spa_ringbuffer_get_read_index(&impl->dequeued.ring, &index) < 1) - return 0; + struct spa_io_position *p = impl->rt.position; - id = impl->dequeued.ids[index & MASK_BUFFERS]; - buffer = &impl->buffers[id]; - if (r) { - buffer->this.requested = r->size; - res = r->size > 0 ? 1 : 0; - } else { - buffer->this.requested = impl->quantum; - res = 1; + SEQ_WRITE(impl->seq); + if (SPA_LIKELY(p != NULL)) { + impl->time.now = p->clock.nsec; + impl->time.rate = p->clock.rate; + if (SPA_UNLIKELY(impl->clock_id != p->clock.id)) { + impl->base_pos = p->clock.position - impl->time.ticks; + impl->clock_id = p->clock.id; + } + impl->time.ticks = p->clock.position - impl->base_pos; + impl->time.delay = 0; + impl->time.queued = queued; + impl->quantum = p->clock.duration; } - pw_log_trace_fp("%p: update buffer:%u size:%"PRIu64, impl, id, buffer->this.requested); - return res; + if (SPA_LIKELY(impl->rate_match != NULL)) + impl->rate_queued = impl->rate_match->delay; + SEQ_WRITE(impl->seq); } static int impl_send_command(void *object, const struct spa_command *command) @@ -613,8 +637,8 @@ static int impl_send_command(void *object, const struct spa_command *command) if (impl->direction == SPA_DIRECTION_INPUT) impl->io->status = SPA_STATUS_NEED_DATA; else if (!impl->process_rt && !impl->driving) { - if (update_requested(impl) > 0) - call_process(impl); + copy_position(impl, impl->queued.incount); + call_process(impl); } stream_set_state(stream, PW_STREAM_STATE_STREAMING, NULL); @@ -963,28 +987,6 @@ static int impl_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffe return 0; } -static inline void copy_position(struct stream *impl, int64_t queued) -{ - struct spa_io_position *p = impl->rt.position; - - SEQ_WRITE(impl->seq); - if (SPA_LIKELY(p != NULL)) { - impl->time.now = p->clock.nsec; - impl->time.rate = p->clock.rate; - if (SPA_UNLIKELY(impl->clock_id != p->clock.id)) { - impl->base_pos = p->clock.position - impl->time.ticks; - impl->clock_id = p->clock.id; - } - impl->time.ticks = p->clock.position - impl->base_pos; - impl->time.delay = 0; - impl->time.queued = queued; - impl->quantum = p->clock.duration; - } - if (SPA_LIKELY(impl->rate_match != NULL)) - impl->rate_queued = impl->rate_match->delay; - SEQ_WRITE(impl->seq); -} - static int impl_node_process_input(void *object) { struct stream *impl = object; @@ -1081,8 +1083,7 @@ again: /* we're not draining, not a driver check if we need to get * more buffers */ if (ask_more) { - if (update_requested(impl) > 0) - call_process(impl); + call_process(impl); /* realtime, we can try again now if there is something. * non-realtime, we will have to try in the next round */ if (impl->process_rt && @@ -1403,7 +1404,7 @@ struct match { struct pw_stream *stream; int count; }; -#define MATCH_INIT(s) (struct match){ .stream = s } +#define MATCH_INIT(s) ((struct match){ .stream = (s) }) static int execute_match(void *data, const char *location, const char *action, const char *val, size_t len) @@ -2331,7 +2332,7 @@ int pw_stream_flush(struct pw_stream *stream, bool drain) struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); pw_loop_invoke(impl->context->data_loop, drain ? do_drain : do_flush, 1, NULL, 0, true, impl); - if (!drain) + if (!drain && impl->node != NULL) spa_node_send_command(impl->node->node, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Flush)); return 0; @@ -2352,7 +2353,7 @@ do_trigger_process(struct spa_loop *loop, int res; if (impl->direction == SPA_DIRECTION_OUTPUT) { if (impl->process_rt) - spa_callbacks_call(&impl->rt_callbacks, struct pw_stream_events, process, 0); + call_process(impl); res = impl->node_methods.process(impl); } else { res = SPA_STATUS_NEED_DATA; @@ -2386,11 +2387,9 @@ int pw_stream_trigger_process(struct pw_stream *stream) if (!impl->driving && !impl->trigger) { res = trigger_request_process(impl); } else { - if (impl->direction == SPA_DIRECTION_OUTPUT && - !impl->process_rt) { - pw_loop_invoke(impl->context->main_loop, - do_call_process, 1, NULL, 0, false, impl); - } + if (!impl->process_rt) + call_process(impl); + res = pw_loop_invoke(impl->context->data_loop, do_trigger_process, 1, NULL, 0, false, impl); } diff --git a/src/pipewire/thread-loop.c b/src/pipewire/thread-loop.c index 5588b63461f29acc1b60bfeaa0b878b112989cc1..2fdd50e224d309d464d7ea3d3a5bcf2852ee3fe0 100644 --- a/src/pipewire/thread-loop.c +++ b/src/pipewire/thread-loop.c @@ -91,7 +91,7 @@ static void do_stop(void *data, uint64_t count) #define CHECK(expression,label) \ do { \ - if ((errno = expression) != 0) { \ + if ((errno = (expression)) != 0) { \ res = -errno; \ pw_log_error(#expression ": %s", strerror(errno)); \ goto label; \ diff --git a/src/pipewire/thread.c b/src/pipewire/thread.c index 72be387bd82f96826346d45df51615d1ef183bad..02b8b9ed1729a7592531b209f534222f0b7fbfa9 100644 --- a/src/pipewire/thread.c +++ b/src/pipewire/thread.c @@ -37,7 +37,7 @@ #define CHECK(expression,label) \ do { \ - if ((errno = expression) != 0) { \ + if ((errno = (expression)) != 0) { \ res = -errno; \ pw_log_error(#expression ": %s", strerror(errno)); \ goto label; \ @@ -116,6 +116,17 @@ static int impl_get_rt_range(void *object, const struct spa_dict *props, *max = sched_get_priority_max(SCHED_OTHER); return 0; } +static int impl_acquire_rt(void *object, struct spa_thread *thread, int priority) +{ + pw_log_warn("acquire_rt thread:%p prio:%d not implemented", thread, priority); + return -ENOTSUP; +} + +static int impl_drop_rt(void *object, struct spa_thread *thread) +{ + pw_log_warn("drop_rt thread:%p not implemented", thread); + return -ENOTSUP; +} static struct { struct spa_thread_utils utils; @@ -128,7 +139,9 @@ static struct { { SPA_VERSION_THREAD_UTILS_METHODS, .create = impl_create, .join = impl_join, - .get_rt_range = impl_get_rt_range + .get_rt_range = impl_get_rt_range, + .acquire_rt = impl_acquire_rt, + .drop_rt = impl_drop_rt, } }; diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index ea502d6473069ec02241c154c557ec1fc02d5186..ab43bd12e25e7c586f55defd6b5f11285c93c6ff 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -174,20 +174,18 @@ static const struct format_info { static const struct format_info *format_info_by_name(const char *str) { - uint32_t i; - for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) - if (spa_streq(str, format_info[i].name)) - return &format_info[i]; + SPA_FOR_EACH_ELEMENT_VAR(format_info, i) + if (spa_streq(str, i->name)) + return i; return NULL; } static const struct format_info *format_info_by_sf_format(int format) { - uint32_t i; int sub_type = (format & SF_FORMAT_SUBMASK); - for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) - if (format_info[i].sf_format == sub_type) - return &format_info[i]; + SPA_FOR_EACH_ELEMENT_VAR(format_info, i) + if (i->sf_format == sub_type) + return i; return NULL; } @@ -431,10 +429,10 @@ static int parse_channelmap(const char *channel_map, struct channelmap *map) int i, nch; char **ch; - for (i = 0; i < (int) SPA_N_ELEMENTS(maps); i++) { - if (spa_streq(maps[i].name, channel_map)) { - map->n_channels = maps[i].channels; - spa_memcpy(map->channels, &maps[i].values, + SPA_FOR_EACH_ELEMENT_VAR(maps, m) { + if (spa_streq(m->name, channel_map)) { + map->n_channels = m->channels; + spa_memcpy(map->channels, &m->values, map->n_channels * sizeof(unsigned int)); return 0; } @@ -1589,17 +1587,16 @@ int main(int argc, char *argv[]) case TYPE_DSD: { struct spa_audio_info_dsd info; - size_t i; spa_zero(info); info.channels = data.dsf.info.channels; info.rate = data.dsf.info.rate / 8; - for (i = 0; i < SPA_N_ELEMENTS(dsd_layouts); i++) { - if (dsd_layouts[i].type != data.dsf.info.channel_type) + SPA_FOR_EACH_ELEMENT_VAR(dsd_layouts, i) { + if (i->type != data.dsf.info.channel_type) continue; - info.channels = dsd_layouts[i].info.n_channels; - memcpy(info.position, dsd_layouts[i].info.position, + info.channels = i->info.n_channels; + memcpy(info.position, i->info.position, info.channels * sizeof(uint32_t)); } params[0] = spa_format_audio_dsd_build(&b, SPA_PARAM_EnumFormat, &info); diff --git a/src/tools/pw-cli.c b/src/tools/pw-cli.c index 4d6655b062b37249d9951aed04db8a27973db6d8..bb5ab99cb521fa6ad2fc5af668ffc39424f4184a 100644 --- a/src/tools/pw-cli.c +++ b/src/tools/pw-cli.c @@ -262,14 +262,11 @@ static bool do_quit(struct data *data, const char *cmd, char *args, char **error static bool do_help(struct data *data, const char *cmd, char *args, char **error) { - size_t i; - printf("Available commands:\n"); - for (i = 0; i < SPA_N_ELEMENTS(command_list); i++) { + SPA_FOR_EACH_ELEMENT_VAR(command_list, c) { char cmd[256]; - snprintf(cmd, sizeof(cmd), "%s | %s", - command_list[i].name, command_list[i].alias); - printf("\t%-20.20s\t%s\n", cmd, command_list[i].description); + snprintf(cmd, sizeof(cmd), "%s | %s", c->name, c->alias); + printf("\t%-20.20s\t%s\n", cmd, c->description); } return true; } @@ -1312,11 +1309,10 @@ static const struct class *classes[] = static const struct class *find_class(const char *type, uint32_t version) { - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(classes); i++) { - if (spa_streq(classes[i]->type, type) && - classes[i]->version <= version) - return classes[i]; + SPA_FOR_EACH_ELEMENT_VAR(classes, c) { + if (spa_streq((*c)->type, type) && + (*c)->version <= version) + return *c; } return NULL; } @@ -2110,7 +2106,6 @@ static bool parse(struct data *data, char *buf, char **error) { char *a[2]; int n; - size_t i; char *p, *cmd, *args; if ((p = strchr(buf, '#'))) @@ -2128,10 +2123,10 @@ static bool parse(struct data *data, char *buf, char **error) cmd = a[0]; args = n > 1 ? a[1] : ""; - for (i = 0; i < SPA_N_ELEMENTS(command_list); i++) { - if (spa_streq(command_list[i].name, cmd) || - spa_streq(command_list[i].alias, cmd)) { - return command_list[i].func(data, cmd, args, error); + SPA_FOR_EACH_ELEMENT_VAR(command_list, c) { + if (spa_streq(c->name, cmd) || + spa_streq(c->alias, cmd)) { + return c->func(data, cmd, args, error); } } *error = spa_aprintf("Command \"%s\" does not exist. Type 'help' for usage.", cmd); @@ -2231,13 +2226,13 @@ readline_command_completion(const char *text, int start, int end) return matches; } -static void readline_init() +static void readline_init(void) { rl_attempted_completion_function = readline_command_completion; rl_callback_handler_install(">> ", input_process_line); } -static void readline_cleanup() +static void readline_cleanup(void) { rl_callback_handler_remove(); } @@ -2374,8 +2369,8 @@ int main(int argc, char *argv[]) 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) { - data.current->prompt_pending = pw_core_sync(data.current->core, 0, 0); pw_main_loop_run(data.loop); if (!monitor) break; diff --git a/src/tools/pw-dot.c b/src/tools/pw-dot.c index 68abc3b95434356bbc3d2947e6cdcb7238785ed8..680bd248843e976c196871bc328a15dfa8919870 100644 --- a/src/tools/pw-dot.c +++ b/src/tools/pw-dot.c @@ -94,7 +94,7 @@ struct global { struct spa_hook object_listener; }; -static char *dot_str_new() +static char *dot_str_new(void) { return strdup(""); } @@ -514,7 +514,7 @@ static int draw_graph(struct data *d, const char *path) fputs(d->dot_str, stdout); } else { /* open the file */ - fp = fopen(path, "w"); + fp = fopen(path, "we"); if (fp == NULL) { printf("open error: could not open %s for writing\n", path); return -1; diff --git a/src/tools/pw-dump.c b/src/tools/pw-dump.c index 8b8cc76f1e3d9d9652f6242bfd033f846410a38d..b25e7863332392000f0dbed6fafa0fdb0a95983d 100644 --- a/src/tools/pw-dump.c +++ b/src/tools/pw-dump.c @@ -88,6 +88,7 @@ struct data { struct param { uint32_t id; + int32_t seq; struct spa_list link; struct spa_pod *param; }; @@ -116,6 +117,8 @@ struct object { const struct class *class; void *info; + struct spa_param_info *params; + uint32_t n_params; int changed; struct spa_list param_list; @@ -148,7 +151,8 @@ static uint32_t clear_params(struct spa_list *param_list, uint32_t id) return count; } -static struct param *add_param(struct spa_list *params, uint32_t id, const struct spa_pod *param) +static struct param *add_param(struct spa_list *params, int seq, + uint32_t id, const struct spa_pod *param) { struct param *p; @@ -165,6 +169,7 @@ static struct param *add_param(struct spa_list *params, uint32_t id, const struc return NULL; p->id = id; + p->seq = seq; if (param != NULL) { p->param = SPA_PTROFF(p, sizeof(*p), struct spa_pod); memcpy(p->param, param, SPA_POD_SIZE(param)); @@ -187,17 +192,30 @@ static struct object *find_object(struct data *d, uint32_t id) return NULL; } -static void object_update_params(struct object *o) +static void object_update_params(struct spa_list *param_list, struct spa_list *pending_list, + uint32_t n_params, struct spa_param_info *params) { - struct param *p; + struct param *p, *t; + uint32_t i; + + for (i = 0; i < n_params; i++) { + spa_list_for_each_safe(p, t, pending_list, link) { + if (p->id == params[i].id && + p->seq != params[i].seq && + p->param != NULL) { + spa_list_remove(&p->link); + free(p); + } + } + } - spa_list_consume(p, &o->pending_list, link) { + spa_list_consume(p, pending_list, link) { spa_list_remove(&p->link); if (p->param == NULL) { - clear_params(&o->param_list, p->id); + clear_params(param_list, p->id); free(p); } else { - spa_list_append(&o->param_list, &p->link); + spa_list_append(param_list, &p->link); } } } @@ -587,9 +605,11 @@ static void client_event_info(void *data, const struct pw_client_info *info) struct object *o = data; int changed = 0; - pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->id, info->change_mask); + pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->id, info->change_mask); - info = o->info = pw_client_info_update(o->info, info); + info = o->info = pw_client_info_update(o->info, info); + if (info == NULL) + return; if (info->change_mask & PW_CLIENT_CHANGE_MASK_PROPS) changed++; @@ -644,12 +664,14 @@ static void module_dump(struct object *o) static void module_event_info(void *data, const struct pw_module_info *info) { - struct object *o = data; + struct object *o = data; int changed = 0; - pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->id, info->change_mask); + pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->id, info->change_mask); - info = o->info = pw_module_info_update(o->info, info); + info = o->info = pw_module_info_update(o->info, info); + if (info == NULL) + return; if (info->change_mask & PW_MODULE_CHANGE_MASK_PROPS) changed++; @@ -704,12 +726,14 @@ static void factory_dump(struct object *o) static void factory_event_info(void *data, const struct pw_factory_info *info) { - struct object *o = data; + struct object *o = data; int changed = 0; - pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->id, info->change_mask); + pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->id, info->change_mask); - info = o->info = pw_factory_info_update(o->info, info); + info = o->info = pw_factory_info_update(o->info, info); + if (info == NULL) + return; if (info->change_mask & PW_FACTORY_CHANGE_MASK_PROPS) changed++; @@ -765,10 +789,16 @@ static void device_event_info(void *data, const struct pw_device_info *info) { struct object *o = data; uint32_t i, changed = 0; + int res; pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->id, info->change_mask); info = o->info = pw_device_info_update(o->info, info); + if (info == NULL) + return; + + o->params = info->params; + o->n_params = info->n_params; if (info->change_mask & PW_DEVICE_CHANGE_MASK_PROPS) changed++; @@ -782,12 +812,14 @@ static void device_event_info(void *data, const struct pw_device_info *info) info->params[i].user = 0; changed++; - clear_params(&o->pending_list, id); + add_param(&o->pending_list, 0, id, NULL); if (!(info->params[i].flags & SPA_PARAM_INFO_READ)) continue; - pw_device_enum_params((struct pw_device*)o->proxy, - 0, id, 0, -1, NULL); + res = pw_device_enum_params((struct pw_device*)o->proxy, + ++info->params[i].seq, id, 0, -1, NULL); + if (SPA_RESULT_IS_ASYNC(res)) + info->params[i].seq = res; } } if (changed) { @@ -801,7 +833,7 @@ static void device_event_param(void *data, int seq, const struct spa_pod *param) { struct object *o = data; - add_param(&o->pending_list, id, param); + add_param(&o->pending_list, seq, id, param); } static const struct pw_device_events device_events = { @@ -859,10 +891,16 @@ static void node_event_info(void *data, const struct pw_node_info *info) { struct object *o = data; uint32_t i, changed = 0; + int res; pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->id, info->change_mask); info = o->info = pw_node_info_update(o->info, info); + if (info == NULL) + return; + + o->params = info->params; + o->n_params = info->n_params; if (info->change_mask & PW_NODE_CHANGE_MASK_STATE) changed++; @@ -879,12 +917,14 @@ static void node_event_info(void *data, const struct pw_node_info *info) info->params[i].user = 0; changed++; - add_param(&o->pending_list, id, NULL); + add_param(&o->pending_list, 0, id, NULL); if (!(info->params[i].flags & SPA_PARAM_INFO_READ)) continue; - pw_node_enum_params((struct pw_node*)o->proxy, - 0, id, 0, -1, NULL); + res = pw_node_enum_params((struct pw_node*)o->proxy, + ++info->params[i].seq, id, 0, -1, NULL); + if (SPA_RESULT_IS_ASYNC(res)) + info->params[i].seq = res; } } if (changed) { @@ -898,7 +938,7 @@ static void node_event_param(void *data, int seq, const struct spa_pod *param) { struct object *o = data; - add_param(&o->pending_list, id, param); + add_param(&o->pending_list, seq, id, param); } static const struct pw_node_events node_events = { @@ -948,10 +988,16 @@ static void port_event_info(void *data, const struct pw_port_info *info) { struct object *o = data; uint32_t i, changed = 0; + int res; pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->id, info->change_mask); info = o->info = pw_port_info_update(o->info, info); + if (info == NULL) + return; + + o->params = info->params; + o->n_params = info->n_params; if (info->change_mask & PW_PORT_CHANGE_MASK_PROPS) changed++; @@ -965,12 +1011,14 @@ static void port_event_info(void *data, const struct pw_port_info *info) info->params[i].user = 0; changed++; - add_param(&o->pending_list, id, NULL); + add_param(&o->pending_list, 0, id, NULL); if (!(info->params[i].flags & SPA_PARAM_INFO_READ)) continue; - pw_port_enum_params((struct pw_port*)o->proxy, - 0, id, 0, -1, NULL); + res = pw_port_enum_params((struct pw_port*)o->proxy, + ++info->params[i].seq, id, 0, -1, NULL); + if (SPA_RESULT_IS_ASYNC(res)) + info->params[i].seq = res; } } if (changed) { @@ -984,7 +1032,7 @@ static void port_event_param(void *data, int seq, const struct spa_pod *param) { struct object *o = data; - add_param(&o->pending_list, id, param); + add_param(&o->pending_list, seq, id, param); } static const struct pw_port_events port_events = { @@ -1044,6 +1092,8 @@ static void link_event_info(void *data, const struct pw_link_info *info) pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->id, info->change_mask); info = o->info = pw_link_info_update(o->info, info); + if (info == NULL) + return; if (info->change_mask & PW_LINK_CHANGE_MASK_STATE) changed++; @@ -1241,11 +1291,10 @@ static const struct class *classes[] = static const struct class *find_class(const char *type, uint32_t version) { - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(classes); i++) { - if (spa_streq(classes[i]->type, type) && - classes[i]->version <= version) - return classes[i]; + SPA_FOR_EACH_ELEMENT_VAR(classes, c) { + if (spa_streq((*c)->type, type) && + (*c)->version <= version) + return *c; } return NULL; } @@ -1455,7 +1504,8 @@ static void on_core_done(void *data, uint32_t id, int seq) pw_log_debug("sync end %u/%u", d->sync_seq, seq); spa_list_for_each(o, &d->object_list, link) - object_update_params(o); + object_update_params(&o->param_list, &o->pending_list, + o->n_params, o->params); dump_objects(d); if (!d->monitor) diff --git a/src/tools/pw-link.c b/src/tools/pw-link.c index 9e008aa60147ac201cfa223f6cd58f45afba2568..8696eb296c76380d74dd34255f3bb16576dfe1da 100644 --- a/src/tools/pw-link.c +++ b/src/tools/pw-link.c @@ -400,8 +400,16 @@ static int do_link_ports(struct data *data) struct object *port_out = find_node_port(data, out_node, PW_DIRECTION_OUTPUT, port_id); struct object *port_in = find_node_port(data, in_node, PW_DIRECTION_INPUT, port_id); - if (!port_out || !port_in) - break; + if (!port_out && !port_in) { + fprintf(stderr, "Input & output port do not exist\n"); + goto no_port; + } else if (!port_in) { + fprintf(stderr, "Input port does not exist\n"); + goto no_port; + } else if (!port_out) { + fprintf(stderr, "Output port does not exist\n"); + goto no_port; + } pw_properties_setf(data->props, PW_KEY_LINK_OUTPUT_PORT, "%u", port_out->id); pw_properties_setf(data->props, PW_KEY_LINK_INPUT_PORT, "%u", port_in->id); @@ -422,6 +430,9 @@ static int do_link_ports(struct data *data) pw_properties_setf(data->props, PW_KEY_LINK_INPUT_PORT, "%u", in_port); return create_link(data); + +no_port: + return -ENOENT; } static int do_unlink_ports(struct data *data) diff --git a/src/tools/pw-loopback.c b/src/tools/pw-loopback.c index 4f4fb963c344bf0f1da4d85b84ebddf66032124f..7ec93a1bb02478838cff62e704d92dd2ee2226be 100644 --- a/src/tools/pw-loopback.c +++ b/src/tools/pw-loopback.c @@ -57,6 +57,7 @@ struct data { uint32_t channels; uint32_t latency; + float delay; struct pw_properties *capture_props; struct pw_properties *playback_props; @@ -93,6 +94,7 @@ static void show_help(struct data *data, const char *name, bool error) " -c, --channels Number of channels (default %d)\n" " -m, --channel-map Channel map (default '%s')\n" " -l, --latency Desired latency in ms\n" + " -d, --delay Desired delay in float s\n" " -C --capture Capture source to connect to\n" " --capture-props Capture stream properties\n" " -P --playback Playback sink to connect to\n" @@ -109,7 +111,7 @@ int main(int argc, char *argv[]) struct data data = { 0 }; struct pw_loop *l; const char *opt_remote = NULL; - char cname[256]; + char cname[256], value[256]; char *args; size_t size; FILE *f; @@ -121,6 +123,7 @@ int main(int argc, char *argv[]) { "name", required_argument, NULL, 'n' }, { "channels", required_argument, NULL, 'c' }, { "latency", required_argument, NULL, 'l' }, + { "delay", required_argument, NULL, 'd' }, { "capture", required_argument, NULL, 'C' }, { "playback", required_argument, NULL, 'P' }, { "capture-props", required_argument, NULL, 'i' }, @@ -146,7 +149,7 @@ int main(int argc, char *argv[]) goto exit; } - while ((c = getopt_long(argc, argv, "hVr:n:g:c:m:l:C:P:i:o:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hVr:n:g:c:m:l:d:C:P:i:o:", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(&data, argv[0], false); @@ -177,6 +180,9 @@ int main(int argc, char *argv[]) case 'l': data.latency = atoi(optarg) * DEFAULT_RATE / SPA_MSEC_PER_SEC; break; + case 'd': + data.delay = atof(optarg); + break; case 'C': pw_properties_set(data.capture_props, PW_KEY_NODE_TARGET, optarg); break; @@ -223,6 +229,9 @@ int main(int argc, char *argv[]) fprintf(f, " remote.name = \"%s\"", opt_remote); if (data.latency != 0) fprintf(f, " node.latency = %u/%u", data.latency, DEFAULT_RATE); + if (data.delay != 0.0f) + fprintf(f, " target.delay.sec = %s", + spa_json_format_float(value, sizeof(value), data.delay)); if (data.channels != 0) fprintf(f, " audio.channels = %u", data.channels); if (data.opt_channel_map != NULL) diff --git a/src/tools/pw-profiler.c b/src/tools/pw-profiler.c index f2000a9860c8342d1cc83f379881125acbe019db..00dc4104a0c2464a3f718341eeed29a34584fa1f 100644 --- a/src/tools/pw-profiler.c +++ b/src/tools/pw-profiler.c @@ -262,7 +262,7 @@ static void dump_scripts(struct data *d) printf("\ndumping scripts for %d followers\n", d->n_followers); - out = fopen("Timing1.plot", "w"); + out = fopen("Timing1.plot", "we"); if (out == NULL) { pw_log_error("Can't open Timing1.plot: %m"); } else { @@ -282,7 +282,7 @@ static void dump_scripts(struct data *d) fclose(out); } - out = fopen("Timing2.plot", "w"); + out = fopen("Timing2.plot", "we"); if (out == NULL) { pw_log_error("Can't open Timing2.plot: %m"); } else { @@ -298,7 +298,7 @@ static void dump_scripts(struct data *d) fclose(out); } - out = fopen("Timing3.plot", "w"); + out = fopen("Timing3.plot", "we"); if (out == NULL) { pw_log_error("Can't open Timing3.plot: %m"); } else { @@ -328,7 +328,7 @@ static void dump_scripts(struct data *d) fclose(out); } - out = fopen("Timing4.plot", "w"); + out = fopen("Timing4.plot", "we"); if (out == NULL) { pw_log_error("Can't open Timing4.plot: %m"); } else { @@ -355,7 +355,7 @@ static void dump_scripts(struct data *d) fclose(out); } - out = fopen("Timing5.plot", "w"); + out = fopen("Timing5.plot", "we"); if (out == NULL) { pw_log_error("Can't open Timing5.plot: %m"); } else { @@ -381,7 +381,7 @@ static void dump_scripts(struct data *d) "unset output\n"); fclose(out); } - out = fopen("Timings.html", "w"); + out = fopen("Timings.html", "we"); if (out == NULL) { pw_log_error("Can't open Timings.html: %m"); } else { @@ -409,7 +409,7 @@ static void dump_scripts(struct data *d) fclose(out); } - out = fopen("generate_timings.sh", "w"); + out = fopen("generate_timings.sh", "we"); if (out == NULL) { pw_log_error("Can't open generate_timings.sh: %m"); } else { @@ -624,7 +624,7 @@ int main(int argc, char *argv[]) data.filename = opt_output; - data.output = fopen(data.filename, "w"); + data.output = fopen(data.filename, "we"); if (data.output == NULL) { fprintf(stderr, "Can't open file %s: %m\n", data.filename); return -1; diff --git a/src/tools/pw-top.c b/src/tools/pw-top.c index 459dacb11fcf5e774d25b64485b8cfad7713cbaa..61a05c5958f0ace96a628efc696c9ff3e240171e 100644 --- a/src/tools/pw-top.c +++ b/src/tools/pw-top.c @@ -62,8 +62,10 @@ struct measurement { struct node { struct spa_list link; + struct data *data; uint32_t id; char name[MAX_NAME+1]; + enum pw_node_state state; struct measurement measurement; struct driver info; struct node *driver; @@ -73,6 +75,7 @@ struct node { char format[MAX_FORMAT+1]; struct pw_proxy *proxy; struct spa_hook proxy_listener; + unsigned int inactive:1; struct spa_hook object_listener; }; @@ -95,6 +98,7 @@ struct data { int n_nodes; struct spa_list node_list; uint32_t generation; + unsigned pending_refresh:1; WINDOW *win; }; @@ -159,12 +163,29 @@ static const struct pw_proxy_events proxy_events = { .destroy = on_node_destroy, }; +static void do_refresh(struct data *d); + +static void node_info(void *data, const struct pw_node_info *info) +{ + struct node *n = data; + + if (n->state != info->state) { + n->state = info->state; + do_refresh(n->data); + } +} + static void node_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) { struct node *n = data; + if (param == NULL) { + spa_zero(n->format); + goto done; + } + switch (id) { case SPA_PARAM_Format: { @@ -177,17 +198,18 @@ static void node_param(void *data, int seq, switch(media_subtype) { case SPA_MEDIA_SUBTYPE_raw: { - struct spa_audio_info_raw info; + struct spa_audio_info_raw info = { 0 }; if (spa_format_audio_raw_parse(param, &info) >= 0) { snprintf(n->format, sizeof(n->format), "%6.6s %d %d", - spa_debug_type_find_short_name(spa_type_audio_format, info.format), + spa_debug_type_find_short_name( + spa_type_audio_format, info.format), info.channels, info.rate); } break; } case SPA_MEDIA_SUBTYPE_dsd: { - struct spa_audio_info_dsd info; + struct spa_audio_info_dsd info = { 0 }; if (spa_format_audio_dsd_parse(param, &info) >= 0) { snprintf(n->format, sizeof(n->format), "DSD%d %d ", 8 * info.rate / 44100, info.channels); @@ -195,13 +217,25 @@ static void node_param(void *data, int seq, } break; } + case SPA_MEDIA_SUBTYPE_iec958: + { + struct spa_audio_info_iec958 info = { 0 }; + if (spa_format_audio_iec958_parse(param, &info) >= 0) { + snprintf(n->format, sizeof(n->format), "IEC958 %s %d", + spa_debug_type_find_short_name( + spa_type_audio_iec958_codec, info.codec), + info.rate); + + } + break; + } } break; case SPA_MEDIA_TYPE_video: switch(media_subtype) { case SPA_MEDIA_SUBTYPE_raw: { - struct spa_video_info_raw info; + struct spa_video_info_raw info = { 0 }; if (spa_format_video_raw_parse(param, &info) >= 0) { snprintf(n->format, sizeof(n->format), "%6.6s %dx%d", spa_debug_type_find_short_name(spa_type_video_format, info.format), @@ -209,6 +243,24 @@ static void node_param(void *data, int seq, } break; } + case SPA_MEDIA_SUBTYPE_mjpg: + { + struct spa_video_info_mjpg info = { 0 }; + if (spa_format_video_mjpg_parse(param, &info) >= 0) { + snprintf(n->format, sizeof(n->format), "MJPG %dx%d", + info.size.width, info.size.height); + } + break; + } + case SPA_MEDIA_SUBTYPE_h264: + { + struct spa_video_info_h264 info = { 0 }; + if (spa_format_video_h264_parse(param, &info) >= 0) { + snprintf(n->format, sizeof(n->format), "H264 %dx%d", + info.size.width, info.size.height); + } + break; + } } break; case SPA_MEDIA_TYPE_application: @@ -224,10 +276,13 @@ static void node_param(void *data, int seq, default: break; } +done: + do_refresh(n->data); } static const struct pw_node_events node_events = { PW_VERSION_NODE, + .info = node_info, .param = node_param, }; @@ -242,6 +297,7 @@ static struct node *add_node(struct data *d, uint32_t id, const char *name) strncpy(n->name, name, MAX_NAME); else snprintf(n->name, sizeof(n->name), "%u", id); + n->data = d; n->id = id; n->driver = n; n->proxy = pw_registry_bind(d->registry, id, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0); @@ -258,6 +314,7 @@ static struct node *add_node(struct data *d, uint32_t id, const char *name) } spa_list_append(&d->node_list, &n->link); d->n_nodes++; + d->pending_refresh = true; return n; } @@ -268,6 +325,7 @@ static void remove_node(struct data *d, struct node *n) pw_proxy_destroy(n->proxy); spa_list_remove(&n->link); d->n_nodes--; + d->pending_refresh = true; free(n); } @@ -332,7 +390,10 @@ static int process_follower_block(struct data *d, const struct spa_pod *pod, str return -ENOENT; n->measurement = m; - n->driver = point->driver; + if (n->driver != point->driver) { + n->driver = point->driver; + d->pending_refresh = true; + } n->generation = d->generation; if (m.status != 3) { n->errors++; @@ -342,9 +403,9 @@ static int process_follower_block(struct data *d, const struct spa_pod *pod, str return 0; } -static const char *print_time(char *buf, size_t len, uint64_t val) +static const char *print_time(char *buf, bool active, size_t len, uint64_t val) { - if (val == (uint64_t)-1) + if (val == (uint64_t)-1 || !active) snprintf(buf, len, " --- "); else if (val == (uint64_t)-2) snprintf(buf, len, " +++ "); @@ -357,9 +418,9 @@ static const char *print_time(char *buf, size_t len, uint64_t val) return buf; } -static const char *print_perc(char *buf, size_t len, uint64_t val, float quantum) +static const char *print_perc(char *buf, bool active, size_t len, uint64_t val, float quantum) { - if (val == (uint64_t)-1) { + if (val == (uint64_t)-1 || !active) { snprintf(buf, len, " --- "); } else if (val == (uint64_t)-2) { snprintf(buf, len, " +++ "); @@ -370,6 +431,23 @@ static const char *print_perc(char *buf, size_t len, uint64_t val, float quantum return buf; } +static const char *state_as_string(enum pw_node_state state) +{ + switch (state) { + case PW_NODE_STATE_ERROR: + return "E"; + case PW_NODE_STATE_CREATING: + return "C"; + case PW_NODE_STATE_SUSPENDED: + return "S"; + case PW_NODE_STATE_IDLE: + return "I"; + case PW_NODE_STATE_RUNNING: + return "R"; + } + return "!"; +} + static void print_node(struct data *d, struct driver *i, struct node *n, int y) { char buf1[64]; @@ -379,8 +457,13 @@ static void print_node(struct data *d, struct driver *i, struct node *n, int y) uint64_t waiting, busy; float quantum; struct spa_fraction frac; + bool active; + + active = n->state == PW_NODE_STATE_RUNNING || n->state == PW_NODE_STATE_IDLE; - if (n->driver == n) + if (!active) + frac = SPA_FRACTION(0, 0); + else if (n->driver == n) frac = SPA_FRACTION((uint32_t)(i->clock.duration * i->clock.rate.num), i->clock.rate.denom); else frac = SPA_FRACTION(n->measurement.latency.num, n->measurement.latency.denom); @@ -405,19 +488,28 @@ static void print_node(struct data *d, struct driver *i, struct node *n, int y) busy = -1; mvwprintw(d->win, y, 0, "%s %4.1u %6.1u %6.1u %s %s %s %s %3.1u %16.16s %s%s", - n->measurement.status != 3 ? "!" : " ", + state_as_string(n->state), n->id, frac.num, frac.denom, - print_time(buf1, 64, waiting), - print_time(buf2, 64, busy), - print_perc(buf3, 64, waiting, quantum), - print_perc(buf4, 64, busy, quantum), + print_time(buf1, active, 64, waiting), + print_time(buf2, active, 64, busy), + print_perc(buf3, active, 64, waiting, quantum), + print_perc(buf4, active, 64, busy, quantum), i->xrun_count + n->errors, - n->measurement.status != 3 ? "" : n->format, + active ? n->format : "", n->driver == n ? "" : " + ", n->name); } +static void clear_node(struct node *n) +{ + n->driver = n; + spa_zero(n->measurement); + spa_zero(n->info); + n->errors = 0; + n->last_error_status = 0; +} + static void do_refresh(struct data *d) { struct node *n, *t, *f; @@ -438,14 +530,8 @@ static void do_refresh(struct data *d) break; spa_list_for_each(f, &d->node_list, link) { - if (d->generation > f->generation + 2) { - f->driver = f; - spa_zero(f->measurement); - spa_zero(f->info); - spa_zero(f->format); - f->errors = 0; - f->last_error_status = 0; - } + if (d->generation > f->generation + 22) + clear_node(f); if (f->driver != n || f == n) continue; @@ -462,6 +548,7 @@ static void do_refresh(struct data *d) wclrtobot(d->win); wrefresh(d->win); + d->pending_refresh = false; } static void do_timeout(void *data, uint64_t expirations) @@ -507,6 +594,8 @@ static void profiler_profile(void *data, const struct spa_pod *pod) if (res < 0) continue; } + if (d->pending_refresh) + do_refresh(d); } static const struct pw_profiler_events profiler_events = { @@ -545,7 +634,8 @@ static void registry_event_global(void *data, uint32_t id, d->profiler = proxy; pw_proxy_add_object_listener(proxy, &d->profiler_listener, &profiler_events, d); } - + if (d->pending_refresh) + do_refresh(d); return; error_proxy: @@ -559,6 +649,8 @@ static void registry_event_global_remove(void *data, uint32_t id) struct node *n; if ((n = find_node(d, id)) != NULL) remove_node(d, n); + if (d->pending_refresh) + do_refresh(d); } static const struct pw_registry_events registry_events = { @@ -586,8 +678,9 @@ static void on_core_done(void *_data, uint32_t id, int seq) if (d->profiler == NULL) { pw_log_error("no Profiler Interface found, please load one in the server"); pw_main_loop_quit(d->loop); - } else + } else { do_refresh(d); + } } } @@ -612,7 +705,7 @@ static void show_help(const char *name, bool error) name); } -static void terminal_start() +static void terminal_start(void) { initscr(); cbreak(); @@ -620,7 +713,7 @@ static void terminal_start() refresh(); } -static void terminal_stop() +static void terminal_stop(void) { endwin(); } diff --git a/test/pwtest.c b/test/pwtest.c index 18be5c6ad5a38679eb2965fbce6d045443b1395e..968805b5e6ac4682b860f95143808f0f33f1bdcd 100644 --- a/test/pwtest.c +++ b/test/pwtest.c @@ -358,16 +358,13 @@ pwtest_spa_plugin_new(void) void pwtest_spa_plugin_destroy(struct pwtest_spa_plugin *plugin) { - void **dll; - struct spa_handle **hnd; - - SPA_FOR_EACH_ELEMENT(plugin->handles, hnd) { + SPA_FOR_EACH_ELEMENT_VAR(plugin->handles, hnd) { if (*hnd) { spa_handle_clear(*hnd); free(*hnd); } } - SPA_FOR_EACH_ELEMENT(plugin->dlls, dll) { + SPA_FOR_EACH_ELEMENT_VAR(plugin->dlls, dll) { if (*dll) dlclose(*dll); } @@ -835,7 +832,7 @@ static int init_pipes(int read_fds[_FD_LAST], int write_fds[_FD_LAST]) #ifdef __linux__ { FILE *f; - f = fopen("/proc/sys/fs/pipe-max-size", "r"); + f = fopen("/proc/sys/fs/pipe-max-size", "re"); if (f) { if (fscanf(f, "%d", &r) == 1) pipe_max_size = SPA_MIN(r, pipe_max_size); @@ -1244,7 +1241,7 @@ static char* make_xdg_runtime_dir(void) /* Marker file to avoid removing a random directory during cleanup */ r = spa_scnprintf(path, sizeof(path), "%s/pwtest.dir", dir); spa_assert_se((size_t)r == strlen(dir) + 11); - fp = fopen(path, "w"); + fp = fopen(path, "we"); spa_assert_se(fp); fprintf(fp, "pwtest\n"); fclose(fp); diff --git a/test/test-config.c b/test/test-config.c index d1397bafbc66d9c5a1a8c676a2f854fed2adfa31..9ebbc26d17225470446ffcfc32074f81e24bfce6 100644 --- a/test/test-config.c +++ b/test/test-config.c @@ -35,7 +35,7 @@ PWTEST(config_load_abspath) char *basename; pwtest_mkstemp(path); - fp = fopen(path, "w"); + fp = fopen(path, "we"); fputs("data = x", fp); fclose(fp); diff --git a/test/test-functional.c b/test/test-functional.c index 620ce37322862053dff7f5661a94dc6a6f9ad2f6..ae837b41ccb400cf4209740ebca5ac56b15fce44 100644 --- a/test/test-functional.c +++ b/test/test-functional.c @@ -29,10 +29,8 @@ PWTEST(openal_info_test) { - int status; - #ifdef OPENAL_INFO_PATH - status = pwtest_spawn(OPENAL_INFO_PATH, (char *[]){ "openal-info", NULL }); + int status = pwtest_spawn(OPENAL_INFO_PATH, (char *[]){ "openal-info", NULL }); pwtest_int_eq(WEXITSTATUS(status), 0); return PWTEST_PASS; #else @@ -42,10 +40,8 @@ PWTEST(openal_info_test) PWTEST(pactl_test) { - int status; - #ifdef PACTL_PATH - status = pwtest_spawn(PACTL_PATH, (char *[]){ "pactl", "info", NULL }); + int status = pwtest_spawn(PACTL_PATH, (char *[]){ "pactl", "info", NULL }); pwtest_int_eq(WEXITSTATUS(status), 0); return PWTEST_PASS; #else diff --git a/test/test-logger.c b/test/test-logger.c index a1b0ff01dd466c0e35be379262c982016a14927d..5aca7d1998448472ebc274bfa473517069601136 100644 --- a/test/test-logger.c +++ b/test/test-logger.c @@ -63,7 +63,7 @@ PWTEST(logger_truncate_long_lines) /* Print a line expected to be truncated */ spa_log_error(iface, "MARK: %1100s", "foo"); - fp = fopen(fname, "r"); + fp = fopen(fname, "re"); while (fgets(buffer, sizeof(buffer), fp) != NULL) { if (strstr(buffer, "MARK:")) { const char *suffix = ".. (truncated)\n"; @@ -110,7 +110,7 @@ PWTEST(logger_no_ansi) * tty so expect none despite colors being enabled */ spa_log_error(iface, "MARK\n"); - fp = fopen(fname, "r"); + fp = fopen(fname, "re"); while (fgets(buffer, sizeof(buffer), fp) != NULL) { if (strstr(buffer, "MARK")) { mark_line_found = true; @@ -157,7 +157,7 @@ test_log_levels(enum spa_log_level level) if (level < SPA_LOG_LEVEL_TRACE) pw_log(level + 1, "ABOVE"); - fp = fopen(fname, "r"); + fp = fopen(fname, "re"); while (fgets(buffer, sizeof(buffer), fp) != NULL) { if (strstr(buffer, "CURRENT")) current_level_found = true; @@ -427,7 +427,7 @@ PWTEST(logger_topics) spa_logt_info(iface, &topic, "MARK\n"); - fp = fopen(fname, "r"); + fp = fopen(fname, "re"); while (fgets(buffer, sizeof(buffer), fp) != NULL) { if (strstr(buffer, "MARK")) { mark_line_found = true; @@ -602,7 +602,7 @@ PWTEST(logger_journal_chain) /* Now check that the line is in the chained file logger too */ spa_memzero(buffer, sizeof(buffer)); mark_line_found = false; - fp = fopen(fname, "r"); + fp = fopen(fname, "re"); while (fgets(buffer, sizeof(buffer), fp) != NULL) { if (strstr(buffer, token)) { mark_line_found = true; diff --git a/test/test-properties.c b/test/test-properties.c index 0a4d38754ba07204238ccfcdd96b6a58afc8d4cd..89e66bb370a7fe70e151948fabba6dc07f82b5bd 100644 --- a/test/test-properties.c +++ b/test/test-properties.c @@ -484,7 +484,7 @@ PWTEST(properties_serialize_dict_stack_overflow) dict = SPA_DICT_INIT(items, 2); pwtest_mkstemp(tmpfile); - fp = fopen(tmpfile, "w"); + fp = fopen(tmpfile, "we"); pwtest_ptr_notnull(fp); r = pw_properties_serialize_dict(fp, &dict, 0); pwtest_int_eq(r, 1); diff --git a/test/test-spa-pod.c b/test/test-spa-pod.c index 989d3dc5dd9dab3606a9aef7632a6b2a2bfbf705..24c3303cb07b78ed623e5b3a23192b2daf5df5c1 100644 --- a/test/test-spa-pod.c +++ b/test/test-spa-pod.c @@ -1639,6 +1639,54 @@ PWTEST(pod_overflow) return PWTEST_PASS; } +static int handle_overflow(void *data, uint32_t size) +{ + uint32_t *d = data; + (*d)++; + return -ENOSPC; +} + +static struct spa_pod_builder_callbacks overflow_cb = { + SPA_VERSION_POD_BUILDER_CALLBACKS, + .overflow = handle_overflow +}; + +PWTEST(pod_overflow2) +{ + uint8_t buffer[1024]; + struct spa_pod_builder b = { 0 }; + struct spa_pod_builder_state state; + struct spa_pod_frame f[2]; + uint32_t idx, overflow_count = 0; + struct spa_pod *pod; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_pod_builder_set_callbacks(&b, &overflow_cb, &overflow_count); + + spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo); + spa_pod_builder_add(&b, + SPA_PROP_INFO_id, SPA_POD_Id(32567359), + SPA_PROP_INFO_type, SPA_POD_CHOICE_ENUM_Int(1, 0), + SPA_PROP_INFO_description, SPA_POD_String("DV Timings"), + 0); + + spa_pod_builder_get_state(&b, &state), + + spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0); + spa_pod_builder_push_struct(&b, &f[1]); + + for (idx = 0; idx < 512; idx++) { + spa_pod_builder_int(&b, idx); + spa_pod_builder_string(&b, "foo"); + } + spa_assert_se(b.state.offset > sizeof(buffer)); + pod = spa_pod_builder_pop(&b, &f[1]); + spa_assert_se(pod == NULL); + spa_assert_se(overflow_count == 1); + + return PWTEST_PASS; +} + PWTEST_SUITE(spa_pod) { pwtest_add(pod_abi_sizes, PWTEST_NOARG); @@ -1652,6 +1700,7 @@ PWTEST_SUITE(spa_pod) pwtest_add(pod_parser2, PWTEST_NOARG); pwtest_add(pod_static, PWTEST_NOARG); pwtest_add(pod_overflow, PWTEST_NOARG); + pwtest_add(pod_overflow2, PWTEST_NOARG); return PWTEST_PASS; } diff --git a/test/test-spa-utils.c b/test/test-spa-utils.c index 2f198f90a7c2b898c1df8ad95ff5a4c2f0c37cec..4184f435b793c8039aef981b7e643b8e5c376a0a 100644 --- a/test/test-spa-utils.c +++ b/test/test-spa-utils.c @@ -184,9 +184,8 @@ PWTEST(utils_macros) #define check_traversal(array_) \ { \ - __typeof__(array_[0]) *it; \ int count = 0; \ - SPA_FOR_EACH_ELEMENT(array_, it) \ + SPA_FOR_EACH_ELEMENT_VAR(array_, it) \ *it = count++; \ for (size_t i = 0; i < SPA_N_ELEMENTS(array_); i++) \ pwtest_int_eq(array_[i], i); \ diff --git a/test/test-utils.c b/test/test-utils.c index 3ceb2d25afacd806246ebbbf40006adfb28f84fd..9b7c520e244679b43945c8297722f68dd922f011 100644 --- a/test/test-utils.c +++ b/test/test-utils.c @@ -172,9 +172,7 @@ static void test__pw_split_walk(void) }, }; - const struct test_case *tc; - - SPA_FOR_EACH_ELEMENT(test_cases, tc) { + SPA_FOR_EACH_ELEMENT_VAR(test_cases, tc) { const char *str = tc->input, *s; const char *state = NULL; size_t j = 0, len;