Compare commits

..

18 commits

Author SHA1 Message Date
Siva Mahadevan
e6898f2cb6 Merge branch 'autostart-desktop' into 'master'
meson.build: add back pipewire.desktop autostart entry

See merge request pipewire/pipewire!2574
2025-10-28 08:23:46 +00:00
Wim Taymans
a813830024 po: update Turkish translation 2025-10-28 08:48:18 +01:00
Wim Taymans
a837dcd40b audioadapter: renegotiate when driver changes
The renegotiated format can depend on the clock rate of the new
driver.

See #4933
2025-10-28 08:32:03 +01:00
Rui Matos
752de866ae spa: node-driver: Expose the clock id as param properties 2025-10-28 07:18:59 +00:00
Rui Matos
ec11859a48 spa: Add predefined properties for clock identifiers 2025-10-28 07:18:59 +00:00
Carlos Rafael Giani
1096d63468 module-rtp-source: implement IGMP recovery for multicast subscription loss
Add IGMP recovery mechanism that monitors RTP packet reception and
triggers multicast group refresh when no packets are received if
a deadline is reached. The deadline is configurable via a new stream
property "igmp.deadline.sec" (in seconds), with the default value
being 30 seconds (and a minimum of 5 seconds).

A timer checks regularly if the deadline was reached. That timer's
interval is set by the igmp.check.interval.sec property (in seconds),
with the default value being 5 seconds (and a minimum of 1 second).

When the deadline is reached, the mechanism performs IGMP leave/rejoin
operations to refresh multicast group membership. This ensures RTP
data continues to be received when network conditions cause IGMP
membership to expire or become stale due to router timeouts or
network issues.
2025-10-27 22:40:22 +01:00
Carlos Rafael Giani
955c9ae837 module-rtp: Get the current stream time in a reusable manner
That way, redundant pw_stream_get_nsec() and clock_gettime()
calls can be avoided.
2025-10-27 22:40:22 +01:00
Carlos Rafael Giani
3e0f4daf60 module-rtp-sap: implement IGMP recovery for multicast subscription loss
Add IGMP recovery mechanism that monitors SAP packet reception and
triggers multicast group refresh when no packets are received if
a deadline is reached. The deadline is set to half of the cleanup
interval, with a minimum of 1 second.

When the deadline is reached, the mechanism performs IGMP leave/rejoin
operations to refresh multicast group membership. This ensures SAP
announcements continue to be received when network conditions cause
IGMP membership to expire or become stale due to router timeouts or
network issues.
2025-10-27 22:40:22 +01:00
Carlos Rafael Giani
5d21e12658 module-rtp-source: Use make_socket() error value instead of errno
make_socket() already returns the negative errno.
2025-10-27 22:14:09 +01:00
Carlos Rafael Giani
f1ffd5e5e8 module-rtp-source: Read cleanup.sec property from stream properties
This allows for setting the cleanup.sec value in the create-stream
block in the module-rtp-sap configuration.
2025-10-27 22:14:09 +01:00
Carlos Rafael Giani
80e7302a05 module-rtp-sap: Add retry code for when start_sap() fails due to ENODEV 2025-10-27 22:14:09 +01:00
Carlos Rafael Giani
b57bd00be0 module-rtp-sap: Improve names for clearer code 2025-10-27 22:14:09 +01:00
Rui Matos
c1e737bbe4 module-rtp: Attempt to reconnect the ptp management socket
This should gracefully recover the cases where the other end of the
socket isn't ready yet when we start or terminates and gets restarted.
2025-10-27 18:08:00 +01:00
Jonas Holmberg
76a31a47c2 module-echo-cancel: Avoid discontinuity
Keep the samples in the ringbuffer that are needed the next cycle to
avoid discontinuity when the aec blocksize is not equal to or divisible
by quantum.
2025-10-27 14:39:29 +01:00
Wim Taymans
23c449af5d test: add test for an array with odd number of items
We have to use the relax version to get the expected container type
correct.
2025-10-27 14:20:25 +01:00
Wim Taymans
94d0d8bc09 spa: add spa_json_init_relax
spa_json_init assumes that we start in an object and always requires a
key/value pair. If the last part is a key, it returns and error and does
not want to return the key value.

This causes problems when parsing AUX0,AUX1,AUX2 or any relaxed array
withand odd number of elements.

Make a new spa_json_init_relax that takes the type of the container
we're assuming we're in and set the state of the parser to array when we
are parsing a relaxed array.

Fixes #4944
2025-10-27 13:32:03 +01:00
Wim Taymans
0276bb5b06 modules: ringbuffer avail is signed 2025-10-27 11:43:04 +01:00
Jonas Holmberg
614186a590 module-echo-cancel: Sync capture and sink buffers
Call process() when capture and sink ringbuffers contain data from the
same graph cycle and only process the latest block from them to avoid
adding latency that can accumulate if one of the streams gets more than
one buffer before the other gets its first buffer when starting up.
2025-10-27 08:43:08 +01:00
18 changed files with 1059 additions and 291 deletions

341
po/tr.po
View file

@ -1,46 +1,51 @@
# Turkish translation for PipeWire.
# Copyright (C) 2014-2024 PipeWire's COPYRIGHT HOLDER
# Copyright (C) 2014-2025 PipeWire's COPYRIGHT HOLDER
# This file is distributed under the same license as the PipeWire package.
#
# Necdet Yücel <necdetyucel@gmail.com>, 2014.
# Kaan Özdinçer <kaanozdincer@gmail.com>, 2014.
# Muhammet Kara <muhammetk@gmail.com>, 2015, 2016, 2017.
# Oğuz Ersen <oguz@ersen.moe>, 2021-2022.
# Sabri Ünal <libreajans@gmail.com>, 2024.
# Sabri Ünal <yakushabb@gmail.com>, 2024, 2025.
#
msgid ""
msgstr ""
"Project-Id-Version: PipeWire master\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-02-25 03:43+0300\n"
"PO-Revision-Date: 2024-02-25 03:49+0300\n"
"Last-Translator: Sabri Ünal <libreajans@gmail.com>\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/"
"issues\n"
"POT-Creation-Date: 2025-10-24 15:37+0000\n"
"PO-Revision-Date: 2025-10-24 20:15+0300\n"
"Last-Translator: Sabri Ünal <yakushabb@gmail.com>\n"
"Language-Team: Türkçe <takim@gnome.org.tr>\n"
"Language: tr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 3.4.2\n"
"X-Generator: Poedit 3.8\n"
#: src/daemon/pipewire.c:26
#: src/daemon/pipewire.c:29
#, c-format
msgid ""
"%s [options]\n"
" -h, --help Show this help\n"
" -v, --verbose Increase verbosity by one level\n"
" --version Show version\n"
" -c, --config Load config (Default %s)\n"
" -P --properties Set context properties\n"
msgstr ""
"%s [seçenekler]\n"
" -h, --help Bu yardımı göster\n"
" -v, --verbose Ayrıntı düzeyini bir düzey artır\n"
" --version Sürümü göster\n"
" -c, --config Yapılandırmayı yükle (Öntanımlı %s)\n"
" -P --properties Bağlam özelliklerini ayarla\n"
#: src/daemon/pipewire.desktop.in:4
#: src/daemon/pipewire.desktop.in:3
msgid "PipeWire Media System"
msgstr "PipeWire Ortam Sistemi"
#: src/daemon/pipewire.desktop.in:5
#: src/daemon/pipewire.desktop.in:4
msgid "Start the PipeWire Media System"
msgstr "PipeWire Ortam Sistemini Başlat"
@ -54,26 +59,26 @@ msgstr "%s%s%s tüneli"
msgid "Dummy Output"
msgstr "Temsili Çıkış"
#: src/modules/module-pulse-tunnel.c:774
#: src/modules/module-pulse-tunnel.c:760
#, c-format
msgid "Tunnel for %s@%s"
msgstr "%s@%s için tünel"
#: src/modules/module-zeroconf-discover.c:315
#: src/modules/module-zeroconf-discover.c:320
msgid "Unknown device"
msgstr "Bilinmeyen aygıt"
#: src/modules/module-zeroconf-discover.c:327
#: src/modules/module-zeroconf-discover.c:332
#, c-format
msgid "%s on %s@%s"
msgstr "%s, %s@%s"
#: src/modules/module-zeroconf-discover.c:331
#: src/modules/module-zeroconf-discover.c:336
#, c-format
msgid "%s on %s"
msgstr "%s, %s"
#: src/tools/pw-cat.c:991
#: src/tools/pw-cat.c:1096
#, c-format
msgid ""
"%s [options] [<file>|-]\n"
@ -88,7 +93,7 @@ msgstr ""
" -v, --verbose Ayrıntılı işlemleri etkinleştir\n"
"\n"
#: src/tools/pw-cat.c:998
#: src/tools/pw-cat.c:1103
#, c-format
msgid ""
" -R, --remote Remote daemon name\n"
@ -122,7 +127,7 @@ msgstr ""
" -P --properties Düğüm özelliklerini ayarla\n"
"\n"
#: src/tools/pw-cat.c:1016
#: src/tools/pw-cat.c:1121
#, c-format
msgid ""
" --rate Sample rate (req. for rec) (default "
@ -139,6 +144,10 @@ msgid ""
" --volume Stream volume 0-1.0 (default %.3f)\n"
" -q --quality Resampler quality (0 - 15) (default "
"%d)\n"
" -a, --raw RAW mode\n"
" -M, --force-midi Force midi format, one of \"midi\" "
"or \"ump\", (default ump)\n"
" -n, --sample-count COUNT Stop after COUNT samples\n"
"\n"
msgstr ""
" --rate Örnekleme oranı (kayıt için gerekli) "
@ -156,15 +165,21 @@ msgstr ""
"%.3f)\n"
" -q --quality Yeniden örnekleyici kalitesi (0 - "
"15) (öntanımlı %d)\n"
" -a, --raw HAM kipi\n"
" -M, --force-midi Midi biçimini zorla, ikisinden "
"birisi \"midi\" ya da\"ump\", (öntanımlı ump)\n"
" -n, --sample-count COUNT COUNT örnekleme sonrası dur\n"
"\n"
#: src/tools/pw-cat.c:1033
#: src/tools/pw-cat.c:1141
msgid ""
" -p, --playback Playback mode\n"
" -r, --record Recording mode\n"
" -m, --midi Midi mode\n"
" -d, --dsd DSD mode\n"
" -o, --encoded Encoded mode\n"
" -s, --sysex SysEx mode\n"
" -c, --midi-clip MIDI clip mode\n"
"\n"
msgstr ""
" -p, --playback Çalma kipi\n"
@ -172,9 +187,11 @@ msgstr ""
" -m, --midi Midi kipi\n"
" -d, --dsd DSD kipi\n"
" -o, --encoded Kodlanmış kip\n"
" -s, --sysex SysEx kipi\n"
" -c, --midi-clip MIDI klip kipi\n"
"\n"
#: src/tools/pw-cli.c:2252
#: src/tools/pw-cli.c:2386
#, c-format
msgid ""
"%s [options] [command]\n"
@ -193,195 +210,203 @@ msgstr ""
" -r, --remote Uzak arka plan programı adı\n"
" -m, --monitor Etkinliği izle\n"
#: spa/plugins/alsa/acp/acp.c:327
#: spa/plugins/alsa/acp/acp.c:361
msgid "Pro Audio"
msgstr "Profesyonel Ses"
#: spa/plugins/alsa/acp/acp.c:488 spa/plugins/alsa/acp/alsa-mixer.c:4633
#: spa/plugins/bluez5/bluez5-device.c:1701
#: spa/plugins/alsa/acp/acp.c:537 spa/plugins/alsa/acp/alsa-mixer.c:4699
#: spa/plugins/bluez5/bluez5-device.c:1976
msgid "Off"
msgstr "Kapalı"
#: spa/plugins/alsa/acp/alsa-mixer.c:2652
#: spa/plugins/alsa/acp/acp.c:620
#, c-format
msgid "%s [ALSA UCM error]"
msgstr "%s [ALSA UCM hatası]"
#: spa/plugins/alsa/acp/alsa-mixer.c:2721
msgid "Input"
msgstr "Giriş"
#: spa/plugins/alsa/acp/alsa-mixer.c:2653
#: spa/plugins/alsa/acp/alsa-mixer.c:2722
msgid "Docking Station Input"
msgstr "Yerleştirme İstasyonu Girişi"
#: spa/plugins/alsa/acp/alsa-mixer.c:2654
#: spa/plugins/alsa/acp/alsa-mixer.c:2723
msgid "Docking Station Microphone"
msgstr "Yerleştirme İstasyonu Mikrofonu"
#: spa/plugins/alsa/acp/alsa-mixer.c:2655
#: spa/plugins/alsa/acp/alsa-mixer.c:2724
msgid "Docking Station Line In"
msgstr "Yerleştirme İstasyonu Hat Girişi"
#: spa/plugins/alsa/acp/alsa-mixer.c:2656
#: spa/plugins/alsa/acp/alsa-mixer.c:2747
#: spa/plugins/alsa/acp/alsa-mixer.c:2725
#: spa/plugins/alsa/acp/alsa-mixer.c:2816
msgid "Line In"
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:1989
#: spa/plugins/alsa/acp/alsa-mixer.c:2726
#: spa/plugins/alsa/acp/alsa-mixer.c:2810
#: spa/plugins/bluez5/bluez5-device.c:2374
msgid "Microphone"
msgstr "Mikrofon"
#: spa/plugins/alsa/acp/alsa-mixer.c:2658
#: spa/plugins/alsa/acp/alsa-mixer.c:2742
#: spa/plugins/alsa/acp/alsa-mixer.c:2727
#: spa/plugins/alsa/acp/alsa-mixer.c:2811
msgid "Front Microphone"
msgstr "Ön Mikrofon"
#: spa/plugins/alsa/acp/alsa-mixer.c:2659
#: spa/plugins/alsa/acp/alsa-mixer.c:2743
#: spa/plugins/alsa/acp/alsa-mixer.c:2728
#: spa/plugins/alsa/acp/alsa-mixer.c:2812
msgid "Rear Microphone"
msgstr "Arka Mikrofon"
#: spa/plugins/alsa/acp/alsa-mixer.c:2660
#: spa/plugins/alsa/acp/alsa-mixer.c:2729
msgid "External Microphone"
msgstr "Harici Mikrofon"
#: spa/plugins/alsa/acp/alsa-mixer.c:2661
#: spa/plugins/alsa/acp/alsa-mixer.c:2745
#: spa/plugins/alsa/acp/alsa-mixer.c:2730
#: spa/plugins/alsa/acp/alsa-mixer.c:2814
msgid "Internal Microphone"
msgstr "Dahili Mikrofon"
#: spa/plugins/alsa/acp/alsa-mixer.c:2662
#: spa/plugins/alsa/acp/alsa-mixer.c:2748
#: spa/plugins/alsa/acp/alsa-mixer.c:2731
#: spa/plugins/alsa/acp/alsa-mixer.c:2817
msgid "Radio"
msgstr "Radyo"
#: spa/plugins/alsa/acp/alsa-mixer.c:2663
#: spa/plugins/alsa/acp/alsa-mixer.c:2749
#: spa/plugins/alsa/acp/alsa-mixer.c:2732
#: spa/plugins/alsa/acp/alsa-mixer.c:2818
msgid "Video"
msgstr "Video"
#: spa/plugins/alsa/acp/alsa-mixer.c:2664
#: spa/plugins/alsa/acp/alsa-mixer.c:2733
msgid "Automatic Gain Control"
msgstr "Otomatik Kazanç Denetimi"
#: spa/plugins/alsa/acp/alsa-mixer.c:2665
#: spa/plugins/alsa/acp/alsa-mixer.c:2734
msgid "No Automatic Gain Control"
msgstr "Otomatik Kazanç Denetimi Yok"
#: spa/plugins/alsa/acp/alsa-mixer.c:2666
#: spa/plugins/alsa/acp/alsa-mixer.c:2735
msgid "Boost"
msgstr "Artır"
#: spa/plugins/alsa/acp/alsa-mixer.c:2667
#: spa/plugins/alsa/acp/alsa-mixer.c:2736
msgid "No Boost"
msgstr "Artırma Yok"
#: spa/plugins/alsa/acp/alsa-mixer.c:2668
#: spa/plugins/alsa/acp/alsa-mixer.c:2737
msgid "Amplifier"
msgstr "Yükseltici"
#: spa/plugins/alsa/acp/alsa-mixer.c:2669
#: spa/plugins/alsa/acp/alsa-mixer.c:2738
msgid "No Amplifier"
msgstr "Yükseltici Yok"
#: spa/plugins/alsa/acp/alsa-mixer.c:2670
#: spa/plugins/alsa/acp/alsa-mixer.c:2739
msgid "Bass Boost"
msgstr "Bas Artır"
#: spa/plugins/alsa/acp/alsa-mixer.c:2671
#: spa/plugins/alsa/acp/alsa-mixer.c:2740
msgid "No Bass Boost"
msgstr "Bas Artırma Yok"
#: spa/plugins/alsa/acp/alsa-mixer.c:2672
#: spa/plugins/bluez5/bluez5-device.c:1995
#: spa/plugins/alsa/acp/alsa-mixer.c:2741
#: spa/plugins/bluez5/bluez5-device.c:2380
msgid "Speaker"
msgstr "Hoparlör"
#: spa/plugins/alsa/acp/alsa-mixer.c:2673
#: spa/plugins/alsa/acp/alsa-mixer.c:2751
#. Don't call it "headset", the HF one has the mic
#: spa/plugins/alsa/acp/alsa-mixer.c:2742
#: spa/plugins/alsa/acp/alsa-mixer.c:2820
#: spa/plugins/bluez5/bluez5-device.c:2386
#: spa/plugins/bluez5/bluez5-device.c:2453
msgid "Headphones"
msgstr "Kulaklık"
#: spa/plugins/alsa/acp/alsa-mixer.c:2740
#: spa/plugins/alsa/acp/alsa-mixer.c:2809
msgid "Analog Input"
msgstr "Analog Giriş"
#: spa/plugins/alsa/acp/alsa-mixer.c:2744
#: spa/plugins/alsa/acp/alsa-mixer.c:2813
msgid "Dock Microphone"
msgstr "Yapışık Mikrofon"
#: spa/plugins/alsa/acp/alsa-mixer.c:2746
#: spa/plugins/alsa/acp/alsa-mixer.c:2815
msgid "Headset Microphone"
msgstr "Mikrofonlu Kulaklık"
#: spa/plugins/alsa/acp/alsa-mixer.c:2750
#: spa/plugins/alsa/acp/alsa-mixer.c:2819
msgid "Analog Output"
msgstr "Analog Çıkış"
#: spa/plugins/alsa/acp/alsa-mixer.c:2752
#: spa/plugins/alsa/acp/alsa-mixer.c:2821
msgid "Headphones 2"
msgstr "Kulaklık 2"
#: spa/plugins/alsa/acp/alsa-mixer.c:2753
#: spa/plugins/alsa/acp/alsa-mixer.c:2822
msgid "Headphones Mono Output"
msgstr "Kulaklık Tek Kanallı Çıkış"
#: spa/plugins/alsa/acp/alsa-mixer.c:2754
#: spa/plugins/alsa/acp/alsa-mixer.c:2823
msgid "Line Out"
msgstr "Hat Çıkışı"
#: spa/plugins/alsa/acp/alsa-mixer.c:2755
#: spa/plugins/alsa/acp/alsa-mixer.c:2824
msgid "Analog Mono Output"
msgstr "Analog Tek Kanallı Çıkış"
#: spa/plugins/alsa/acp/alsa-mixer.c:2756
#: spa/plugins/alsa/acp/alsa-mixer.c:2825
msgid "Speakers"
msgstr "Hoparlörler"
#: spa/plugins/alsa/acp/alsa-mixer.c:2757
#: spa/plugins/alsa/acp/alsa-mixer.c:2826
msgid "HDMI / DisplayPort"
msgstr "HDMI / DisplayPort"
#: spa/plugins/alsa/acp/alsa-mixer.c:2758
#: spa/plugins/alsa/acp/alsa-mixer.c:2827
msgid "Digital Output (S/PDIF)"
msgstr "Sayısal Çıkış (S/PDIF)"
#: spa/plugins/alsa/acp/alsa-mixer.c:2759
#: spa/plugins/alsa/acp/alsa-mixer.c:2828
msgid "Digital Input (S/PDIF)"
msgstr "Sayısal Giriş (S/PDIF)"
#: spa/plugins/alsa/acp/alsa-mixer.c:2760
#: spa/plugins/alsa/acp/alsa-mixer.c:2829
msgid "Multichannel Input"
msgstr "Çok Kanallı Giriş"
#: spa/plugins/alsa/acp/alsa-mixer.c:2761
#: spa/plugins/alsa/acp/alsa-mixer.c:2830
msgid "Multichannel Output"
msgstr "Çok Kanallı Çıkış"
#: spa/plugins/alsa/acp/alsa-mixer.c:2762
#: spa/plugins/alsa/acp/alsa-mixer.c:2831
msgid "Game Output"
msgstr "Oyun Çıkışı"
#: spa/plugins/alsa/acp/alsa-mixer.c:2763
#: spa/plugins/alsa/acp/alsa-mixer.c:2764
#: spa/plugins/alsa/acp/alsa-mixer.c:2832
#: spa/plugins/alsa/acp/alsa-mixer.c:2833
msgid "Chat Output"
msgstr "Sohbet Çıkışı"
#: spa/plugins/alsa/acp/alsa-mixer.c:2765
#: spa/plugins/alsa/acp/alsa-mixer.c:2834
msgid "Chat Input"
msgstr "Sohbet Girişi"
#: spa/plugins/alsa/acp/alsa-mixer.c:2766
#: spa/plugins/alsa/acp/alsa-mixer.c:2835
msgid "Virtual Surround 7.1"
msgstr "Sanal Çevresel Ses 7.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4456
#: spa/plugins/alsa/acp/alsa-mixer.c:4522
msgid "Analog Mono"
msgstr "Analog Tek Kanallı"
#: spa/plugins/alsa/acp/alsa-mixer.c:4457
#: spa/plugins/alsa/acp/alsa-mixer.c:4523
msgid "Analog Mono (Left)"
msgstr "Analog Tek Kanallı (Sol)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4458
#: spa/plugins/alsa/acp/alsa-mixer.c:4524
msgid "Analog Mono (Right)"
msgstr "Analog Tek Kanallı (Sağ)"
@ -390,147 +415,147 @@ msgstr "Analog Tek Kanallı (Sağ)"
#. * 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:4459
#: spa/plugins/alsa/acp/alsa-mixer.c:4467
#: spa/plugins/alsa/acp/alsa-mixer.c:4468
#: spa/plugins/alsa/acp/alsa-mixer.c:4525
#: spa/plugins/alsa/acp/alsa-mixer.c:4533
#: spa/plugins/alsa/acp/alsa-mixer.c:4534
msgid "Analog Stereo"
msgstr "Analog Stereo"
#: spa/plugins/alsa/acp/alsa-mixer.c:4460
#: spa/plugins/alsa/acp/alsa-mixer.c:4526
msgid "Mono"
msgstr "Tek Kanallı"
#: spa/plugins/alsa/acp/alsa-mixer.c:4461
#: spa/plugins/alsa/acp/alsa-mixer.c:4527
msgid "Stereo"
msgstr "Stereo"
#: spa/plugins/alsa/acp/alsa-mixer.c:4469
#: spa/plugins/alsa/acp/alsa-mixer.c:4627
#: spa/plugins/bluez5/bluez5-device.c:1977
#: spa/plugins/alsa/acp/alsa-mixer.c:4535
#: spa/plugins/alsa/acp/alsa-mixer.c:4693
#: spa/plugins/bluez5/bluez5-device.c:2362
msgid "Headset"
msgstr "Kulaklık"
#: spa/plugins/alsa/acp/alsa-mixer.c:4470
#: spa/plugins/alsa/acp/alsa-mixer.c:4628
#: spa/plugins/alsa/acp/alsa-mixer.c:4536
#: spa/plugins/alsa/acp/alsa-mixer.c:4694
msgid "Speakerphone"
msgstr "Hoparlör"
#: spa/plugins/alsa/acp/alsa-mixer.c:4471
#: spa/plugins/alsa/acp/alsa-mixer.c:4472
#: spa/plugins/alsa/acp/alsa-mixer.c:4537
#: spa/plugins/alsa/acp/alsa-mixer.c:4538
msgid "Multichannel"
msgstr "Çok kanallı"
#: spa/plugins/alsa/acp/alsa-mixer.c:4473
#: spa/plugins/alsa/acp/alsa-mixer.c:4539
msgid "Analog Surround 2.1"
msgstr "Analog Çevresel Ses 2.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4474
#: spa/plugins/alsa/acp/alsa-mixer.c:4540
msgid "Analog Surround 3.0"
msgstr "Analog Çevresel Ses 3.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4475
#: spa/plugins/alsa/acp/alsa-mixer.c:4541
msgid "Analog Surround 3.1"
msgstr "Analog Çevresel Ses 3.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4476
#: spa/plugins/alsa/acp/alsa-mixer.c:4542
msgid "Analog Surround 4.0"
msgstr "Analog Çevresel Ses 4.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4477
#: spa/plugins/alsa/acp/alsa-mixer.c:4543
msgid "Analog Surround 4.1"
msgstr "Analog Çevresel Ses 4.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4478
#: spa/plugins/alsa/acp/alsa-mixer.c:4544
msgid "Analog Surround 5.0"
msgstr "Analog Çevresel Ses 5.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4479
#: spa/plugins/alsa/acp/alsa-mixer.c:4545
msgid "Analog Surround 5.1"
msgstr "Analog Çevresel Ses 5.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4480
#: spa/plugins/alsa/acp/alsa-mixer.c:4546
msgid "Analog Surround 6.0"
msgstr "Analog Çevresel Ses 6.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4481
#: spa/plugins/alsa/acp/alsa-mixer.c:4547
msgid "Analog Surround 6.1"
msgstr "Analog Çevresel Ses 6.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4482
#: spa/plugins/alsa/acp/alsa-mixer.c:4548
msgid "Analog Surround 7.0"
msgstr "Analog Çevresel Ses 7.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4483
#: spa/plugins/alsa/acp/alsa-mixer.c:4549
msgid "Analog Surround 7.1"
msgstr "Analog Çevresel Ses 7.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4484
#: spa/plugins/alsa/acp/alsa-mixer.c:4550
msgid "Digital Stereo (IEC958)"
msgstr "Sayısal Stereo (IEC958)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4485
#: spa/plugins/alsa/acp/alsa-mixer.c:4551
msgid "Digital Surround 4.0 (IEC958/AC3)"
msgstr "Sayısal Çevresel Ses 4.0 (IEC958/AC3)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4486
#: spa/plugins/alsa/acp/alsa-mixer.c:4552
msgid "Digital Surround 5.1 (IEC958/AC3)"
msgstr "Sayısal Çevresel Ses 5.1 (IEC958/AC3)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4487
#: spa/plugins/alsa/acp/alsa-mixer.c:4553
msgid "Digital Surround 5.1 (IEC958/DTS)"
msgstr "Sayısal Çevresel Ses 5.1 (IEC958/DTS)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4488
#: spa/plugins/alsa/acp/alsa-mixer.c:4554
msgid "Digital Stereo (HDMI)"
msgstr "Sayısal Stereo (HDMI)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4489
#: spa/plugins/alsa/acp/alsa-mixer.c:4555
msgid "Digital Surround 5.1 (HDMI)"
msgstr "Sayısal Çevresel Ses 5.1 (HDMI)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4490
#: spa/plugins/alsa/acp/alsa-mixer.c:4556
msgid "Chat"
msgstr "Sohbet"
#: spa/plugins/alsa/acp/alsa-mixer.c:4491
#: spa/plugins/alsa/acp/alsa-mixer.c:4557
msgid "Game"
msgstr "Oyun"
#: spa/plugins/alsa/acp/alsa-mixer.c:4625
#: spa/plugins/alsa/acp/alsa-mixer.c:4691
msgid "Analog Mono Duplex"
msgstr "Analog Tek Kanallı İkili"
#: spa/plugins/alsa/acp/alsa-mixer.c:4626
#: spa/plugins/alsa/acp/alsa-mixer.c:4692
msgid "Analog Stereo Duplex"
msgstr "Analog İkili Stereo"
#: spa/plugins/alsa/acp/alsa-mixer.c:4629
#: spa/plugins/alsa/acp/alsa-mixer.c:4695
msgid "Digital Stereo Duplex (IEC958)"
msgstr "Sayısal İkili Stereo (IEC958)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4630
#: spa/plugins/alsa/acp/alsa-mixer.c:4696
msgid "Multichannel Duplex"
msgstr "Çok Kanallı İkili"
#: spa/plugins/alsa/acp/alsa-mixer.c:4631
#: spa/plugins/alsa/acp/alsa-mixer.c:4697
msgid "Stereo Duplex"
msgstr "İkili Stereo"
#: spa/plugins/alsa/acp/alsa-mixer.c:4632
#: spa/plugins/alsa/acp/alsa-mixer.c:4698
msgid "Mono Chat + 7.1 Surround"
msgstr "Tek Kanallı Sohbet + 7.1 Çevresel Ses"
#: spa/plugins/alsa/acp/alsa-mixer.c:4733
#: spa/plugins/alsa/acp/alsa-mixer.c:4799
#, c-format
msgid "%s Output"
msgstr "%s Çıkışı"
#: spa/plugins/alsa/acp/alsa-mixer.c:4741
#: spa/plugins/alsa/acp/alsa-mixer.c:4807
#, c-format
msgid "%s Input"
msgstr "%s Girişi"
#: spa/plugins/alsa/acp/alsa-util.c:1220 spa/plugins/alsa/acp/alsa-util.c:1314
#: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327
#, c-format
msgid ""
"snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu "
@ -547,16 +572,16 @@ 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:1299
#, 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] ""
@ -564,7 +589,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:1333
#: spa/plugins/alsa/acp/alsa-util.c:1346
#, c-format
msgid ""
"snd_pcm_avail_delay() returned strange values: delay %lu is less than avail "
@ -577,7 +602,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:1376
#: spa/plugins/alsa/acp/alsa-util.c:1389
#, c-format
msgid ""
"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte "
@ -595,112 +620,112 @@ 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:457
#: spa/plugins/alsa/acp/channelmap.h:460
msgid "(invalid)"
msgstr "(geçersiz)"
#: spa/plugins/alsa/acp/compat.c:193
#: spa/plugins/alsa/acp/compat.c:194
msgid "Built-in Audio"
msgstr "Dahili Ses"
#: spa/plugins/alsa/acp/compat.c:198
#: spa/plugins/alsa/acp/compat.c:199
msgid "Modem"
msgstr "Modem"
#: spa/plugins/bluez5/bluez5-device.c:1712
#: spa/plugins/bluez5/bluez5-device.c:1987
msgid "Audio Gateway (A2DP Source & HSP/HFP AG)"
msgstr "Ses Geçidi (A2DP Kaynak & HSP/HFP AG)"
#: spa/plugins/bluez5/bluez5-device.c:1760
#: spa/plugins/bluez5/bluez5-device.c:2016
msgid "Audio Streaming for Hearing Aids (ASHA Sink)"
msgstr "İşitme Aygıtları İçin Ses Akışı (ASHA Alıcı)"
#: spa/plugins/bluez5/bluez5-device.c:2059
#, 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:1763
#: spa/plugins/bluez5/bluez5-device.c:2062
#, 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:1771
#: spa/plugins/bluez5/bluez5-device.c:2070
msgid "High Fidelity Playback (A2DP Sink)"
msgstr "Yüksek Kaliteli Çalma (A2DP Alıcı)"
#: spa/plugins/bluez5/bluez5-device.c:1773
#: spa/plugins/bluez5/bluez5-device.c:2072
msgid "High Fidelity Duplex (A2DP Source/Sink)"
msgstr "Yüksek Kaliteli İkili (A2DP Kaynak/Alıcı)"
#: spa/plugins/bluez5/bluez5-device.c:1823
#: spa/plugins/bluez5/bluez5-device.c:2146
#, 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:1828
#: spa/plugins/bluez5/bluez5-device.c:2151
#, 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:1832
#: spa/plugins/bluez5/bluez5-device.c:2155
#, 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:1841
#: spa/plugins/bluez5/bluez5-device.c:2164
msgid "High Fidelity Playback (BAP Sink)"
msgstr "Yüksek Kaliteli Çalma (BAP Alıcı)"
#: spa/plugins/bluez5/bluez5-device.c:1845
#: spa/plugins/bluez5/bluez5-device.c:2168
msgid "High Fidelity Input (BAP Source)"
msgstr "Yüksek Kaliteli Giriş (BAP Kaynak)"
#: spa/plugins/bluez5/bluez5-device.c:1848
#: spa/plugins/bluez5/bluez5-device.c:2171
msgid "High Fidelity Duplex (BAP Source/Sink)"
msgstr "Yüksek Kaliteli İkili (BAP Kaynak/Alıcı)"
#: spa/plugins/bluez5/bluez5-device.c:1897
#: spa/plugins/bluez5/bluez5-device.c:2211
#, 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:1978
#: spa/plugins/bluez5/bluez5-device.c:1983
#: spa/plugins/bluez5/bluez5-device.c:1990
#: spa/plugins/bluez5/bluez5-device.c:1996
#: spa/plugins/bluez5/bluez5-device.c:2002
#: spa/plugins/bluez5/bluez5-device.c:2008
#: spa/plugins/bluez5/bluez5-device.c:2014
#: spa/plugins/bluez5/bluez5-device.c:2020
#: spa/plugins/bluez5/bluez5-device.c:2026
#: spa/plugins/bluez5/bluez5-device.c:2363
#: spa/plugins/bluez5/bluez5-device.c:2368
#: spa/plugins/bluez5/bluez5-device.c:2375
#: spa/plugins/bluez5/bluez5-device.c:2381
#: spa/plugins/bluez5/bluez5-device.c:2387
#: spa/plugins/bluez5/bluez5-device.c:2393
#: spa/plugins/bluez5/bluez5-device.c:2399
#: spa/plugins/bluez5/bluez5-device.c:2405
#: spa/plugins/bluez5/bluez5-device.c:2411
msgid "Handsfree"
msgstr "Ahizesiz"
#: spa/plugins/bluez5/bluez5-device.c:1984
#: spa/plugins/bluez5/bluez5-device.c:2369
msgid "Handsfree (HFP)"
msgstr "Ahizesiz (HFP)"
#: spa/plugins/bluez5/bluez5-device.c:2001
msgid "Headphone"
msgstr "Kulaklık"
#: spa/plugins/bluez5/bluez5-device.c:2007
#: spa/plugins/bluez5/bluez5-device.c:2392
msgid "Portable"
msgstr "Taşınabilir"
#: spa/plugins/bluez5/bluez5-device.c:2013
#: spa/plugins/bluez5/bluez5-device.c:2398
msgid "Car"
msgstr "Araba"
#: spa/plugins/bluez5/bluez5-device.c:2019
#: spa/plugins/bluez5/bluez5-device.c:2404
msgid "HiFi"
msgstr "Yüksek Kalite"
#: spa/plugins/bluez5/bluez5-device.c:2025
#: spa/plugins/bluez5/bluez5-device.c:2410
msgid "Phone"
msgstr "Telefon"
#: spa/plugins/bluez5/bluez5-device.c:2032
#: spa/plugins/bluez5/bluez5-device.c:2417
msgid "Bluetooth"
msgstr "Bluetooth"
#: spa/plugins/bluez5/bluez5-device.c:2033
#: spa/plugins/bluez5/bluez5-device.c:2418
msgid "Bluetooth (HFP)"
msgstr "Bluetooth (HFP)"

View file

@ -40,6 +40,9 @@ static const struct spa_type_info spa_type_props[] = {
{ SPA_PROP_quality, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "quality", NULL },
{ SPA_PROP_bluetoothAudioCodec, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "bluetoothAudioCodec", spa_type_bluetooth_audio_codec },
{ SPA_PROP_bluetoothOffloadActive, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "bluetoothOffloadActive", NULL },
{ SPA_PROP_clockId, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "clockId", NULL },
{ SPA_PROP_clockDevice, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "clockDevice", NULL },
{ SPA_PROP_clockInterface, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "clockInterface", NULL },
{ SPA_PROP_waveType, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "waveType", NULL },
{ SPA_PROP_frequency, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "frequency", NULL },

View file

@ -55,6 +55,9 @@ enum spa_prop {
SPA_PROP_quality,
SPA_PROP_bluetoothAudioCodec,
SPA_PROP_bluetoothOffloadActive,
SPA_PROP_clockId,
SPA_PROP_clockDevice,
SPA_PROP_clockInterface,
SPA_PROP_START_Audio = 0x10000, /**< audio related properties */
SPA_PROP_waveType,

View file

@ -54,6 +54,15 @@ SPA_API_JSON void spa_json_init(struct spa_json * iter, const char *data, size_t
{
*iter = SPA_JSON_INIT(data, size);
}
#define SPA_JSON_INIT_RELAX(type,data,size) \
((struct spa_json) { (data), (data)+(size), NULL, (uint32_t)((type) == '[' ? 0x10 : 0x0), 0 })
SPA_API_JSON void spa_json_init_relax(struct spa_json * iter, char type, const char *data, size_t size)
{
*iter = SPA_JSON_INIT_RELAX(type, data, size);
}
#define SPA_JSON_ENTER(iter) ((struct spa_json) { (iter)->cur, (iter)->end, (iter), (iter)->state & 0xff0, 0 })
SPA_API_JSON void spa_json_enter(struct spa_json * iter, struct spa_json * sub)

View file

@ -105,7 +105,7 @@ SPA_API_JSON_UTILS int spa_json_begin_container(struct spa_json * iter,
spa_json_init(iter, data, size);
res = spa_json_enter_container(iter, iter, type);
if (res == -EPROTO && relax)
spa_json_init(iter, data, size);
spa_json_init_relax(iter, type, data, size);
else if (res <= 0)
return res;
return 1;

View file

@ -931,6 +931,7 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
switch (id) {
case SPA_IO_Position:
this->io_position = data;
this->recheck_format = true;
break;
default:
break;

View file

@ -26,6 +26,8 @@
#include <spa/node/io.h>
#include <spa/node/utils.h>
#include <spa/param/param.h>
#include <spa/pod/filter.h>
#include <spa/pod/parser.h>
SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.driver");
@ -48,12 +50,16 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.driver");
#define BW_PERIOD (3 * SPA_NSEC_PER_SEC)
#define MAX_ERROR_MS 1
#define CLOCK_NAME_MAX 64
struct props {
bool freewheel;
char clock_name[64];
char clock_name[CLOCK_NAME_MAX];
clockid_t clock_id;
uint32_t freewheel_wait;
float resync_ms;
char clock_device[CLOCK_NAME_MAX];
char clock_interface[CLOCK_NAME_MAX];
};
struct clock_offset {
@ -73,7 +79,10 @@ struct impl {
uint64_t info_all;
struct spa_node_info info;
struct spa_param_info params[1];
#define NODE_PropInfo 0
#define NODE_Props 1
#define N_NODE_PARAMS 2
struct spa_param_info params[N_NODE_PARAMS];
struct spa_hook_list hooks;
struct spa_callbacks callbacks;
@ -99,13 +108,20 @@ struct impl {
struct clock_offset nsec_offset;
};
static void reset_props_strings(struct props *props)
{
spa_zero(props->clock_name);
spa_zero(props->clock_device);
spa_zero(props->clock_interface);
}
static void reset_props(struct props *props)
{
props->freewheel = DEFAULT_FREEWHEEL;
spa_zero(props->clock_name);
props->clock_id = CLOCK_MONOTONIC;
props->freewheel_wait = DEFAULT_FREEWHEEL_WAIT;
props->resync_ms = DEFAULT_RESYNC_MS;
reset_props_strings(props);
}
static const struct clock_info {
@ -598,10 +614,280 @@ static int impl_node_process(void *object)
return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA;
}
static int impl_node_enum_params(void *object, int seq,
uint32_t id, uint32_t start, uint32_t num,
const struct spa_pod *filter)
{
struct impl *this = object;
struct spa_pod *param;
struct spa_pod_builder b = { 0 };
uint8_t buffer[4096];
struct spa_result_node_params result;
uint32_t count = 0;
spa_return_val_if_fail(this != NULL, -EINVAL);
spa_return_val_if_fail(num != 0, -EINVAL);
result.id = id;
result.next = start;
next:
result.index = result.next++;
spa_pod_builder_init(&b, buffer, sizeof(buffer));
switch (id) {
case SPA_PARAM_PropInfo:
{
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_clockId),
SPA_PROP_INFO_description, SPA_POD_String("The clock id (monotonic, realtime, etc.)"),
SPA_PROP_INFO_type, SPA_POD_String(clock_id_to_name(p->clock_id)));
break;
case 1:
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_clockDevice),
SPA_PROP_INFO_description, SPA_POD_String("The clock device (eg. /dev/ptp0)"),
SPA_PROP_INFO_type, SPA_POD_Stringn(p->clock_device, sizeof(p->clock_device)));
break;
case 2:
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_clockInterface),
SPA_PROP_INFO_description, SPA_POD_String("The clock network interface (eg. eth0)"),
SPA_PROP_INFO_type, SPA_POD_Stringn(p->clock_interface, sizeof(p->clock_interface)));
break;
default:
return 0;
}
break;
}
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_clockId, SPA_POD_String(clock_id_to_name(p->clock_id))
);
break;
case 1:
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Props, id,
SPA_PROP_clockDevice, SPA_POD_Stringn(p->clock_device, sizeof(p->clock_device))
);
break;
case 2:
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Props, id,
SPA_PROP_clockInterface, SPA_POD_Stringn(p->clock_interface, sizeof(p->clock_interface))
);
break;
default:
return 0;
}
break;
}
default:
return -ENOENT;
}
if (spa_pod_filter(&b, &result.param, param, filter) < 0)
goto next;
spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
if (++count != num)
goto next;
return 0;
}
static int get_phc_index(struct spa_system *s, const char *name) {
#ifdef ETHTOOL_GET_TS_INFO
struct ethtool_ts_info info = {0};
struct ifreq ifr = {0};
int fd, err;
info.cmd = ETHTOOL_GET_TS_INFO;
strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
ifr.ifr_data = (char *) &info;
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
return -errno;
err = spa_system_ioctl(s, fd, SIOCETHTOOL, &ifr);
close(fd);
if (err < 0)
return -errno;
return info.phc_index;
#else
return -ENOTSUP;
#endif
}
static bool parse_clock_id(struct impl *this, const char *s)
{
int id = clock_name_to_id(s);
if (id == -1) {
spa_log_info(this->log, "unknown clock id '%s'", s);
return false;
}
this->props.clock_id = id;
if (this->clock_fd >= 0) {
close(this->clock_fd);
this->clock_fd = -1;
}
return true;
}
static bool parse_clock_device(struct impl *this, const char *s)
{
int fd = open(s, O_RDONLY);
if (fd == -1) {
spa_log_info(this->log, "failed to open clock device '%s': %m", s);
return false;
}
if (this->clock_fd >= 0) {
close(this->clock_fd);
}
this->clock_fd = fd;
this->props.clock_id = FD_TO_CLOCKID(this->clock_fd);
return true;
}
static bool parse_clock_interface(struct impl *this, const char *s)
{
int phc_index = get_phc_index(this->data_system, s);
if (phc_index < 0) {
spa_log_info(this->log, "failed to get phc device index for interface '%s': %s",
s, spa_strerror(phc_index));
return false;
} else {
char dev[19];
spa_scnprintf(dev, sizeof(dev), "/dev/ptp%d", phc_index);
if (!parse_clock_device(this, dev)) {
spa_log_info(this->log, "failed to open clock device '%s' "
"for interface '%s': %m", dev, s);
return false;
}
}
return true;
}
static void ensure_clock_name(struct impl *this)
{
struct props *p = &this->props;
if (p->clock_name[0] == '\0') {
const char *name = clock_id_to_name(p->clock_id);
if (p->clock_device[0])
name = p->clock_device;
if (p->clock_interface[0])
name = p->clock_interface;
spa_scnprintf(p->clock_name, sizeof(p->clock_name),
"%s.%s", DEFAULT_CLOCK_PREFIX, name);
}
}
static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
const struct spa_pod *param)
{
struct impl *this = object;
spa_return_val_if_fail(this != NULL, -EINVAL);
switch (id) {
case SPA_PARAM_Props:
{
struct props *p = &this->props;
bool notify = false;
char buffer[CLOCK_NAME_MAX];
int count;
if (param == NULL) {
return 0;
}
/* Note that the length passed to SPA_POD_OPT_Stringn() also
* includes room for the null terminator, so the content of the
* buffer variable is always guaranteed to be null terminated. */
spa_zero(buffer);
count = spa_pod_parse_object(param,
SPA_TYPE_OBJECT_Props, NULL,
SPA_PROP_clockId, SPA_POD_OPT_Stringn(buffer, sizeof(buffer))
);
if (count && parse_clock_id(this, buffer))
{
reset_props_strings(p);
notify = true;
}
spa_zero(buffer);
count = spa_pod_parse_object(param,
SPA_TYPE_OBJECT_Props, NULL,
SPA_PROP_clockDevice, SPA_POD_OPT_Stringn(buffer, sizeof(buffer))
);
if (count && parse_clock_device(this, buffer))
{
reset_props_strings(p);
strncpy(p->clock_device, buffer, sizeof(p->clock_device));
notify = true;
}
spa_zero(buffer);
count = spa_pod_parse_object(param,
SPA_TYPE_OBJECT_Props, NULL,
SPA_PROP_clockInterface, SPA_POD_OPT_Stringn(buffer, sizeof(buffer))
);
if (count && parse_clock_interface(this, buffer))
{
reset_props_strings(p);
strncpy(p->clock_interface, buffer, sizeof(p->clock_interface));
notify = true;
}
if (notify)
{
ensure_clock_name(this);
spa_log_info(this->log, "%p: setting clock to '%s'", this, p->clock_name);
if (this->started) {
do_stop(this);
do_start(this);
}
emit_node_info(this, true);
}
break;
}
default:
return -ENOENT;
break;
}
return 0;
}
static const struct spa_node_methods impl_node = {
SPA_VERSION_NODE_METHODS,
.add_listener = impl_node_add_listener,
.set_callbacks = impl_node_set_callbacks,
.enum_params = impl_node_enum_params,
.set_param = impl_node_set_param,
.set_io = impl_node_set_io,
.send_command = impl_node_send_command,
.process = impl_node_process,
@ -655,31 +941,6 @@ impl_get_size(const struct spa_handle_factory *factory,
return sizeof(struct impl);
}
static int get_phc_index(struct spa_system *s, const char *name) {
#ifdef ETHTOOL_GET_TS_INFO
struct ethtool_ts_info info = {0};
struct ifreq ifr = {0};
int fd, err;
info.cmd = ETHTOOL_GET_TS_INFO;
strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
ifr.ifr_data = (char *) &info;
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
return -errno;
err = spa_system_ioctl(s, fd, SIOCETHTOOL, &ifr);
close(fd);
if (err < 0)
return -errno;
return info.phc_index;
#else
return -ENOTSUP;
#endif
}
static int
impl_init(const struct spa_handle_factory *factory,
struct spa_handle *handle,
@ -727,9 +988,10 @@ impl_init(const struct spa_handle_factory *factory,
this->info.max_input_ports = 0;
this->info.max_output_ports = 0;
this->info.flags = SPA_NODE_FLAG_RT;
this->params[0] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE);
this->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ);
this->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE);
this->info.params = this->params;
this->info.n_params = 0;
this->info.n_params = N_NODE_PARAMS;
reset_props(&this->props);
@ -742,37 +1004,17 @@ impl_init(const struct spa_handle_factory *factory,
spa_scnprintf(this->props.clock_name,
sizeof(this->props.clock_name), "%s", s);
} else if (spa_streq(k, "clock.id") && this->clock_fd < 0) {
this->props.clock_id = clock_name_to_id(s);
if (this->props.clock_id == -1) {
spa_log_warn(this->log, "unknown clock id '%s'", s);
this->props.clock_id = DEFAULT_CLOCK_ID;
}
if (parse_clock_id(this, s))
reset_props_strings(&this->props);
} else if (spa_streq(k, "clock.device")) {
if (this->clock_fd >= 0) {
close(this->clock_fd);
}
this->clock_fd = open(s, O_RDONLY);
if (this->clock_fd == -1) {
spa_log_warn(this->log, "failed to open clock device '%s': %m", s);
} else {
this->props.clock_id = FD_TO_CLOCKID(this->clock_fd);
if (parse_clock_device(this, s)) {
reset_props_strings(&this->props);
strncpy(this->props.clock_device, s, sizeof(this->props.clock_device)-1);
}
} else if (spa_streq(k, "clock.interface") && this->clock_fd < 0) {
int phc_index = get_phc_index(this->data_system, s);
if (phc_index < 0) {
spa_log_warn(this->log, "failed to get phc device index for interface '%s': %s",
s, spa_strerror(phc_index));
} else {
char dev[19];
spa_scnprintf(dev, sizeof(dev), "/dev/ptp%d", phc_index);
this->clock_fd = open(dev, O_RDONLY);
if (this->clock_fd == -1) {
spa_log_warn(this->log, "failed to open clock device '%s' "
"for interface '%s': %m", dev, s);
} else {
this->props.clock_id = FD_TO_CLOCKID(this->clock_fd);
}
if (parse_clock_interface(this, s)) {
reset_props_strings(&this->props);
strncpy(this->props.clock_interface, s, sizeof(this->props.clock_interface)-1);
}
} else if (spa_streq(k, "freewheel.wait")) {
this->props.freewheel_wait = atoi(s);
@ -785,6 +1027,7 @@ impl_init(const struct spa_handle_factory *factory,
"%s.%s", DEFAULT_CLOCK_PREFIX,
clock_id_to_name(this->props.clock_id));
}
ensure_clock_name(this);
this->tracking = !clock_for_timerfd(this->props.clock_id);
this->timer_clockid = this->tracking ? CLOCK_MONOTONIC : this->props.clock_id;

View file

@ -900,6 +900,7 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
switch (id) {
case SPA_IO_Position:
this->io_position = data;
this->recheck_format = true;
break;
default:
break;

View file

@ -229,8 +229,10 @@ struct impl {
struct spa_audio_aec *aec;
uint32_t aec_blocksize;
unsigned int capture_ready:1;
unsigned int sink_ready:1;
struct spa_io_position *capture_position;
struct spa_io_position *sink_position;
uint32_t capture_cycle;
uint32_t sink_cycle;
unsigned int do_disconnect:1;
@ -307,13 +309,24 @@ static void process(struct impl *impl)
const float *play_delayed[impl->play_info.channels];
float *out[impl->out_info.channels];
struct spa_data *dd;
uint32_t i, size;
uint32_t rindex, pindex, oindex, pdindex, avail;
uint32_t i;
uint32_t rindex, pindex, oindex, pdindex, size;
int32_t avail, pavail, pdavail;
size = impl->aec_blocksize;
/* First read a block from the playback and capture ring buffers */
spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex);
/* First read a block from the capture ring buffer */
avail = spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex);
while (avail >= (int32_t)size * 2) {
/* drop samples that are not needed this or next cycle. Note
* that samples are kept in the ringbuffer until next cycle if
* size is not equal to or divisible by quantum, to avoid
* discontinuity */
pw_log_debug("avail %d", avail);
spa_ringbuffer_read_update(&impl->rec_ring, rindex + size);
avail = spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex);
pw_log_debug("new avail %d, size %u", avail, size);
}
for (i = 0; i < impl->rec_info.channels; i++) {
/* captured samples, with echo from sink */
@ -331,19 +344,34 @@ static void process(struct impl *impl)
out[i] = &out_buf[i][0];
}
spa_ringbuffer_get_read_index(&impl->play_ring, &pindex);
spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex);
pavail = spa_ringbuffer_get_read_index(&impl->play_ring, &pindex);
pdavail = spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex);
if (impl->playback != NULL && (pout = pw_stream_dequeue_buffer(impl->playback)) == NULL) {
pw_log_debug("out of playback buffers: %m");
/* playback stream may not yet be in streaming state, drop play
* data to avoid introducing additional playback latency */
spa_ringbuffer_read_update(&impl->play_ring, pindex + size);
spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + size);
spa_ringbuffer_read_update(&impl->play_ring, pindex + pavail);
spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + pdavail);
goto done;
}
if (pavail > avail) {
/* drop too old samples from previous graph cycles */
pw_log_debug("pavail %d, dropping %d", pavail, pavail - avail);
spa_ringbuffer_read_update(&impl->play_ring, pindex + pavail - avail);
pavail = spa_ringbuffer_get_read_index(&impl->play_ring, &pindex);
pw_log_debug("new pavail %d, avail %d", pavail, avail);
}
if (pdavail > avail) {
/* drop too old samples from previous graph cycles */
pw_log_debug("pdavail %d, dropping %d", pdavail, pdavail - avail);
spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + pdavail - avail);
pdavail = spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex);
pw_log_debug("new pdavail %d, avail %d", pdavail, avail);
}
for (i = 0; i < impl->play_info.channels; i++) {
/* echo from sink */
play[i] = &play_buf[i][0];
@ -431,7 +459,7 @@ static void process(struct impl *impl)
* available on the source */
avail = spa_ringbuffer_get_read_index(&impl->out_ring, &oindex);
while (avail >= size) {
while (avail >= (int32_t)size) {
if ((cout = pw_stream_dequeue_buffer(impl->source)) != NULL) {
for (i = 0; i < impl->out_info.channels; i++) {
dd = &cout->buffer->datas[i];
@ -454,8 +482,8 @@ static void process(struct impl *impl)
}
done:
impl->sink_ready = false;
impl->capture_ready = false;
impl->capture_cycle = 0;
impl->sink_cycle = 0;
}
static void reset_buffers(struct impl *impl)
@ -479,8 +507,8 @@ static void reset_buffers(struct impl *impl)
spa_ringbuffer_get_read_index(&impl->play_ring, &index);
spa_ringbuffer_read_update(&impl->play_ring, index + (sizeof(float) * (impl->buffer_delay)));
impl->sink_ready = false;
impl->capture_ready = false;
impl->capture_cycle = 0;
impl->sink_cycle = 0;
}
static void capture_destroy(void *d)
@ -546,8 +574,11 @@ static void capture_process(void *data)
spa_ringbuffer_write_update(&impl->rec_ring, index + size);
if (avail + size >= impl->aec_blocksize) {
impl->capture_ready = true;
if (impl->sink_ready)
if (impl->capture_position)
impl->capture_cycle = impl->capture_position->clock.cycle;
else
pw_log_warn("no capture position");
if (impl->capture_cycle == impl->sink_cycle)
process(impl);
}
@ -740,12 +771,26 @@ static void input_param_changed(void *data, uint32_t id, const struct spa_pod* p
}
}
static void capture_io_changed(void *data, uint32_t id, void *area, uint32_t size)
{
struct impl *impl = data;
switch (id) {
case SPA_IO_Position:
impl->capture_position = area;
break;
default:
break;
}
}
static const struct pw_stream_events capture_events = {
PW_VERSION_STREAM_EVENTS,
.destroy = capture_destroy,
.state_changed = capture_state_changed,
.process = capture_process,
.param_changed = input_param_changed
.param_changed = input_param_changed,
.io_changed = capture_io_changed
};
static void source_destroy(void *d)
@ -930,10 +975,15 @@ static void sink_process(void *data)
SPA_PTROFF(d->data, offs, void), size);
}
spa_ringbuffer_write_update(&impl->play_ring, index + size);
spa_ringbuffer_get_write_index(&impl->play_delayed_ring, &index);
spa_ringbuffer_write_update(&impl->play_delayed_ring, index + size);
if (avail + size >= impl->aec_blocksize) {
impl->sink_ready = true;
if (impl->capture_ready)
if (impl->sink_position)
impl->sink_cycle = impl->sink_position->clock.cycle;
else
pw_log_warn("no sink position");
if (impl->capture_cycle == impl->sink_cycle)
process(impl);
}
@ -955,12 +1005,27 @@ static const struct pw_stream_events playback_events = {
.state_changed = playback_state_changed,
.param_changed = output_param_changed
};
static void sink_io_changed(void *data, uint32_t id, void *area, uint32_t size)
{
struct impl *impl = data;
switch (id) {
case SPA_IO_Position:
impl->sink_position = area;
break;
default:
break;
}
}
static const struct pw_stream_events sink_events = {
PW_VERSION_STREAM_EVENTS,
.destroy = sink_destroy,
.process = sink_process,
.state_changed = sink_state_changed,
.param_changed = output_param_changed
.param_changed = output_param_changed,
.io_changed = sink_io_changed
};
#define MAX_PARAMS 512u

View file

@ -248,6 +248,16 @@ struct node {
struct session *session;
};
struct igmp_recovery {
struct pw_timer timer;
int socket_fd;
struct sockaddr_storage mcast_addr;
socklen_t mcast_len;
uint32_t if_index;
bool is_ipv6;
uint32_t deadline;
};
struct impl {
struct pw_properties *props;
@ -265,7 +275,11 @@ struct impl {
struct pw_registry *registry;
struct spa_hook registry_listener;
struct pw_timer timer;
struct pw_timer sap_send_timer;
/* This timer is used when the first start_sap() call fails because
* of an ENODEV error (see the start_sap() code for details) */
struct pw_timer start_sap_retry_timer;
char *ifname;
uint32_t ttl;
@ -281,6 +295,10 @@ struct impl {
struct spa_source *sap_source;
uint32_t cleanup_interval;
/* IGMP recovery (triggers when no SAP packets are
* received after the recovery deadline is reached) */
struct igmp_recovery igmp_recovery;
uint32_t max_sessions;
uint32_t n_sessions;
struct spa_list sessions;
@ -288,7 +306,7 @@ struct impl {
char *extra_attrs_preamble;
char *extra_attrs_end;
char *ptp_mgmt_socket;
char *ptp_mgmt_socket_path;
int ptp_fd;
uint32_t ptp_seq;
uint8_t clock_id[8];
@ -322,6 +340,7 @@ static const struct format_info *find_audio_format_info(const char *mime)
return NULL;
}
static int start_sap(struct impl *impl);
static int send_sap(struct impl *impl, struct session *sess, bool bye);
@ -383,7 +402,7 @@ static bool is_multicast(struct sockaddr *sa, socklen_t salen)
return false;
}
static int make_unix_socket(const char *path) {
static int make_unix_ptp_mgmt_socket(const char *path) {
struct sockaddr_un addr;
spa_autoclose int fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
@ -419,7 +438,7 @@ static int make_send_socket(
af = src->ss_family;
if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) {
pw_log_error("socket failed: %m");
pw_log_error("socket() failed: %m");
return -errno;
}
if (bind(fd, (struct sockaddr*)src, src_len) < 0) {
@ -451,6 +470,9 @@ static int make_send_socket(
pw_log_warn("setsockopt(IPV6_MULTICAST_HOPS) failed: %m");
}
}
pw_log_info("sender socket up and running");
return fd;
error:
close(fd);
@ -458,7 +480,7 @@ error:
}
static int make_recv_socket(struct sockaddr_storage *sa, socklen_t salen,
char *ifname)
char *ifname, struct igmp_recovery *igmp_recovery)
{
int af, fd, val, res;
struct ifreq req;
@ -468,13 +490,13 @@ static int make_recv_socket(struct sockaddr_storage *sa, socklen_t salen,
af = sa->ss_family;
if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) {
pw_log_error("socket failed: %m");
pw_log_error("socket() failed: %m");
return -errno;
}
val = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
res = -errno;
pw_log_error("setsockopt failed: %m");
pw_log_error("setsockopt() failed: %m");
goto error;
}
spa_zero(req);
@ -528,6 +550,16 @@ static int make_recv_socket(struct sockaddr_storage *sa, socklen_t salen,
goto error;
}
/* Store multicast info for recovery */
igmp_recovery->socket_fd = fd;
igmp_recovery->mcast_addr = ba;
igmp_recovery->mcast_len = salen;
igmp_recovery->if_index = req.ifr_ifindex;
igmp_recovery->is_ipv6 = (af == AF_INET6);
pw_log_debug("stored %s multicast info: socket_fd=%d, "
"if_index=%d", igmp_recovery->is_ipv6 ?
"IPv6" : "IPv4", fd, req.ifr_ifindex);
if (bind(fd, (struct sockaddr*)&ba, salen) < 0) {
res = -errno;
pw_log_error("bind() failed: %m");
@ -540,6 +572,9 @@ static int make_recv_socket(struct sockaddr_storage *sa, socklen_t salen,
goto error;
}
}
pw_log_info("receiver socket up and running");
return fd;
error:
close(fd);
@ -548,8 +583,13 @@ error:
static bool update_ts_refclk(struct impl *impl)
{
if (!impl->ptp_mgmt_socket || impl->ptp_fd < 0)
if (!impl->ptp_mgmt_socket_path)
return false;
if (impl->ptp_fd < 0) {
impl->ptp_fd = make_unix_ptp_mgmt_socket(impl->ptp_mgmt_socket_path);
if (impl->ptp_fd < 0)
return false;
}
// Read if something is left in the socket
int avail;
@ -581,6 +621,12 @@ static bool update_ts_refclk(struct impl *impl)
if (write(impl->ptp_fd, &req, sizeof(req)) == -1) {
pw_log_warn("Failed to send PTP management request: %m");
if (errno != ENOTCONN)
return false;
close(impl->ptp_fd);
impl->ptp_fd = make_unix_ptp_mgmt_socket(impl->ptp_mgmt_socket_path);
if (impl->ptp_fd > -1)
pw_log_info("Reopened PTP management socket");
return false;
}
@ -922,7 +968,98 @@ static int send_sap(struct impl *impl, struct session *sess, bool bye)
return res;
}
static void on_timer_event(void *data)
static void on_igmp_recovery_timer_event(void *data)
{
struct impl *impl = data;
char addr[128];
int res = 0;
/* Only attempt recovery if we have a valid socket and multicast address */
if (impl->igmp_recovery.socket_fd < 0) {
pw_log_debug("no socket, skipping IGMP recovery");
goto finish;
}
pw_net_get_ip(&impl->igmp_recovery.mcast_addr, addr, sizeof(addr), NULL, NULL);
pw_log_info("IGMP recovery triggered for %s", addr);
/* Force IGMP membership refresh by leaving the group first, then rejoin */
if (impl->igmp_recovery.is_ipv6) {
struct ipv6_mreq mr6;
memset(&mr6, 0, sizeof(mr6));
mr6.ipv6mr_multiaddr = ((struct sockaddr_in6*)&impl->igmp_recovery.mcast_addr)->sin6_addr;
mr6.ipv6mr_interface = impl->igmp_recovery.if_index;
/* Leave the group first */
res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP,
&mr6, sizeof(mr6));
if (SPA_LIKELY(res == 0)) {
pw_log_info("left IPv6 multicast group");
} else {
if (errno == EADDRNOTAVAIL) {
pw_log_info("attempted to leave IPv6 multicast group, but "
"membership was already silently dropped");
} else {
pw_log_warn("failed to leave IPv6 multicast group: %m");
}
}
res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
&mr6, sizeof(mr6));
if (res < 0) {
pw_log_warn("failed to re-join IPv6 multicast group: %m");
} else {
pw_log_info("re-joined IPv6 multicast group successfully");
}
} else {
struct ip_mreqn mr4;
memset(&mr4, 0, sizeof(mr4));
mr4.imr_multiaddr = ((struct sockaddr_in*)&impl->igmp_recovery.mcast_addr)->sin_addr;
mr4.imr_ifindex = impl->igmp_recovery.if_index;
/* Leave the group first */
res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP,
&mr4, sizeof(mr4));
if (SPA_LIKELY(res == 0)) {
pw_log_info("left IPv4 multicast group");
} else {
if (errno == EADDRNOTAVAIL) {
pw_log_info("attempted to leave IPv4 multicast group, but "
"membership was already silently dropped");
} else {
pw_log_warn("failed to leave IPv4 multicast group: %m");
}
}
res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&mr4, sizeof(mr4));
if (res < 0) {
pw_log_warn("failed to re-join IPv4 multicast group: %m");
} else {
pw_log_info("re-joined IPv4 multicast group successfully");
}
}
finish:
/* If rejoining failed, try again in 1 second. This can happen
* for example when the network interface went down, and is not
* yet up and running again, and ENODEV is returned as a result.
* Otherwise, continue with the usual deadline. */
pw_timer_queue_add(impl->timer_queue, &impl->igmp_recovery.timer,
&impl->igmp_recovery.timer.timeout,
((res == 0) ? impl->igmp_recovery.deadline : 1) * SPA_NSEC_PER_SEC,
on_igmp_recovery_timer_event, impl);
}
static void rearm_igmp_recovery_timer(struct impl *impl)
{
pw_timer_queue_cancel(&impl->igmp_recovery.timer);
pw_timer_queue_add(impl->timer_queue, &impl->igmp_recovery.timer,
NULL, impl->igmp_recovery.deadline * SPA_NSEC_PER_SEC,
on_igmp_recovery_timer_event, impl);
}
static void on_sap_send_timer_event(void *data)
{
struct impl *impl = data;
struct session *sess, *tmp;
@ -956,9 +1093,16 @@ static void on_timer_event(void *data)
}
}
pw_timer_queue_add(impl->timer_queue, &impl->timer,
&impl->timer.timeout, SAP_INTERVAL_SEC * SPA_NSEC_PER_SEC,
on_timer_event, impl);
pw_timer_queue_add(impl->timer_queue, &impl->sap_send_timer,
&impl->sap_send_timer.timeout, SAP_INTERVAL_SEC * SPA_NSEC_PER_SEC,
on_sap_send_timer_event, impl);
}
static void on_start_sap_retry_timer_event(void *data)
{
struct impl *impl = data;
pw_log_debug("trying again to start SAP send after previous attempt failed with ENODEV");
start_sap(impl);
}
static struct session *session_find(struct impl *impl, const struct sdp_info *info)
@ -1646,23 +1790,70 @@ on_sap_io(void *data, int fd, uint32_t mask)
buffer[len] = 0;
if ((res = parse_sap(impl, buffer, len)) < 0)
pw_log_warn("error parsing SAP: %s", spa_strerror(res));
rearm_igmp_recovery_timer(impl);
}
}
static int start_sap(struct impl *impl)
{
int fd = -1, res;
int fd = -1, res = 0;
char addr[128] = "invalid";
pw_log_info("starting SAP timer");
if ((res = pw_timer_queue_add(impl->timer_queue, &impl->timer,
pw_log_info("starting SAP send timer");
/* start_sap() might be called more than once. See the make_recv_socket()
* call below for why that can happen. In such a case, the timer was
* started already. The easiest way of handling it is to just cancel it.
* Such cases are not expected to occur often, so canceling and then
* adding the timer again is acceptable. */
pw_timer_queue_cancel(&impl->sap_send_timer);
if ((res = pw_timer_queue_add(impl->timer_queue, &impl->sap_send_timer,
NULL, SAP_INTERVAL_SEC * SPA_NSEC_PER_SEC,
on_timer_event, impl)) < 0) {
pw_log_error("can't add timer: %s", spa_strerror(res));
on_sap_send_timer_event, impl)) < 0) {
pw_log_error("can't add SAP send timer: %s", spa_strerror(res));
goto error;
}
if ((fd = make_recv_socket(&impl->sap_addr, impl->sap_len, impl->ifname)) < 0)
return fd;
if ((fd = make_recv_socket(&impl->sap_addr, impl->sap_len, impl->ifname,
&(impl->igmp_recovery))) < 0) {
/* If make_recv_socket() tries to create a socket and join to a multicast
* group while the network interfaces are not ready yet to do so
* (usually because a network manager component is still setting up
* those network interfaces), ENODEV will be returned. This is essentially
* a race condition. There is no discernible way to be notified when the
* network interfaces are ready for that operation, so the next best
* approach is to essentially do a form of polling by retrying the
* start_sap() call after some time. The start_sap_retry_timer exists
* precisely for that purpose. This means that ENODEV is not treated as
* an error, but instead, it triggers the creation of that timer. */
if (fd == -ENODEV) {
pw_log_warn("failed to create receiver socket because network device "
"is not ready and present yet; will try again");
pw_timer_queue_cancel(&impl->start_sap_retry_timer);
/* Use a 1-second retry interval. The network interfaces
* are likely to be up and running then. */
pw_timer_queue_add(impl->timer_queue, &impl->start_sap_retry_timer,
NULL, 1 * SPA_NSEC_PER_SEC,
on_start_sap_retry_timer_event, impl);
/* It is important to return 0 in this case. Otherwise, the nonzero return
* value will later be propagated through the core as an error. */
res = 0;
goto finish;
} else {
pw_log_error("failed to create socket: %s", spa_strerror(-fd));
/* If ENODEV was returned earlier, and the start_sap_retry_timer
* was consequently created, but then a non-ENODEV error occurred,
* the timer must be stopped and removed. */
pw_timer_queue_cancel(&impl->start_sap_retry_timer);
res = fd;
goto error;
}
}
/* Cleanup the timer in case ENODEV occurred earlier, and this time,
* the socket creation succeeded. */
pw_timer_queue_cancel(&impl->start_sap_retry_timer);
pw_net_get_ip(&impl->sap_addr, addr, sizeof(addr), NULL, NULL);
pw_log_info("starting SAP listener on %s", addr);
@ -1673,11 +1864,15 @@ static int start_sap(struct impl *impl)
goto error;
}
return 0;
rearm_igmp_recovery_timer(impl);
finish:
return res;
error:
if (fd > 0)
close(fd);
return res;
goto finish;
}
static void node_event_info(void *data, const struct pw_node_info *info)
@ -1807,7 +2002,9 @@ static void impl_destroy(struct impl *impl)
if (impl->core && impl->do_disconnect)
pw_core_disconnect(impl->core);
pw_timer_queue_cancel(&impl->timer);
pw_timer_queue_cancel(&impl->sap_send_timer);
pw_timer_queue_cancel(&impl->start_sap_retry_timer);
pw_timer_queue_cancel(&impl->igmp_recovery.timer);
if (impl->sap_source)
pw_loop_destroy_source(impl->loop, impl->sap_source);
@ -1821,7 +2018,7 @@ static void impl_destroy(struct impl *impl)
free(impl->extra_attrs_preamble);
free(impl->extra_attrs_end);
free(impl->ptp_mgmt_socket);
free(impl->ptp_mgmt_socket_path);
free(impl->ifname);
free(impl);
}
@ -1874,6 +2071,9 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
impl->ptp_fd = -1;
spa_list_init(&impl->sessions);
impl->igmp_recovery.socket_fd = -1;
impl->igmp_recovery.if_index = -1;
if (args == NULL)
args = "";
@ -1893,11 +2093,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
impl->ifname = str ? strdup(str) : NULL;
str = pw_properties_get(props, "ptp.management-socket");
impl->ptp_mgmt_socket = str ? strdup(str) : NULL;
impl->ptp_mgmt_socket_path = str ? strdup(str) : NULL;
// TODO: support UDP management access as well
if (impl->ptp_mgmt_socket)
impl->ptp_fd = make_unix_socket(impl->ptp_mgmt_socket);
if (impl->ptp_mgmt_socket_path)
impl->ptp_fd = make_unix_ptp_mgmt_socket(impl->ptp_mgmt_socket_path);
if ((str = pw_properties_get(props, "sap.ip")) == NULL)
str = DEFAULT_SAP_IP;
@ -1909,6 +2109,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
impl->cleanup_interval = pw_properties_get_uint32(impl->props,
"sap.cleanup.sec", DEFAULT_CLEANUP_SEC);
/* We will use half of the cleanup interval for IGMP deadline, minimum 1 second */
impl->igmp_recovery.deadline = SPA_MAX(impl->cleanup_interval / 2, 1u);
pw_log_info("using IGMP deadline of %" PRIu32 " second(s)",
impl->igmp_recovery.deadline);
impl->ttl = pw_properties_get_uint32(props, "net.ttl", DEFAULT_TTL);
impl->mcast_loop = pw_properties_get_bool(props, "net.loop", DEFAULT_LOOP);
impl->max_sessions = pw_properties_get_uint32(props, "sap.max-sessions", DEFAULT_MAX_SESSIONS);

View file

@ -1043,8 +1043,11 @@ on_data_io(void *data, int fd, uint32_t mask)
if (sess == NULL)
goto unknown_ssrc;
if (sess->data_ready && sess->receiving)
rtp_stream_receive_packet(sess->recv, buffer, len);
if (sess->data_ready && sess->receiving) {
uint64_t current_time = rtp_stream_get_nsec(sess->recv);
rtp_stream_receive_packet(sess->recv, buffer, len,
current_time);
}
}
}
return;

View file

@ -15,6 +15,7 @@
#include <net/if.h>
#include <ctype.h>
#include <spa/utils/atomic.h>
#include <spa/utils/hook.h>
#include <spa/utils/result.h>
#include <spa/utils/ringbuffer.h>
@ -156,6 +157,9 @@
PW_LOG_TOPIC(mod_topic, "mod." NAME);
#define PW_LOG_TOPIC_DEFAULT mod_topic
#define DEFAULT_IGMP_CHECK_INTERVAL_SEC 5
#define DEFAULT_IGMP_DEADLINE_SEC 30
#define DEFAULT_CLEANUP_SEC 60
#define DEFAULT_SOURCE_IP "224.0.0.56"
@ -180,6 +184,23 @@ static const struct spa_dict_item module_info[] = {
{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
};
struct igmp_recovery {
struct pw_timer timer;
int socket_fd;
struct sockaddr_storage mcast_addr;
socklen_t mcast_len;
uint32_t if_index;
bool is_ipv6;
/* This is the interval the recovery timer runs at. The timer
* checks at each interval if recovery is required. This value
* is defined by the igmp.check.interval.sec property. */
uint32_t check_interval;
/* This is the deadline for packets to arrive. If the deadline
* is exceeded, an IGMP recovery is attempted. This value is
* defined by the igmp.deadline.sec property. */
uint32_t deadline;
};
struct impl {
struct pw_impl_module *module;
struct spa_hook module_listener;
@ -201,6 +222,15 @@ struct impl {
bool always_process;
uint32_t cleanup_interval;
/* IGMP recovery (triggers when no RTP packets are
* received after the recovery deadline is reached) */
struct igmp_recovery igmp_recovery;
/* Monotonic timestamp of the last time a packet was
* received. This is accessed with atomic accessors
* to avoid race conditions. */
uint64_t last_packet_time;
struct pw_timer standby_timer;
/* This timer is used when the first stream_start() call fails because
* of an ENODEV error (see the stream_start() code for details) */
@ -227,13 +257,6 @@ struct impl {
bool waiting;
};
static inline uint64_t get_time_ns(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return SPA_TIMESPEC_TO_NSEC(&ts);
}
static int do_start(struct spa_loop *loop, bool async, uint32_t seq, const void *data,
size_t size, void *user_data)
{
@ -261,6 +284,9 @@ on_rtp_io(void *data, int fd, uint32_t mask)
struct impl *impl = data;
ssize_t len;
int suppressed;
uint64_t current_time;
current_time = rtp_stream_get_nsec(impl->stream);
if (mask & SPA_IO_IN) {
if ((len = recv(fd, impl->buffer, impl->buffer_size, 0)) < 0)
@ -270,10 +296,17 @@ on_rtp_io(void *data, int fd, uint32_t mask)
goto short_packet;
if (SPA_LIKELY(impl->stream)) {
if (rtp_stream_receive_packet(impl->stream, impl->buffer, len) < 0)
if (rtp_stream_receive_packet(impl->stream, impl->buffer, len,
current_time) < 0)
goto receive_error;
}
/* Update last packet timestamp for IGMP recovery.
* The recovery timer will check this to see if recovery
* is necessary. Do this _before_ invoking do_start()
* in case the stream is waking up from standby. */
SPA_ATOMIC_STORE(impl->last_packet_time, current_time);
if (SPA_ATOMIC_LOAD(impl->state) != STATE_RECEIVING) {
if (!SPA_ATOMIC_CAS(impl->state, STATE_PROBE, STATE_RECEIVING)) {
if (SPA_ATOMIC_CAS(impl->state, STATE_IDLE, STATE_RECEIVING))
@ -284,17 +317,148 @@ on_rtp_io(void *data, int fd, uint32_t mask)
return;
receive_error:
if ((suppressed = spa_ratelimit_test(&impl->rate_limit, get_time_ns())) >= 0)
if ((suppressed = spa_ratelimit_test(&impl->rate_limit, current_time)) >= 0)
pw_log_warn("(%d suppressed) recv() error: %m", suppressed);
return;
short_packet:
if ((suppressed = spa_ratelimit_test(&impl->rate_limit, get_time_ns())) >= 0)
if ((suppressed = spa_ratelimit_test(&impl->rate_limit, current_time)) >= 0)
pw_log_warn("(%d suppressed) short packet of len %zd received",
suppressed, len);
return;
}
static int make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname)
static int rejoin_igmp_group(struct spa_loop *loop, bool async, uint32_t seq,
const void *data, size_t size, void *user_data)
{
/* IMPORTANT: This must be run from within the data loop. */
int res;
struct impl *impl = user_data;
uint64_t current_time;
/* Force IGMP membership refresh by leaving the group first, then rejoin */
if (impl->igmp_recovery.is_ipv6) {
struct ipv6_mreq mr6;
memset(&mr6, 0, sizeof(mr6));
mr6.ipv6mr_multiaddr = ((struct sockaddr_in6*)&impl->igmp_recovery.mcast_addr)->sin6_addr;
mr6.ipv6mr_interface = impl->igmp_recovery.if_index;
/* Leave the group first */
res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP,
&mr6, sizeof(mr6));
if (SPA_LIKELY(res == 0)) {
pw_log_info("left IPv6 multicast group");
} else {
if (errno == EADDRNOTAVAIL) {
pw_log_info("attempted to leave IPv6 multicast group, but "
"membership was already silently dropped");
} else {
pw_log_warn("failed to leave IPv6 multicast group: %m");
}
}
res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
&mr6, sizeof(mr6));
if (res < 0) {
pw_log_warn("failed to re-join IPv6 multicast group: %m");
} else {
pw_log_info("re-joined IPv6 multicast group successfully");
}
} else {
struct ip_mreqn mr4;
memset(&mr4, 0, sizeof(mr4));
mr4.imr_multiaddr = ((struct sockaddr_in*)&impl->igmp_recovery.mcast_addr)->sin_addr;
mr4.imr_ifindex = impl->igmp_recovery.if_index;
/* Leave the group first */
res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP,
&mr4, sizeof(mr4));
if (SPA_LIKELY(res == 0)) {
pw_log_info("left IPv4 multicast group");
} else {
if (errno == EADDRNOTAVAIL) {
pw_log_info("attempted to leave IPv4 multicast group, but "
"membership was already silently dropped");
} else {
pw_log_warn("failed to leave IPv4 multicast group: %m");
}
}
res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&mr4, sizeof(mr4));
if (res < 0) {
pw_log_warn("failed to re-join IPv4 multicast group: %m");
} else {
pw_log_info("re-joined IPv4 multicast group successfully");
}
}
current_time = rtp_stream_get_nsec(impl->stream);
SPA_ATOMIC_STORE(impl->last_packet_time, current_time);
return res;
}
static void on_igmp_recovery_timer_event(void *data)
{
int res;
struct impl *impl = data;
char addr[128];
uint64_t current_time, elapsed_seconds, last_packet_time;
/* Only attempt recovery if we have a valid socket and multicast address */
if (SPA_UNLIKELY(impl->igmp_recovery.socket_fd < 0)) {
pw_log_trace("no socket, skipping IGMP recovery");
goto finish;
}
/* This check if performed even if standby = false or
* receiving != STATE_RECEIVING , because the very reason
* for these states may be that the receiver socket was
* silently kicked out of the IGMP group (which causes data
* to no longer arrive, thus leading to these states). */
current_time = rtp_stream_get_nsec(impl->stream);
last_packet_time = SPA_ATOMIC_LOAD(impl->last_packet_time);
elapsed_seconds = (current_time - last_packet_time) / SPA_NSEC_PER_SEC;
/* Only trigger recovery if enough time has elapsed since last packet */
if (elapsed_seconds < impl->igmp_recovery.deadline) {
pw_log_trace("IGMP recovery check: %" PRIu64 " seconds elapsed, "
"need %" PRIu32 " seconds", elapsed_seconds,
impl->igmp_recovery.deadline);
goto finish;
}
pw_net_get_ip(&impl->igmp_recovery.mcast_addr, addr, sizeof(addr), NULL, NULL);
pw_log_info("starting IGMP recovery for %s", addr);
/* Run the actual recovery in the data loop, since recovery involves
* rejoining the socket to the IGMP group. By running this in the
* data loop, race conditions due to stray packets causing an on_rtp_io()
* invocation at the same time when the IGMP group rejoining takes place
* is avoided, since on_rtp_io() too runs in the data loop.
* This is a blocking call to make sure the rejoin attempt was fully
* done by the time this callback ends. (rejoin_igmp_group() does not
* do work that takes a long time to finish. )*/
res = pw_loop_locked(impl->data_loop, rejoin_igmp_group, 1, NULL, 0, impl);
if (SPA_LIKELY(res == 0)) {
pw_log_info("IGMP recovery for %s finished", addr);
} else {
pw_log_error("error while finishing IGMP recovery for %s: %s",
addr, spa_strerror(res));
}
finish:
pw_timer_queue_add(impl->timer_queue, &impl->igmp_recovery.timer,
&impl->igmp_recovery.timer.timeout,
impl->igmp_recovery.check_interval * SPA_NSEC_PER_SEC,
on_igmp_recovery_timer_event, impl);
}
static int make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname,
struct igmp_recovery *igmp_recovery)
{
int af, fd, val, res;
struct ifreq req;
@ -374,6 +538,16 @@ static int make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname)
goto error;
}
/* Store multicast info for recovery */
igmp_recovery->socket_fd = fd;
igmp_recovery->mcast_addr = ba;
igmp_recovery->mcast_len = salen;
igmp_recovery->if_index = req.ifr_ifindex;
igmp_recovery->is_ipv6 = (af == AF_INET6);
pw_log_debug("stored %s multicast info: socket_fd=%d, "
"if_index=%d", igmp_recovery->is_ipv6 ?
"IPv6" : "IPv4", fd, req.ifr_ifindex);
if (bind(fd, (struct sockaddr*)&ba, salen) < 0) {
res = -errno;
pw_log_error("bind() failed: %m");
@ -422,7 +596,8 @@ static void stream_open_connection(void *data, int *result)
pw_log_info("starting RTP listener");
if ((fd = make_socket((const struct sockaddr *)&impl->src_addr,
impl->src_len, impl->ifname)) < 0) {
impl->src_len, impl->ifname,
&(impl->igmp_recovery))) < 0) {
/* If make_socket() tries to create a socket and join to a multicast
* group while the network interfaces are not ready yet to do so
* (usually because a network manager component is still setting up
@ -433,7 +608,7 @@ static void stream_open_connection(void *data, int *result)
* stream_start() call after some time. The stream_start_retry_timer exists
* precisely for that purpose. This means that ENODEV is not treated as
* an error, but instead, it triggers the creation of that timer. */
if (errno == ENODEV) {
if (fd == -ENODEV) {
pw_log_warn("failed to create socket because network device is not ready "
"and present yet; will try again");
@ -449,12 +624,12 @@ static void stream_open_connection(void *data, int *result)
res = 0;
goto finish;
} else {
pw_log_error("failed to create socket: %m");
pw_log_error("failed to create socket: %s", spa_strerror(fd));
/* If ENODEV was returned earlier, and the stream_start_retry_timer
* was consequently created, but then a non-ENODEV error occurred,
* the timer must be stopped and removed. */
pw_timer_queue_cancel(&impl->stream_start_retry_timer);
res = -errno;
res = fd;
goto finish;
}
}
@ -472,6 +647,13 @@ static void stream_open_connection(void *data, int *result)
goto finish;
}
if ((res = pw_timer_queue_add(impl->timer_queue, &impl->igmp_recovery.timer,
NULL, impl->igmp_recovery.check_interval * SPA_NSEC_PER_SEC,
on_igmp_recovery_timer_event, impl)) < 0) {
pw_log_error("can't add timer: %s", spa_strerror(res));
goto finish;
}
finish:
if (res != 0) {
pw_log_error("failed to start RTP stream: %s", spa_strerror(res));
@ -495,6 +677,7 @@ static void stream_close_connection(void *data, int *result)
pw_log_info("stopping RTP listener");
pw_timer_queue_cancel(&impl->stream_start_retry_timer);
pw_timer_queue_cancel(&impl->igmp_recovery.timer);
pw_loop_destroy_source(impl->data_loop, impl->source);
impl->source = NULL;
@ -633,6 +816,7 @@ static void impl_destroy(struct impl *impl)
pw_timer_queue_cancel(&impl->standby_timer);
pw_timer_queue_cancel(&impl->stream_start_retry_timer);
pw_timer_queue_cancel(&impl->igmp_recovery.timer);
if (impl->data_loop)
pw_context_release_loop(impl->context, impl->data_loop);
@ -797,9 +981,20 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
* till we make it (or get timed out) */
pw_properties_set(stream_props, "rtp.receiving", "true");
impl->cleanup_interval = pw_properties_get_uint32(props,
impl->cleanup_interval = pw_properties_get_uint32(stream_props,
"cleanup.sec", DEFAULT_CLEANUP_SEC);
impl->igmp_recovery.check_interval = SPA_MAX(pw_properties_get_uint32(stream_props,
"igmp.check.interval.sec",
DEFAULT_IGMP_CHECK_INTERVAL_SEC), 1u);
pw_log_info("using IGMP check interval of %" PRIu32 " second(s)",
impl->igmp_recovery.check_interval);
impl->igmp_recovery.deadline = SPA_MAX(pw_properties_get_uint32(stream_props,
"igmp.deadline.sec", DEFAULT_IGMP_DEADLINE_SEC), 5u);
pw_log_info("using IGMP deadline of %" PRIu32 " second(s)",
impl->igmp_recovery.deadline);
impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core);
if (impl->core == NULL) {
str = pw_properties_get(props, PW_KEY_REMOTE_NAME);

View file

@ -233,7 +233,8 @@ static void rtp_audio_process_playback(void *data)
pw_stream_queue_buffer(impl->stream, buf);
}
static int rtp_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len)
static int rtp_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len,
uint64_t current_time)
{
struct rtp_header *hdr;
ssize_t hlen, plen;
@ -273,7 +274,7 @@ static int rtp_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len)
timestamp = ntohl(hdr->timestamp) - impl->ts_offset;
impl->receiving = true;
impl->last_recv_timestamp = pw_stream_get_nsec(impl->stream);
impl->last_recv_timestamp = current_time;
plen = len - hlen;
samples = plen / stride;

