mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
Compare commits
18 commits
c6d0b364ab
...
9f3c553298
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f3c553298 | ||
|
|
a813830024 | ||
|
|
a837dcd40b | ||
|
|
752de866ae | ||
|
|
ec11859a48 | ||
|
|
1096d63468 | ||
|
|
955c9ae837 | ||
|
|
3e0f4daf60 | ||
|
|
5d21e12658 | ||
|
|
f1ffd5e5e8 | ||
|
|
80e7302a05 | ||
|
|
b57bd00be0 | ||
|
|
c1e737bbe4 | ||
|
|
76a31a47c2 | ||
|
|
23c449af5d | ||
|
|
94d0d8bc09 | ||
|
|
0276bb5b06 | ||
|
|
614186a590 |
19 changed files with 1060 additions and 291 deletions
341
po/tr.po
341
po/tr.po
|
|
@ -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)"
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -871,6 +871,7 @@ static void show_help(struct data *data, const char *name, bool error)
|
|||
" -o, --output List output ports\n"
|
||||
" -i, --input List input ports\n"
|
||||
" -l, --links List links\n"
|
||||
" -t, --latency List port latencies\n"
|
||||
" -m, --monitor Monitor links and ports\n"
|
||||
" -I, --id List IDs\n"
|
||||
" -v, --verbose Verbose port properties\n"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue