Compare commits

...

22 commits

Author SHA1 Message Date
Mohamed Akram
16203c5733 Merge branch 'fix-raop' into 'master'
raop: fix auth, reconnect, apple tv compat

See merge request pulseaudio/pulseaudio!820
2025-10-13 19:27:36 +04:00
Fco. Javier F. Serrador
eee0e8f22f Translated using Weblate (Spanish)
Currently translated at 100.0% (572 of 572 strings)

Translation: pulseaudio/pulseaudio
Translate-URL: https://translate.fedoraproject.org/projects/pulseaudio/pulseaudio/es/
2025-10-01 05:20:04 +02:00
Fco. Javier F. Serrador
b50c28af2c Translated using Weblate (Spanish)
Currently translated at 100.0% (572 of 572 strings)

Translation: pulseaudio/pulseaudio
Translate-URL: https://translate.fedoraproject.org/projects/pulseaudio/pulseaudio/es/
2025-10-01 05:20:04 +02:00
Martin Srebotnjak
210f4742e7 Translated using Weblate (Slovenian)
Currently translated at 100.0% (572 of 572 strings)

Translation: pulseaudio/pulseaudio
Translate-URL: https://translate.fedoraproject.org/projects/pulseaudio/pulseaudio/sl/
2025-10-01 05:20:04 +02:00
Temuri Doghonadze
53532e63bf Translated using Weblate (Georgian)
Currently translated at 79.3% (454 of 572 strings)

Translation: pulseaudio/pulseaudio
Translate-URL: https://translate.fedoraproject.org/projects/pulseaudio/pulseaudio/ka/
2025-10-01 05:20:04 +02:00
Jim Spentzos
6834e0041c Translated using Weblate (Greek)
Currently translated at 100.0% (572 of 572 strings)

Translation: pulseaudio/pulseaudio
Translate-URL: https://translate.fedoraproject.org/projects/pulseaudio/pulseaudio/el/
2025-10-01 05:20:04 +02:00
김인수
329c05b04f Translated using Weblate (Korean)
Currently translated at 100.0% (572 of 572 strings)

Translation: pulseaudio/pulseaudio
Translate-URL: https://translate.fedoraproject.org/projects/pulseaudio/pulseaudio/ko/
2025-10-01 05:20:04 +02:00
Jim Spentzos
76320675d8 Translated using Weblate (Greek)
Currently translated at 100.0% (572 of 572 strings)

Translation: pulseaudio/pulseaudio
Translate-URL: https://translate.fedoraproject.org/projects/pulseaudio/pulseaudio/el/
2025-10-01 05:20:04 +02:00
Salvatore Cocuzza
d5b58d29ea Translated using Weblate (Italian)
Currently translated at 99.6% (570 of 572 strings)

Translation: pulseaudio/pulseaudio
Translate-URL: https://translate.fedoraproject.org/projects/pulseaudio/pulseaudio/it/
2025-10-01 05:20:04 +02:00
Salvatore Cocuzza
511926ab4b Translated using Weblate (Italian)
Currently translated at 99.3% (568 of 572 strings)

Translation: pulseaudio/pulseaudio
Translate-URL: https://translate.fedoraproject.org/projects/pulseaudio/pulseaudio/it/
2025-10-01 05:20:04 +02:00
Fco. Javier F. Serrador
f6e1124942 Translated using Weblate (Spanish)
Currently translated at 100.0% (572 of 572 strings)

Translation: pulseaudio/pulseaudio
Translate-URL: https://translate.fedoraproject.org/projects/pulseaudio/pulseaudio/es/
2025-10-01 05:20:04 +02:00
Fco. Javier F. Serrador
c7db9d60a6 Translated using Weblate (Spanish)
Currently translated at 100.0% (572 of 572 strings)

Translation: pulseaudio/pulseaudio
Translate-URL: https://translate.fedoraproject.org/projects/pulseaudio/pulseaudio/es/
2025-10-01 05:20:04 +02:00
Fco. Javier F. Serrador
7493990c84 Translated using Weblate (Spanish)
Currently translated at 100.0% (572 of 572 strings)

Translation: pulseaudio/pulseaudio
Translate-URL: https://translate.fedoraproject.org/projects/pulseaudio/pulseaudio/es/
2025-10-01 05:20:04 +02:00
김인수
79455f69f7 Translated using Weblate (Korean)
Currently translated at 100.0% (572 of 572 strings)

Translation: pulseaudio/pulseaudio
Translate-URL: https://translate.fedoraproject.org/projects/pulseaudio/pulseaudio/ko/
2025-10-01 05:20:04 +02:00
Remus-Gabriel Chelu
a1fef186d0 Translated using Weblate (Romanian)
Currently translated at 100.0% (572 of 572 strings)

Translation: pulseaudio/pulseaudio
Translate-URL: https://translate.fedoraproject.org/projects/pulseaudio/pulseaudio/ro/
2025-10-01 05:20:04 +02:00
Sergey A
4fa2e83ac3 Translated using Weblate (Russian)
Currently translated at 100.0% (572 of 572 strings)

Translation: pulseaudio/pulseaudio
Translate-URL: https://translate.fedoraproject.org/projects/pulseaudio/pulseaudio/ru/
2025-10-01 05:20:04 +02:00
Temuri Doghonadze
f081327511 Translated using Weblate (Georgian)
Currently translated at 78.8% (451 of 572 strings)

Translation: pulseaudio/pulseaudio
Translate-URL: https://translate.fedoraproject.org/projects/pulseaudio/pulseaudio/ka/
2025-10-01 05:20:04 +02:00
Rafael Fontenelle
d3f1e217e7 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (572 of 572 strings)

Translation: pulseaudio/pulseaudio
Translate-URL: https://translate.fedoraproject.org/projects/pulseaudio/pulseaudio/pt_BR/
2025-10-01 05:20:04 +02:00
Zayed Al-Saidi
5a3b45cda1 Translated using Weblate (Arabic)
Currently translated at 34.4% (197 of 572 strings)

Translation: pulseaudio/pulseaudio
Translate-URL: https://translate.fedoraproject.org/projects/pulseaudio/pulseaudio/ar/
2025-10-01 05:20:04 +02:00
Rômulo Borges
b4b3889f3c pactl: add JSON output format
Previously, using the -f json or --format=json flags did not return JSON for the following commands:
- get-sink-volume
- get-source-volume
- get-sink-mute
- get-source-mute

This change adds proper JSON output for these commands.
2025-09-28 12:25:49 -03:00
Mohamed Akram
31b6ecd507 raop: fix auth, reconnect, apple tv compat
The digest was not used in subsequent requests resulting in a 401 from
ANNOUNCE. This was changed so that the Authorization header is present
on all requests.

Reconnection did not handle the case where pa_rtsp_connect returned
immediately with a failure. This error was also not checked by
pa_raop_client_authenticate and pa_raop_client_announce, which caused
the raop client to be permanently stuck on failure as it assumed the
connection was ongoing. Similarly, a failure in SETUP was also not
reported and caused the client to be stuck. These issues were fixed and
conflicting disconnects/reconnects that caused crashes due to race
conditions were removed. We also avoid unloading the module due to a
failure since it might be recoverable. If a device is not accessible
anymore, the discover module will remove it.

Apple TV requires that the timing port is open and responding before
a SETUP response is provided, otherwise a 500 error is returned after
some time. This was added and the connection callback is called earlier
so that the sink loop can respond to timing requests in this phase. A
keep-alive request was also added as it is required by Apple TV to
prevent the stream from stopping.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/820>
2024-06-22 01:41:40 +04:00
Mohamed Akram
fa8943c80e ioline: add length to callback
This is required to be able to skip over a HTTP/RTSP body which might
contain nul bytes.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/820>
2024-06-06 18:00:05 +04:00
28 changed files with 990 additions and 769 deletions

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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à: "

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -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;

View file

@ -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

View file

@ -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");

View file

@ -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, &current);
while ((token = pa_split(wath, comma, &current))) {
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) {

View file

@ -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);

View 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

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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,

View file

@ -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, &current);
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, &current))) {
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;
}

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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

View file

@ -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;

View file

@ -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;
}

View file

@ -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);

View file

@ -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);

View file

@ -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();
}