View file

@ -318,7 +318,8 @@ static int rtp_midi_receive_midi(struct impl *impl, uint8_t *packet, uint32_t ti
return 0;
}
static int rtp_midi_receive(struct impl *impl, uint8_t *buffer, ssize_t len)
static int rtp_midi_receive(struct impl *impl, uint8_t *buffer, ssize_t len,
uint64_t current_time)
{
struct rtp_header *hdr;
ssize_t hlen;

View file

@ -99,7 +99,8 @@ static void rtp_opus_process_playback(void *data)
pw_stream_queue_buffer(impl->stream, buf);
}
static int rtp_opus_receive(struct impl *impl, uint8_t *buffer, ssize_t len)
static int rtp_opus_receive(struct impl *impl, uint8_t *buffer, ssize_t len,
uint64_t current_time)
{
struct rtp_header *hdr;
ssize_t hlen, plen;

View file

@ -151,7 +151,8 @@ struct impl {
* access below for the reason why. */
uint8_t timer_running;
int (*receive_rtp)(struct impl *impl, uint8_t *buffer, ssize_t len);
int (*receive_rtp)(struct impl *impl, uint8_t *buffer, ssize_t len,
uint64_t current_time);
/* Used for resetting the ring buffer before the stream starts, to prevent
* reading from uninitialized memory. This can otherwise happen in direct
* timestamp mode when the read index is set to an uninitialized location.
@ -1036,10 +1037,17 @@ int rtp_stream_update_properties(struct rtp_stream *s, const struct spa_dict *di
return pw_stream_update_properties(impl->stream, dict);
}
int rtp_stream_receive_packet(struct rtp_stream *s, uint8_t *buffer, size_t len)
int rtp_stream_receive_packet(struct rtp_stream *s, uint8_t *buffer, size_t len,
uint64_t current_time)
{
struct impl *impl = (struct impl*)s;
return impl->receive_rtp(impl, buffer, len);
return impl->receive_rtp(impl, buffer, len, current_time);
}
uint64_t rtp_stream_get_nsec(struct rtp_stream *s)
{
struct impl *impl = (struct impl*)s;
return pw_stream_get_nsec(impl->stream);
}
uint64_t rtp_stream_get_time(struct rtp_stream *s, uint32_t *rate)

View file

@ -62,7 +62,10 @@ void rtp_stream_destroy(struct rtp_stream *s);
int rtp_stream_update_properties(struct rtp_stream *s, const struct spa_dict *dict);
int rtp_stream_receive_packet(struct rtp_stream *s, uint8_t *buffer, size_t len);
int rtp_stream_receive_packet(struct rtp_stream *s, uint8_t *buffer, size_t len,
uint64_t current_time);
uint64_t rtp_stream_get_nsec(struct rtp_stream *s);
uint64_t rtp_stream_get_time(struct rtp_stream *s, uint32_t *rate);

View file

@ -609,7 +609,7 @@ static void test_array(const char *str, const char * const vals[])
spa_json_init(&it[0], str, strlen(str));
if (spa_json_enter_array(&it[0], &it[1]) <= 0)
spa_json_init(&it[1], str, strlen(str));
spa_json_init_relax(&it[1], '[', str, strlen(str));
for (i = 0; vals[i]; i++) {
pwtest_int_gt(spa_json_get_string(&it[1], val, sizeof(val)), 0);
pwtest_str_eq(val, vals[i]);
@ -624,6 +624,7 @@ PWTEST(json_array)
test_array("[FL FR]", (const char *[]){ "FL", "FR", NULL });
test_array("FL FR", (const char *[]){ "FL", "FR", NULL });
test_array("[ FL FR ]", (const char *[]){ "FL", "FR", NULL });
test_array("FL FR FC", (const char *[]){ "FL", "FR", "FC", NULL });
return PWTEST_PASS;
}