mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2025-10-29 05:40:23 -04:00
Compare commits
22 commits
474e3337da
...
16203c5733
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16203c5733 | ||
|
|
eee0e8f22f | ||
|
|
b50c28af2c | ||
|
|
210f4742e7 | ||
|
|
53532e63bf | ||
|
|
6834e0041c | ||
|
|
329c05b04f | ||
|
|
76320675d8 | ||
|
|
d5b58d29ea | ||
|
|
511926ab4b | ||
|
|
f6e1124942 | ||
|
|
c7db9d60a6 | ||
|
|
7493990c84 | ||
|
|
79455f69f7 | ||
|
|
a1fef186d0 | ||
|
|
4fa2e83ac3 | ||
|
|
f081327511 | ||
|
|
d3f1e217e7 | ||
|
|
5a3b45cda1 | ||
|
|
b4b3889f3c | ||
|
|
31b6ecd507 | ||
|
|
fa8943c80e |
28 changed files with 990 additions and 769 deletions
35
po/ar.po
35
po/ar.po
|
|
@ -9,9 +9,8 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/"
|
||||
"issues/new\n"
|
||||
"POT-Creation-Date: 2022-06-18 09:49+0300\n"
|
||||
"PO-Revision-Date: 2025-04-27 12:48+0000\n"
|
||||
"Last-Translator: Weblate Translation Memory <noreply-mt-weblate-translation-"
|
||||
"memory@weblate.org>\n"
|
||||
"PO-Revision-Date: 2025-05-08 08:25+0000\n"
|
||||
"Last-Translator: Zayed Al-Saidi <zayed.alsaidi@gmail.com>\n"
|
||||
"Language-Team: Arabic <https://translate.fedoraproject.org/projects/"
|
||||
"pulseaudio/pulseaudio/ar/>\n"
|
||||
"Language: ar\n"
|
||||
|
|
@ -20,7 +19,7 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
|
||||
"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
|
||||
"X-Generator: Weblate 5.11\n"
|
||||
"X-Generator: Weblate 5.11.3\n"
|
||||
|
||||
#: src/daemon/cmdline.c:113
|
||||
#, c-format
|
||||
|
|
@ -462,7 +461,7 @@ msgstr "نظام صوت بلس أوديو"
|
|||
|
||||
#: src/daemon/pulseaudio.desktop.in:5
|
||||
msgid "Start the PulseAudio Sound System"
|
||||
msgstr ""
|
||||
msgstr "ابدأ نظام صوت بلس أوديو"
|
||||
|
||||
#: src/modules/alsa/alsa-mixer.c:2708
|
||||
msgid "Input"
|
||||
|
|
@ -525,27 +524,27 @@ msgstr "لا يوجد التحكم في الرفع الصوت آلي"
|
|||
|
||||
#: src/modules/alsa/alsa-mixer.c:2722
|
||||
msgid "Boost"
|
||||
msgstr ""
|
||||
msgstr "تعزيز"
|
||||
|
||||
#: src/modules/alsa/alsa-mixer.c:2723
|
||||
msgid "No Boost"
|
||||
msgstr ""
|
||||
msgstr "دون تعزيز"
|
||||
|
||||
#: src/modules/alsa/alsa-mixer.c:2724
|
||||
msgid "Amplifier"
|
||||
msgstr ""
|
||||
msgstr "مُضَخِّم"
|
||||
|
||||
#: src/modules/alsa/alsa-mixer.c:2725
|
||||
msgid "No Amplifier"
|
||||
msgstr ""
|
||||
msgstr "دون مُضَخِّم"
|
||||
|
||||
#: src/modules/alsa/alsa-mixer.c:2726
|
||||
msgid "Bass Boost"
|
||||
msgstr ""
|
||||
msgstr "تعزيز القرار"
|
||||
|
||||
#: src/modules/alsa/alsa-mixer.c:2727
|
||||
msgid "No Bass Boost"
|
||||
msgstr ""
|
||||
msgstr "دون تعزيز القرار"
|
||||
|
||||
#: src/modules/alsa/alsa-mixer.c:2728 src/modules/bluetooth/module-bluez5-device.c:1964
|
||||
#: src/utils/pactl.c:333
|
||||
|
|
@ -1046,7 +1045,7 @@ msgstr ""
|
|||
|
||||
#: src/modules/reserve-wrap.c:149
|
||||
msgid "PulseAudio Sound Server"
|
||||
msgstr ""
|
||||
msgstr "خادم صوت بلس أوديو"
|
||||
|
||||
#: src/pulse/channelmap.c:105
|
||||
msgid "Front Center"
|
||||
|
|
@ -1074,7 +1073,7 @@ msgstr "خلف يمين"
|
|||
|
||||
#: src/pulse/channelmap.c:113
|
||||
msgid "Subwoofer"
|
||||
msgstr "مضخم صوت"
|
||||
msgstr "مضخم الترددات المنخفضة"
|
||||
|
||||
#: src/pulse/channelmap.c:115
|
||||
msgid "Front Left-of-center"
|
||||
|
|
@ -1332,7 +1331,7 @@ msgstr "نعم"
|
|||
|
||||
#: src/pulsecore/core-util.h:97
|
||||
msgid "no"
|
||||
msgstr ""
|
||||
msgstr "لا"
|
||||
|
||||
#: src/pulsecore/lock-autospawn.c:141 src/pulsecore/lock-autospawn.c:227
|
||||
msgid "Cannot access autospawn lock."
|
||||
|
|
@ -1370,7 +1369,7 @@ msgstr "ممنوع الوصول"
|
|||
|
||||
#: src/pulse/error.c:40
|
||||
msgid "Unknown command"
|
||||
msgstr ""
|
||||
msgstr "أمر مجهول"
|
||||
|
||||
#: src/pulse/error.c:41
|
||||
msgid "Invalid argument"
|
||||
|
|
@ -1402,7 +1401,7 @@ msgstr ""
|
|||
|
||||
#: src/pulse/error.c:48
|
||||
msgid "Internal error"
|
||||
msgstr "خطأ داخلي."
|
||||
msgstr "خطأ داخلي"
|
||||
|
||||
#: src/pulse/error.c:49
|
||||
msgid "Connection terminated"
|
||||
|
|
@ -1438,7 +1437,7 @@ msgstr ""
|
|||
|
||||
#: src/pulse/error.c:57
|
||||
msgid "Not supported"
|
||||
msgstr "غير معتمد"
|
||||
msgstr "غير مدعوم"
|
||||
|
||||
#: src/pulse/error.c:58
|
||||
msgid "Unknown error code"
|
||||
|
|
@ -1466,7 +1465,7 @@ msgstr "خطأ في الإدخال/الإخراج"
|
|||
|
||||
#: src/pulse/error.c:64
|
||||
msgid "Device or resource busy"
|
||||
msgstr ""
|
||||
msgstr "الجهاز أو المورد مشغول"
|
||||
|
||||
#: src/pulse/sample.c:179
|
||||
#, c-format
|
||||
|
|
|
|||
12
po/el.po
12
po/el.po
|
|
@ -9,8 +9,8 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/"
|
||||
"issues/new\n"
|
||||
"POT-Creation-Date: 2022-06-18 09:49+0300\n"
|
||||
"PO-Revision-Date: 2024-09-04 16:38+0000\n"
|
||||
"Last-Translator: Giannis Antypas <gianni.antypas@gmail.com>\n"
|
||||
"PO-Revision-Date: 2025-06-22 10:49+0000\n"
|
||||
"Last-Translator: Jim Spentzos <jimspentzos2000@gmail.com>\n"
|
||||
"Language-Team: Greek <https://translate.fedoraproject.org/projects/"
|
||||
"pulseaudio/pulseaudio/el/>\n"
|
||||
"Language: el\n"
|
||||
|
|
@ -18,7 +18,7 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.7.1\n"
|
||||
"X-Generator: Weblate 5.12.2\n"
|
||||
|
||||
#: src/daemon/cmdline.c:113
|
||||
#, c-format
|
||||
|
|
@ -359,7 +359,7 @@ msgstr "Περιγραφή: %s\n"
|
|||
#: src/daemon/dumpmodules.c:67
|
||||
#, c-format
|
||||
msgid "Author: %s\n"
|
||||
msgstr "Συγγραφέας: %s\n"
|
||||
msgstr "Δημιουργός: %s\n"
|
||||
|
||||
#: src/daemon/dumpmodules.c:69
|
||||
#, c-format
|
||||
|
|
@ -2298,9 +2298,7 @@ msgstr "ΠΛΑΙΣΙΑ"
|
|||
|
||||
#: src/utils/pacmd.c:80 src/utils/pactl.c:2659
|
||||
msgid "RECIPIENT MESSAGE [MESSAGE_PARAMETERS]"
|
||||
msgstr ""
|
||||
"ΜΗΝΥΜΑ ΠΑΡΑΛΗΠΤΗ\n"
|
||||
"[ΠΑΡΑΜΕΤΡΟΙ_ΜΗΝΥΜΑΤΟΣ]"
|
||||
msgstr "ΜΗΝΥΜΑ ΠΑΡΑΛΗΠΤΗ [MESSAGE_PARAMETERS]"
|
||||
|
||||
#: src/utils/pacmd.c:82
|
||||
#, c-format
|
||||
|
|
|
|||
59
po/es.po
59
po/es.po
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/"
|
||||
"issues/new\n"
|
||||
"POT-Creation-Date: 2022-06-18 09:49+0300\n"
|
||||
"PO-Revision-Date: 2022-12-18 11:19+0000\n"
|
||||
"Last-Translator: Toni Estevez <toni.estevez@gmail.com>\n"
|
||||
"PO-Revision-Date: 2025-07-29 09:34+0000\n"
|
||||
"Last-Translator: \"Fco. Javier F. Serrador\" <fserrador@gmail.com>\n"
|
||||
"Language-Team: Spanish <https://translate.fedoraproject.org/projects/"
|
||||
"pulseaudio/pulseaudio/es/>\n"
|
||||
"Language: es\n"
|
||||
|
|
@ -23,7 +23,7 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.15\n"
|
||||
"X-Generator: Weblate 5.12.2\n"
|
||||
"X-Poedit-Language: Spanish\n"
|
||||
|
||||
#: src/daemon/cmdline.c:113
|
||||
|
|
@ -97,7 +97,7 @@ msgid ""
|
|||
"\n"
|
||||
" -n Don't load default script file\n"
|
||||
msgstr ""
|
||||
"%s [opciones]\n"
|
||||
"--dl-search-path%s [opciones]\n"
|
||||
"\n"
|
||||
"ÓRDENES:\n"
|
||||
" -h, --help Mostrar esta ayuda\n"
|
||||
|
|
@ -123,8 +123,8 @@ msgstr ""
|
|||
"inicio\n"
|
||||
" --fail[=BOOL] Salir cuando falla el inicio\n"
|
||||
" --high-priority[=BOOL] Intentar asignar una prioridad alta\n"
|
||||
" (solo disponible como superusuario, "
|
||||
"con SUID\n"
|
||||
" (solo disponible como admin, con "
|
||||
"SUID\n"
|
||||
" o con un valor RLIMIT_NICE elevado)\n"
|
||||
" --realtime[=BOOL] Intentar activar la programación en "
|
||||
"tiempo real\n"
|
||||
|
|
@ -136,10 +136,10 @@ msgstr ""
|
|||
" del usuario tras el inicio\n"
|
||||
" --disallow-exit[=BOOL] Denegar la salida a petición del "
|
||||
"usuario\n"
|
||||
" --exit-idle-time=SEGUNDOS Finalizar el servicio cuando esté "
|
||||
" --exit-idle-time=SEGS Finalizar el servicio cuando esté "
|
||||
"inactivo y\n"
|
||||
" haya pasado este tiempo\n"
|
||||
" --scache-idle-time=SEGUNDOS Descargar las muestras cargadas "
|
||||
" --scache-idle-time=SEGS Descargar las muestras cargadas "
|
||||
"automáticamente\n"
|
||||
" cuando esté inactivo y haya pasado "
|
||||
"este tiempo\n"
|
||||
|
|
@ -148,7 +148,7 @@ msgstr ""
|
|||
" -v --verbose Aumentar el nivel de registro\n"
|
||||
" --log-target={auto,syslog,stderr,file:RUTA,newfile:RUTA}\n"
|
||||
" Especificar el destino del registro\n"
|
||||
" --log-meta[=BOOL] Incluir ubicaciones del código en el "
|
||||
" --log-meta[=BOOL] Incluir lugares del código en el "
|
||||
"registro\n"
|
||||
" --log-time[=BOOL] Incluir marcas de tiempo en el "
|
||||
"registro\n"
|
||||
|
|
@ -156,16 +156,19 @@ msgstr ""
|
|||
"registro\n"
|
||||
" -p, --dl-search-path=RUTA Configurar la ruta de búsqueda de "
|
||||
"objetos\n"
|
||||
" dinámicos compartidos (complementos)"
|
||||
"\n"
|
||||
" "
|
||||
"dinámicos compartidos (complementos)\n"
|
||||
" --resample-method=MÉTODO Usar el método de remuestreo "
|
||||
"especificado\n"
|
||||
" "
|
||||
"(Consulte los valores posibles con\n"
|
||||
" "
|
||||
"--dump-resample-methods)\n"
|
||||
" --use-pid-file[=BOOL] Crear un archivo PID\n"
|
||||
" --no-cpu-limit[=BOOL] No instalar el limitador de carga de "
|
||||
"la CPU\n"
|
||||
" en las plataformas compatibles.\n"
|
||||
" en las "
|
||||
"plataformas compatibles.\n"
|
||||
" --disable-shm[=BOOL] Desactivar el uso de memoria "
|
||||
"compartida\n"
|
||||
" --enable-memfd[=BOOL] Activar el uso de memoria compartida "
|
||||
|
|
@ -174,10 +177,12 @@ msgstr ""
|
|||
"GUION DE INICIO:\n"
|
||||
" -L, --load=\"ARGUMENTO DEL MÓDULO\" Cargar el módulo del complemento "
|
||||
"especificado\n"
|
||||
" "
|
||||
"con el argumento especificado\n"
|
||||
" -F, --file=ARCHIVO Ejecutar el guion especificado\n"
|
||||
" -C Abrir una línea de órdenes en el TTY "
|
||||
"en\n"
|
||||
" -F, --file=ARCHIVO Ejecutar el guion "
|
||||
"especificado\n"
|
||||
" -C Abrir una línea de "
|
||||
"órdenes en el TTY en\n"
|
||||
" ejecución tras el inicio\n"
|
||||
" -n No cargar el archivo de órdenes "
|
||||
"predeterminado\n"
|
||||
|
|
@ -195,9 +200,9 @@ msgid ""
|
|||
"--log-level expects log level argument (either numeric in range 0..4 or one "
|
||||
"of error, warn, notice, info, debug)."
|
||||
msgstr ""
|
||||
"--log-level espera un argumento para el nivel de registro (un valor numérico "
|
||||
"en el rango de 0-4 o bien uno de estos valores: error, warn, notice, info, "
|
||||
"debug)."
|
||||
"--log-level espera un argumento para el nivel de bitácora (un valor numérico "
|
||||
"en el intervalo de 0..4 o bien uno de estos valores: error, warn, notice, "
|
||||
"info, debug)."
|
||||
|
||||
#: src/daemon/cmdline.c:277
|
||||
msgid "--high-priority expects boolean argument"
|
||||
|
|
@ -217,7 +222,7 @@ msgstr "--disallow-exit espera un argumento booleano"
|
|||
|
||||
#: src/daemon/cmdline.c:309
|
||||
msgid "--use-pid-file expects boolean argument"
|
||||
msgstr "--use pid-file espera un argumento booleano"
|
||||
msgstr "--use-pid-file espera un argumento booleano"
|
||||
|
||||
#: src/daemon/cmdline.c:328
|
||||
msgid ""
|
||||
|
|
@ -750,11 +755,11 @@ msgstr "Salida de juego"
|
|||
|
||||
#: src/modules/alsa/alsa-mixer.c:2819 src/modules/alsa/alsa-mixer.c:2820
|
||||
msgid "Chat Output"
|
||||
msgstr "Salida de chat"
|
||||
msgstr "Salida de charla"
|
||||
|
||||
#: src/modules/alsa/alsa-mixer.c:2821
|
||||
msgid "Chat Input"
|
||||
msgstr "Entrada de chat"
|
||||
msgstr "Entrada de charla"
|
||||
|
||||
#: src/modules/alsa/alsa-mixer.c:2822
|
||||
msgid "Virtual Surround 7.1"
|
||||
|
|
@ -1546,10 +1551,10 @@ msgid ""
|
|||
"e.g. happen if you try to connect to a non-root PulseAudio as a root user, "
|
||||
"over the native protocol. Don't do that.)"
|
||||
msgstr ""
|
||||
"XDG_RUNTIME_DIR (%s) no es de nuestra propiedad (usuario %d), sino del "
|
||||
"usuario %d. (Esto puede pasar, por ejemplo, al intentar conectarse como "
|
||||
"superusuario a un servidor PulseAudio que se ejecuta sin privilegios de "
|
||||
"administrador mediante el protocolo nativo. No lo haga.)"
|
||||
"XDG_RUNTIME_DIR (%s) no es de nuestra propiedad (uid %d), sino del usuario "
|
||||
"%d. (Esto puede pasar, por ejemplo, al intentar conectarse como superusuario "
|
||||
"a un servidor PulseAudio que se ejecuta sin privilegios de administrador "
|
||||
"mediante el protocolo nativo. No lo haga.)"
|
||||
|
||||
#: src/pulsecore/core-util.h:97
|
||||
msgid "yes"
|
||||
|
|
@ -1742,7 +1747,7 @@ msgstr "pa_stream_drain(): %s"
|
|||
#: src/utils/pacat.c:194 src/utils/pacat.c:543
|
||||
#, c-format
|
||||
msgid "pa_stream_begin_write() failed: %s"
|
||||
msgstr "Ha fallado pa_stream_write(): %s"
|
||||
msgstr "Ha fallado pa_stream_begin_write(): %s"
|
||||
|
||||
#: src/utils/pacat.c:244 src/utils/pacat.c:274
|
||||
#, c-format
|
||||
|
|
@ -2285,7 +2290,7 @@ msgstr "NOMBRE|NÚMERO PUERTO"
|
|||
|
||||
#: src/utils/pacmd.c:74 src/utils/pactl.c:2658
|
||||
msgid "CARD-NAME|CARD-#N PORT OFFSET"
|
||||
msgstr "NOMBRE-TARJETA|NÚMERO-TARJETA PUERTO COMPENSACIÓN"
|
||||
msgstr "NOMBRE-TARJETA|Nº-TARJETA PUERTO COMPENSACIÓN"
|
||||
|
||||
#: src/utils/pacmd.c:75
|
||||
msgid "TARGET"
|
||||
|
|
|
|||
9
po/it.po
9
po/it.po
|
|
@ -12,7 +12,7 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/"
|
||||
"issues/new\n"
|
||||
"POT-Creation-Date: 2022-06-18 09:49+0300\n"
|
||||
"PO-Revision-Date: 2025-04-18 18:51+0000\n"
|
||||
"PO-Revision-Date: 2025-06-08 14:42+0000\n"
|
||||
"Last-Translator: Salvatore Cocuzza <info@salvatorecocuzza.it>\n"
|
||||
"Language-Team: Italian <https://translate.fedoraproject.org/projects/"
|
||||
"pulseaudio/pulseaudio/it/>\n"
|
||||
|
|
@ -21,7 +21,7 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.11\n"
|
||||
"X-Generator: Weblate 5.11.4\n"
|
||||
|
||||
# mamma mia che impressione
|
||||
#: src/daemon/cmdline.c:113
|
||||
|
|
@ -1245,7 +1245,6 @@ msgid "Virtual surround sink"
|
|||
msgstr "Sink surround virtuale"
|
||||
|
||||
#: src/modules/module-virtual-surround-sink.c:54
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"sink_name=<name for the sink> sink_properties=<properties for the sink> "
|
||||
"master=<name of sink to filter> sink_master=<name of sink to filter> "
|
||||
|
|
@ -2418,7 +2417,7 @@ msgstr "Recupero delle informazioni del server non riuscito: %s"
|
|||
#: src/utils/pactl.c:224 src/utils/pactl.c:236
|
||||
#, c-format
|
||||
msgid "%s\n"
|
||||
msgstr ""
|
||||
msgstr "%s\n"
|
||||
|
||||
#: src/utils/pactl.c:281
|
||||
#, c-format
|
||||
|
|
@ -2489,7 +2488,6 @@ msgid "Mic"
|
|||
msgstr "Mic"
|
||||
|
||||
#: src/utils/pactl.c:338
|
||||
#, fuzzy
|
||||
msgid "Handset"
|
||||
msgstr "Cuffie con microfono"
|
||||
|
||||
|
|
@ -2584,7 +2582,6 @@ msgid "\t\t%s: %s (type: %s, priority: %u%s%s, %s)\n"
|
|||
msgstr "\t\t%s: %s (tipo: %s, priorità: %u%s%s, %s)\n"
|
||||
|
||||
#: src/utils/pactl.c:710 src/utils/pactl.c:894 src/utils/pactl.c:1256
|
||||
#, fuzzy
|
||||
msgid ", availability group: "
|
||||
msgstr ", gruppo disponibilità: "
|
||||
|
||||
|
|
|
|||
24
po/ka.po
24
po/ka.po
|
|
@ -9,7 +9,7 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/"
|
||||
"issues/new\n"
|
||||
"POT-Creation-Date: 2022-06-18 09:49+0300\n"
|
||||
"PO-Revision-Date: 2025-04-13 20:52+0000\n"
|
||||
"PO-Revision-Date: 2025-07-21 10:49+0000\n"
|
||||
"Last-Translator: Temuri Doghonadze <temuri.doghonadze@gmail.com>\n"
|
||||
"Language-Team: Georgian <https://translate.fedoraproject.org/projects/"
|
||||
"pulseaudio/pulseaudio/ka/>\n"
|
||||
|
|
@ -18,7 +18,7 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.10.4\n"
|
||||
"X-Generator: Weblate 5.12.2\n"
|
||||
|
||||
#: src/daemon/cmdline.c:113
|
||||
#, c-format
|
||||
|
|
@ -328,7 +328,7 @@ msgstr ""
|
|||
#: src/daemon/main.c:287 src/daemon/main.c:292
|
||||
#, c-format
|
||||
msgid "Failed to create '%s': %s"
|
||||
msgstr "შეცდომა %s-ის გახსნისას: %s"
|
||||
msgstr "'%s'-ის შექმნა ჩავარდა: %s"
|
||||
|
||||
#: src/daemon/main.c:299
|
||||
#, c-format
|
||||
|
|
@ -554,11 +554,11 @@ msgstr "გამაძლიერებლის გარეშე"
|
|||
|
||||
#: src/modules/alsa/alsa-mixer.c:2726
|
||||
msgid "Bass Boost"
|
||||
msgstr "Bass-ის გაძლიერება"
|
||||
msgstr "ბასის გაძლიერება"
|
||||
|
||||
#: src/modules/alsa/alsa-mixer.c:2727
|
||||
msgid "No Bass Boost"
|
||||
msgstr "Bass-ის გაძლიერების გარეშე"
|
||||
msgstr "ბასის გაძლიერების გარეშე"
|
||||
|
||||
#: src/modules/alsa/alsa-mixer.c:2728
|
||||
#: src/modules/bluetooth/module-bluez5-device.c:1964 src/utils/pactl.c:333
|
||||
|
|
@ -1338,7 +1338,7 @@ msgstr "xcb_connect() ჩავარდა"
|
|||
|
||||
#: src/pulse/client-conf-x11.c:66 src/utils/pax11publish.c:102
|
||||
msgid "xcb_connection_has_error() returned true"
|
||||
msgstr "xcb_connection_has_error()-მა 1 დააბრუნა"
|
||||
msgstr "xcb_connection_has_error()-მა დააბრუნა ჭეშმარიტი მნისვნელობა"
|
||||
|
||||
#: src/pulse/client-conf-x11.c:102
|
||||
msgid "Failed to parse cookie data"
|
||||
|
|
@ -2112,8 +2112,8 @@ msgstr "სტატისტიკის მიღების შეცდო
|
|||
#, c-format
|
||||
msgid "Currently in use: %u block containing %s bytes total.\n"
|
||||
msgid_plural "Currently in use: %u blocks containing %s bytes total.\n"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "ამჟამად გამოიყენება: %u ბლოკი, რომელიც შეიცავს სულ %s ბაიტს.\n"
|
||||
msgstr[1] "ამჟამად გამოიყენება: %u ბლოკი, რომელიც შეიცავს სულ %s ბაიტს.\n"
|
||||
|
||||
#: src/utils/pactl.c:205
|
||||
#, c-format
|
||||
|
|
@ -2121,7 +2121,11 @@ msgid "Allocated during whole lifetime: %u block containing %s bytes total.\n"
|
|||
msgid_plural ""
|
||||
"Allocated during whole lifetime: %u blocks containing %s bytes total.\n"
|
||||
msgstr[0] ""
|
||||
"გამოყოფილია მთელი სიცოცხლის განმავლობაში: %u ბლოკი, რომელიც სულ %s ბაიტს "
|
||||
"შეიცავს.\n"
|
||||
msgstr[1] ""
|
||||
"გამოყოფილია მთელი სიცოცხლის განმავლობაში: %u ბლოკი, რომელიც სულ %s ბაიტს "
|
||||
"შეიცავს.\n"
|
||||
|
||||
#: src/utils/pactl.c:211
|
||||
#, c-format
|
||||
|
|
@ -2560,7 +2564,11 @@ msgid_plural ""
|
|||
"Failed to set volume: You tried to set volumes for %d channels, whereas "
|
||||
"channel(s) supported = %d\n"
|
||||
msgstr[0] ""
|
||||
"ხმის დაყენაბა ჩავარდა: სცადეთ, დაგეყენებინათ ხმა %d არხზე მაშინ, როცა "
|
||||
"მხარდაჭერილი არხები = %d\n"
|
||||
msgstr[1] ""
|
||||
"ხმის დაყენაბა ჩავარდა: სცადეთ, დაგეყენებინათ ხმა %d არხზე მაშინ, როცა "
|
||||
"მხარდაჭერილი არხები = %d\n"
|
||||
|
||||
#: src/utils/pactl.c:2107
|
||||
#, c-format
|
||||
|
|
|
|||
38
po/ko.po
38
po/ko.po
|
|
@ -7,7 +7,7 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/"
|
||||
"issues/new\n"
|
||||
"POT-Creation-Date: 2022-06-18 09:49+0300\n"
|
||||
"PO-Revision-Date: 2024-08-04 19:41+0000\n"
|
||||
"PO-Revision-Date: 2025-06-14 13:17+0000\n"
|
||||
"Last-Translator: 김인수 <simmon@nplob.com>\n"
|
||||
"Language-Team: Korean <https://translate.fedoraproject.org/projects/"
|
||||
"pulseaudio/pulseaudio/ko/>\n"
|
||||
|
|
@ -16,7 +16,7 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Weblate 5.6.2\n"
|
||||
"X-Generator: Weblate 5.11.4\n"
|
||||
|
||||
#: src/daemon/cmdline.c:113
|
||||
#, c-format
|
||||
|
|
@ -230,7 +230,7 @@ msgstr "--disable-shm 에는 부울 인자 값이 필요합니다"
|
|||
|
||||
#: src/daemon/cmdline.c:397
|
||||
msgid "--enable-memfd expects boolean argument"
|
||||
msgstr "--enable-memfd는 부울 인수가 필요합니다"
|
||||
msgstr "--enable-memfd 는 부울 인수가 예상됩니다"
|
||||
|
||||
#: src/daemon/daemon-conf.c:270
|
||||
#, c-format
|
||||
|
|
@ -423,9 +423,8 @@ msgstr "명령어 행 분석 실패."
|
|||
msgid ""
|
||||
"System mode refused for non-root user. Only starting the D-Bus server lookup "
|
||||
"service."
|
||||
msgstr ""
|
||||
"비 루트 사용자의 시스템 모드 전환을 거부했습니다. D-Bus 서버 검색 서비스만 시"
|
||||
"작합니다."
|
||||
msgstr "non-root 사용자에게 시스템 방식이 거부되었습니다. D-Bus 서버 검색 서비스만 "
|
||||
"시작합니다."
|
||||
|
||||
#: src/daemon/main.c:788
|
||||
#, c-format
|
||||
|
|
@ -436,9 +435,8 @@ msgstr "데몬 종료 실패: %s"
|
|||
msgid ""
|
||||
"This program is not intended to be run as root (unless --system is "
|
||||
"specified)."
|
||||
msgstr ""
|
||||
"이 프로그램은 루트 계정으로 실행하도록 만들지 않았습니다. (실행하려면 --"
|
||||
"system을 명기하십시오)."
|
||||
msgstr "이와 같은 프로그램은 root로 동작하도록 의도되지 않았습니다 (--system 이 "
|
||||
"지정되지 않은 경우)."
|
||||
|
||||
#: src/daemon/main.c:820
|
||||
msgid "Root privileges required."
|
||||
|
|
@ -451,24 +449,22 @@ msgstr "--start는 시스템 인스턴스에 대해 지원되지 않습니다."
|
|||
#: src/daemon/main.c:867
|
||||
#, c-format
|
||||
msgid "User-configured server at %s, refusing to start/autospawn."
|
||||
msgstr "%s에서 사용자 설정한 서버, start/autospawn을 거부하고 있습니다."
|
||||
msgstr "%s에 User-configured 서버, start/autospawn을 거부하고 있습니다."
|
||||
|
||||
#: src/daemon/main.c:873
|
||||
#, c-format
|
||||
msgid ""
|
||||
"User-configured server at %s, which appears to be local. Probing deeper."
|
||||
msgstr "%s에 사용자가 설정한 서버, 이는 로컬에 있습니다. 상세히 조사합니다."
|
||||
msgstr "%s에 User-configured 서버, 이는 로컬에 있습니다. 상세히 조사합니다."
|
||||
|
||||
#: src/daemon/main.c:878
|
||||
msgid "Running in system mode, but --disallow-exit not set."
|
||||
msgstr ""
|
||||
"시스템 모드에서 실행 중입니다. 하지만 --disallow-exit을 설정하지 않았습니다."
|
||||
msgstr "시스템 방식에서 실행 중이지만, --disallow-exit 를 설정하지 않았습니다."
|
||||
|
||||
#: src/daemon/main.c:881
|
||||
msgid "Running in system mode, but --disallow-module-loading not set."
|
||||
msgstr ""
|
||||
"시스템 모드에서 실행 중입니다. 하지만 --disallow-module-loading을 설정하지 않"
|
||||
"았습니다."
|
||||
msgstr "시스템 방식에서 실행 중이지만, --disallow-module-loading 를 설정하지 "
|
||||
"않았습니다."
|
||||
|
||||
#: src/daemon/main.c:884
|
||||
msgid "Running in system mode, forcibly disabling SHM mode."
|
||||
|
|
@ -1014,11 +1010,11 @@ msgstr "전화기"
|
|||
|
||||
#: src/modules/bluetooth/module-bluez5-device.c:2042
|
||||
msgid "High Fidelity Playback (A2DP Sink)"
|
||||
msgstr "Hi-Fi 재생 (A2DP Sink)"
|
||||
msgstr "고음질 재생 (A2DP Sink)"
|
||||
|
||||
#: src/modules/bluetooth/module-bluez5-device.c:2054
|
||||
msgid "High Fidelity Capture (A2DP Source)"
|
||||
msgstr "Hi-Fi 캡쳐 (A2DP Source)"
|
||||
msgstr "고음질 캡쳐 (A2DP Source)"
|
||||
|
||||
#: src/modules/bluetooth/module-bluez5-device.c:2066
|
||||
msgid "Headset Head Unit (HSP)"
|
||||
|
|
@ -1479,9 +1475,9 @@ msgid ""
|
|||
"e.g. happen if you try to connect to a non-root PulseAudio as a root user, "
|
||||
"over the native protocol. Don't do that.)"
|
||||
msgstr ""
|
||||
"XDG_RUNTIME_DIR (%s)은 우리(uid %d)가 아니라 uid %d가 소유합니다! (자체 프로"
|
||||
"토콜로 비 루트 펄스오디오 사용자가 루트 사용자 권한으로 연결할 때 이 문제가 "
|
||||
"일어납니다. 그렇게 하지 마십시오.)"
|
||||
"XDG_RUNTIME_DIR (%s)는 우리(uid %d)가 아니라 uid %d가 소유합니다! (자체 "
|
||||
"통신규약을 통해 root 사용자로 root가 아닌 PluseAudio에 연결을 시도 할 때에 "
|
||||
"예시로 발생 할 수 있습니다. 그렇게 하지 않습니다.)"
|
||||
|
||||
#: src/pulsecore/core-util.h:97
|
||||
msgid "yes"
|
||||
|
|
|
|||
95
po/pt_BR.po
95
po/pt_BR.po
|
|
@ -11,7 +11,7 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/"
|
||||
"issues/new\n"
|
||||
"POT-Creation-Date: 2022-06-18 09:49+0300\n"
|
||||
"PO-Revision-Date: 2024-10-11 16:01+0000\n"
|
||||
"PO-Revision-Date: 2025-05-10 22:55+0000\n"
|
||||
"Last-Translator: Rafael Fontenelle <rafaelff@gnome.org>\n"
|
||||
"Language-Team: Portuguese (Brazil) <https://translate.fedoraproject.org/"
|
||||
"projects/pulseaudio/pulseaudio/pt_BR/>\n"
|
||||
|
|
@ -20,7 +20,7 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 5.7.2\n"
|
||||
"X-Generator: Weblate 5.11.3\n"
|
||||
|
||||
#: src/daemon/cmdline.c:113
|
||||
#, c-format
|
||||
|
|
@ -1908,7 +1908,7 @@ msgid "pa_stream_update_timing_info() failed: %s"
|
|||
msgstr "pa_stream_update_timing_info() falhou: %s"
|
||||
|
||||
#: src/utils/pacat.c:676
|
||||
#, fuzzy, c-format
|
||||
#, c-format
|
||||
msgid ""
|
||||
"%s [options]\n"
|
||||
"%s\n"
|
||||
|
|
@ -1987,62 +1987,58 @@ msgstr ""
|
|||
" -v, --verbose Habilita operações no modo "
|
||||
"detalhado\n"
|
||||
"\n"
|
||||
" -s, --server=SERVIDOR O nome do servidor a conectar-se\n"
|
||||
" -d, --device=DISPOSITIVO O nome do destino/fonte a conectar-"
|
||||
"se\n"
|
||||
" -s, --server=SERVIDOR O nome do servidor para se conectar\n"
|
||||
" -d, --device=DISPOSITIVO O nome do destino/fonte para se "
|
||||
"conectar\n"
|
||||
" -n, --client-name=NOME Como chamar este cliente no "
|
||||
"servidor\n"
|
||||
" --stream-name=NOME Como chamar este fluxo no servidor\n"
|
||||
" --volume=VOLUME Especifica a faixa (linear) inicial\n"
|
||||
" de volume no intervalo 0...65536\n"
|
||||
" --rate=TAXA_DE_AMOSTRAGEM Taxa de amostragem, Hz (padrão "
|
||||
" --volume=VOLUME Especifica o volume (linear) inicial "
|
||||
"no intervalo 0...65536\n"
|
||||
" --rate=TAXA_DE_AMOSTRAGEM Taxa de amostragem em Hz (padrão: "
|
||||
"44100)\n"
|
||||
" --format=FORMATO_DE_AMOSTRAGEM Tipo de amostragem, veja\n"
|
||||
" --format=FORMATO_DE_AMOSTRAGEM Formato da amostragem, veja\n"
|
||||
" https://www.freedesktop.org/wiki/"
|
||||
"Software/PulseAudio/Documentation/User/SupportedAudioFormats/\n"
|
||||
" para valores possíveis (padrão: "
|
||||
"s16ne)\n"
|
||||
" --channels=CANAIS O número de canais, 1 para mono,\n"
|
||||
" 2 para estéreo (padrão: 2)\n"
|
||||
" --channel-map=MAPA_DE_CANAIS Mapeamento de canais a ser usado no\n"
|
||||
" lugar do padrão\n"
|
||||
" --fix-format Obtém o formato da amostragem do\n"
|
||||
" destino/fonte onde o fluxo está\n"
|
||||
" sendo conectado.\n"
|
||||
" --fix-rate Obtém a taxa de amostragem do\n"
|
||||
" destino/fonte onde o fluxo está\n"
|
||||
" sendo conectado.\n"
|
||||
" --fix-channels Obtém o número de canais e o mapa "
|
||||
"de\n"
|
||||
" canais do destino onde o fluxo está\n"
|
||||
" sendo conectado.\n"
|
||||
" --no-remix Não faz upmix nem downmix dos "
|
||||
"canais.\n"
|
||||
" --no-remap Mapeia os canais por índice em vez\n"
|
||||
" de nome\n"
|
||||
" --latency=BYTES Requisita a latência especificada "
|
||||
"em\n"
|
||||
" --channels=CANAIS O número de canais, 1 para mono, 2 "
|
||||
"para estéreo\n"
|
||||
" (padrão: 2)\n"
|
||||
" --channel-map=MAPA_DE_CANAIS Mapeamento de canais para usar em "
|
||||
"vez do padrão\n"
|
||||
" --fix-format Obtém o formato da amostragem do "
|
||||
"destino/fonte onde\n"
|
||||
" o fluxo está sendo conectado.\n"
|
||||
" --fix-rate Obtém a taxa de amostragem do "
|
||||
"destino/fonte onde\n"
|
||||
" o fluxo está sendo conectado.\n"
|
||||
" --fix-channels Obtém o número de canais e o mapa de "
|
||||
"canais do destino\n"
|
||||
" onde o fluxo está sendo conectado.\n"
|
||||
" --no-remix Não faz upmix nem downmix dos canais."
|
||||
"\n"
|
||||
" --no-remap Mapeia os canais por índice em vez "
|
||||
"de nome.\n"
|
||||
" --latency=BYTES Requisita a latência especificada em "
|
||||
"bytes.\n"
|
||||
" --process-time=BYTES Requisita o tempo de processo\n"
|
||||
" especificado por requisições em "
|
||||
"bytes.\n"
|
||||
" --latency-msec=MSEGUNDOS Requisita a latência especificada "
|
||||
"em\n"
|
||||
" --process-time=BYTES Requisita o tempo de processo por "
|
||||
"requisições em bytes.\n"
|
||||
" --latency-msec=MSEGUNDOS Requisita a latência especificada em "
|
||||
"milissegundos.\n"
|
||||
" --process-time-msec=MSEGUNDOS Requisita a o tempo do processo por\n"
|
||||
" requisição em milissegundos.\n"
|
||||
" --process-time-msec=MSEGUNDOS Requisita o tempo de processo por "
|
||||
"requisições em milissegundos.\n"
|
||||
" --property=PROPRIEDADE=VALOR Define a propriedade especificada "
|
||||
"para\n"
|
||||
" o valor especificado.\n"
|
||||
"para o valor especificado.\n"
|
||||
" --raw Grava/reproduz dados PCM não "
|
||||
"tratados.\n"
|
||||
" --passthrough Dados para conversão.\n"
|
||||
" --file-format[=FORMATO_ARQUIVO] Grava/reproduz dados PCM "
|
||||
"formatados.\n"
|
||||
" --file-format[=FORMATO_ARQUIVO] Grava/reproduz dados PCM formatados."
|
||||
"\n"
|
||||
" --list-file-formats Lista formatos de arquivo "
|
||||
"disponíveis.\n"
|
||||
" --monitor-stream=ÍNDICE Grava da entrada do destino com "
|
||||
"índice.\n"
|
||||
"índice ÍNDICE.\n"
|
||||
|
||||
#: src/utils/pacat.c:793
|
||||
msgid "Play back encoded audio files on a PulseAudio sound server."
|
||||
|
|
@ -2875,11 +2871,12 @@ msgstr "mensagem list-handlers falhou: %s"
|
|||
|
||||
#: src/utils/pactl.c:1711 src/utils/pactl.c:1760
|
||||
msgid "list-handlers message response could not be parsed correctly"
|
||||
msgstr "a resposta da mensagem list-handlers não pôde ser tratada corretamente"
|
||||
msgstr ""
|
||||
"a resposta da mensagem de list-handlers não pôde ser tratada corretamente"
|
||||
|
||||
#: src/utils/pactl.c:1718
|
||||
msgid "list-handlers message response is not a JSON array"
|
||||
msgstr "a resposta da mensagem list-handlers não é um array JSON"
|
||||
msgstr "a resposta da mensagem de list-handlers não é um array JSON"
|
||||
|
||||
#: src/utils/pactl.c:1729
|
||||
#, c-format
|
||||
|
|
@ -3051,7 +3048,7 @@ msgstr ""
|
|||
"padrão.\n"
|
||||
|
||||
#: src/utils/pactl.c:2664
|
||||
#, fuzzy, c-format
|
||||
#, c-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" -h, --help Show this help\n"
|
||||
|
|
@ -3068,7 +3065,9 @@ msgstr ""
|
|||
" -h, --help Mostra esta ajuda\n"
|
||||
" --version Mostra a versão\n"
|
||||
"\n"
|
||||
" -s, --server=SERVIDOR Nome do servidor a ser conectado\n"
|
||||
" -f, --format=FORMATO O formato da saída. \"normal\" ou "
|
||||
"\"json\"\n"
|
||||
" -s, --server=SERVIDOR Nome do servidor para se conectar\n"
|
||||
" -n, --client-name=NOME Como chamar este cliente no "
|
||||
"servidor\n"
|
||||
|
||||
|
|
@ -3084,9 +3083,9 @@ msgstr ""
|
|||
"Vinculado com libpulse %s\n"
|
||||
|
||||
#: src/utils/pactl.c:2751
|
||||
#, fuzzy, c-format
|
||||
#, c-format
|
||||
msgid "Invalid format value '%s'"
|
||||
msgstr "Nome do fluxo “%s” inválido"
|
||||
msgstr "Valor de formato “%s” inválido"
|
||||
|
||||
#: src/utils/pactl.c:2778
|
||||
#, c-format
|
||||
|
|
|
|||
8
po/ro.po
8
po/ro.po
|
|
@ -9,7 +9,7 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/"
|
||||
"issues/new\n"
|
||||
"POT-Creation-Date: 2022-06-18 09:49+0300\n"
|
||||
"PO-Revision-Date: 2025-02-23 20:53+0000\n"
|
||||
"PO-Revision-Date: 2025-05-11 11:49+0000\n"
|
||||
"Last-Translator: Remus-Gabriel Chelu <remusgabriel.chelu@disroot.org>\n"
|
||||
"Language-Team: Romanian <https://translate.fedoraproject.org/projects/"
|
||||
"pulseaudio/pulseaudio/ro/>\n"
|
||||
|
|
@ -19,7 +19,7 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < "
|
||||
"20)) ? 1 : 2;\n"
|
||||
"X-Generator: Weblate 5.10\n"
|
||||
"X-Generator: Weblate 5.11.3\n"
|
||||
|
||||
#: src/daemon/cmdline.c:113
|
||||
#, c-format
|
||||
|
|
@ -502,7 +502,7 @@ msgstr ""
|
|||
|
||||
#: src/daemon/main.c:922
|
||||
msgid "Failed to acquire stdio."
|
||||
msgstr "Nu s-a reușit să se achizționeze stdio."
|
||||
msgstr "Nu s-a reușit să se achiziționeze stdio."
|
||||
|
||||
#: src/daemon/main.c:928 src/daemon/main.c:999
|
||||
#, c-format
|
||||
|
|
@ -2135,7 +2135,7 @@ msgstr "Nu s-a putut determina specificația eșantionului din fișier."
|
|||
|
||||
#: src/utils/pacat.c:1100
|
||||
msgid "Warning: Failed to determine channel map from file."
|
||||
msgstr "Avertisment: Nu s-a reușit determinarea schemei canalelor din fișier."
|
||||
msgstr "Avertisment: Nu s-a reușit să se determine schema canalelor din fișier."
|
||||
|
||||
#: src/utils/pacat.c:1111
|
||||
msgid "Channel map doesn't match sample specification"
|
||||
|
|
|
|||
8
po/ru.po
8
po/ru.po
|
|
@ -10,8 +10,8 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/"
|
||||
"issues/new\n"
|
||||
"POT-Creation-Date: 2022-06-18 09:49+0300\n"
|
||||
"PO-Revision-Date: 2023-09-12 13:35+0000\n"
|
||||
"Last-Translator: \"Sergey A.\" <sw@atrus.ru>\n"
|
||||
"PO-Revision-Date: 2025-05-11 00:38+0000\n"
|
||||
"Last-Translator: \"Sergey A.\" <Ser82-png@yandex.ru>\n"
|
||||
"Language-Team: Russian <https://translate.fedoraproject.org/projects/"
|
||||
"pulseaudio/pulseaudio/ru/>\n"
|
||||
"Language: ru\n"
|
||||
|
|
@ -20,7 +20,7 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
||||
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
|
||||
"X-Generator: Weblate 5.0.1\n"
|
||||
"X-Generator: Weblate 5.11.3\n"
|
||||
|
||||
#: src/daemon/cmdline.c:113
|
||||
#, c-format
|
||||
|
|
@ -397,7 +397,7 @@ msgstr ""
|
|||
|
||||
#: src/daemon/ltdl-bind-now.c:144
|
||||
msgid "Failed to add bind-now-loader."
|
||||
msgstr "Не удалось добавить новый загрузчик bind-now."
|
||||
msgstr "Не удалось добавить bind-now-loader."
|
||||
|
||||
#: src/daemon/main.c:265
|
||||
#, c-format
|
||||
|
|
|
|||
10
po/sl.po
10
po/sl.po
|
|
@ -9,7 +9,7 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/"
|
||||
"issues/new\n"
|
||||
"POT-Creation-Date: 2022-06-18 09:49+0300\n"
|
||||
"PO-Revision-Date: 2024-08-17 18:38+0000\n"
|
||||
"PO-Revision-Date: 2025-07-25 04:53+0000\n"
|
||||
"Last-Translator: Martin Srebotnjak <miles@filmsi.net>\n"
|
||||
"Language-Team: Slovenian <https://translate.fedoraproject.org/projects/"
|
||||
"pulseaudio/pulseaudio/sl/>\n"
|
||||
|
|
@ -19,7 +19,7 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || "
|
||||
"n%100==4 ? 2 : 3;\n"
|
||||
"X-Generator: Weblate 5.6.2\n"
|
||||
"X-Generator: Weblate 5.12.2\n"
|
||||
|
||||
#: src/daemon/cmdline.c:113
|
||||
#, c-format
|
||||
|
|
@ -447,7 +447,7 @@ msgstr "Ni uspelo ubiti zalednega procesa: %s"
|
|||
|
||||
#: src/daemon/main.c:817
|
||||
msgid "This program is not intended to be run as root (unless --system is specified)."
|
||||
msgstr "Ta program ni namenjen zagonu kot root (razen če je določeno --sistem)."
|
||||
msgstr "Ta program ni namenjen zagonu kot root (razen če je določeno --system)."
|
||||
|
||||
#: src/daemon/main.c:820
|
||||
msgid "Root privileges required."
|
||||
|
|
@ -1526,7 +1526,7 @@ msgid ""
|
|||
"happen if you try to connect to a non-root PulseAudio as a root user, over the native "
|
||||
"protocol. Don't do that.)"
|
||||
msgstr ""
|
||||
"XDG_RUNTIME_DIR (%s) ni v naši lasti (uiid %d), ampak v lasti uid %d (to se "
|
||||
"XDG_RUNTIME_DIR (%s) ni v naši lasti (uid %d), ampak v lasti uid %d (to se "
|
||||
"lahko npr. zgodi, če se poskušate povezati z nekorenskim PulseAudio kot "
|
||||
"korenski uporabnik prek izvornega protokola; ne počnite tega)!"
|
||||
|
||||
|
|
@ -1966,7 +1966,7 @@ msgstr ""
|
|||
"uporabiti namesto privzete\n"
|
||||
" --fix-format Vzemite obliko vzorca iz ponora/"
|
||||
"vira, s katerim je tok povezan.\n"
|
||||
" —fix-rate Hitrost vzorčenja vzemite iz ponora/"
|
||||
" --fix-rate Hitrost vzorčenja vzemite iz ponora/"
|
||||
"vira, s katerim je tok povezan.\n"
|
||||
" --fix-channels Vzame število kanalov in preslikavo "
|
||||
"kanalov\n"
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ struct userdata {
|
|||
pa_ioline *line;
|
||||
};
|
||||
|
||||
static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
||||
static void line_callback(pa_ioline *line, const char *s, size_t l, void *userdata) {
|
||||
struct userdata *u = userdata;
|
||||
pa_module *m = NULL;
|
||||
unsigned devnum;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ libraop_sources = [
|
|||
'raop-crypto.c',
|
||||
'raop-packet-buffer.c',
|
||||
'raop-sink.c',
|
||||
'raop-util.c',
|
||||
]
|
||||
|
||||
libraop_headers = [
|
||||
|
|
@ -11,7 +10,7 @@ libraop_headers = [
|
|||
'raop-crypto.h',
|
||||
'raop-packet-buffer.h',
|
||||
'raop-sink.h',
|
||||
'raop-util.h',
|
||||
'raop-common.h',
|
||||
]
|
||||
|
||||
# FIXME: meson doesn't support multiple RPATH arguments currently
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
#include <pulsecore/namereg.h>
|
||||
#include <pulsecore/avahi-wrap.h>
|
||||
|
||||
#include "raop-util.h"
|
||||
#include "raop-common.h"
|
||||
|
||||
PA_MODULE_AUTHOR("Colin Guthrie");
|
||||
PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery of RAOP devices");
|
||||
|
|
|
|||
|
|
@ -58,12 +58,13 @@
|
|||
#include <pulsecore/random.h>
|
||||
#include <pulsecore/poll.h>
|
||||
|
||||
#include <modules/rtp/rtsp_client.h>
|
||||
#include <modules/rtp/rtsp-client.h>
|
||||
#include <modules/rtp/rtsp-util.h>
|
||||
|
||||
#include "raop-client.h"
|
||||
#include "raop-packet-buffer.h"
|
||||
#include "raop-crypto.h"
|
||||
#include "raop-util.h"
|
||||
#include "raop-common.h"
|
||||
|
||||
#define DEFAULT_RAOP_PORT 5000
|
||||
|
||||
|
|
@ -98,8 +99,10 @@ struct pa_raop_client {
|
|||
uint16_t port;
|
||||
pa_rtsp_client *rtsp;
|
||||
char *sci, *sid;
|
||||
char *password;
|
||||
const char *password;
|
||||
bool waiting;
|
||||
bool autoreconnect;
|
||||
bool has_post;
|
||||
|
||||
pa_raop_protocol_t protocol;
|
||||
pa_raop_encryption_t encryption;
|
||||
|
|
@ -113,11 +116,13 @@ struct pa_raop_client {
|
|||
int udp_cfd;
|
||||
int udp_tfd;
|
||||
|
||||
uint16_t udp_tport;
|
||||
|
||||
pa_raop_packet_buffer *pbuf;
|
||||
|
||||
uint16_t seq;
|
||||
uint32_t rtptime;
|
||||
bool is_recording;
|
||||
bool is_streaming;
|
||||
uint32_t ssrc;
|
||||
|
||||
bool is_first_packet;
|
||||
|
|
@ -193,19 +198,6 @@ static const uint8_t udp_timing_header[8] = {
|
|||
0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
/**
|
||||
* Function to trim a given character at the end of a string (no realloc).
|
||||
* @param str Pointer to string
|
||||
* @param rc Character to trim
|
||||
*/
|
||||
static inline void rtrim_char(char *str, char rc) {
|
||||
char *sp = str + strlen(str) - 1;
|
||||
while (sp >= str && *sp == rc) {
|
||||
*sp = '\0';
|
||||
sp -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to convert a timeval to ntp timestamp.
|
||||
* @param tv Pointer to the timeval structure
|
||||
|
|
@ -584,7 +576,7 @@ static ssize_t send_udp_sync_packet(pa_raop_client *c, uint32_t stamp) {
|
|||
return written;
|
||||
}
|
||||
|
||||
static size_t handle_udp_control_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
|
||||
static ssize_t handle_udp_control_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
|
||||
uint8_t payload = 0;
|
||||
uint16_t seq, nbp = 0;
|
||||
ssize_t written = 0;
|
||||
|
|
@ -655,11 +647,22 @@ static ssize_t send_udp_timing_packet(pa_raop_client *c, const uint32_t data[6],
|
|||
return written;
|
||||
}
|
||||
|
||||
static size_t handle_udp_timing_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
|
||||
static char *get_rtsp_url(pa_raop_client *c) {
|
||||
pa_assert(c->rtsp);
|
||||
pa_assert(c->sid);
|
||||
const char *ip = pa_rtsp_localip(c->rtsp);
|
||||
if (pa_is_ip6_address(ip)) {
|
||||
return pa_sprintf_malloc("rtsp://[%s]/%s", ip, c->sid);
|
||||
} else {
|
||||
return pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid);
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t handle_udp_timing_packet(pa_raop_client *c, const uint8_t packet[], ssize_t size) {
|
||||
const uint32_t * data = NULL;
|
||||
uint8_t payload = 0;
|
||||
struct timeval tv;
|
||||
size_t written = 0;
|
||||
ssize_t written = 0;
|
||||
uint64_t rci = 0;
|
||||
|
||||
/* Timing packets are 32 bytes long: 1 x 8 RTP header (no ssrc) + 3 x 8 NTP timestamps */
|
||||
|
|
@ -673,6 +676,10 @@ static size_t handle_udp_timing_packet(pa_raop_client *c, const uint8_t packet[]
|
|||
payload = packet[1] ^ 0x80;
|
||||
switch (payload) {
|
||||
case PAYLOAD_TIMING_REQUEST:
|
||||
if (c->has_post && !c->waiting) {
|
||||
pa_log_debug("Sending keep-alive");
|
||||
pa_rtsp_post(c->rtsp, "/feedback");
|
||||
}
|
||||
pa_log_debug("Sending timing packet at %" PRIu64 , rci);
|
||||
written = send_udp_timing_packet(c, data, rci);
|
||||
break;
|
||||
|
|
@ -798,6 +805,10 @@ static int open_bind_udp_socket(pa_raop_client *c, uint16_t *actual_port) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
/* If the socket queue is full, let's drop packets */
|
||||
pa_make_udp_socket_low_delay(fd);
|
||||
pa_make_fd_nonblock(fd);
|
||||
|
||||
#ifdef SO_TIMESTAMP
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0) {
|
||||
pa_log("setsockopt(SO_TIMESTAMP) failed: %s", pa_cstrerror(errno));
|
||||
|
|
@ -869,32 +880,13 @@ static void tcp_connection_cb(pa_socket_client *sc, pa_iochannel *io, void *user
|
|||
c->state_callback(PA_RAOP_CONNECTED, c->state_userdata);
|
||||
}
|
||||
|
||||
static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_status_t status, pa_headerlist *headers, void *userdata) {
|
||||
pa_raop_client *c = userdata;
|
||||
|
||||
pa_assert(c);
|
||||
pa_assert(rtsp);
|
||||
pa_assert(rtsp == c->rtsp);
|
||||
|
||||
switch (state) {
|
||||
case STATE_CONNECT: {
|
||||
static char *get_sdp(pa_raop_client *c) {
|
||||
char *key, *iv, *sdp = NULL;
|
||||
int frames = 0;
|
||||
const char *ip;
|
||||
char *url;
|
||||
int ipv;
|
||||
|
||||
pa_log_debug("RAOP: CONNECTED");
|
||||
|
||||
ip = pa_rtsp_localip(c->rtsp);
|
||||
if (pa_is_ip6_address(ip)) {
|
||||
ipv = 6;
|
||||
url = pa_sprintf_malloc("rtsp://[%s]/%s", ip, c->sid);
|
||||
} else {
|
||||
ipv = 4;
|
||||
url = pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid);
|
||||
}
|
||||
pa_rtsp_set_url(c->rtsp, url);
|
||||
ipv = pa_is_ip6_address(ip) ? 6 : 4;
|
||||
|
||||
if (c->protocol == PA_RAOP_PROTOCOL_TCP)
|
||||
frames = FRAMES_PER_TCP_PACKET;
|
||||
|
|
@ -924,11 +916,7 @@ static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_
|
|||
key = pa_raop_secret_get_key(c->secret);
|
||||
if (!key) {
|
||||
pa_log("pa_raop_secret_get_key() failed.");
|
||||
pa_rtsp_disconnect(rtsp);
|
||||
/* FIXME: This is an unrecoverable failure. We should notify
|
||||
* the pa_raop_client owner so that it could shut itself
|
||||
* down. */
|
||||
goto connect_finish;
|
||||
break;
|
||||
}
|
||||
|
||||
iv = pa_raop_secret_get_iv(c->secret);
|
||||
|
|
@ -952,48 +940,116 @@ static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_
|
|||
}
|
||||
}
|
||||
|
||||
pa_rtsp_announce(c->rtsp, sdp);
|
||||
return sdp;
|
||||
}
|
||||
|
||||
connect_finish:
|
||||
pa_xfree(sdp);
|
||||
static void rtsp_stream_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_status_t status, pa_headerlist *headers, void *userdata) {
|
||||
pa_raop_client *c = userdata;
|
||||
|
||||
pa_assert(c);
|
||||
pa_assert(rtsp);
|
||||
pa_assert(rtsp == c->rtsp);
|
||||
|
||||
switch (state) {
|
||||
case STATE_CONNECT: {
|
||||
char *url, *sdp;
|
||||
|
||||
pa_log_debug("RAOP: CONNECTED");
|
||||
|
||||
url = get_rtsp_url(c);
|
||||
pa_rtsp_set_url(c->rtsp, url);
|
||||
pa_xfree(url);
|
||||
|
||||
if (!(sdp = get_sdp(c)))
|
||||
goto connect_error;
|
||||
|
||||
/* We might need to re-authenticate, so reset this */
|
||||
c->waiting = false;
|
||||
pa_rtsp_announce(c->rtsp, sdp);
|
||||
pa_xfree(sdp);
|
||||
|
||||
break;
|
||||
|
||||
connect_error:
|
||||
pa_log_error("Failed to get RTSP SDP");
|
||||
pa_raop_client_disconnect(c);
|
||||
if (c->state_callback)
|
||||
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||
break;
|
||||
}
|
||||
|
||||
case STATE_OPTIONS: {
|
||||
pa_log_debug("RAOP: OPTIONS (stream cb)");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case STATE_ANNOUNCE: {
|
||||
uint16_t cport = DEFAULT_UDP_CONTROL_PORT;
|
||||
uint16_t tport = DEFAULT_UDP_TIMING_PORT;
|
||||
char *trs = NULL;
|
||||
char *sdp, *trs = NULL;
|
||||
|
||||
pa_log_debug("RAOP: ANNOUNCE");
|
||||
|
||||
if (c->protocol == PA_RAOP_PROTOCOL_TCP) {
|
||||
/* The authentication might need to be refreshed on a reconnect even
|
||||
* though we authenticated in OPTIONS previously */
|
||||
if (STATUS_UNAUTHORIZED == status) {
|
||||
if (c->waiting)
|
||||
goto announce_fail;
|
||||
if (!(sdp = get_sdp(c)))
|
||||
goto announce_error;
|
||||
c->waiting = true;
|
||||
pa_rtsp_announce(c->rtsp, sdp);
|
||||
pa_xfree(sdp);
|
||||
break;
|
||||
}
|
||||
|
||||
if (STATUS_OK != status)
|
||||
goto announce_error;
|
||||
|
||||
switch (c->protocol) {
|
||||
case PA_RAOP_PROTOCOL_TCP:
|
||||
trs = pa_sprintf_malloc(
|
||||
"RTP/AVP/TCP;unicast;interleaved=0-1;mode=record");
|
||||
} else if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
|
||||
break;
|
||||
case PA_RAOP_PROTOCOL_UDP:
|
||||
c->udp_cfd = open_bind_udp_socket(c, &cport);
|
||||
c->udp_tfd = open_bind_udp_socket(c, &tport);
|
||||
if (c->udp_cfd < 0 || c->udp_tfd < 0)
|
||||
goto annonce_error;
|
||||
goto announce_error;
|
||||
|
||||
trs = pa_sprintf_malloc(
|
||||
"RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;"
|
||||
"control_port=%d;timing_port=%d",
|
||||
cport, tport);
|
||||
break;
|
||||
}
|
||||
|
||||
pa_assert(trs);
|
||||
|
||||
/* Don't send a keep-alive until we're ready */
|
||||
c->waiting = true;
|
||||
c->udp_tport = 0;
|
||||
pa_rtsp_setup(c->rtsp, trs);
|
||||
|
||||
if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
|
||||
if (c->state_callback)
|
||||
c->state_callback(PA_RAOP_CONNECTED, c->state_userdata);
|
||||
}
|
||||
|
||||
pa_xfree(trs);
|
||||
|
||||
break;
|
||||
|
||||
annonce_error:
|
||||
announce_fail:
|
||||
pa_log_error("Aborting RTSP announce, authentication failed");
|
||||
pa_raop_client_disconnect(c);
|
||||
if (c->state_callback)
|
||||
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||
break;
|
||||
|
||||
announce_error:
|
||||
pa_log_error("Aborting RTSP announce, failed creating required sockets");
|
||||
|
||||
if (c->udp_cfd >= 0)
|
||||
pa_close(c->udp_cfd);
|
||||
c->udp_cfd = -1;
|
||||
|
|
@ -1001,12 +1057,10 @@ connect_finish:
|
|||
pa_close(c->udp_tfd);
|
||||
c->udp_tfd = -1;
|
||||
|
||||
pa_rtsp_client_free(c->rtsp);
|
||||
pa_raop_client_disconnect(c);
|
||||
if (c->state_callback)
|
||||
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||
|
||||
pa_log_error("Aborting RTSP announce, failed creating required sockets");
|
||||
|
||||
c->rtsp = NULL;
|
||||
pa_xfree(trs);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1014,13 +1068,17 @@ connect_finish:
|
|||
pa_socket_client *sc = NULL;
|
||||
uint32_t sport = DEFAULT_UDP_AUDIO_PORT;
|
||||
uint32_t cport =0, tport = 0;
|
||||
char *ajs, *token, *pc, *trs;
|
||||
const char *ajs, *trs;
|
||||
char *token = NULL, *pc;
|
||||
const char *token_state = NULL;
|
||||
char delimiters[] = ";";
|
||||
|
||||
pa_log_debug("RAOP: SETUP");
|
||||
|
||||
ajs = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status"));
|
||||
if (STATUS_OK != status)
|
||||
goto setup_error;
|
||||
|
||||
ajs = pa_headerlist_gets(headers, "Audio-Jack-Status");
|
||||
|
||||
if (ajs) {
|
||||
c->jack_type = JACK_TYPE_ANALOG;
|
||||
|
|
@ -1037,7 +1095,6 @@ connect_finish:
|
|||
}
|
||||
pa_xfree(token);
|
||||
}
|
||||
|
||||
} else {
|
||||
pa_log_warn("\"Audio-Jack-Status\" missing in RTSP setup response");
|
||||
}
|
||||
|
|
@ -1057,7 +1114,7 @@ connect_finish:
|
|||
pa_socket_client_unref(sc);
|
||||
sc = NULL;
|
||||
} else if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
|
||||
trs = pa_xstrdup(pa_headerlist_gets(headers, "Transport"));
|
||||
trs = pa_headerlist_gets(headers, "Transport");
|
||||
|
||||
if (trs) {
|
||||
/* Now parse out the server port component of the response. */
|
||||
|
|
@ -1076,11 +1133,13 @@ connect_finish:
|
|||
}
|
||||
pa_xfree(token);
|
||||
}
|
||||
pa_xfree(trs);
|
||||
} else {
|
||||
pa_log_warn("\"Transport\" missing in RTSP setup response");
|
||||
}
|
||||
|
||||
if (!tport)
|
||||
tport = c->udp_tport;
|
||||
|
||||
if (cport <= 0 || tport <= 0)
|
||||
goto setup_error;
|
||||
|
||||
|
|
@ -1093,57 +1152,43 @@ connect_finish:
|
|||
|
||||
pa_log_debug("Connection established (UDP;control_port=%d;timing_port=%d)", cport, tport);
|
||||
|
||||
if (!c->udp_tport) {
|
||||
/* Send an initial UDP packet so a connection tracking firewall
|
||||
* knows the src_ip:src_port <-> dest_ip:dest_port relation
|
||||
* and accepts the incoming timing packets.
|
||||
*/
|
||||
send_initial_udp_timing_packet(c);
|
||||
pa_log_debug("Sent initial timing packet to UDP port %d", tport);
|
||||
|
||||
if (c->state_callback)
|
||||
c->state_callback(PA_RAOP_CONNECTED, c->state_userdata);
|
||||
}
|
||||
}
|
||||
|
||||
pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime);
|
||||
|
||||
pa_xfree(ajs);
|
||||
break;
|
||||
|
||||
setup_error_parse:
|
||||
pa_log("Failed parsing server port components");
|
||||
pa_xfree(token);
|
||||
pa_xfree(trs);
|
||||
/* fall-thru */
|
||||
setup_error:
|
||||
if (c->tcp_sfd >= 0)
|
||||
pa_close(c->tcp_sfd);
|
||||
c->tcp_sfd = -1;
|
||||
|
||||
if (c->udp_sfd >= 0)
|
||||
pa_close(c->udp_sfd);
|
||||
c->udp_sfd = -1;
|
||||
|
||||
c->udp_cfd = c->udp_tfd = -1;
|
||||
|
||||
pa_rtsp_client_free(c->rtsp);
|
||||
|
||||
pa_log_error("aborting RTSP setup, failed creating required sockets");
|
||||
|
||||
pa_raop_client_disconnect(c);
|
||||
if (c->state_callback)
|
||||
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||
|
||||
c->rtsp = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
case STATE_RECORD: {
|
||||
int32_t latency = 0;
|
||||
uint32_t ssrc;
|
||||
char *alt;
|
||||
const char *alt;
|
||||
|
||||
pa_log_debug("RAOP: RECORD");
|
||||
|
||||
alt = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Latency"));
|
||||
if (STATUS_OK != status)
|
||||
goto record_error;
|
||||
|
||||
alt = pa_headerlist_gets(headers, "Audio-Latency");
|
||||
if (alt) {
|
||||
if (pa_atoi(alt, &latency) < 0)
|
||||
pa_log("Failed to parse audio latency");
|
||||
|
|
@ -1152,15 +1197,23 @@ connect_finish:
|
|||
pa_raop_packet_buffer_reset(c->pbuf, c->seq);
|
||||
|
||||
pa_random(&ssrc, sizeof(ssrc));
|
||||
c->is_first_packet = true;
|
||||
c->is_recording = true;
|
||||
c->sync_count = 0;
|
||||
c->ssrc = ssrc;
|
||||
|
||||
pa_raop_client_stream(c);
|
||||
|
||||
if (c->state_callback)
|
||||
c->state_callback((int) PA_RAOP_RECORDING, c->state_userdata);
|
||||
|
||||
pa_xfree(alt);
|
||||
/* Now the keep-alive can be sent */
|
||||
c->waiting = false;
|
||||
|
||||
break;
|
||||
|
||||
record_error:
|
||||
pa_log_error("aborting RTSP record due to error");
|
||||
pa_raop_client_disconnect(c);
|
||||
if (c->state_callback)
|
||||
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1170,6 +1223,12 @@ connect_finish:
|
|||
break;
|
||||
}
|
||||
|
||||
case STATE_POST: {
|
||||
pa_log_debug("RAOP: POST");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case STATE_FLUSH: {
|
||||
pa_log_debug("RAOP: FLUSHED");
|
||||
|
||||
|
|
@ -1179,23 +1238,7 @@ connect_finish:
|
|||
case STATE_TEARDOWN: {
|
||||
pa_log_debug("RAOP: TEARDOWN");
|
||||
|
||||
if (c->tcp_sfd >= 0)
|
||||
pa_close(c->tcp_sfd);
|
||||
c->tcp_sfd = -1;
|
||||
|
||||
if (c->udp_sfd >= 0)
|
||||
pa_close(c->udp_sfd);
|
||||
c->udp_sfd = -1;
|
||||
|
||||
/* Polling sockets will be closed by sink */
|
||||
c->udp_cfd = c->udp_tfd = -1;
|
||||
c->tcp_sfd = -1;
|
||||
|
||||
pa_rtsp_client_free(c->rtsp);
|
||||
pa_xfree(c->sid);
|
||||
c->rtsp = NULL;
|
||||
c->sid = NULL;
|
||||
|
||||
pa_raop_client_disconnect(c);
|
||||
if (c->state_callback)
|
||||
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||
|
||||
|
|
@ -1205,29 +1248,10 @@ connect_finish:
|
|||
case STATE_DISCONNECTED: {
|
||||
pa_log_debug("RAOP: DISCONNECTED");
|
||||
|
||||
c->is_recording = false;
|
||||
|
||||
if (c->tcp_sfd >= 0)
|
||||
pa_close(c->tcp_sfd);
|
||||
c->tcp_sfd = -1;
|
||||
|
||||
if (c->udp_sfd >= 0)
|
||||
pa_close(c->udp_sfd);
|
||||
c->udp_sfd = -1;
|
||||
|
||||
/* Polling sockets will be closed by sink */
|
||||
c->udp_cfd = c->udp_tfd = -1;
|
||||
c->tcp_sfd = -1;
|
||||
|
||||
pa_log_error("RTSP control channel closed (disconnected)");
|
||||
|
||||
pa_rtsp_client_free(c->rtsp);
|
||||
pa_xfree(c->sid);
|
||||
c->rtsp = NULL;
|
||||
c->sid = NULL;
|
||||
|
||||
pa_raop_client_disconnect(c);
|
||||
if (c->state_callback)
|
||||
c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -1257,127 +1281,60 @@ static void rtsp_auth_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_st
|
|||
|
||||
pa_random(rac, APPLE_CHALLENGE_LENGTH);
|
||||
/* Generate a random Apple-Challenge key */
|
||||
pa_raop_base64_encode(rac, APPLE_CHALLENGE_LENGTH, &sac);
|
||||
rtrim_char(sac, '=');
|
||||
pa_rtsp_base64_encode(rac, APPLE_CHALLENGE_LENGTH, &sac);
|
||||
pa_rtsp_rtrim_char(sac, '=');
|
||||
pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac);
|
||||
|
||||
/* Reset this for the authentication */
|
||||
c->waiting = false;
|
||||
pa_rtsp_options(c->rtsp);
|
||||
|
||||
pa_xfree(sac);
|
||||
pa_xfree(sci);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case STATE_OPTIONS: {
|
||||
static bool waiting = false;
|
||||
const char *current = NULL;
|
||||
char space[] = " ";
|
||||
char *token, *ath = NULL;
|
||||
char *publ, *wath, *mth = NULL, *val;
|
||||
char *realm = NULL, *nonce = NULL, *response = NULL;
|
||||
char comma[] = ",";
|
||||
|
||||
pa_log_debug("RAOP: OPTIONS (auth cb)");
|
||||
/* We do not consider the Apple-Response */
|
||||
pa_rtsp_remove_header(c->rtsp, "Apple-Challenge");
|
||||
|
||||
if (STATUS_UNAUTHORIZED == status) {
|
||||
wath = pa_xstrdup(pa_headerlist_gets(headers, "WWW-Authenticate"));
|
||||
if (true == waiting) {
|
||||
pa_xfree(wath);
|
||||
if (c->waiting)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (wath) {
|
||||
mth = pa_split(wath, space, ¤t);
|
||||
while ((token = pa_split(wath, comma, ¤t))) {
|
||||
if ((val = strstr(token, "="))) {
|
||||
if (NULL == realm && val > strstr(token, "realm"))
|
||||
realm = pa_xstrdup(val + 2);
|
||||
else if (NULL == nonce && val > strstr(token, "nonce"))
|
||||
nonce = pa_xstrdup(val + 2);
|
||||
}
|
||||
|
||||
pa_xfree(token);
|
||||
}
|
||||
}
|
||||
|
||||
if (pa_safe_streq(mth, "Basic") && realm) {
|
||||
rtrim_char(realm, '\"');
|
||||
|
||||
pa_raop_basic_response(DEFAULT_USER_NAME, c->password, &response);
|
||||
ath = pa_sprintf_malloc("Basic %s",
|
||||
response);
|
||||
} else if (pa_safe_streq(mth, "Digest") && realm && nonce) {
|
||||
rtrim_char(realm, '\"');
|
||||
rtrim_char(nonce, '\"');
|
||||
|
||||
pa_raop_digest_response(DEFAULT_USER_NAME, realm, c->password, nonce, "*", &response);
|
||||
ath = pa_sprintf_malloc("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"*\", response=\"%s\"",
|
||||
DEFAULT_USER_NAME, realm, nonce,
|
||||
response);
|
||||
} else {
|
||||
pa_log_error("unsupported authentication method: %s", mth);
|
||||
pa_xfree(realm);
|
||||
pa_xfree(nonce);
|
||||
pa_xfree(wath);
|
||||
pa_xfree(mth);
|
||||
goto error;
|
||||
}
|
||||
|
||||
pa_xfree(response);
|
||||
pa_xfree(realm);
|
||||
pa_xfree(nonce);
|
||||
pa_xfree(wath);
|
||||
pa_xfree(mth);
|
||||
|
||||
pa_rtsp_add_header(c->rtsp, "Authorization", ath);
|
||||
pa_xfree(ath);
|
||||
|
||||
waiting = true;
|
||||
c->waiting = true;
|
||||
pa_rtsp_options(c->rtsp);
|
||||
break;
|
||||
}
|
||||
|
||||
if (STATUS_OK == status) {
|
||||
publ = pa_xstrdup(pa_headerlist_gets(headers, "Public"));
|
||||
if (STATUS_OK != status)
|
||||
goto error;
|
||||
|
||||
const char *publ = pa_headerlist_gets(headers, "Public");
|
||||
if (publ && strstr(publ, "POST"))
|
||||
c->has_post = true;
|
||||
c->sci = pa_xstrdup(pa_rtsp_get_header(c->rtsp, "Client-Instance"));
|
||||
|
||||
if (c->password)
|
||||
pa_xfree(c->password);
|
||||
pa_xfree(publ);
|
||||
c->password = NULL;
|
||||
}
|
||||
|
||||
pa_rtsp_client_free(c->rtsp);
|
||||
c->rtsp = NULL;
|
||||
/* Ensure everything is cleaned before calling the callback, otherwise it may raise a crash */
|
||||
pa_raop_client_disconnect(c);
|
||||
/* We call with authenticated, not disconnected */
|
||||
if (c->state_callback)
|
||||
c->state_callback((int) PA_RAOP_AUTHENTICATED, c->state_userdata);
|
||||
c->state_callback(PA_RAOP_AUTHENTICATED, c->state_userdata);
|
||||
|
||||
waiting = false;
|
||||
break;
|
||||
|
||||
fail:
|
||||
if (c->state_callback)
|
||||
c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||
pa_rtsp_client_free(c->rtsp);
|
||||
c->rtsp = NULL;
|
||||
|
||||
pa_log_error("aborting authentication, wrong password");
|
||||
|
||||
waiting = false;
|
||||
pa_raop_client_disconnect(c);
|
||||
if (c->state_callback)
|
||||
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||
break;
|
||||
|
||||
error:
|
||||
if (c->state_callback)
|
||||
c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||
pa_rtsp_client_free(c->rtsp);
|
||||
c->rtsp = NULL;
|
||||
|
||||
pa_log_error("aborting authentication, unexpected failure");
|
||||
|
||||
waiting = false;
|
||||
pa_raop_client_disconnect(c);
|
||||
if (c->state_callback)
|
||||
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1389,15 +1346,14 @@ static void rtsp_auth_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_st
|
|||
case STATE_TEARDOWN:
|
||||
case STATE_DISCONNECTED:
|
||||
default: {
|
||||
if (c->state_callback)
|
||||
c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||
pa_rtsp_client_free(c->rtsp);
|
||||
c->rtsp = NULL;
|
||||
|
||||
if (c->sci)
|
||||
pa_xfree(c->sci);
|
||||
c->sci = NULL;
|
||||
|
||||
pa_raop_client_disconnect(c);
|
||||
if (c->state_callback)
|
||||
c->state_callback(PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -1405,11 +1361,7 @@ static void rtsp_auth_cb(pa_rtsp_client *rtsp, pa_rtsp_state_t state, pa_rtsp_st
|
|||
|
||||
|
||||
void pa_raop_client_disconnect(pa_raop_client *c) {
|
||||
c->is_recording = false;
|
||||
|
||||
if (c->tcp_sfd >= 0)
|
||||
pa_close(c->tcp_sfd);
|
||||
c->tcp_sfd = -1;
|
||||
c->is_streaming = false;
|
||||
|
||||
if (c->udp_sfd >= 0)
|
||||
pa_close(c->udp_sfd);
|
||||
|
|
@ -1419,18 +1371,12 @@ void pa_raop_client_disconnect(pa_raop_client *c) {
|
|||
c->udp_cfd = c->udp_tfd = -1;
|
||||
c->tcp_sfd = -1;
|
||||
|
||||
pa_log_error("RTSP control channel closed (disconnected)");
|
||||
|
||||
if (c->rtsp)
|
||||
pa_rtsp_client_free(c->rtsp);
|
||||
if (c->sid)
|
||||
pa_xfree(c->sid);
|
||||
c->rtsp = NULL;
|
||||
c->sid = NULL;
|
||||
|
||||
if (c->state_callback)
|
||||
c->state_callback((int) PA_RAOP_DISCONNECTED, c->state_userdata);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1460,10 +1406,9 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot
|
|||
c->port = a.port;
|
||||
else
|
||||
c->port = DEFAULT_RAOP_PORT;
|
||||
c->rtsp = NULL;
|
||||
c->sci = c->sid = NULL;
|
||||
c->password = NULL;
|
||||
c->autoreconnect = autoreconnect;
|
||||
c->waiting = false;
|
||||
c->has_post = false;
|
||||
|
||||
c->protocol = protocol;
|
||||
c->encryption = encryption;
|
||||
|
|
@ -1475,7 +1420,6 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot
|
|||
c->udp_cfd = -1;
|
||||
c->udp_tfd = -1;
|
||||
|
||||
c->secret = NULL;
|
||||
if (c->encryption != PA_RAOP_ENCRYPTION_NONE)
|
||||
c->secret = pa_raop_secret_new();
|
||||
|
||||
|
|
@ -1483,7 +1427,7 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_prot
|
|||
if (c->protocol == PA_RAOP_PROTOCOL_UDP)
|
||||
size = RTX_BUFFERING_SECONDS * ss.rate / FRAMES_PER_UDP_PACKET;
|
||||
|
||||
c->is_recording = false;
|
||||
c->is_streaming = false;
|
||||
c->is_first_packet = true;
|
||||
/* Packet sync interval should be around 1s (UDP only) */
|
||||
c->sync_interval = ss.rate / FRAMES_PER_UDP_PACKET;
|
||||
|
|
@ -1503,7 +1447,6 @@ void pa_raop_client_free(pa_raop_client *c) {
|
|||
pa_xfree(c->sci);
|
||||
if (c->secret)
|
||||
pa_raop_secret_free(c->secret);
|
||||
pa_xfree(c->password);
|
||||
c->sci = c->sid = NULL;
|
||||
c->password = NULL;
|
||||
c->secret = NULL;
|
||||
|
|
@ -1521,20 +1464,24 @@ int pa_raop_client_authenticate (pa_raop_client *c, const char *password) {
|
|||
|
||||
pa_assert(c);
|
||||
|
||||
if (c->rtsp || c->password) {
|
||||
if (c->rtsp) {
|
||||
pa_log_debug("Authentication/Connection already in progress...");
|
||||
return 0;
|
||||
}
|
||||
|
||||
c->password = NULL;
|
||||
if (password)
|
||||
c->password = pa_xstrdup(password);
|
||||
c->password = password;
|
||||
c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, DEFAULT_USER_AGENT, c->autoreconnect);
|
||||
|
||||
pa_assert(c->rtsp);
|
||||
|
||||
pa_rtsp_set_credentials(c->rtsp, DEFAULT_USER_NAME, c->password);
|
||||
pa_rtsp_set_callback(c->rtsp, rtsp_auth_cb, c);
|
||||
rv = pa_rtsp_connect(c->rtsp);
|
||||
|
||||
if ((rv = pa_rtsp_connect(c->rtsp))) {
|
||||
pa_rtsp_client_free(c->rtsp);
|
||||
c->rtsp = NULL;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
|
@ -1563,23 +1510,27 @@ int pa_raop_client_announce(pa_raop_client *c) {
|
|||
pa_assert(c->rtsp);
|
||||
|
||||
c->sync_count = 0;
|
||||
c->is_recording = false;
|
||||
c->is_streaming = false;
|
||||
c->is_first_packet = true;
|
||||
pa_random(&sid, sizeof(sid));
|
||||
c->sid = pa_sprintf_malloc("%u", sid);
|
||||
pa_rtsp_add_header(c->rtsp, "Client-Instance", c->sci);
|
||||
pa_rtsp_set_credentials(c->rtsp, DEFAULT_USER_NAME, c->password);
|
||||
pa_rtsp_set_callback(c->rtsp, rtsp_stream_cb, c);
|
||||
|
||||
rv = pa_rtsp_connect(c->rtsp);
|
||||
if ((rv = pa_rtsp_connect(c->rtsp))) {
|
||||
pa_rtsp_client_free(c->rtsp);
|
||||
c->rtsp = NULL;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool pa_raop_client_is_alive(pa_raop_client *c) {
|
||||
pa_assert(c);
|
||||
|
||||
if (!c->rtsp || !c->sci) {
|
||||
pa_log_debug("Not alive, connection not established yet...");
|
||||
if (!c->rtsp || !c->sci)
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (c->protocol) {
|
||||
case PA_RAOP_PROTOCOL_TCP:
|
||||
|
|
@ -1597,64 +1548,28 @@ bool pa_raop_client_is_alive(pa_raop_client *c) {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool pa_raop_client_can_stream(pa_raop_client *c) {
|
||||
bool pa_raop_client_is_streaming(pa_raop_client *c) {
|
||||
pa_assert(c);
|
||||
|
||||
if (!c->rtsp || !c->sci) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (c->protocol) {
|
||||
case PA_RAOP_PROTOCOL_TCP:
|
||||
if (c->tcp_sfd >= 0 && c->is_recording)
|
||||
return true;
|
||||
break;
|
||||
case PA_RAOP_PROTOCOL_UDP:
|
||||
if (c->udp_sfd >= 0 && c->is_recording)
|
||||
return true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool pa_raop_client_is_recording(pa_raop_client *c) {
|
||||
return c->is_recording;
|
||||
return pa_raop_client_is_alive(c) && c->is_streaming;
|
||||
}
|
||||
|
||||
int pa_raop_client_stream(pa_raop_client *c) {
|
||||
int rv = 0;
|
||||
|
||||
pa_assert(c);
|
||||
|
||||
if (!c->rtsp || !c->sci) {
|
||||
if (!pa_raop_client_is_alive(c)) {
|
||||
pa_log_debug("Streaming's impossible, connection not established yet...");
|
||||
return 1;
|
||||
} else if (pa_raop_client_is_streaming(c)) {
|
||||
pa_log_debug("Already streaming...");
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (c->protocol) {
|
||||
case PA_RAOP_PROTOCOL_TCP:
|
||||
if (c->tcp_sfd >= 0 && !c->is_recording) {
|
||||
c->is_recording = true;
|
||||
c->is_streaming = true;
|
||||
c->is_first_packet = true;
|
||||
c->sync_count = 0;
|
||||
}
|
||||
break;
|
||||
case PA_RAOP_PROTOCOL_UDP:
|
||||
if (c->udp_sfd >= 0 && !c->is_recording) {
|
||||
c->is_recording = true;
|
||||
c->is_first_packet = true;
|
||||
c->sync_count = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
rv = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
return rv;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume) {
|
||||
|
|
@ -1664,11 +1579,8 @@ int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume) {
|
|||
|
||||
pa_assert(c);
|
||||
|
||||
if (!c->rtsp) {
|
||||
if (!pa_raop_client_is_alive(c)) {
|
||||
pa_log_debug("Cannot SET_PARAMETER, connection not established yet...");
|
||||
return 0;
|
||||
} else if (!c->sci) {
|
||||
pa_log_debug("SET_PARAMETER requires a preliminary authentication");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -1682,49 +1594,36 @@ int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume) {
|
|||
|
||||
param = pa_sprintf_malloc("volume: %0.6f\r\n", db);
|
||||
/* We just hit and hope, cannot wait for the callback. */
|
||||
if (c->rtsp != NULL && pa_rtsp_exec_ready(c->rtsp))
|
||||
rv = pa_rtsp_setparameter(c->rtsp, param);
|
||||
|
||||
pa_xfree(param);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
int pa_raop_client_flush(pa_raop_client *c) {
|
||||
int rv = 0;
|
||||
|
||||
pa_assert(c);
|
||||
|
||||
if (!c->rtsp || !pa_rtsp_exec_ready(c->rtsp)) {
|
||||
pa_log_debug("Cannot FLUSH, connection not established yet...)");
|
||||
return 0;
|
||||
} else if (!c->sci) {
|
||||
pa_log_debug("FLUSH requires a preliminary authentication");
|
||||
if (!pa_raop_client_is_alive(c)) {
|
||||
pa_log_debug("Cannot FLUSH, connection not established yet...");
|
||||
return 1;
|
||||
}
|
||||
|
||||
c->is_recording = false;
|
||||
c->is_streaming = false;
|
||||
|
||||
rv = pa_rtsp_flush(c->rtsp, c->seq, c->rtptime);
|
||||
return rv;
|
||||
return pa_rtsp_flush(c->rtsp, c->seq, c->rtptime);
|
||||
}
|
||||
|
||||
int pa_raop_client_teardown(pa_raop_client *c) {
|
||||
int rv = 0;
|
||||
|
||||
pa_assert(c);
|
||||
|
||||
if (!c->rtsp) {
|
||||
if (!pa_raop_client_is_alive(c)) {
|
||||
pa_log_debug("Cannot TEARDOWN, connection not established yet...");
|
||||
return 0;
|
||||
} else if (!c->sci) {
|
||||
pa_log_debug("TEARDOWN requires a preliminary authentication");
|
||||
return 1;
|
||||
}
|
||||
|
||||
c->is_recording = false;
|
||||
c->is_streaming = false;
|
||||
|
||||
rv = pa_rtsp_teardown(c->rtsp);
|
||||
return rv;
|
||||
return pa_rtsp_teardown(c->rtsp);
|
||||
}
|
||||
|
||||
void pa_raop_client_get_frames_per_block(pa_raop_client *c, size_t *frames) {
|
||||
|
|
@ -1803,20 +1702,44 @@ pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume)
|
|||
return volume - volume * (minv / maxv) + minv;
|
||||
}
|
||||
|
||||
void pa_raop_client_handle_oob_packet(pa_raop_client *c, const int fd, const uint8_t packet[], ssize_t size) {
|
||||
ssize_t pa_raop_client_handle_oob_packet(pa_raop_client *c, const int fd) {
|
||||
pa_assert(c);
|
||||
pa_assert(fd >= 0);
|
||||
pa_assert(packet);
|
||||
|
||||
if (c->protocol == PA_RAOP_PROTOCOL_UDP) {
|
||||
uint8_t packet[32];
|
||||
ssize_t size;
|
||||
struct sockaddr_storage sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
|
||||
size = recvfrom(fd, packet, sizeof(packet), 0, (struct sockaddr *) &sa, &salen);
|
||||
|
||||
if (size < 0)
|
||||
return size;
|
||||
|
||||
if (fd == c->udp_cfd) {
|
||||
pa_log_debug("Received UDP control packet...");
|
||||
handle_udp_control_packet(c, packet, size);
|
||||
return handle_udp_control_packet(c, packet, size);
|
||||
} else if (fd == c->udp_tfd) {
|
||||
pa_log_debug("Received UDP timing packet...");
|
||||
handle_udp_timing_packet(c, packet, size);
|
||||
if (!c->udp_tport) {
|
||||
/* Apple TV sends timing packets after the SETUP request
|
||||
* which we use to get the port. It sends timing_port=0 in the
|
||||
* SETUP response.
|
||||
*/
|
||||
if (sa.ss_family == AF_INET)
|
||||
c->udp_tport = ntohs(((struct sockaddr_in *) &sa)->sin_port);
|
||||
#ifdef HAVE_IPV6
|
||||
else if (sa.ss_family == AF_INET6)
|
||||
c->udp_tport = ntohs(((struct sockaddr_in6 *) &sa)->sin6_port);
|
||||
#endif
|
||||
c->udp_tfd = connect_udp_socket(c, c->udp_tfd, c->udp_tport);
|
||||
}
|
||||
return handle_udp_timing_packet(c, packet, size);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t pa_raop_client_send_audio_packet(pa_raop_client *c, pa_memchunk *block, size_t offset) {
|
||||
|
|
|
|||
|
|
@ -65,8 +65,7 @@ bool pa_raop_client_is_authenticated(pa_raop_client *c);
|
|||
|
||||
int pa_raop_client_announce(pa_raop_client *c);
|
||||
bool pa_raop_client_is_alive(pa_raop_client *c);
|
||||
bool pa_raop_client_is_recording(pa_raop_client *c);
|
||||
bool pa_raop_client_can_stream(pa_raop_client *c);
|
||||
bool pa_raop_client_is_streaming(pa_raop_client *c);
|
||||
int pa_raop_client_stream(pa_raop_client *c);
|
||||
int pa_raop_client_set_volume(pa_raop_client *c, pa_volume_t volume);
|
||||
int pa_raop_client_flush(pa_raop_client *c);
|
||||
|
|
@ -77,7 +76,7 @@ void pa_raop_client_get_frames_per_block(pa_raop_client *c, size_t *size);
|
|||
bool pa_raop_client_register_pollfd(pa_raop_client *c, pa_rtpoll *poll, pa_rtpoll_item **poll_item);
|
||||
bool pa_raop_client_is_timing_fd(pa_raop_client *c, const int fd);
|
||||
pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume);
|
||||
void pa_raop_client_handle_oob_packet(pa_raop_client *c, const int fd, const uint8_t packet[], ssize_t size);
|
||||
ssize_t pa_raop_client_handle_oob_packet(pa_raop_client *c, const int fd);
|
||||
ssize_t pa_raop_client_send_audio_packet(pa_raop_client *c, pa_memchunk *block, size_t offset);
|
||||
|
||||
typedef void (*pa_raop_client_state_cb_t)(pa_raop_state_t state, void *userdata);
|
||||
|
|
|
|||
27
src/modules/raop/raop-common.h
Normal file
27
src/modules/raop/raop-common.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef fooraopcommonfoo
|
||||
#define fooraopcommonfoo
|
||||
|
||||
/***
|
||||
This file is part of PulseAudio.
|
||||
|
||||
Copyright 2008 Colin Guthrie
|
||||
Copyright Kungliga Tekniska högskolan
|
||||
Copyright 2013 Martin Blanchard
|
||||
|
||||
PulseAudio is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
PulseAudio is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#define RAOP_DEFAULT_LATENCY 2000 /* msec */
|
||||
|
||||
#endif
|
||||
|
|
@ -37,8 +37,9 @@
|
|||
#include <pulsecore/macro.h>
|
||||
#include <pulsecore/random.h>
|
||||
|
||||
#include <modules/rtp/rtsp-util.h>
|
||||
|
||||
#include "raop-crypto.h"
|
||||
#include "raop-util.h"
|
||||
|
||||
#define AES_CHUNK_SIZE 16
|
||||
|
||||
|
|
@ -92,7 +93,7 @@ static int rsa_encrypt(uint8_t *data, int len, uint8_t *str) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
size = pa_raop_base64_decode(rsa_modulus, modulus);
|
||||
size = pa_rtsp_base64_decode(rsa_modulus, modulus);
|
||||
|
||||
n_bn = BN_bin2bn(modulus, size, NULL);
|
||||
if (!n_bn) {
|
||||
|
|
@ -100,7 +101,7 @@ static int rsa_encrypt(uint8_t *data, int len, uint8_t *str) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
size = pa_raop_base64_decode(rsa_exponent, exponent);
|
||||
size = pa_rtsp_base64_decode(rsa_exponent, exponent);
|
||||
|
||||
e_bn = BN_bin2bn(exponent, size, NULL);
|
||||
if (!e_bn) {
|
||||
|
|
@ -165,7 +166,7 @@ char* pa_raop_secret_get_iv(pa_raop_secret *s) {
|
|||
|
||||
pa_assert(s);
|
||||
|
||||
pa_raop_base64_encode(s->iv, AES_CHUNK_SIZE, &base64_iv);
|
||||
pa_rtsp_base64_encode(s->iv, AES_CHUNK_SIZE, &base64_iv);
|
||||
|
||||
return base64_iv;
|
||||
}
|
||||
|
|
@ -184,7 +185,7 @@ char* pa_raop_secret_get_key(pa_raop_secret *s) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
pa_raop_base64_encode(rsa_key, size, &base64_key);
|
||||
pa_rtsp_base64_encode(rsa_key, size, &base64_key);
|
||||
|
||||
return base64_key;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@
|
|||
|
||||
#include "raop-sink.h"
|
||||
#include "raop-client.h"
|
||||
#include "raop-util.h"
|
||||
#include "raop-common.h"
|
||||
|
||||
#define UDP_TIMING_PACKET_LOSS_MAX (30 * PA_USEC_PER_SEC)
|
||||
#define UDP_TIMING_PACKET_DISCONNECT_CYCLE 3
|
||||
|
|
@ -79,6 +79,7 @@ struct userdata {
|
|||
pa_module *module;
|
||||
pa_sink *sink;
|
||||
pa_card *card;
|
||||
pa_subscription *subscription;
|
||||
|
||||
pa_thread *thread;
|
||||
pa_thread_mq thread_mq;
|
||||
|
|
@ -91,6 +92,7 @@ struct userdata {
|
|||
pa_raop_protocol_t protocol;
|
||||
pa_raop_encryption_t encryption;
|
||||
pa_raop_codec_t codec;
|
||||
char *password;
|
||||
bool autoreconnect;
|
||||
/* if true, behaves like a null-sink when disconnected */
|
||||
bool autonull;
|
||||
|
|
@ -115,7 +117,9 @@ struct userdata {
|
|||
|
||||
enum {
|
||||
PA_SINK_MESSAGE_SET_RAOP_STATE = PA_SINK_MESSAGE_MAX,
|
||||
PA_SINK_MESSAGE_DISCONNECT_REQUEST
|
||||
PA_SINK_MESSAGE_CONNECTED,
|
||||
PA_SINK_MESSAGE_DISCONNECTED,
|
||||
PA_SINK_MESSAGE_CONNECT_REQUEST
|
||||
};
|
||||
|
||||
static void userdata_free(struct userdata *u);
|
||||
|
|
@ -129,7 +133,8 @@ static void raop_state_cb(pa_raop_state_t state, void *userdata) {
|
|||
|
||||
pa_log_debug("State change received, informing IO thread...");
|
||||
|
||||
pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_SET_RAOP_STATE, PA_INT_TO_PTR(state), 0, NULL, NULL);
|
||||
pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->sink),
|
||||
PA_SINK_MESSAGE_SET_RAOP_STATE, PA_INT_TO_PTR(state), 0, NULL);
|
||||
}
|
||||
|
||||
static int64_t sink_get_latency(const struct userdata *u) {
|
||||
|
|
@ -156,6 +161,44 @@ static int64_t sink_get_latency(const struct userdata *u) {
|
|||
return latency;
|
||||
}
|
||||
|
||||
static void disconnect(struct userdata *u) {
|
||||
unsigned int nbfds = 0;
|
||||
struct pollfd *pollfd;
|
||||
unsigned int i;
|
||||
|
||||
if (u->rtpoll_item) {
|
||||
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, &nbfds);
|
||||
if (pollfd) {
|
||||
for (i = 0; i < nbfds; i++) {
|
||||
if (pollfd->fd >= 0)
|
||||
pa_close(pollfd->fd);
|
||||
pollfd++;
|
||||
}
|
||||
}
|
||||
pa_rtpoll_item_free(u->rtpoll_item);
|
||||
u->rtpoll_item = NULL;
|
||||
}
|
||||
|
||||
pa_raop_client_disconnect(u->raop);
|
||||
|
||||
if (u->sink->thread_info.state == PA_SINK_SUSPENDED)
|
||||
pa_rtpoll_set_timer_disabled(u->rtpoll);
|
||||
|
||||
if (u->sink->thread_info.state == PA_SINK_RUNNING) {
|
||||
if (!u->autonull)
|
||||
pa_rtpoll_set_timer_disabled(u->rtpoll);
|
||||
|
||||
if (u->autoreconnect) {
|
||||
if (pa_raop_client_is_authenticated(u->raop))
|
||||
pa_raop_client_announce(u->raop);
|
||||
else
|
||||
pa_raop_client_authenticate(u->raop, u->password);
|
||||
} else
|
||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink),
|
||||
PA_SINK_MESSAGE_DISCONNECTED, NULL, 0, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
|
||||
struct userdata *u = PA_SINK(o)->userdata;
|
||||
|
||||
|
|
@ -163,23 +206,34 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
|
|||
pa_assert(u->raop);
|
||||
|
||||
switch (code) {
|
||||
/* Exception : for this message, we are in main thread, msg sent from the IO/thread
|
||||
Done here, as alloc/free of rtsp_client is also done in this thread for other cases */
|
||||
case PA_SINK_MESSAGE_DISCONNECT_REQUEST: {
|
||||
if (u->sink->state == PA_SINK_RUNNING) {
|
||||
/* Disconnect raop client, and restart the whole chain since
|
||||
* the authentication token might be outdated */
|
||||
pa_raop_client_disconnect(u->raop);
|
||||
pa_raop_client_authenticate(u->raop, NULL);
|
||||
case PA_SINK_MESSAGE_CONNECTED: {
|
||||
pa_assert_ctl_context();
|
||||
pa_device_port_set_available(u->sink->active_port, PA_AVAILABLE_YES);
|
||||
return 0;
|
||||
}
|
||||
|
||||
case PA_SINK_MESSAGE_DISCONNECTED: {
|
||||
pa_assert_ctl_context();
|
||||
/* Mark the port as unavailable so a different sink can be used */
|
||||
pa_device_port_set_available(u->sink->active_port, PA_AVAILABLE_NO);
|
||||
return 0;
|
||||
}
|
||||
|
||||
case PA_SINK_MESSAGE_CONNECT_REQUEST: {
|
||||
pa_assert_io_context();
|
||||
pa_log_debug("Received connect request");
|
||||
if (pa_raop_client_is_authenticated(u->raop))
|
||||
pa_raop_client_announce(u->raop);
|
||||
else
|
||||
pa_raop_client_authenticate(u->raop, u->password);
|
||||
return 0;
|
||||
}
|
||||
|
||||
case PA_SINK_MESSAGE_GET_LATENCY: {
|
||||
pa_assert_io_context();
|
||||
int64_t r = 0;
|
||||
|
||||
if (u->autonull || pa_raop_client_can_stream(u->raop))
|
||||
if (u->autonull || pa_raop_client_is_streaming(u->raop))
|
||||
r = sink_get_latency(u);
|
||||
|
||||
*((int64_t*) data) = r;
|
||||
|
|
@ -188,13 +242,10 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
|
|||
}
|
||||
|
||||
case PA_SINK_MESSAGE_SET_RAOP_STATE: {
|
||||
pa_assert_io_context();
|
||||
switch ((pa_raop_state_t) PA_PTR_TO_UINT(data)) {
|
||||
case PA_RAOP_AUTHENTICATED: {
|
||||
if (!pa_raop_client_is_authenticated(u->raop)) {
|
||||
pa_module_unload_request(u->module, true);
|
||||
}
|
||||
|
||||
if (u->autoreconnect && u->sink->state == PA_SINK_RUNNING) {
|
||||
if (u->sink->state == PA_SINK_RUNNING) {
|
||||
pa_usec_t now;
|
||||
now = pa_rtclock_now();
|
||||
#ifdef USE_SMOOTHER_2
|
||||
|
|
@ -202,12 +253,9 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
|
|||
#else
|
||||
pa_smoother_reset(u->smoother, now, false);
|
||||
#endif
|
||||
|
||||
if (!pa_raop_client_is_alive(u->raop)) {
|
||||
/* Connecting will trigger a RECORD and start steaming */
|
||||
/* Connecting will trigger a RECORD and start streaming */
|
||||
pa_raop_client_announce(u->raop);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -217,6 +265,9 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
|
|||
|
||||
u->oob = pa_raop_client_register_pollfd(u->raop, u->rtpoll, &u->rtpoll_item);
|
||||
|
||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink),
|
||||
PA_SINK_MESSAGE_CONNECTED, NULL, 0, NULL, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -244,40 +295,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
|
|||
|
||||
case PA_RAOP_INVALID_STATE:
|
||||
case PA_RAOP_DISCONNECTED: {
|
||||
unsigned int nbfds = 0;
|
||||
struct pollfd *pollfd;
|
||||
unsigned int i;
|
||||
|
||||
if (u->rtpoll_item) {
|
||||
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, &nbfds);
|
||||
if (pollfd) {
|
||||
for (i = 0; i < nbfds; i++) {
|
||||
if (pollfd->fd >= 0)
|
||||
pa_close(pollfd->fd);
|
||||
pollfd++;
|
||||
}
|
||||
}
|
||||
pa_rtpoll_item_free(u->rtpoll_item);
|
||||
u->rtpoll_item = NULL;
|
||||
}
|
||||
|
||||
if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
|
||||
pa_rtpoll_set_timer_disabled(u->rtpoll);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (u->autoreconnect) {
|
||||
if (u->sink->thread_info.state != PA_SINK_IDLE) {
|
||||
if (!u->autonull)
|
||||
pa_rtpoll_set_timer_disabled(u->rtpoll);
|
||||
pa_raop_client_authenticate(u->raop, NULL);
|
||||
}
|
||||
} else {
|
||||
if (u->sink->thread_info.state != PA_SINK_IDLE)
|
||||
pa_module_unload_request(u->module, true);
|
||||
}
|
||||
|
||||
disconnect(u);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -286,6 +304,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
|
|||
}
|
||||
}
|
||||
|
||||
pa_assert_io_context();
|
||||
|
||||
return pa_sink_process_msg(o, code, data, offset, chunk);
|
||||
}
|
||||
|
||||
|
|
@ -308,9 +328,8 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state,
|
|||
pa_assert(PA_SINK_IS_OPENED(s->thread_info.state));
|
||||
|
||||
/* Issue a TEARDOWN if we are still connected */
|
||||
if (pa_raop_client_is_alive(u->raop)) {
|
||||
if (pa_raop_client_is_alive(u->raop))
|
||||
pa_raop_client_teardown(u->raop);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
|
|
@ -348,9 +367,12 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state,
|
|||
|
||||
if (!pa_raop_client_is_alive(u->raop)) {
|
||||
/* Connecting will trigger a RECORD and start streaming */
|
||||
if (pa_raop_client_is_authenticated(u->raop))
|
||||
pa_raop_client_announce(u->raop);
|
||||
} else if (!pa_raop_client_is_recording(u->raop)) {
|
||||
/* RECORD alredy sent, simply start streaming */
|
||||
else
|
||||
pa_raop_client_authenticate(u->raop, u->password);
|
||||
} else if (!pa_raop_client_is_streaming(u->raop)) {
|
||||
/* RECORD already sent, simply start streaming */
|
||||
pa_raop_client_stream(u->raop);
|
||||
pa_rtpoll_set_timer_absolute(u->rtpoll, now);
|
||||
u->write_count = 0;
|
||||
|
|
@ -445,7 +467,7 @@ static void thread_func(void *userdata) {
|
|||
uint64_t position;
|
||||
size_t index;
|
||||
int ret;
|
||||
bool canstream, sendstream, on_timeout;
|
||||
bool is_streaming, on_timeout;
|
||||
#ifndef USE_SMOOTHER_2
|
||||
pa_usec_t estimated;
|
||||
#endif
|
||||
|
|
@ -478,25 +500,26 @@ static void thread_func(void *userdata) {
|
|||
|
||||
/* if oob: streaming managed by timing, pollfd for oob sockets */
|
||||
if (pollfd && u->oob && !on_timeout) {
|
||||
uint8_t packet[32];
|
||||
ssize_t read;
|
||||
|
||||
for (i = 0; i < nbfds; i++) {
|
||||
if (pollfd->revents & POLLERR) {
|
||||
if (u->autoreconnect && pa_raop_client_is_alive(u->raop)) {
|
||||
pollfd->revents = 0;
|
||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink),
|
||||
PA_SINK_MESSAGE_DISCONNECT_REQUEST, 0, 0, NULL, NULL);
|
||||
disconnect(u);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* one of UDP fds is in faulty state, may have been disconnected, this is fatal */
|
||||
goto fail;
|
||||
}
|
||||
if (pollfd->revents & pollfd->events) {
|
||||
pollfd->revents = 0;
|
||||
read = pa_read(pollfd->fd, packet, sizeof(packet), NULL);
|
||||
pa_raop_client_handle_oob_packet(u->raop, pollfd->fd, packet, read);
|
||||
if (pa_raop_client_handle_oob_packet(u->raop, pollfd->fd) < 0) {
|
||||
if (errno == EINTR) {
|
||||
pa_log_debug("Failed to handle oob packet (EINTR), ignoring");
|
||||
continue;
|
||||
} else if (errno == EAGAIN) {
|
||||
pa_log_debug("Failed to handle oob packet (EAGAIN), ignoring");
|
||||
continue;
|
||||
} else {
|
||||
pa_log("Failed to handle oob packet: %s", pa_cstrerror(errno));
|
||||
disconnect(u);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (pa_raop_client_is_timing_fd(u->raop, pollfd->fd)) {
|
||||
last_timing = pa_rtclock_now();
|
||||
check_timing_count = 1;
|
||||
|
|
@ -510,9 +533,8 @@ static void thread_func(void *userdata) {
|
|||
}
|
||||
}
|
||||
|
||||
if (u->sink->thread_info.state != PA_SINK_RUNNING) {
|
||||
if (u->sink->thread_info.state != PA_SINK_RUNNING)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (u->first) {
|
||||
last_timing = 0;
|
||||
|
|
@ -521,11 +543,11 @@ static void thread_func(void *userdata) {
|
|||
u->first = false;
|
||||
}
|
||||
|
||||
canstream = pa_raop_client_can_stream(u->raop);
|
||||
is_streaming = pa_raop_client_is_streaming(u->raop);
|
||||
now = pa_rtclock_now();
|
||||
|
||||
if (u->oob && u->autoreconnect && on_timeout) {
|
||||
if (!canstream) {
|
||||
if (!is_streaming) {
|
||||
last_timing = 0;
|
||||
} else if (last_timing != 0) {
|
||||
pa_usec_t since = now - last_timing;
|
||||
|
|
@ -543,28 +565,23 @@ static void thread_func(void *userdata) {
|
|||
UDP_TIMING_PACKET_DISCONNECT_CYCLE-1, since_in_sec, u->server);
|
||||
check_timing_count++;
|
||||
} else {
|
||||
/* Limit reached, then request disconnect */
|
||||
/* Limit reached, then disconnect */
|
||||
check_timing_count = 1;
|
||||
last_timing = 0;
|
||||
if (pa_raop_client_is_alive(u->raop)) {
|
||||
pa_log_warn("UDP Timing Packets Warn limit reached - Requesting reconnect");
|
||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink),
|
||||
PA_SINK_MESSAGE_DISCONNECT_REQUEST, 0, 0, NULL, NULL);
|
||||
pa_log_warn("UDP Timing Packets Warn limit reached - disconnecting");
|
||||
disconnect(u);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!u->autonull) {
|
||||
if (!canstream) {
|
||||
pa_log_debug("Can't stream, connection not established yet...");
|
||||
if (!is_streaming)
|
||||
continue;
|
||||
}
|
||||
/* This assertion is meant to silence a complaint from Coverity about
|
||||
* pollfd being possibly NULL when we access it later. That's a false
|
||||
* positive, because we check pa_raop_client_can_stream() above, and if
|
||||
* positive, because we check pa_raop_client_is_streaming() above, and if
|
||||
* that returns true, it means that the connection is up, and when the
|
||||
* connection is up, pollfd will be non-NULL. */
|
||||
pa_assert(pollfd);
|
||||
|
|
@ -584,36 +601,30 @@ static void thread_func(void *userdata) {
|
|||
|
||||
if (u->memchunk.length > 0) {
|
||||
index = u->memchunk.index;
|
||||
sendstream = !u->autonull || (u->autonull && canstream);
|
||||
if (sendstream && pa_raop_client_send_audio_packet(u->raop, &u->memchunk, offset) < 0) {
|
||||
if (is_streaming && pa_raop_client_send_audio_packet(u->raop, &u->memchunk, offset) < 0) {
|
||||
if (errno == EINTR) {
|
||||
/* Just try again. */
|
||||
pa_log_debug("Failed to write data to FIFO (EINTR), retrying");
|
||||
if (u->autoreconnect) {
|
||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_DISCONNECT_REQUEST,
|
||||
0, 0, NULL, NULL);
|
||||
pa_log_debug("Failed to write audio packet (EINTR), retrying");
|
||||
continue;
|
||||
} else
|
||||
goto fail;
|
||||
} else if (errno != EAGAIN && !u->oob) {
|
||||
} else if (errno == EAGAIN) {
|
||||
if (u->oob) {
|
||||
/* Just try again. */
|
||||
pa_log_debug("Failed to write audio packet (EAGAIN), retrying");
|
||||
continue;
|
||||
} else {
|
||||
/* Buffer is full, wait for POLLOUT. */
|
||||
if (!u->oob) {
|
||||
pollfd->events = POLLOUT;
|
||||
pollfd->revents = 0;
|
||||
}
|
||||
} else {
|
||||
pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));
|
||||
if (u->autoreconnect) {
|
||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_DISCONNECT_REQUEST,
|
||||
0, 0, NULL, NULL);
|
||||
pa_log("Failed to write audio packet: %s", pa_cstrerror(errno));
|
||||
disconnect(u);
|
||||
continue;
|
||||
} else
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
if (sendstream) {
|
||||
if (is_streaming) {
|
||||
u->write_count += (uint64_t) u->memchunk.index - (uint64_t) index;
|
||||
} else {
|
||||
} else if (u->autonull) {
|
||||
u->write_count += u->memchunk.length;
|
||||
u->memchunk.length = 0;
|
||||
}
|
||||
|
|
@ -627,7 +638,7 @@ static void thread_func(void *userdata) {
|
|||
pa_smoother_put(u->smoother, now, estimated);
|
||||
#endif
|
||||
|
||||
if ((u->autonull && !canstream) || (u->oob && canstream && on_timeout)) {
|
||||
if ((u->autonull && !is_streaming) || (u->oob && is_streaming && on_timeout)) {
|
||||
/* Sleep until next packet transmission */
|
||||
intvl = u->start + pa_bytes_to_usec(u->write_count, &u->sink->sample_spec);
|
||||
pa_rtpoll_set_timer_absolute(u->rtpoll, intvl);
|
||||
|
|
@ -660,6 +671,18 @@ static int sink_set_port_cb(pa_sink *s, pa_device_port *p) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
|
||||
struct userdata *u = userdata;
|
||||
|
||||
pa_assert_ctl_context();
|
||||
|
||||
/* Try to reconnect on server changes */
|
||||
if (u->sink->active_port->available == PA_AVAILABLE_NO) {
|
||||
pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink),
|
||||
PA_SINK_MESSAGE_CONNECT_REQUEST, NULL, 0, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static pa_device_port *raop_create_port(struct userdata *u, const char *server) {
|
||||
pa_device_port_new_data data;
|
||||
pa_device_port *port;
|
||||
|
|
@ -902,6 +925,11 @@ pa_sink* pa_raop_sink_new(pa_module *m, pa_modargs *ma, const char *driver) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
u->subscription = pa_subscription_new(
|
||||
m->core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE,
|
||||
subscribe_callback, u
|
||||
);
|
||||
|
||||
u->sink->parent.process_msg = sink_process_msg;
|
||||
u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb;
|
||||
pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
|
||||
|
|
@ -939,7 +967,8 @@ pa_sink* pa_raop_sink_new(pa_module *m, pa_modargs *ma, const char *driver) {
|
|||
|
||||
/* username = pa_modargs_get_value(ma, "username", NULL); */
|
||||
password = pa_modargs_get_value(ma, "password", NULL);
|
||||
pa_raop_client_authenticate(u->raop, password );
|
||||
if (password)
|
||||
u->password = pa_xstrdup(password);
|
||||
|
||||
return u->sink;
|
||||
|
||||
|
|
@ -969,6 +998,10 @@ static void userdata_free(struct userdata *u) {
|
|||
pa_sink_unref(u->sink);
|
||||
u->sink = NULL;
|
||||
|
||||
if (u->subscription)
|
||||
pa_subscription_free(u->subscription);
|
||||
u->subscription = NULL;
|
||||
|
||||
if (u->rtpoll_item)
|
||||
pa_rtpoll_item_free(u->rtpoll_item);
|
||||
if (u->rtpoll)
|
||||
|
|
@ -995,6 +1028,8 @@ static void userdata_free(struct userdata *u) {
|
|||
pa_card_free(u->card);
|
||||
if (u->server)
|
||||
pa_xfree(u->server);
|
||||
if (u->password)
|
||||
pa_xfree(u->password);
|
||||
|
||||
pa_xfree(u);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,16 +2,18 @@ librtp_sources = [
|
|||
'rtp-common.c',
|
||||
'sdp.c',
|
||||
'sap.c',
|
||||
'rtsp_client.c',
|
||||
'rtsp-client.c',
|
||||
'headerlist.c',
|
||||
'rtsp-util.c'
|
||||
]
|
||||
|
||||
librtp_headers = [
|
||||
'rtp.h',
|
||||
'sdp.h',
|
||||
'sap.h',
|
||||
'rtsp_client.h',
|
||||
'rtsp-client.h',
|
||||
'headerlist.h',
|
||||
'rtsp-util.h'
|
||||
]
|
||||
|
||||
if have_gstreamer
|
||||
|
|
@ -26,7 +28,7 @@ librtp = shared_library('rtp',
|
|||
c_args : [pa_c_args, server_c_args],
|
||||
link_args : [nodelete_link_args],
|
||||
include_directories : [configinc, topinc],
|
||||
dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, libatomic_ops_dep, gst_dep, gstapp_dep, gstrtp_dep, gio_dep],
|
||||
dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, libatomic_ops_dep, gst_dep, gstapp_dep, gstrtp_dep, gio_dep, openssl_dep],
|
||||
install : true,
|
||||
install_rpath : privlibdir,
|
||||
install_dir : modlibexecdir,
|
||||
|
|
|
|||
|
|
@ -40,16 +40,30 @@
|
|||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/macro.h>
|
||||
#include <pulsecore/mutex.h>
|
||||
#include <pulsecore/strbuf.h>
|
||||
#include <pulsecore/ioline.h>
|
||||
#include <pulsecore/arpa-inet.h>
|
||||
#include <pulsecore/random.h>
|
||||
#include <pulsecore/core-rtclock.h>
|
||||
|
||||
#include "rtsp_client.h"
|
||||
#include "rtsp-client.h"
|
||||
#include "rtsp-util.h"
|
||||
|
||||
#define RECONNECT_INTERVAL (5 * PA_USEC_PER_SEC)
|
||||
|
||||
enum wait_state {
|
||||
WAIT_NONE,
|
||||
WAIT_RESPONSE,
|
||||
WAIT_HEADERS
|
||||
};
|
||||
|
||||
enum auth_method {
|
||||
AUTH_NONE,
|
||||
AUTH_BASIC,
|
||||
AUTH_DIGEST
|
||||
};
|
||||
|
||||
struct pa_rtsp_client {
|
||||
pa_mainloop_api *mainloop;
|
||||
char *hostname;
|
||||
|
|
@ -62,10 +76,16 @@ struct pa_rtsp_client {
|
|||
|
||||
void *userdata;
|
||||
const char *useragent;
|
||||
const char *username;
|
||||
const char *password;
|
||||
enum auth_method mth;
|
||||
char *realm, *nonce;
|
||||
|
||||
pa_rtsp_state_t state;
|
||||
pa_rtsp_status_t status;
|
||||
uint8_t waiting;
|
||||
enum wait_state waiting;
|
||||
pa_mutex *mutex;
|
||||
int length;
|
||||
|
||||
pa_headerlist* headers;
|
||||
char *last_header;
|
||||
|
|
@ -100,7 +120,13 @@ pa_rtsp_client* pa_rtsp_client_new(pa_mainloop_api *mainloop, const char *hostna
|
|||
else
|
||||
c->useragent = "PulseAudio RTSP Client";
|
||||
|
||||
c->mth = AUTH_NONE;
|
||||
c->autoreconnect = autoreconnect;
|
||||
|
||||
c->waiting = WAIT_NONE;
|
||||
c->mutex = pa_mutex_new(false, false);
|
||||
c->length = 0;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
|
|
@ -128,6 +154,10 @@ void pa_rtsp_client_free(pa_rtsp_client *c) {
|
|||
pa_xfree(c->session);
|
||||
pa_xfree(c->transport);
|
||||
pa_xfree(c->last_header);
|
||||
pa_xfree(c->realm);
|
||||
pa_xfree(c->nonce);
|
||||
pa_mutex_free(c->mutex);
|
||||
c->mutex = NULL;
|
||||
if (c->header_buffer)
|
||||
pa_strbuf_free(c->header_buffer);
|
||||
if (c->response_headers)
|
||||
|
|
@ -137,14 +167,78 @@ void pa_rtsp_client_free(pa_rtsp_client *c) {
|
|||
pa_xfree(c);
|
||||
}
|
||||
|
||||
static void authenticate(pa_rtsp_client *c) {
|
||||
const char *current = NULL;
|
||||
const char *wath;
|
||||
char space[] = " ";
|
||||
char *token = NULL;
|
||||
char *val = NULL, *mth = NULL;
|
||||
char comma[] = ",";
|
||||
|
||||
pa_xfree(c->realm);
|
||||
pa_xfree(c->nonce);
|
||||
|
||||
c->mth = AUTH_NONE;
|
||||
c->realm = c->nonce = NULL;
|
||||
|
||||
if (!c->username || !c->password)
|
||||
return;
|
||||
|
||||
wath = pa_headerlist_gets(c->response_headers, "WWW-Authenticate");
|
||||
|
||||
if (!wath)
|
||||
return;
|
||||
|
||||
mth = pa_split(wath, space, ¤t);
|
||||
|
||||
if (pa_safe_streq(mth, "Basic"))
|
||||
c->mth = AUTH_BASIC;
|
||||
else if (pa_safe_streq(mth, "Digest"))
|
||||
c->mth = AUTH_DIGEST;
|
||||
else
|
||||
goto done;
|
||||
|
||||
while ((token = pa_split(wath, comma, ¤t))) {
|
||||
if ((val = strstr(token, "="))) {
|
||||
if (NULL == c->realm && val > strstr(token, "realm")) {
|
||||
if (!(c->realm = pa_xstrdup(val + 2)))
|
||||
goto done;
|
||||
pa_rtsp_rtrim_char(c->realm, '\"');
|
||||
}
|
||||
else if (NULL == c->nonce && val > strstr(token, "nonce")) {
|
||||
if (!(c->nonce = pa_xstrdup(val + 2)))
|
||||
goto done;
|
||||
pa_rtsp_rtrim_char(c->nonce, '\"');
|
||||
}
|
||||
}
|
||||
|
||||
pa_xfree(token);
|
||||
token = NULL;
|
||||
}
|
||||
|
||||
done:
|
||||
pa_xfree(token);
|
||||
pa_xfree(mth);
|
||||
}
|
||||
|
||||
static void headers_read(pa_rtsp_client *c) {
|
||||
char delimiters[] = ";";
|
||||
char* token;
|
||||
char* token = NULL;
|
||||
const char *clength;
|
||||
|
||||
pa_assert(c);
|
||||
pa_assert(c->response_headers);
|
||||
pa_assert(c->callback);
|
||||
|
||||
c->length = 0;
|
||||
|
||||
clength = pa_headerlist_gets(c->response_headers, "Content-Length");
|
||||
if (clength && pa_atoi(clength, &c->length) < 0)
|
||||
pa_log_warn("Unexpected value in content-length: %s", clength);
|
||||
|
||||
if (c->status == STATUS_UNAUTHORIZED)
|
||||
authenticate(c);
|
||||
|
||||
/* Deal with a SETUP response */
|
||||
if (STATE_SETUP == c->state) {
|
||||
const char* token_state = NULL;
|
||||
|
|
@ -154,7 +248,7 @@ static void headers_read(pa_rtsp_client *c) {
|
|||
|
||||
if (!c->session || !c->transport) {
|
||||
pa_log("Invalid SETUP response.");
|
||||
return;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Now parse out the server port component of the response. */
|
||||
|
|
@ -165,12 +259,10 @@ static void headers_read(pa_rtsp_client *c) {
|
|||
|
||||
if (pa_atou(pc + 1, &p) < 0 || p <= 0 || p > 0xffff) {
|
||||
pa_log("Invalid SETUP response (invalid server_port).");
|
||||
pa_xfree(token);
|
||||
return;
|
||||
goto done;
|
||||
}
|
||||
|
||||
c->rtp_port = p;
|
||||
pa_xfree(token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -179,15 +271,20 @@ static void headers_read(pa_rtsp_client *c) {
|
|||
if (0 == c->rtp_port) {
|
||||
/* Error no server_port in response */
|
||||
pa_log("Invalid SETUP response (no port number).");
|
||||
return;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
pa_xfree(token);
|
||||
|
||||
c->waiting = WAIT_NONE;
|
||||
|
||||
/* Call our callback */
|
||||
c->callback(c, c->state, c->status, c->response_headers, c->userdata);
|
||||
}
|
||||
|
||||
static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
||||
static void line_callback(pa_ioline *line, const char *s, size_t l, void *userdata) {
|
||||
pa_rtsp_client *c = userdata;
|
||||
char *delimpos;
|
||||
char *s2, *s2p;
|
||||
|
|
@ -203,6 +300,25 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
|||
return;
|
||||
}
|
||||
|
||||
/* Skip any body from the last response */
|
||||
if (c->length) {
|
||||
if (l > c->length) {
|
||||
l -= c->length;
|
||||
s += c->length;
|
||||
c->length = 0;
|
||||
} else {
|
||||
c->length -= l;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pa_assert(l);
|
||||
|
||||
if (c->waiting == WAIT_NONE) {
|
||||
pa_log_warn("Received more data than content length");
|
||||
return;
|
||||
}
|
||||
|
||||
s2 = pa_xstrdup(s);
|
||||
/* Trim trailing carriage returns */
|
||||
s2p = s2 + strlen(s2) - 1;
|
||||
|
|
@ -211,23 +327,27 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
|||
s2p -= 1;
|
||||
}
|
||||
|
||||
if (c->waiting && pa_streq(s2, "RTSP/1.0 200 OK")) {
|
||||
if (c->waiting == WAIT_RESPONSE && pa_streq(s2, "RTSP/1.0 200 OK")) {
|
||||
if (c->response_headers)
|
||||
pa_headerlist_free(c->response_headers);
|
||||
c->response_headers = pa_headerlist_new();
|
||||
|
||||
c->status = STATUS_OK;
|
||||
c->waiting = 0;
|
||||
c->waiting = WAIT_HEADERS;
|
||||
goto exit;
|
||||
} else if (c->waiting && pa_streq(s2, "RTSP/1.0 401 Unauthorized")) {
|
||||
} else if (c->waiting == WAIT_RESPONSE && pa_streq(s2, "RTSP/1.0 401 Unauthorized")) {
|
||||
if (c->response_headers)
|
||||
pa_headerlist_free(c->response_headers);
|
||||
c->response_headers = pa_headerlist_new();
|
||||
|
||||
c->status = STATUS_UNAUTHORIZED;
|
||||
c->waiting = 0;
|
||||
c->waiting = WAIT_HEADERS;
|
||||
goto exit;
|
||||
} else if (c->waiting) {
|
||||
} else if (c->waiting == WAIT_RESPONSE) {
|
||||
if (c->response_headers)
|
||||
pa_headerlist_free(c->response_headers);
|
||||
c->response_headers = pa_headerlist_new();
|
||||
|
||||
pa_log_warn("Unexpected/Unhandled response: %s", s2);
|
||||
|
||||
if (pa_streq(s2, "RTSP/1.0 400 Bad Request"))
|
||||
|
|
@ -236,6 +356,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
|||
c->status = STATUS_INTERNAL_ERROR;
|
||||
else
|
||||
c->status = STATUS_NO_RESPONSE;
|
||||
c->waiting = WAIT_HEADERS;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
|
|
@ -252,7 +373,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
|||
c->header_buffer = NULL;
|
||||
}
|
||||
|
||||
pa_log_debug("Full response received. Dispatching");
|
||||
pa_log_debug("Response received. Dispatching");
|
||||
headers_read(c);
|
||||
goto exit;
|
||||
}
|
||||
|
|
@ -312,10 +433,10 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
|||
}
|
||||
|
||||
static void reconnect_cb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) {
|
||||
if (userdata) {
|
||||
pa_rtsp_client *c = userdata;
|
||||
pa_rtsp_connect(c);
|
||||
}
|
||||
pa_assert(c);
|
||||
if (pa_rtsp_connect(c))
|
||||
c->callback(c, STATE_DISCONNECTED, STATUS_NO_RESPONSE, NULL, c->userdata);
|
||||
}
|
||||
|
||||
static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) {
|
||||
|
|
@ -333,6 +454,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata
|
|||
pa_assert(c->sc == sc);
|
||||
pa_socket_client_unref(c->sc);
|
||||
c->sc = NULL;
|
||||
c->waiting = WAIT_NONE;
|
||||
|
||||
if (!io) {
|
||||
if (c->autoreconnect) {
|
||||
|
|
@ -346,6 +468,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata
|
|||
c->mainloop->time_restart(c->reconnect_event, pa_timeval_rtstore(&tv, pa_rtclock_now() + RECONNECT_INTERVAL, true));
|
||||
} else {
|
||||
pa_log("Connection to server %s:%d failed: %s", c->hostname, c->port, pa_cstrerror(errno));
|
||||
c->callback(c, STATE_DISCONNECTED, STATUS_NO_RESPONSE, NULL, c->userdata);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -389,7 +512,7 @@ int pa_rtsp_connect(pa_rtsp_client *c) {
|
|||
}
|
||||
|
||||
pa_socket_client_set_callback(c->sc, on_connection, c);
|
||||
c->waiting = 1;
|
||||
c->waiting = WAIT_RESPONSE;
|
||||
c->state = STATE_CONNECT;
|
||||
c->status = STATUS_NO_RESPONSE;
|
||||
return 0;
|
||||
|
|
@ -424,18 +547,18 @@ uint32_t pa_rtsp_serverport(pa_rtsp_client *c) {
|
|||
return c->rtp_port;
|
||||
}
|
||||
|
||||
bool pa_rtsp_exec_ready(const pa_rtsp_client *c) {
|
||||
pa_assert(c);
|
||||
|
||||
return c->url != NULL && c->ioline != NULL;
|
||||
}
|
||||
|
||||
void pa_rtsp_set_url(pa_rtsp_client *c, const char *url) {
|
||||
pa_assert(c);
|
||||
|
||||
pa_xfree(c->url);
|
||||
c->url = pa_xstrdup(url);
|
||||
}
|
||||
|
||||
void pa_rtsp_set_credentials(pa_rtsp_client *c, const char *username, const char*password) {
|
||||
c->username = username;
|
||||
c->password = password;
|
||||
}
|
||||
|
||||
bool pa_rtsp_has_header(pa_rtsp_client *c, const char *key) {
|
||||
pa_assert(c);
|
||||
pa_assert(key);
|
||||
|
|
@ -465,22 +588,63 @@ void pa_rtsp_remove_header(pa_rtsp_client *c, const char *key) {
|
|||
pa_headerlist_remove(c->headers, key);
|
||||
}
|
||||
|
||||
static int rtsp_exec(pa_rtsp_client *c, const char *cmd,
|
||||
const char *content_type, const char *content,
|
||||
int expect_response,
|
||||
pa_headerlist *headers) {
|
||||
static char *get_auth(pa_rtsp_client *c, const char *method, const char *url) {
|
||||
char *ath = NULL, *response = NULL;
|
||||
|
||||
pa_assert(method);
|
||||
pa_assert(url);
|
||||
|
||||
if (!c->username || !c->password)
|
||||
return NULL;
|
||||
|
||||
switch (c->mth) {
|
||||
case AUTH_NONE:
|
||||
break;
|
||||
case AUTH_BASIC:
|
||||
pa_rtsp_basic_response(c->username, c->password, &response);
|
||||
ath = pa_sprintf_malloc("Basic %s", response);
|
||||
break;
|
||||
case AUTH_DIGEST:
|
||||
pa_rtsp_digest_response(c->username, c->realm, c->password, c->nonce, method, url, &response);
|
||||
ath = pa_sprintf_malloc("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"",
|
||||
c->username, c->realm, c->nonce, url, response);
|
||||
break;
|
||||
}
|
||||
|
||||
pa_xfree(response);
|
||||
|
||||
return ath;
|
||||
}
|
||||
|
||||
static int rtsp_exec(pa_rtsp_client *c, const char *cmd, const char *url,
|
||||
pa_headerlist *headers, const char *content_type, const char *content) {
|
||||
pa_strbuf *buf;
|
||||
char *hdrs;
|
||||
|
||||
pa_assert(c);
|
||||
pa_assert(c->url);
|
||||
pa_assert(cmd);
|
||||
pa_assert(url);
|
||||
pa_assert(c);
|
||||
pa_assert(c->ioline);
|
||||
|
||||
if (!pa_mutex_try_lock(c->mutex)) {
|
||||
pa_log_warn("Can't send command (locked): %s", cmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (c->waiting != WAIT_NONE) {
|
||||
pa_log_warn("Can't send command (busy): %s", cmd);
|
||||
pa_mutex_unlock(c->mutex);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pa_log_debug("Sending command: %s", cmd);
|
||||
|
||||
c->waiting = WAIT_RESPONSE;
|
||||
|
||||
pa_mutex_unlock(c->mutex);
|
||||
|
||||
buf = pa_strbuf_new();
|
||||
pa_strbuf_printf(buf, "%s %s RTSP/1.0\r\nCSeq: %d\r\n", cmd, c->url, ++c->cseq);
|
||||
pa_strbuf_printf(buf, "%s %s RTSP/1.0\r\nCSeq: %d\r\n", cmd, url, ++c->cseq);
|
||||
if (c->session)
|
||||
pa_strbuf_printf(buf, "Session: %s\r\n", c->session);
|
||||
|
||||
|
|
@ -496,6 +660,13 @@ static int rtsp_exec(pa_rtsp_client *c, const char *cmd,
|
|||
content_type, (int)strlen(content));
|
||||
}
|
||||
|
||||
char *auth = get_auth(c, cmd, url);
|
||||
|
||||
if (auth) {
|
||||
pa_strbuf_printf(buf, "Authorization: %s\r\n", auth);
|
||||
pa_xfree(auth);
|
||||
}
|
||||
|
||||
pa_strbuf_printf(buf, "User-Agent: %s\r\n", c->useragent);
|
||||
|
||||
if (c->headers) {
|
||||
|
|
@ -516,24 +687,18 @@ static int rtsp_exec(pa_rtsp_client *c, const char *cmd,
|
|||
pa_log_debug(hdrs);*/
|
||||
pa_ioline_puts(c->ioline, hdrs);
|
||||
pa_xfree(hdrs);
|
||||
/* The command is sent we can configure the rtsp client structure to handle a new answer */
|
||||
c->waiting = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pa_rtsp_options(pa_rtsp_client *c) {
|
||||
char *url;
|
||||
int rv;
|
||||
|
||||
pa_assert(c);
|
||||
|
||||
url = c->url;
|
||||
if (!(rv = rtsp_exec(c, "OPTIONS", "*", NULL, NULL, NULL)))
|
||||
c->state = STATE_OPTIONS;
|
||||
|
||||
c->url = (char *)"*";
|
||||
rv = rtsp_exec(c, "OPTIONS", NULL, NULL, 0, NULL);
|
||||
|
||||
c->url = url;
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
|
@ -545,8 +710,8 @@ int pa_rtsp_announce(pa_rtsp_client *c, const char *sdp) {
|
|||
if (!sdp)
|
||||
return -1;
|
||||
|
||||
if (!(rv = rtsp_exec(c, "ANNOUNCE", c->url, NULL, "application/sdp", sdp)))
|
||||
c->state = STATE_ANNOUNCE;
|
||||
rv = rtsp_exec(c, "ANNOUNCE", "application/sdp", sdp, 1, NULL);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
|
@ -563,8 +728,8 @@ int pa_rtsp_setup(pa_rtsp_client *c, const char *transport) {
|
|||
else
|
||||
pa_headerlist_puts(headers, "Transport", transport);
|
||||
|
||||
if (!(rv = rtsp_exec(c, "SETUP", c->url, headers, NULL, NULL)))
|
||||
c->state = STATE_SETUP;
|
||||
rv = rtsp_exec(c, "SETUP", NULL, NULL, 1, headers);
|
||||
|
||||
pa_headerlist_free(headers);
|
||||
return rv;
|
||||
|
|
@ -591,8 +756,8 @@ int pa_rtsp_record(pa_rtsp_client *c, uint16_t *seq, uint32_t *rtptime) {
|
|||
pa_headerlist_puts(headers, "RTP-Info", info);
|
||||
pa_xfree(info);
|
||||
|
||||
if (!(rv = rtsp_exec(c, "RECORD", c->url, headers, NULL, NULL)))
|
||||
c->state = STATE_RECORD;
|
||||
rv = rtsp_exec(c, "RECORD", NULL, NULL, 1, headers);
|
||||
|
||||
pa_headerlist_free(headers);
|
||||
return rv;
|
||||
|
|
@ -606,8 +771,19 @@ int pa_rtsp_setparameter(pa_rtsp_client *c, const char *param) {
|
|||
if (!param)
|
||||
return -1;
|
||||
|
||||
if (!(rv = rtsp_exec(c, "SET_PARAMETER", c->url, NULL, "text/parameters", param)))
|
||||
c->state = STATE_SET_PARAMETER;
|
||||
rv = rtsp_exec(c, "SET_PARAMETER", "text/parameters", param, 1, NULL);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
int pa_rtsp_post(pa_rtsp_client *c, const char *url) {
|
||||
int rv;
|
||||
|
||||
pa_assert(c);
|
||||
|
||||
if (!(rv = rtsp_exec(c, "POST", url, NULL, NULL, NULL)))
|
||||
c->state = STATE_POST;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
|
@ -624,8 +800,8 @@ int pa_rtsp_flush(pa_rtsp_client *c, uint16_t seq, uint32_t rtptime) {
|
|||
pa_headerlist_puts(headers, "RTP-Info", info);
|
||||
pa_xfree(info);
|
||||
|
||||
if (!(rv = rtsp_exec(c, "FLUSH", c->url, headers, NULL, NULL)))
|
||||
c->state = STATE_FLUSH;
|
||||
rv = rtsp_exec(c, "FLUSH", NULL, NULL, 1, headers);
|
||||
|
||||
pa_headerlist_free(headers);
|
||||
return rv;
|
||||
|
|
@ -636,8 +812,8 @@ int pa_rtsp_teardown(pa_rtsp_client *c) {
|
|||
|
||||
pa_assert(c);
|
||||
|
||||
if (!(rv = rtsp_exec(c, "TEARDOWN", c->url, NULL, NULL, NULL)))
|
||||
c->state = STATE_TEARDOWN;
|
||||
rv = rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
|
@ -39,6 +39,7 @@ typedef enum pa_rtsp_state {
|
|||
STATE_SETUP,
|
||||
STATE_RECORD,
|
||||
STATE_SET_PARAMETER,
|
||||
STATE_POST,
|
||||
STATE_FLUSH,
|
||||
STATE_TEARDOWN,
|
||||
STATE_DISCONNECTED
|
||||
|
|
@ -63,9 +64,9 @@ void pa_rtsp_disconnect(pa_rtsp_client *c);
|
|||
|
||||
const char* pa_rtsp_localip(pa_rtsp_client *c);
|
||||
uint32_t pa_rtsp_serverport(pa_rtsp_client *c);
|
||||
bool pa_rtsp_exec_ready(const pa_rtsp_client *c);
|
||||
|
||||
void pa_rtsp_set_url(pa_rtsp_client *c, const char *url);
|
||||
void pa_rtsp_set_credentials(pa_rtsp_client *c, const char *username, const char*password);
|
||||
|
||||
bool pa_rtsp_has_header(pa_rtsp_client *c, const char *key);
|
||||
void pa_rtsp_add_header(pa_rtsp_client *c, const char *key, const char *value);
|
||||
|
|
@ -77,6 +78,7 @@ int pa_rtsp_announce(pa_rtsp_client *c, const char *sdp);
|
|||
int pa_rtsp_setup(pa_rtsp_client *c, const char *transport);
|
||||
int pa_rtsp_record(pa_rtsp_client *c, uint16_t *seq, uint32_t *rtptime);
|
||||
int pa_rtsp_setparameter(pa_rtsp_client *c, const char *param);
|
||||
int pa_rtsp_post(pa_rtsp_client *c, const char *url);
|
||||
int pa_rtsp_flush(pa_rtsp_client *c, uint16_t seq, uint32_t rtptime);
|
||||
int pa_rtsp_teardown(pa_rtsp_client *c);
|
||||
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/macro.h>
|
||||
|
||||
#include "raop-util.h"
|
||||
#include "rtsp-util.h"
|
||||
|
||||
#ifndef MD5_DIGEST_LENGTH
|
||||
#define MD5_DIGEST_LENGTH 16
|
||||
|
|
@ -94,7 +94,7 @@ static unsigned int token_decode(const char *token) {
|
|||
return (marker << 24) | val;
|
||||
}
|
||||
|
||||
int pa_raop_base64_encode(const void *data, int len, char **str) {
|
||||
int pa_rtsp_base64_encode(const void *data, int len, char **str) {
|
||||
const unsigned char *q;
|
||||
char *p, *s = NULL;
|
||||
int i, c;
|
||||
|
|
@ -130,7 +130,7 @@ int pa_raop_base64_encode(const void *data, int len, char **str) {
|
|||
return strlen(s);
|
||||
}
|
||||
|
||||
int pa_raop_base64_decode(const char *str, void *data) {
|
||||
int pa_rtsp_base64_decode(const char *str, void *data) {
|
||||
const char *p;
|
||||
unsigned char *q;
|
||||
|
||||
|
|
@ -153,7 +153,7 @@ int pa_raop_base64_decode(const char *str, void *data) {
|
|||
return q - (unsigned char *) data;
|
||||
}
|
||||
|
||||
int pa_raop_md5_hash(const char *data, int len, char **str) {
|
||||
int pa_rtsp_md5_hash(const char *data, int len, char **str) {
|
||||
unsigned char d[MD5_DIGEST_LENGTH];
|
||||
char *s = NULL;
|
||||
int i;
|
||||
|
|
@ -162,7 +162,7 @@ int pa_raop_md5_hash(const char *data, int len, char **str) {
|
|||
pa_assert(str);
|
||||
|
||||
MD5((unsigned char*) data, len, d);
|
||||
s = pa_xnew(char, MD5_HASH_LENGTH);
|
||||
s = pa_xnew(char, MD5_HASH_LENGTH+1);
|
||||
for (i = 0; i < MD5_DIGEST_LENGTH; i++)
|
||||
sprintf(&s[2*i], "%02x", (unsigned int) d[i]);
|
||||
|
||||
|
|
@ -171,36 +171,36 @@ int pa_raop_md5_hash(const char *data, int len, char **str) {
|
|||
return strlen(s);
|
||||
}
|
||||
|
||||
int pa_raop_basic_response(const char *user, const char *pwd, char **str) {
|
||||
int pa_rtsp_basic_response(const char *user, const char *pwd, char **str) {
|
||||
char *tmp, *B = NULL;
|
||||
|
||||
pa_assert(str);
|
||||
|
||||
tmp = pa_sprintf_malloc("%s:%s", user, pwd);
|
||||
pa_raop_base64_encode(tmp, strlen(tmp), &B);
|
||||
pa_rtsp_base64_encode(tmp, strlen(tmp), &B);
|
||||
pa_xfree(tmp);
|
||||
|
||||
*str = B;
|
||||
return strlen(B);
|
||||
}
|
||||
|
||||
int pa_raop_digest_response(const char *user, const char *realm, const char *password,
|
||||
const char *nonce, const char *uri, char **str) {
|
||||
int pa_rtsp_digest_response(const char *user, const char *realm, const char *password,
|
||||
const char *nonce, const char *method, const char *uri, char **str) {
|
||||
char *A1, *HA1, *A2, *HA2;
|
||||
char *tmp, *KD = NULL;
|
||||
|
||||
pa_assert(str);
|
||||
|
||||
A1 = pa_sprintf_malloc("%s:%s:%s", user, realm, password);
|
||||
pa_raop_md5_hash(A1, strlen(A1), &HA1);
|
||||
pa_rtsp_md5_hash(A1, strlen(A1), &HA1);
|
||||
pa_xfree(A1);
|
||||
|
||||
A2 = pa_sprintf_malloc("OPTIONS:%s", uri);
|
||||
pa_raop_md5_hash(A2, strlen(A2), &HA2);
|
||||
A2 = pa_sprintf_malloc("%s:%s", method, uri);
|
||||
pa_rtsp_md5_hash(A2, strlen(A2), &HA2);
|
||||
pa_xfree(A2);
|
||||
|
||||
tmp = pa_sprintf_malloc("%s:%s:%s", HA1, nonce, HA2);
|
||||
pa_raop_md5_hash(tmp, strlen(tmp), &KD);
|
||||
pa_rtsp_md5_hash(tmp, strlen(tmp), &KD);
|
||||
pa_xfree(tmp);
|
||||
|
||||
pa_xfree(HA1);
|
||||
|
|
@ -209,3 +209,16 @@ int pa_raop_digest_response(const char *user, const char *realm, const char *pas
|
|||
*str = KD;
|
||||
return strlen(KD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to trim a given character at the end of a string (no realloc).
|
||||
* @param str Pointer to string
|
||||
* @param rc Character to trim
|
||||
*/
|
||||
void pa_rtsp_rtrim_char(char *str, char rc) {
|
||||
char *sp = str + strlen(str) - 1;
|
||||
while (sp >= str && *sp == rc) {
|
||||
*sp = '\0';
|
||||
sp -= 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef fooraoputilfoo
|
||||
#define fooraoputilfoo
|
||||
#ifndef foortsputilfoo
|
||||
#define foortsputilfoo
|
||||
|
||||
/***
|
||||
This file is part of PulseAudio.
|
||||
|
|
@ -27,15 +27,15 @@
|
|||
Kungliga Tekniska högskolan.
|
||||
***/
|
||||
|
||||
#define RAOP_DEFAULT_LATENCY 2000 /* msec */
|
||||
int pa_rtsp_base64_encode(const void *data, int len, char **str);
|
||||
int pa_rtsp_base64_decode(const char *str, void *data);
|
||||
|
||||
int pa_raop_base64_encode(const void *data, int len, char **str);
|
||||
int pa_raop_base64_decode(const char *str, void *data);
|
||||
int pa_rtsp_md5_hash(const char *data, int len, char **str);
|
||||
|
||||
int pa_raop_md5_hash(const char *data, int len, char **str);
|
||||
int pa_rtsp_basic_response(const char *user, const char *pwd, char **str);
|
||||
int pa_rtsp_digest_response(const char *user, const char *realm, const char *password,
|
||||
const char *nonce, const char *method, const char *uri, char **str);
|
||||
|
||||
int pa_raop_basic_response(const char *user, const char *pwd, char **str);
|
||||
int pa_raop_digest_response(const char *user, const char *realm, const char *password,
|
||||
const char *nonce, const char *uri, char **str);
|
||||
void pa_rtsp_rtrim_char(char *str, char rc);
|
||||
|
||||
#endif
|
||||
|
|
@ -57,7 +57,7 @@ struct pa_cli {
|
|||
char *last_line;
|
||||
};
|
||||
|
||||
static void line_callback(pa_ioline *line, const char *s, void *userdata);
|
||||
static void line_callback(pa_ioline *line, const char *s, size_t l, void *userdata);
|
||||
static void client_kill(pa_client *c);
|
||||
|
||||
pa_cli* pa_cli_new(pa_core *core, pa_iochannel *io, pa_module *m) {
|
||||
|
|
@ -117,7 +117,7 @@ static void client_kill(pa_client *client) {
|
|||
c->eof_callback(c, c->userdata);
|
||||
}
|
||||
|
||||
static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
||||
static void line_callback(pa_ioline *line, const char *s, size_t l, void *userdata) {
|
||||
pa_strbuf *buf;
|
||||
pa_cli *c = userdata;
|
||||
char *p;
|
||||
|
|
|
|||
|
|
@ -227,14 +227,14 @@ static void failure(pa_ioline *l, bool process_leftover) {
|
|||
/* Pass the last missing bit to the client */
|
||||
|
||||
if (l->callback) {
|
||||
char *p = pa_xstrndup(l->rbuf+l->rbuf_index, l->rbuf_valid_length);
|
||||
l->callback(l, p, l->userdata);
|
||||
char *p = pa_xmemdup(l->rbuf+l->rbuf_index, l->rbuf_valid_length);
|
||||
l->callback(l, p, l->rbuf_valid_length, l->userdata);
|
||||
pa_xfree(p);
|
||||
}
|
||||
}
|
||||
|
||||
if (l->callback) {
|
||||
l->callback(l, NULL, l->userdata);
|
||||
l->callback(l, NULL, 0, l->userdata);
|
||||
l->callback = NULL;
|
||||
}
|
||||
|
||||
|
|
@ -256,7 +256,7 @@ static void scan_for_lines(pa_ioline *l, size_t skip) {
|
|||
*e = 0;
|
||||
|
||||
p = l->rbuf + l->rbuf_index;
|
||||
m = strlen(p);
|
||||
m = e - p;
|
||||
|
||||
l->rbuf_index += m+1;
|
||||
l->rbuf_valid_length -= m+1;
|
||||
|
|
@ -266,7 +266,7 @@ static void scan_for_lines(pa_ioline *l, size_t skip) {
|
|||
l->rbuf_index = 0;
|
||||
|
||||
if (l->callback)
|
||||
l->callback(l, pa_strip_nl(p), l->userdata);
|
||||
l->callback(l, pa_strip_nl(p), m, l->userdata);
|
||||
|
||||
skip = 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
typedef struct pa_ioline pa_ioline;
|
||||
|
||||
typedef void (*pa_ioline_cb_t)(pa_ioline*io, const char *s, void *userdata);
|
||||
typedef void (*pa_ioline_cb_t)(pa_ioline*io, const char *s, size_t l, void *userdata);
|
||||
typedef void (*pa_ioline_drain_cb_t)(pa_ioline *io, void *userdata);
|
||||
|
||||
pa_ioline* pa_ioline_new(pa_iochannel *io);
|
||||
|
|
|
|||
|
|
@ -629,7 +629,7 @@ static void handle_url(struct connection *c) {
|
|||
html_response(c, 404, "Not Found", NULL);
|
||||
}
|
||||
|
||||
static void line_callback(pa_ioline *line, const char *s, void *userdata) {
|
||||
static void line_callback(pa_ioline *line, const char *s, size_t l, void *userdata) {
|
||||
struct connection *c = userdata;
|
||||
pa_assert(line);
|
||||
pa_assert(c);
|
||||
|
|
|
|||
|
|
@ -1841,8 +1841,18 @@ static void get_sink_mute_callback(pa_context *c, const pa_sink_info *i, int is_
|
|||
|
||||
pa_assert(i);
|
||||
|
||||
if (format == JSON) {
|
||||
pa_json_encoder *encoder = pa_json_encoder_new();
|
||||
pa_json_encoder_begin_element_object(encoder);
|
||||
pa_json_encoder_add_member_bool(encoder, "mute", i->mute);
|
||||
pa_json_encoder_end_object(encoder);
|
||||
char* json_str = pa_json_encoder_to_string_free(encoder);
|
||||
printf("%s\n", json_str);
|
||||
pa_xfree(json_str);
|
||||
} else {
|
||||
printf(("Mute: %s\n"),
|
||||
pa_yes_no_localised(i->mute));
|
||||
}
|
||||
|
||||
complete_action();
|
||||
}
|
||||
|
|
@ -1860,10 +1870,21 @@ static void get_sink_volume_callback(pa_context *c, const pa_sink_info *i, int i
|
|||
pa_assert(i);
|
||||
|
||||
char cv[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
|
||||
if (format == JSON) {
|
||||
pa_json_encoder *encoder = pa_json_encoder_new();
|
||||
pa_json_encoder_begin_element_object(encoder);
|
||||
pa_json_encoder_add_member_raw_json(encoder, "volume", pa_cvolume_to_json_object(&i->volume, &i->channel_map, i->flags & PA_SINK_DECIBEL_VOLUME));
|
||||
pa_json_encoder_add_member_double(encoder, "balance", pa_cvolume_get_balance(&i->volume, &i->channel_map), 2);
|
||||
pa_json_encoder_end_object(encoder);
|
||||
char* json_str = pa_json_encoder_to_string_free(encoder);
|
||||
printf("%s\n", json_str);
|
||||
pa_xfree(json_str);
|
||||
} else {
|
||||
printf(("Volume: %s\n"
|
||||
" balance %0.2f\n"),
|
||||
pa_cvolume_snprint_verbose(cv, sizeof(cv), &i->volume, &i->channel_map, true),
|
||||
pa_cvolume_get_balance(&i->volume, &i->channel_map));
|
||||
}
|
||||
|
||||
complete_action();
|
||||
}
|
||||
|
|
@ -1907,8 +1928,18 @@ static void get_source_mute_callback(pa_context *c, const pa_source_info *i, int
|
|||
|
||||
pa_assert(i);
|
||||
|
||||
if (format == JSON) {
|
||||
pa_json_encoder *encoder = pa_json_encoder_new();
|
||||
pa_json_encoder_begin_element_object(encoder);
|
||||
pa_json_encoder_add_member_bool(encoder, "mute", i->mute);
|
||||
pa_json_encoder_end_object(encoder);
|
||||
char* json_str = pa_json_encoder_to_string_free(encoder);
|
||||
printf("%s\n", json_str);
|
||||
pa_xfree(json_str);
|
||||
} else {
|
||||
printf(("Mute: %s\n"),
|
||||
pa_yes_no_localised(i->mute));
|
||||
}
|
||||
|
||||
complete_action();
|
||||
}
|
||||
|
|
@ -1926,10 +1957,21 @@ static void get_source_volume_callback(pa_context *c, const pa_source_info *i, i
|
|||
pa_assert(i);
|
||||
|
||||
char cv[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
|
||||
if (format == JSON) {
|
||||
pa_json_encoder *encoder = pa_json_encoder_new();
|
||||
pa_json_encoder_begin_element_object(encoder);
|
||||
pa_json_encoder_add_member_raw_json(encoder, "volume", pa_cvolume_to_json_object(&i->volume, &i->channel_map, i->flags & PA_SINK_DECIBEL_VOLUME));
|
||||
pa_json_encoder_add_member_double(encoder, "balance", pa_cvolume_get_balance(&i->volume, &i->channel_map), 2);
|
||||
pa_json_encoder_end_object(encoder);
|
||||
char* json_str = pa_json_encoder_to_string_free(encoder);
|
||||
printf("%s\n", json_str);
|
||||
pa_xfree(json_str);
|
||||
} else {
|
||||
printf(("Volume: %s\n"
|
||||
" balance %0.2f\n"),
|
||||
pa_cvolume_snprint_verbose(cv, sizeof(cv), &i->volume, &i->channel_map, true),
|
||||
pa_cvolume_get_balance(&i->volume, &i->channel_map));
|
||||
}
|
||||
|
||||
complete_action();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue