diff --git a/.cproject b/.cproject
index c6ef1f9b..317be43f 100644
--- a/.cproject
+++ b/.cproject
@@ -3,479 +3,13 @@
-
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -485,192 +19,14 @@
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- make
-
- -j8 EXTRA_CPPFLAGS="-DRECOVERY_APPLICATION=1"
-
- all
-
- true
-
- true
-
- true
-
-
-
-
-
- make
-
- -j8 EXTRA_CPPFLAGS="-DRECOVERY_APPLICATION=1"
-
- app
-
- true
-
- true
-
- true
-
-
-
-
-
- make
-
- -j8 EXTRA_CPPFLAGS="-DRECOVERY_APPLICATION=1"
-
- clean
-
- true
-
- true
-
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/.gitmodules b/.gitmodules
index 9f498e78..847780c7 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -3,5 +3,5 @@
url = https://github.com/seanmiddleditch/libtelnet
branch = develop
[submodule "esp-dsp"]
- path = esp-dsp
+ path = components/esp-dsp
url = https://github.com/philippe44/esp-dsp
diff --git a/.project b/.project
index 18d4dbf1..fb53512a 100644
--- a/.project
+++ b/.project
@@ -1,28 +1,20 @@
- squeezelite-esp32
+ squeezelite-esp32-idfv4
- esp-idf
- org.eclipse.cdt.managedbuilder.core.genmakebuilder
+ org.eclipse.cdt.core.cBuilder
clean,full,incremental,
-
- org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder
- full,incremental,
-
-
-
org.eclipse.cdt.core.cnature
org.eclipse.cdt.core.ccnature
- org.eclipse.cdt.managedbuilder.core.managedBuildNature
- org.eclipse.cdt.managedbuilder.core.ScannerConfigNature
+ com.espressif.idf.core.idfNature
diff --git a/.settings/.gitignore b/.settings/.gitignore
deleted file mode 100644
index aacc3e4e..00000000
--- a/.settings/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/org.eclipse.ltk.core.refactoring.prefs
diff --git a/.settings/language.settings.xml b/.settings/language.settings.xml
deleted file mode 100644
index 219e5f2d..00000000
--- a/.settings/language.settings.xml
+++ /dev/null
@@ -1,124 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.settings/org.eclipse.cdt.core.prefs b/.settings/org.eclipse.cdt.core.prefs
deleted file mode 100644
index ed04c167..00000000
--- a/.settings/org.eclipse.cdt.core.prefs
+++ /dev/null
@@ -1,133 +0,0 @@
-eclipse.preferences.version=1
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/BATCH_BUILD/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/BATCH_BUILD/operation=append
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/BATCH_BUILD/value=1
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/EXTRA_CFLAGS/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/EXTRA_CFLAGS/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/EXTRA_CFLAGS/value=-DRECOVERY_APPLICATION\=1
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/IDF_PATH/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/IDF_PATH/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/IDF_PATH/value=/var/opt/esp-idf
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/append=true
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/appendContributed=true
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/BATCH_BUILD/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/BATCH_BUILD/operation=append
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/BATCH_BUILD/value=1
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/EXTRA_CFLAGS/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/EXTRA_CFLAGS/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/EXTRA_CFLAGS/value=
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/IDF_PATH/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/IDF_PATH/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/IDF_PATH/value=/var/opt/esp-idf
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/PATH/delimiter=;
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/PATH/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/PATH/value=C\:/msys2/opt/xtensa-esp32-elf/bin;C\:/jdk-12.0.2/bin/server;C\:/jdk-12.0.2/bin;C\:\\Windows\\system32;C\:\\Windows;C\:\\Windows\\System32\\Wbem;C\:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C\:\\Program Files\\NVIDIA Corporation\\NVIDIA NvDLISR;C\:\\jdk-12.0.2\\bin;C\:\\Program Files\\PuTTY\\;C\:\\Program Files (x86)\\HP\\IdrsOCR_15.2.10.1114\\;C\:\\eclipse
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/append=true
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/appendContributed=true
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.198620654/BATCH_BUILD/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.198620654/BATCH_BUILD/operation=append
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.198620654/BATCH_BUILD/value=1
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.198620654/IDF_PATH/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.198620654/IDF_PATH/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.198620654/IDF_PATH/value=C\:/msys32/opt/esp-idf
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.198620654/PATH/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.198620654/PATH/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.198620654/PATH/value=C\:\\msys32\\opt\\openocd-esp32\\bin;c\:\\msys32\\opt\\xtensa-esp32-elf\\bin\\;c\:\\msys32\\mingw32\\bin;C\:\\msys32\\usr\\bin;c\:\\Python27;C\:\\msys32\\usr\\bin\\vendor_perl;C\:\\msys32\\usr\\bin\\core_perl
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.198620654/PROJECT_NAME/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.198620654/PROJECT_NAME/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.198620654/PROJECT_NAME/value=recovery
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.198620654/PROJECT_VER/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.198620654/PROJECT_VER/operation=append
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.198620654/PROJECT_VER/value=custom
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.198620654/append=true
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.198620654/appendContributed=true
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/BATCH_BUILD/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/BATCH_BUILD/operation=append
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/BATCH_BUILD/value=1
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/IDF_PATH/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/IDF_PATH/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/IDF_PATH/value=C\:/msys32/opt/esp-idf
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/PATH/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/PATH/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/PATH/value=C\:\\msys32\\usr\\bin;C\:\\msys32\\mingw32\\bin;C\:\\msys32\\opt\\xtensa-esp32-elf\\bin\\
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/PROJECT_NAME/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/PROJECT_NAME/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/PROJECT_NAME/value=recovery.custom
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/PROJECT_VER/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/PROJECT_VER/operation=append
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/PROJECT_VER/value=custom
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/append=true
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/appendContributed=true
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/BATCH_BUILD/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/BATCH_BUILD/operation=append
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/BATCH_BUILD/value=1
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/IDF_PATH/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/IDF_PATH/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/IDF_PATH/value=C\:/msys32/opt/esp-idf-v4.0
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/PATH/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/PATH/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/PATH/value=C\:\\Program Files\\CMake\\bin;C\:\\msys32\\opt\\openocd-esp32\\bin;C\:\\msys32\\opt\\xtensa-esp32-elf-gcc8_2_0\\xtensa-esp32-elf\\bin;c\:\\msys32\\mingw32\\bin;C\:\\msys32\\usr\\bin;c\:\\Python27;C\:\\msys32\\usr\\bin\\vendor_perl;C\:\\msys32\\usr\\bin\\core_perl
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/PROJECT_NAME/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/PROJECT_NAME/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/PROJECT_NAME/value=recovery
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/PROJECT_VER/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/PROJECT_VER/operation=append
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/PROJECT_VER/value=custom
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/append=true
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/appendContributed=true
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/BATCH_BUILD/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/BATCH_BUILD/operation=append
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/BATCH_BUILD/value=1
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/IDF_PATH/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/IDF_PATH/operation=append
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/IDF_PATH/value=/var/opt/esp-idf/
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/PATH/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/PATH/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/PATH/value=/var/opt/xtensa-esp32-elf/bin/\:/sbin\:/bin\:/usr/bin\:/usr/local/bin\:/snap/bin\:/bin\:/sbin\:/usr/bin\:/usr/local/bin\:/snap/bin
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/PROJECT_NAME/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/PROJECT_NAME/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/PROJECT_NAME/value=recovery.custom
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/PROJECT_VER/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/PROJECT_VER/operation=append
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/PROJECT_VER/value=custom
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/append=true
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/appendContributed=true
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/BATCH_BUILD/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/BATCH_BUILD/operation=append
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/BATCH_BUILD/value=1
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/IDF_PATH/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/IDF_PATH/operation=append
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/IDF_PATH/value=/var/opt/esp-idf/
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/PATH/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/PATH/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/PATH/value=/var/opt/xtensa-esp32-elf/bin/\:/sbin\:/bin\:/usr/bin\:/usr/local/bin\:/snap/bin\:/bin\:/sbin\:/usr/bin\:/usr/local/bin\:/snap/bin
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/PROJECT_NAME/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/PROJECT_NAME/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/PROJECT_NAME/value=squeezelite.custom
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/PROJECT_VER/delimiter=\:
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/PROJECT_VER/operation=append
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/PROJECT_VER/value=custom
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/append=true
-environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/appendContributed=true
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/BATCH_BUILD/delimiter=;
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/BATCH_BUILD/operation=append
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/BATCH_BUILD/value=1
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/IDF_PATH/delimiter=;
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/IDF_PATH/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/IDF_PATH/value=c\:/msys32/opt/esp-idf
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/PATH/delimiter=;
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/PATH/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/PATH/value=C\:\\msys32\\usr\\bin;C\:\\msys32\\mingw32\\bin;C\:\\msys32\\opt\\xtensa-esp32-elf\\bin;C\:\\Python27
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/append=true
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/appendContributed=false
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/BATCH_BUILD/delimiter=;
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/BATCH_BUILD/operation=append
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/BATCH_BUILD/value=1
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/IDF_PATH/delimiter=;
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/IDF_PATH/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/IDF_PATH/value=c\:/msys32/opt/esp-idf
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/PATH/delimiter=;
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/PATH/operation=replace
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/PATH/value=C\:\\msys32\\usr\\bin;C\:\\msys32\\mingw32\\bin;C\:\\msys32\\opt\\xtensa-esp32-elf\\bin;C\:\\Python27
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/append=true
-environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/appendContributed=false
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 71cf367e..fa676185 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,13 +1,48 @@
cmake_minimum_required(VERSION 3.5)
+
set(COMPONENT_ADD_INCLUDEDIRS main components/tools)
+
+if((DEFINED "$ENV{PROJECT_BUILD_NAME}") AND (DEFINED "$ENV{PROJECT_CONFIG_TARGET}"))
+ set(PROJECT_VER "$ENV{PROJECT_BUILD_NAME}-$ENV{PROJECT_CONFIG_TARGET}" )
+endif()
+
+
+
+
+
+if(DEFINED "$ENV{PROJECT_NAME}")
+ set(PROJECT_NAME "$ENV{PROJECT_NAME}")
+ message("Project name is $PROJECT_NAME")
+else()
+ message("Project name is not set! Defaulting")
+ set(PROJECT_NAME "squeezelite-esp32")
+endif()
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
-
-
-if(NOT $ENV{PROJECT_NAME} or $ENV{PROJECT_NAME} = "recovery")
- add_definitions(-DRECOVERY_APPLICATION=1)
+if(NOT ${PROJECT_NAME} STREQUAL "squeezelite-esp32" )
+ add_definitions( -DRECOVERY_APPLICATION=1 )
+ message(WARNING "Building Recovery")
project(recovery)
else()
- add_definitions(-DRECOVERY_APPLICATION=0)
+ message(WARNING "Building Squeezelite")
+ add_definitions( -DRECOVERY_APPLICATION=0 )
project(squeezelite)
endif()
+
+
+
+# Include ESP-IDF components in the build, may be thought as an equivalent of
+# add_subdirectory() but with some additional procesing and magic for ESP-IDF build
+# specific build processes.
+#idf_build_process(esp32)
+
+# Create the project executable and plainly link the newlib component to it using
+# its alias, idf::newlib.
+#add_executable(${CMAKE_PROJECT_NAME}.elf main.c)
+#target_link_libraries(${CMAKE_PROJECT_NAME}.elf idf::newlib)
+
+# Let the build system know what the project executable is to attach more targets, dependencies, etc.
+#idf_build_executable(${CMAKE_PROJECT_NAME}.elf)
+
+
+
diff --git a/alltags.txt b/alltags.txt
deleted file mode 100644
index 017a3764..00000000
--- a/alltags.txt
+++ /dev/null
@@ -1,65 +0,0 @@
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.15
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.20
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.47
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.49
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.50
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.51
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.52
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.53
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.54
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.55
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.59
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.60
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.61
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.62
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.63
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.64
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.65
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.66
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.67
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/SqueezeAmp-v0.1.13
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/SqueezeAmp-v0.1.14
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/SqueezeAmp-v0.1.15
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/SqueezeAmp-v0.1.20
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/SqueezeAmp-v0.1.66
-3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.12-I2S-4MFlash
-3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.12-SqueezeAmp
-3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.16-I2S-4MFlash
-3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.16-SqueezeAmp
-d9c6c78df2f3ba49d74d91cf246f300a881af742 refs/tags/v0.1.44-I2S-4MFlash
-d9c6c78df2f3ba49d74d91cf246f300a881af742 refs/tags/v0.1.44-SqueezeAmp
-d9c6c78df2f3ba49d74d91cf246f300a881af742 refs/tags/v0.1.45-I2S-4MFlash
-d9c6c78df2f3ba49d74d91cf246f300a881af742 refs/tags/v0.1.45-SqueezeAmp
-d9c6c78df2f3ba49d74d91cf246f300a881af742 refs/tags/v0.1.46-I2S-4MFlash
-d9c6c78df2f3ba49d74d91cf246f300a881af742 refs/tags/v0.1.46-SqueezeAmp
-d9c6c78df2f3ba49d74d91cf246f300a881af742 refs/tags/v0.1.47-I2S-4MFlash
-d9c6c78df2f3ba49d74d91cf246f300a881af742 refs/tags/v0.1.47-SqueezeAmp
-3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.5-I2S-4MFlash
-3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.5-SqueezeAmp
-c34cf06be18ea4ab14ce28d77e1d48d0a1bb70f7 refs/tags/v0.1.50-I2S-4MFlash
-c34cf06be18ea4ab14ce28d77e1d48d0a1bb70f7 refs/tags/v0.1.50-SqueezeAmp
-c34cf06be18ea4ab14ce28d77e1d48d0a1bb70f7 refs/tags/v0.1.51-I2S-4MFlash
-c34cf06be18ea4ab14ce28d77e1d48d0a1bb70f7 refs/tags/v0.1.51-SqueezeAmp
-20edae43287b4c2d8942ea4263ccf9547f310946 refs/tags/v0.1.52-I2S-4MFlash
-20edae43287b4c2d8942ea4263ccf9547f310946 refs/tags/v0.1.52-SqueezeAmp
-964bb4d57d35bca06badfb504534b42e9b3b8678 refs/tags/v0.1.53-I2S-4MFlash
-964bb4d57d35bca06badfb504534b42e9b3b8678 refs/tags/v0.1.53-SqueezeAmp
-71531bcdc20d7c7da254699855eb2e1e7b2bd48f refs/tags/v0.1.54-I2S-4MFlash
-71531bcdc20d7c7da254699855eb2e1e7b2bd48f refs/tags/v0.1.54-SqueezeAmp
-9c56cfb1d05862bca5763b3fbe9911b4bab9619a refs/tags/v0.1.55-I2S-4MFlash
-9c56cfb1d05862bca5763b3fbe9911b4bab9619a refs/tags/v0.1.55-SqueezeAmp
-53369475dc85471a4d7f2d78c62f37fcd7f6e3da refs/tags/v0.1.57-I2S-4MFlash
-53369475dc85471a4d7f2d78c62f37fcd7f6e3da refs/tags/v0.1.57-SqueezeAmp
-3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.6-I2S-4MFlash
-3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.6-SqueezeAmp
-eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/v0.1.67-WiFi-Manager/I2S-4MFlash
-2cf87d5943caa0d0b15c8692482ff72308c0c0a8 refs/tags/v0.1.71-I2S-4MFlash
-2cf87d5943caa0d0b15c8692482ff72308c0c0a8 refs/tags/v0.1.71-SqueezeAmp
-2cf87d5943caa0d0b15c8692482ff72308c0c0a8 refs/tags/v0.1.72-I2S-4MFlash
-2cf87d5943caa0d0b15c8692482ff72308c0c0a8 refs/tags/v0.1.72-SqueezeAmp
-2cf87d5943caa0d0b15c8692482ff72308c0c0a8 refs/tags/v0.1.73-I2S-4MFlash
-2cf87d5943caa0d0b15c8692482ff72308c0c0a8 refs/tags/v0.1.73-SqueezeAmp
-2cf87d5943caa0d0b15c8692482ff72308c0c0a8 refs/tags/v0.1.74-I2S-4MFlash
-2cf87d5943caa0d0b15c8692482ff72308c0c0a8 refs/tags/v0.1.74-SqueezeAmp
-3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.9-I2S-4MFlash
-3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.9-SqueezeAmp
diff --git a/components/codecs/CMakeLists.txt b/components/codecs/CMakeLists.txt
new file mode 100644
index 00000000..15568e0e
--- /dev/null
+++ b/components/codecs/CMakeLists.txt
@@ -0,0 +1,27 @@
+idf_component_register(SRCS link_helper.c
+ INCLUDE_DIRS . ./inc inc/alac inc/faad2 inc/FLAC inc/helix-aac inc/mad inc/ogg inc/opus inc/opusfile inc/resample16 inc/soxr inc/vorbis
+ REQUIRES esp_common platform_config
+ PRIV_REQUIRES freertos bt nvs_flash esp32 spi_flash newlib log console pthread
+)
+
+add_prebuilt_library(libmad lib/libmad.a)
+add_prebuilt_library(libesp-flac lib/libesp-flac.a )
+add_prebuilt_library(libhelix-aac lib/libhelix-aac.a )
+add_prebuilt_library(libvorbisidec lib/libvorbisidec.a )
+add_prebuilt_library(libogg lib/libogg.a )
+add_prebuilt_library(libalac lib/libalac.a )
+add_prebuilt_library(libremple16 lib/libresample16.a )
+add_prebuilt_library(libsoxr lib/libsoxr.a )
+add_prebuilt_library(libopusfile lib/libopusfile.a )
+add_prebuilt_library(libopus lib/libopus.a )
+target_link_libraries(${COMPONENT_LIB} PRIVATE libmad)
+target_link_libraries(${COMPONENT_LIB} PRIVATE libesp-flac)
+target_link_libraries(${COMPONENT_LIB} PRIVATE libhelix-aac)
+target_link_libraries(${COMPONENT_LIB} PRIVATE libvorbisidec)
+
+target_link_libraries(${COMPONENT_LIB} PRIVATE libogg)
+target_link_libraries(${COMPONENT_LIB} PRIVATE libalac)
+target_link_libraries(${COMPONENT_LIB} PRIVATE libremple16)
+target_link_libraries(${COMPONENT_LIB} PRIVATE libsoxr)
+target_link_libraries(${COMPONENT_LIB} PRIVATE libopusfile)
+target_link_libraries(${COMPONENT_LIB} PRIVATE libopus)
diff --git a/components/codecs/inc/FLAC/all.h b/components/codecs/inc/FLAC/all.h
index 2851cf59..b5476652 100644
--- a/components/codecs/inc/FLAC/all.h
+++ b/components/codecs/inc/FLAC/all.h
@@ -35,8 +35,8 @@
#include "export.h"
-#include "assert.h"
#include "callback.h"
+#include "flac_assert.h"
#include "format.h"
#include "metadata.h"
#include "ordinals.h"
diff --git a/components/codecs/inc/FLAC/assert.h b/components/codecs/inc/FLAC/flac_assert.h
similarity index 100%
rename from components/codecs/inc/FLAC/assert.h
rename to components/codecs/inc/FLAC/flac_assert.h
diff --git a/components/codecs/link_helper.c b/components/codecs/link_helper.c
new file mode 100644
index 00000000..1a3df177
--- /dev/null
+++ b/components/codecs/link_helper.c
@@ -0,0 +1,3 @@
+void dummy_obj() {
+ return;
+}
diff --git a/components/display/CMakeLists.txt b/components/display/CMakeLists.txt
deleted file mode 100644
index df10a103..00000000
--- a/components/display/CMakeLists.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-idf_component_register(SRCS "display.c" "SH1106.c" "SSD1306.c" "SSD132x.c" "core/ifaces/default_if_i2c.c" "core/ifaces/default_if_spi.c" "core/gds_draw.c" "core/gds_font.c" "core/gds_image.c" "core/gds_text.c" "core/gds.c" "fonts/font_droid_sans_fallback_11x13.c" "fonts/font_droid_sans_fallback_15x17.c" "fonts/font_droid_sans_fallback_24x28.c" "fonts/font_droid_sans_mono_13x24.c" "fonts/font_droid_sans_mono_16x31.c" "fonts/font_droid_sans_mono_7x13.c"
- INCLUDE_DIRS . fonts core
-
-)
-
-
diff --git a/components/driver_bt/CMakeLists.txt b/components/driver_bt/CMakeLists.txt
deleted file mode 100644
index 32f96ab5..00000000
--- a/components/driver_bt/CMakeLists.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-idf_component_register(SRCS "bt_app_core.c" "bt_app_sink.c" "bt_app_source.c"
- INCLUDE_DIRS "." "../tools/"
- REQUIRES esp_common
- PRIV_REQUIRES freertos bt nvs_flash esp32 spi_flash newlib log console pthread
-)
-
diff --git a/components/platform_bluetooth/CMakeLists.txt b/components/platform_bluetooth/CMakeLists.txt
new file mode 100644
index 00000000..3c5fb106
--- /dev/null
+++ b/components/platform_bluetooth/CMakeLists.txt
@@ -0,0 +1,8 @@
+idf_component_register(SRCS platform_bt_core.c
+ platform_bt_sink.c
+ platform_bt_source.c
+ INCLUDE_DIRS .
+ REQUIRES bt platform_display esp_common freertos nvs_flash esp32 spi_flash newlib log pthread platform_config
+
+)
+
diff --git a/components/driver_bt/component.mk b/components/platform_bluetooth/component.mk
similarity index 100%
rename from components/driver_bt/component.mk
rename to components/platform_bluetooth/component.mk
diff --git a/components/driver_bt/bt_app_core.c b/components/platform_bluetooth/platform_bt_core.c
similarity index 99%
rename from components/driver_bt/bt_app_core.c
rename to components/platform_bluetooth/platform_bt_core.c
index aa628f27..7c189699 100644
--- a/components/driver_bt/bt_app_core.c
+++ b/components/platform_bluetooth/platform_bt_core.c
@@ -6,8 +6,9 @@
CONDITIONS OF ANY KIND, either express or implied.
*/
+#include "platform_bt_core.h"
+
#include
-#include "bt_app_core.h"
#include "esp_system.h"
#include
#include
diff --git a/components/driver_bt/bt_app_core.h b/components/platform_bluetooth/platform_bt_core.h
similarity index 100%
rename from components/driver_bt/bt_app_core.h
rename to components/platform_bluetooth/platform_bt_core.h
diff --git a/components/driver_bt/bt_app_sink.c b/components/platform_bluetooth/platform_bt_sink.c
similarity index 99%
rename from components/driver_bt/bt_app_sink.c
rename to components/platform_bluetooth/platform_bt_sink.c
index 5e2cc5c2..97620577 100644
--- a/components/driver_bt/bt_app_sink.c
+++ b/components/platform_bluetooth/platform_bt_sink.c
@@ -7,14 +7,13 @@
CONDITIONS OF ANY KIND, either express or implied.
*/
+#include
#include
#include
#include
#include
#include "esp_log.h"
-#include "bt_app_core.h"
-#include "bt_app_sink.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
@@ -29,6 +28,7 @@
#include "audio_controls.h"
#include "sys/lock.h"
#include "display.h"
+#include "platform_bt_core.h"
// AVRCP used transaction label
#define APP_RC_CT_TL_GET_CAPS (0)
@@ -231,6 +231,7 @@ void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param
switch (event) {
case ESP_AVRC_CT_METADATA_RSP_EVT:
bt_app_alloc_meta_buffer(param);
+ /* no break */
/* fall through */
case ESP_AVRC_CT_CONNECTION_STATE_EVT:
case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT:
diff --git a/components/driver_bt/bt_app_sink.h b/components/platform_bluetooth/platform_bt_sink.h
similarity index 97%
rename from components/driver_bt/bt_app_sink.h
rename to components/platform_bluetooth/platform_bt_sink.h
index 6fc7b962..cb72669f 100644
--- a/components/driver_bt/bt_app_sink.h
+++ b/components/platform_bluetooth/platform_bt_sink.h
@@ -10,7 +10,7 @@
#define __BT_APP_SINK_H__
#include
-
+#include
typedef enum { BT_SINK_CONNECTED, BT_SINK_DISCONNECTED, BT_SINK_AUDIO_STARTED, BT_SINK_AUDIO_STOPPED, BT_SINK_PLAY, BT_SINK_STOP, BT_SINK_PAUSE,
BT_SINK_RATE, BT_SINK_VOLUME, BT_SINK_METADATA, BT_SINK_PROGRESS } bt_sink_cmd_t;
diff --git a/components/driver_bt/bt_app_source.c b/components/platform_bluetooth/platform_bt_source.c
similarity index 99%
rename from components/driver_bt/bt_app_source.c
rename to components/platform_bluetooth/platform_bt_source.c
index 7d00c113..dc0fc38c 100644
--- a/components/driver_bt/bt_app_source.c
+++ b/components/platform_bluetooth/platform_bt_source.c
@@ -15,8 +15,8 @@
#include "esp_wifi.h"
#include "freertos/timers.h"
#include "argtable3/argtable3.h"
+#include "platform_bt_core.h"
#include "platform_config.h"
-#include "bt_app_core.h"
#include "trace.h"
static const char * TAG = "platform";
diff --git a/components/platform_config/CMakeLists.txt b/components/platform_config/CMakeLists.txt
index 681021ec..c5397829 100644
--- a/components/platform_config/CMakeLists.txt
+++ b/components/platform_config/CMakeLists.txt
@@ -1,7 +1,7 @@
idf_component_register(SRCS "nvs_utilities.c" "platform_config.c"
INCLUDE_DIRS .
INCLUDE_DIRS . ../tools/
- REQUIRES nvs_flash json platform_console services vfs
+ REQUIRES newlib nvs_flash json platform_console services vfs
)
diff --git a/components/platform_config/platform_config.h b/components/platform_config/platform_config.h
index f83c7276..29509867 100644
--- a/components/platform_config/platform_config.h
+++ b/components/platform_config/platform_config.h
@@ -2,6 +2,7 @@
#include
#include
#include "nvs.h"
+#include "assert.h"
#ifdef __cplusplus
extern "C" {
diff --git a/components/platform_console/CMakeLists.txt b/components/platform_console/CMakeLists.txt
index ca644dcc..c8c318b2 100644
--- a/components/platform_console/CMakeLists.txt
+++ b/components/platform_console/CMakeLists.txt
@@ -2,6 +2,6 @@ set(COMPONENT_ADD_INCLUDEDIRS .)
set(COMPONENT_SRCS "platform_console.c" "cmd_ota.c" "cmd_nvs.c" "cmd_i2ctools.c" "cmd_squeezelite.c" "cmd_system.c" "cmd_wifi.c" )
-set(COMPONENT_REQUIRES console nvs_flash spi_flash app_update platform_config vfs pthread wifi-manager)
+set(COMPONENT_REQUIRES squeezelite console nvs_flash spi_flash app_update platform_config vfs pthread wifi-manager)
register_component()
diff --git a/components/platform_display/CMakeLists.txt b/components/platform_display/CMakeLists.txt
new file mode 100644
index 00000000..2cf79aab
--- /dev/null
+++ b/components/platform_display/CMakeLists.txt
@@ -0,0 +1,33 @@
+
+idf_component_register(SRCS display.c
+ SH1106.c
+ SSD1306.c
+ SSD132x.c
+ core/ifaces/default_if_i2c.c
+ core/ifaces/default_if_spi.c
+ core/gds_draw.c
+ core/gds_font.c
+ core/gds_image.c
+ core/gds_text.c
+ core/gds.c
+ fonts/font_droid_sans_fallback_11x13.c
+ fonts/font_droid_sans_fallback_15x17.c
+ fonts/font_droid_sans_fallback_24x28.c
+ fonts/font_droid_sans_mono_13x24.c
+ fonts/font_droid_sans_mono_16x31.c
+ fonts/font_droid_sans_mono_7x13.c
+ fonts/font_liberation_mono_13x21.c
+ fonts/font_liberation_mono_17x30.c
+ fonts/font_liberation_mono_9x15.c
+ fonts/font_line_1.c
+ fonts/font_line_2.c
+ fonts/font_tarable7seg_16x32.c
+ fonts/font_tarable7seg_32x64.c
+ PRIV_REQUIRES services
+ INCLUDE_DIRS . fonts core
+)
+
+set_source_files_properties(display.c
+ PROPERTIES COMPILE_FLAGS
+ -Wno-format-overflow
+)
\ No newline at end of file
diff --git a/components/display/SH1106.c b/components/platform_display/SH1106.c
similarity index 100%
rename from components/display/SH1106.c
rename to components/platform_display/SH1106.c
diff --git a/components/display/SSD1306.c b/components/platform_display/SSD1306.c
similarity index 100%
rename from components/display/SSD1306.c
rename to components/platform_display/SSD1306.c
diff --git a/components/display/SSD132x.c b/components/platform_display/SSD132x.c
similarity index 100%
rename from components/display/SSD132x.c
rename to components/platform_display/SSD132x.c
diff --git a/components/display/component.mk b/components/platform_display/component.mk
similarity index 100%
rename from components/display/component.mk
rename to components/platform_display/component.mk
diff --git a/components/display/core/gds.c b/components/platform_display/core/gds.c
similarity index 100%
rename from components/display/core/gds.c
rename to components/platform_display/core/gds.c
diff --git a/components/display/core/gds.h b/components/platform_display/core/gds.h
similarity index 100%
rename from components/display/core/gds.h
rename to components/platform_display/core/gds.h
diff --git a/components/display/core/gds_default_if.h b/components/platform_display/core/gds_default_if.h
similarity index 100%
rename from components/display/core/gds_default_if.h
rename to components/platform_display/core/gds_default_if.h
diff --git a/components/display/core/gds_draw.c b/components/platform_display/core/gds_draw.c
similarity index 99%
rename from components/display/core/gds_draw.c
rename to components/platform_display/core/gds_draw.c
index 60467a33..94ad1ce4 100644
--- a/components/display/core/gds_draw.c
+++ b/components/platform_display/core/gds_draw.c
@@ -266,6 +266,6 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
}
*/
}
-
+
Device->Dirty = true;
-}
\ No newline at end of file
+}
diff --git a/components/display/core/gds_draw.h b/components/platform_display/core/gds_draw.h
similarity index 100%
rename from components/display/core/gds_draw.h
rename to components/platform_display/core/gds_draw.h
diff --git a/components/display/core/gds_err.h b/components/platform_display/core/gds_err.h
similarity index 100%
rename from components/display/core/gds_err.h
rename to components/platform_display/core/gds_err.h
diff --git a/components/display/core/gds_font.c b/components/platform_display/core/gds_font.c
similarity index 100%
rename from components/display/core/gds_font.c
rename to components/platform_display/core/gds_font.c
diff --git a/components/display/core/gds_font.h b/components/platform_display/core/gds_font.h
similarity index 100%
rename from components/display/core/gds_font.h
rename to components/platform_display/core/gds_font.h
diff --git a/components/display/core/gds_image.c b/components/platform_display/core/gds_image.c
similarity index 100%
rename from components/display/core/gds_image.c
rename to components/platform_display/core/gds_image.c
diff --git a/components/display/core/gds_image.h b/components/platform_display/core/gds_image.h
similarity index 100%
rename from components/display/core/gds_image.h
rename to components/platform_display/core/gds_image.h
diff --git a/components/display/core/gds_private.h b/components/platform_display/core/gds_private.h
similarity index 90%
rename from components/display/core/gds_private.h
rename to components/platform_display/core/gds_private.h
index 60893be5..ea05e99b 100644
--- a/components/display/core/gds_private.h
+++ b/components/platform_display/core/gds_private.h
@@ -118,7 +118,7 @@ struct GDS_Device {
bool GDS_Reset( struct GDS_Device* Device );
-inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) {
+static inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) {
bool Result = (
( x >= 0 ) &&
( x < Device->Width ) &&
@@ -135,7 +135,7 @@ inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) {
return Result;
}
-inline void IRAM_ATTR GDS_DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
+static inline void IRAM_ATTR GDS_DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint32_t YBit = ( Y & 0x07 );
uint8_t* FBOffset = NULL;
@@ -156,23 +156,23 @@ inline void IRAM_ATTR GDS_DrawPixel1Fast( struct GDS_Device* Device, int X, int
}
}
-inline void IRAM_ATTR GDS_DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
+static inline void IRAM_ATTR GDS_DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset;
FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | (Color << 4) : ((*FBOffset & 0xf0) | Color);
}
-inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
+static inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
if (Device->DrawPixelFast) Device->DrawPixelFast( Device, X, Y, Color );
else if (Device->Depth == 4) GDS_DrawPixel4Fast( Device, X, Y, Color);
else if (Device->Depth == 1) GDS_DrawPixel1Fast( Device, X, Y, Color);
}
-inline void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) {
+static inline void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) {
if ( IsPixelVisible( Device, x, y ) == true ) {
GDS_DrawPixelFast( Device, x, y, Color );
}
}
-#endif
\ No newline at end of file
+#endif
diff --git a/components/display/core/gds_text.c b/components/platform_display/core/gds_text.c
similarity index 100%
rename from components/display/core/gds_text.c
rename to components/platform_display/core/gds_text.c
diff --git a/components/display/core/gds_text.h b/components/platform_display/core/gds_text.h
similarity index 100%
rename from components/display/core/gds_text.h
rename to components/platform_display/core/gds_text.h
diff --git a/components/display/core/ifaces/default_if_i2c.c b/components/platform_display/core/ifaces/default_if_i2c.c
similarity index 100%
rename from components/display/core/ifaces/default_if_i2c.c
rename to components/platform_display/core/ifaces/default_if_i2c.c
diff --git a/components/display/core/ifaces/default_if_spi.c b/components/platform_display/core/ifaces/default_if_spi.c
similarity index 100%
rename from components/display/core/ifaces/default_if_spi.c
rename to components/platform_display/core/ifaces/default_if_spi.c
diff --git a/components/display/display.c b/components/platform_display/display.c
similarity index 99%
rename from components/display/display.c
rename to components/platform_display/display.c
index 98a95b5e..6c7eba41 100644
--- a/components/display/display.c
+++ b/components/platform_display/display.c
@@ -211,7 +211,7 @@ static void displayer_task(void *args) {
scroll_sleep -= sleep;
vTaskDelay(sleep / portTICK_PERIOD_MS);
}
-}
+}
/****************************************************************************************
*
diff --git a/components/display/display.h b/components/platform_display/display.h
similarity index 100%
rename from components/display/display.h
rename to components/platform_display/display.h
diff --git a/components/display/fonts/LICENSE-apache b/components/platform_display/fonts/LICENSE-apache
similarity index 100%
rename from components/display/fonts/LICENSE-apache
rename to components/platform_display/fonts/LICENSE-apache
diff --git a/components/display/fonts/LICENSE-liberation-mono b/components/platform_display/fonts/LICENSE-liberation-mono
similarity index 100%
rename from components/display/fonts/LICENSE-liberation-mono
rename to components/platform_display/fonts/LICENSE-liberation-mono
diff --git a/components/display/fonts/font_droid_sans_fallback_11x13.c b/components/platform_display/fonts/font_droid_sans_fallback_11x13.c
similarity index 100%
rename from components/display/fonts/font_droid_sans_fallback_11x13.c
rename to components/platform_display/fonts/font_droid_sans_fallback_11x13.c
diff --git a/components/display/fonts/font_droid_sans_fallback_15x17.c b/components/platform_display/fonts/font_droid_sans_fallback_15x17.c
similarity index 100%
rename from components/display/fonts/font_droid_sans_fallback_15x17.c
rename to components/platform_display/fonts/font_droid_sans_fallback_15x17.c
diff --git a/components/display/fonts/font_droid_sans_fallback_24x28.c b/components/platform_display/fonts/font_droid_sans_fallback_24x28.c
similarity index 100%
rename from components/display/fonts/font_droid_sans_fallback_24x28.c
rename to components/platform_display/fonts/font_droid_sans_fallback_24x28.c
diff --git a/components/display/fonts/font_droid_sans_mono_13x24.c b/components/platform_display/fonts/font_droid_sans_mono_13x24.c
similarity index 100%
rename from components/display/fonts/font_droid_sans_mono_13x24.c
rename to components/platform_display/fonts/font_droid_sans_mono_13x24.c
diff --git a/components/display/fonts/font_droid_sans_mono_16x31.c b/components/platform_display/fonts/font_droid_sans_mono_16x31.c
similarity index 100%
rename from components/display/fonts/font_droid_sans_mono_16x31.c
rename to components/platform_display/fonts/font_droid_sans_mono_16x31.c
diff --git a/components/display/fonts/font_droid_sans_mono_7x13.c b/components/platform_display/fonts/font_droid_sans_mono_7x13.c
similarity index 100%
rename from components/display/fonts/font_droid_sans_mono_7x13.c
rename to components/platform_display/fonts/font_droid_sans_mono_7x13.c
diff --git a/components/display/fonts/font_liberation_mono_13x21.c b/components/platform_display/fonts/font_liberation_mono_13x21.c
similarity index 100%
rename from components/display/fonts/font_liberation_mono_13x21.c
rename to components/platform_display/fonts/font_liberation_mono_13x21.c
diff --git a/components/display/fonts/font_liberation_mono_17x30.c b/components/platform_display/fonts/font_liberation_mono_17x30.c
similarity index 100%
rename from components/display/fonts/font_liberation_mono_17x30.c
rename to components/platform_display/fonts/font_liberation_mono_17x30.c
diff --git a/components/display/fonts/font_liberation_mono_9x15.c b/components/platform_display/fonts/font_liberation_mono_9x15.c
similarity index 100%
rename from components/display/fonts/font_liberation_mono_9x15.c
rename to components/platform_display/fonts/font_liberation_mono_9x15.c
diff --git a/components/display/fonts/font_line_1.c b/components/platform_display/fonts/font_line_1.c
similarity index 100%
rename from components/display/fonts/font_line_1.c
rename to components/platform_display/fonts/font_line_1.c
diff --git a/components/display/fonts/font_line_2.c b/components/platform_display/fonts/font_line_2.c
similarity index 100%
rename from components/display/fonts/font_line_2.c
rename to components/platform_display/fonts/font_line_2.c
diff --git a/components/display/fonts/font_tarable7seg_16x32.c b/components/platform_display/fonts/font_tarable7seg_16x32.c
similarity index 100%
rename from components/display/fonts/font_tarable7seg_16x32.c
rename to components/platform_display/fonts/font_tarable7seg_16x32.c
diff --git a/components/display/fonts/font_tarable7seg_32x64.c b/components/platform_display/fonts/font_tarable7seg_32x64.c
similarity index 100%
rename from components/display/fonts/font_tarable7seg_32x64.c
rename to components/platform_display/fonts/font_tarable7seg_32x64.c
diff --git a/components/raop/CMakeLists.txt b/components/raop/CMakeLists.txt
new file mode 100644
index 00000000..d4391102
--- /dev/null
+++ b/components/raop/CMakeLists.txt
@@ -0,0 +1,6 @@
+
+idf_component_register(SRCS dmap_parser.c raop_sink.c raop.c rtp.c util.c
+ INCLUDE_DIRS .
+ REQUIRES newlib platform_config services codecs tools
+ PRIV_REQUIRES platform_display
+)
diff --git a/components/raop/raop.c b/components/raop/raop.c
index 07eab618..4677b3b9 100644
--- a/components/raop/raop.c
+++ b/components/raop/raop.c
@@ -1,984 +1,985 @@
-/*
- *
- * (c) Philippe 2019, philippe_44@outlook.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-#include
-
-#include "platform.h"
-
-#ifdef WIN32
-#include
-#include
-#include
-#include
-#include
-#include "mdns.h"
-#include "mdnsd.h"
-#include "mdnssd-itf.h"
-#else
-#include "esp_pthread.h"
-#include "mdns.h"
-#include "mbedtls/version.h"
-#include
-#endif
-
-#include "util.h"
-#include "raop.h"
-#include "rtp.h"
-#include "dmap_parser.h"
-#include "log_util.h"
-
-#define RTSP_STACK_SIZE (8*1024)
-#define SEARCH_STACK_SIZE (2*1048)
-
-typedef struct raop_ctx_s {
-#ifdef WIN32
- struct mdns_service *svc;
- struct mdnsd *svr;
-#endif
- struct in_addr host; // IP of bridge
- short unsigned port; // RTSP port for AirPlay
- int sock; // socket of the above
- struct in_addr peer; // IP of the iDevice (airplay sender)
- bool running;
-#ifdef WIN32
- pthread_t thread, search_thread;
-#else
- TaskHandle_t thread, search_thread, joiner;
- StaticTask_t *xTaskBuffer;
- StackType_t xStack[RTSP_STACK_SIZE] __attribute__ ((aligned (4)));
-#endif
- /*
- Compiler/Execution bug: if this bool is next to 'running', the rtsp_thread
- loop sees 'running' being set to false from at first execution ...
- */
- bool abort;
- unsigned char mac[6];
- int latency;
- struct {
- char *aesiv, *aeskey;
- char *fmtp;
- } rtsp;
- struct rtp_s *rtp;
- raop_cmd_cb_t cmd_cb;
- raop_data_cb_t data_cb;
- struct {
- char DACPid[32], id[32];
- struct in_addr host;
- u16_t port;
-#ifdef WIN32
- struct mDNShandle_s *handle;
-#else
- bool running;
- TaskHandle_t thread, joiner;
- StaticTask_t *xTaskBuffer;
- StackType_t xStack[SEARCH_STACK_SIZE] __attribute__ ((aligned (4)));;
- SemaphoreHandle_t destroy_mutex;
-#endif
- } active_remote;
- void *owner;
-} raop_ctx_t;
-
-extern struct mdnsd* glmDNSServer;
-extern log_level raop_loglevel;
-static log_level *loglevel = &raop_loglevel;
-
-static void* rtsp_thread(void *arg);
-static void abort_rtsp(raop_ctx_t *ctx);
-static bool handle_rtsp(raop_ctx_t *ctx, int sock);
-
-static char* rsa_apply(unsigned char *input, int inlen, int *outlen, int mode);
-static int base64_pad(char *src, char **padded);
-static int base64_encode(const void *data, int size, char **str);
-static int base64_decode(const char *str, void *data);
-static void* search_remote(void *args);
-
-extern char private_key[];
-
enum { RSA_MODE_KEY, RSA_MODE_AUTH };
-
-static void on_dmap_string(void *ctx, const char *code, const char *name, const char *buf, size_t len);
-
-/*----------------------------------------------------------------------------*/
-struct raop_ctx_s *raop_create(struct in_addr host, char *name,
- unsigned char mac[6], int latency,
- raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb) {
- struct raop_ctx_s *ctx = malloc(sizeof(struct raop_ctx_s));
- struct sockaddr_in addr;
- char id[64];
-
#ifdef WIN32
- socklen_t nlen = sizeof(struct sockaddr);
- char *txt[] = { "am=airesp32", "tp=UDP", "sm=false", "sv=false", "ek=1",
- "et=0,1", "md=0,1,2", "cn=0,1", "ch=2",
- "ss=16", "sr=44100", "vn=3", "txtvers=1",
- NULL };
-#else
- mdns_txt_item_t txt[] = {
- {"am", "airesp32"},
- {"tp", "UDP"},
- {"sm","false"},
- {"sv","false"},
- {"ek","1"},
- {"et","0,1"},
- {"md","0,1,2"},
- {"cn","0,1"},
- {"ch","2"},
- {"ss","16"},
- {"sr","44100"},
- {"vn","3"},
- {"txtvers","1"},
- };
-
-#endif
-
- if (!ctx) return NULL;
-
- // make sure we have a clean context
- memset(ctx, 0, sizeof(raop_ctx_t));
-
-#ifdef WIN32
- ctx->svr = glmDNSServer;
-#endif
- ctx->host = host;
- ctx->sock = socket(AF_INET, SOCK_STREAM, 0);
- ctx->cmd_cb = cmd_cb;
- ctx->data_cb = data_cb;
- ctx->latency = min(latency, 88200);
- if (ctx->sock == -1) {
- LOG_ERROR("Cannot create listening socket", NULL);
- free(ctx);
- return NULL;
- }
-
- memset(&addr, 0, sizeof(addr));
- addr.sin_addr.s_addr = host.s_addr;
- addr.sin_family = AF_INET;
-#ifdef WIN32
- ctx->port = 0;
- addr.sin_port = htons(ctx->port);
-#else
- ctx->port = 5000;
- addr.sin_port = htons(ctx->port);
-#endif
-
- if (bind(ctx->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0 || listen(ctx->sock, 1)) {
- LOG_ERROR("Cannot bind or listen RTSP listener: %s", strerror(errno));
- free(ctx);
- closesocket(ctx->sock);
- return NULL;
- }
-
-#ifdef WIN32
- getsockname(ctx->sock, (struct sockaddr *) &addr, &nlen);
- ctx->port = ntohs(addr.sin_port);
-#endif
-
ctx->running = true;
-
memcpy(ctx->mac, mac, 6);
- snprintf(id, 64, "%02X%02X%02X%02X%02X%02X@%s", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], name);
-
#ifdef WIN32
- // seems that Windows snprintf does not add NULL char if actual size > max
- id[63] = '\0';
- ctx->svc = mdnsd_register_svc(ctx->svr, id, "_raop._tcp.local", ctx->port, NULL, (const char**) txt);
- pthread_create(&ctx->thread, NULL, &rtsp_thread, ctx);
-
#else
- LOG_INFO("starting mDNS with %s", id);
- ESP_ERROR_CHECK( mdns_service_add(id, "_raop", "_tcp", ctx->port, txt, sizeof(txt) / sizeof(mdns_txt_item_t)) );
-
- ctx->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
- ctx->thread = xTaskCreateStatic( (TaskFunction_t) rtsp_thread, "RTSP_thread", RTSP_STACK_SIZE, ctx, ESP_TASK_PRIO_MIN + 1, ctx->xStack, ctx->xTaskBuffer);
-#endif
-
- return ctx;
-}
-
-/*----------------------------------------------------------------------------*/
-void raop_abort(struct raop_ctx_s *ctx) {
- LOG_INFO("[%p]: aborting RTSP session at next select() wakeup", ctx);
- ctx->abort = true;
-}
-
-/*----------------------------------------------------------------------------*/
-void raop_delete(struct raop_ctx_s *ctx) {
-#ifdef WIN32
- int sock;
- struct sockaddr addr;
- socklen_t nlen = sizeof(struct sockaddr);
-#endif
-
- if (!ctx) return;
-
-#ifdef WIN32
- ctx->running = false;
-
- // wake-up thread by connecting socket, needed for freeBSD
- sock = socket(AF_INET, SOCK_STREAM, 0);
- getsockname(ctx->sock, (struct sockaddr *) &addr, &nlen);
- connect(sock, (struct sockaddr*) &addr, sizeof(addr));
- closesocket(sock);
-
- pthread_join(ctx->thread, NULL);
-
- rtp_end(ctx->rtp);
-
- shutdown(ctx->sock, SD_BOTH);
- closesocket(ctx->sock);
-
- // terminate search, but do not reclaim memory of pthread if never launched
- if (ctx->active_remote.handle) {
- close_mDNS(ctx->active_remote.handle);
- pthread_join(ctx->search_thread, NULL);
- }
-
- // stop broadcasting devices
- mdns_service_remove(ctx->svr, ctx->svc);
- mdnsd_stop(ctx->svr);
-#else
- // first stop the search task if any
- if (ctx->active_remote.running) {
- ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
- ctx->active_remote.running = false;
-
- vTaskResume(ctx->active_remote.thread);
- ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
- vTaskDelete(ctx->active_remote.thread);
-
- heap_caps_free(ctx->active_remote.xTaskBuffer);
- }
-
- // then the RTSP task
- ctx->joiner = xTaskGetCurrentTaskHandle();
- ctx->running = false;
-
- ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
- vTaskDelete(ctx->thread);
- heap_caps_free(ctx->xTaskBuffer);
-
- rtp_end(ctx->rtp);
-
- shutdown(ctx->sock, SHUT_RDWR);
- closesocket(ctx->sock);
-
- mdns_service_remove("_raop", "_tcp");
-#endif
-
- NFREE(ctx->rtsp.aeskey);
- NFREE(ctx->rtsp.aesiv);
- NFREE(ctx->rtsp.fmtp);
-
- free(ctx);
-}
-
-/*----------------------------------------------------------------------------*/
-bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
- struct sockaddr_in addr;
- int sock;
- char *command = NULL;
-
- // first notify the remote controller (if any)
- switch(event) {
- case RAOP_REW:
- command = strdup("beginrew");
- break;
- case RAOP_FWD:
- command = strdup("beginff");
- break;
- case RAOP_PREV:
- command = strdup("previtem");
- break;
- case RAOP_NEXT:
- command = strdup("nextitem");
- break;
- case RAOP_TOGGLE:
- command = strdup("playpause");
- break;
- case RAOP_PAUSE:
- command = strdup("pause");
- break;
- case RAOP_PLAY:
- command = strdup("play");
- break;
- case RAOP_RESUME:
- command = strdup("playresume");
- break;
- case RAOP_STOP:
- command = strdup("stop");
- break;
- case RAOP_VOLUME_UP:
- command = strdup("volumeup");
- break;
- case RAOP_VOLUME_DOWN:
- command = strdup("volumedown");
- break;
- case RAOP_VOLUME: {
- float Volume = *((float*) param);
- Volume = Volume ? (Volume - 1) * 30 : -144;
- asprintf(&command,"setproperty?dmcp.device-volume=%0.4lf", Volume);
- break;
- }
- default:
- break;
- }
-
- // no command to send to remote or no remote found yet
- if (!command || !ctx->active_remote.port) {
- NFREE(command);
- return false;
- }
-
- sock = socket(AF_INET, SOCK_STREAM, 0);
-
- memset(&addr, 0, sizeof(addr));
- addr.sin_family = AF_INET;
- addr.sin_addr.s_addr = S_ADDR(ctx->active_remote.host);
- addr.sin_port = htons(ctx->active_remote.port);
-
- if (!connect(sock, (struct sockaddr*) &addr, sizeof(addr))) {
- char *method, *buf, resp[512] = "";
- int len;
- key_data_t headers[4] = { {NULL, NULL} };
-
- asprintf(&method, "GET /ctrl-int/1/%s HTTP/1.0", command);
- kd_add(headers, "Active-Remote", ctx->active_remote.id);
- kd_add(headers, "Connection", "close");
-
- buf = http_send(sock, method, headers);
- len = recv(sock, resp, 512, 0);
- if (len > 0) resp[len-1] = '\0';
- LOG_INFO("[%p]: sending airplay remote\n%s<== received ==>\n%s", ctx, buf, resp);
-
- NFREE(method);
- NFREE(buf);
- kd_free(headers);
- }
-
- free(command);
- closesocket(sock);
-
- return true;
-}
-
-/*----------------------------------------------------------------------------*/
-static void *rtsp_thread(void *arg) {
- raop_ctx_t *ctx = (raop_ctx_t*) arg;
- int sock = -1;
-
- while (ctx->running) {
- fd_set rfds;
- struct timeval timeout = {0, 100*1000};
- int n;
- bool res = false;
-
- if (sock == -1) {
- struct sockaddr_in peer;
- socklen_t addrlen = sizeof(struct sockaddr_in);
-
- sock = accept(ctx->sock, (struct sockaddr*) &peer, &addrlen);
- ctx->peer.s_addr = peer.sin_addr.s_addr;
- ctx->abort = false;
-
- if (sock != -1 && ctx->running) {
- LOG_INFO("got RTSP connection %u", sock);
- } else continue;
- }
-
- FD_ZERO(&rfds);
- FD_SET(sock, &rfds);
-
- n = select(sock + 1, &rfds, NULL, NULL, &timeout);
-
- if (!n && !ctx->abort) continue;
-
- if (n > 0) res = handle_rtsp(ctx, sock);
-
- if (n < 0 || !res || ctx->abort) {
- abort_rtsp(ctx);
- closesocket(sock);
- LOG_INFO("RTSP close %u", sock);
- sock = -1;
- }
- }
-
- if (sock != -1) closesocket(sock);
-
-#ifndef WIN32
- xTaskNotifyGive(ctx->joiner);
- vTaskSuspend(NULL);
-#endif
-
- return NULL;
-}
-
-
-/*----------------------------------------------------------------------------*/
-static bool handle_rtsp(raop_ctx_t *ctx, int sock)
-{
- char *buf = NULL, *body = NULL, method[16] = "";
- key_data_t headers[16], resp[8] = { {NULL, NULL} };
- int len;
- bool success = true;
-
- if (!http_parse(sock, method, headers, &body, &len)) {
- NFREE(body);
- kd_free(headers);
- return false;
- }
-
- if (strcmp(method, "OPTIONS")) {
- LOG_INFO("[%p]: received %s", ctx, method);
- }
-
- if ((buf = kd_lookup(headers, "Apple-Challenge")) != NULL) {
- int n;
- char *buf_pad, *p, *data_b64 = NULL, data[32];
-
- LOG_INFO("[%p]: challenge %s", ctx, buf);
-
- // need to pad the base64 string as apple device don't
- base64_pad(buf, &buf_pad);
-
- p = data + min(base64_decode(buf_pad, data), 32-10);
- p = (char*) memcpy(p, &S_ADDR(ctx->host), 4) + 4;
- p = (char*) memcpy(p, ctx->mac, 6) + 6;
- memset(p, 0, 32 - (p - data));
- p = rsa_apply((unsigned char*) data, 32, &n, RSA_MODE_AUTH);
- n = base64_encode(p, n, &data_b64);
-
- // remove padding as well (seems to be optional now)
- for (n = strlen(data_b64) - 1; n > 0 && data_b64[n] == '='; data_b64[n--] = '\0');
-
- kd_add(resp, "Apple-Response", data_b64);
-
- NFREE(p);
- NFREE(buf_pad);
- NFREE(data_b64);
- }
-
- if (!strcmp(method, "OPTIONS")) {
-
- kd_add(resp, "Public", "ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER");
-
- } else if (!strcmp(method, "ANNOUNCE")) {
- char *padded, *p;
-
- NFREE(ctx->rtsp.aeskey);
- NFREE(ctx->rtsp.aesiv);
- NFREE(ctx->rtsp.fmtp);
-
- if ((p = strcasestr(body, "rsaaeskey")) != NULL) {
- unsigned char *aeskey;
- int len, outlen;
-
- p = strextract(p, ":", "\r\n");
- base64_pad(p, &padded);
- aeskey = malloc(strlen(padded));
- len = base64_decode(padded, aeskey);
- ctx->rtsp.aeskey = rsa_apply(aeskey, len, &outlen, RSA_MODE_KEY);
-
- NFREE(p);
- NFREE(aeskey);
- NFREE(padded);
- }
-
- if ((p = strcasestr(body, "aesiv")) != NULL) {
- p = strextract(p, ":", "\r\n");
- base64_pad(p, &padded);
- ctx->rtsp.aesiv = malloc(strlen(padded));
- base64_decode(padded, ctx->rtsp.aesiv);
-
- NFREE(p);
- NFREE(padded);
- }
-
- if ((p = strcasestr(body, "fmtp")) != NULL) {
- p = strextract(p, ":", "\r\n");
- ctx->rtsp.fmtp = strdup(p);
- NFREE(p);
- }
-
- // on announce, search remote
- if ((buf = kd_lookup(headers, "DACP-ID")) != NULL) strcpy(ctx->active_remote.DACPid, buf);
- if ((buf = kd_lookup(headers, "Active-Remote")) != NULL) strcpy(ctx->active_remote.id, buf);
-
-#ifdef WIN32
- ctx->active_remote.handle = init_mDNS(false, ctx->host);
- pthread_create(&ctx->search_thread, NULL, &search_remote, ctx);
-#else
- ctx->active_remote.running = true;
- ctx->active_remote.destroy_mutex = xSemaphoreCreateMutex();
- ctx->active_remote.xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
- ctx->active_remote.thread = xTaskCreateStatic( (TaskFunction_t) search_remote, "search_remote", SEARCH_STACK_SIZE, ctx, ESP_TASK_PRIO_MIN + 1, ctx->active_remote.xStack, ctx->active_remote.xTaskBuffer);
-#endif
-
- } else if (!strcmp(method, "SETUP") && ((buf = kd_lookup(headers, "Transport")) != NULL)) {
- char *p;
- rtp_resp_t rtp = { 0 };
- short unsigned tport = 0, cport = 0;
-
- // we are about to stream, do something if needed
- success = ctx->cmd_cb(RAOP_SETUP);
-
- if ((p = strcasestr(buf, "timing_port")) != NULL) sscanf(p, "%*[^=]=%hu", &tport);
- if ((p = strcasestr(buf, "control_port")) != NULL) sscanf(p, "%*[^=]=%hu", &cport);
-
- rtp = rtp_init(ctx->peer, ctx->latency, ctx->rtsp.aeskey, ctx->rtsp.aesiv,
- ctx->rtsp.fmtp, cport, tport, ctx->cmd_cb, ctx->data_cb);
-
- ctx->rtp = rtp.ctx;
-
- if ( (cport * tport * rtp.cport * rtp.tport * rtp.aport) != 0 && rtp.ctx) {
- char *transport;
- asprintf(&transport, "RTP/AVP/UDP;unicast;mode=record;control_port=%u;timing_port=%u;server_port=%u", rtp.cport, rtp.tport, rtp.aport);
- LOG_DEBUG("[%p]: audio=(%hu:%hu), timing=(%hu:%hu), control=(%hu:%hu)", ctx, 0, rtp.aport, tport, rtp.tport, cport, rtp.cport);
- kd_add(resp, "Transport", transport);
- kd_add(resp, "Session", "DEADBEEF");
- free(transport);
- } else {
- success = false;
- LOG_INFO("[%p]: cannot start session, missing ports", ctx);
- }
-
- } else if (!strcmp(method, "RECORD")) {
- unsigned short seqno = 0;
- unsigned rtptime = 0;
- char *p;
-
- if (ctx->latency) {
- char latency[6];
- snprintf(latency, 6, "%u", ctx->latency);
- kd_add(resp, "Audio-Latency", latency);
- }
-
- buf = kd_lookup(headers, "RTP-Info");
- if (buf && (p = strcasestr(buf, "seq")) != NULL) sscanf(p, "%*[^=]=%hu", &seqno);
- if (buf && (p = strcasestr(buf, "rtptime")) != NULL) sscanf(p, "%*[^=]=%u", &rtptime);
-
- if (ctx->rtp) rtp_record(ctx->rtp, seqno, rtptime);
-
- success = ctx->cmd_cb(RAOP_STREAM);
-
- } else if (!strcmp(method, "FLUSH")) {
- unsigned short seqno = 0;
- unsigned rtptime = 0;
- char *p;
-
- buf = kd_lookup(headers, "RTP-Info");
- if ((p = strcasestr(buf, "seq")) != NULL) sscanf(p, "%*[^=]=%hu", &seqno);
- if ((p = strcasestr(buf, "rtptime")) != NULL) sscanf(p, "%*[^=]=%u", &rtptime);
-
- // only send FLUSH if useful (discards frames above buffer head and top)
- if (ctx->rtp && rtp_flush(ctx->rtp, seqno, rtptime))
- success = ctx->cmd_cb(RAOP_FLUSH);
-
- } else if (!strcmp(method, "TEARDOWN")) {
-
- rtp_end(ctx->rtp);
-
- ctx->rtp = NULL;
-
- // need to make sure no search is on-going and reclaim pthread memory
-#ifdef WIN32
- if (ctx->active_remote.handle) close_mDNS(ctx->active_remote.handle);
- pthread_join(ctx->search_thread, NULL);
-#else
- ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
- ctx->active_remote.running = false;
-
- xSemaphoreTake(ctx->active_remote.destroy_mutex, portMAX_DELAY);
- vTaskDelete(ctx->active_remote.thread);
- vSemaphoreDelete(ctx->active_remote.thread);
-
- heap_caps_free(ctx->active_remote.xTaskBuffer);
-
- LOG_INFO("[%p]: mDNS search task terminated", ctx);
-#endif
-
- memset(&ctx->active_remote, 0, sizeof(ctx->active_remote));
- NFREE(ctx->rtsp.aeskey);
- NFREE(ctx->rtsp.aesiv);
- NFREE(ctx->rtsp.fmtp);
-
- success = ctx->cmd_cb(RAOP_STOP);
-
- } else if (!strcmp(method, "SET_PARAMETER")) {
- char *p;
-
- if (body && (p = strcasestr(body, "volume")) != NULL) {
- float volume;
-
- sscanf(p, "%*[^:]:%f", &volume);
- LOG_INFO("[%p]: SET PARAMETER volume %f", ctx, volume);
- volume = (volume == -144.0) ? 0 : (1 + volume / 30);
- success = ctx->cmd_cb(RAOP_VOLUME, volume);
- } else if (body && (p = strcasestr(body, "progress")) != NULL) {
- int start, current, stop = 0;
-
- // we want ms, not s
- sscanf(p, "%*[^:]:%u/%u/%u", &start, ¤t, &stop);
- current = ((current - start) / 44100) * 1000;
- if (stop) stop = ((stop - start) / 44100) * 1000;
- else stop = -1;
- LOG_INFO("[%p]: SET PARAMETER progress %u/%u %s", ctx, current, stop, p);
- success = ctx->cmd_cb(RAOP_PROGRESS, current, stop);
- }
-
- if (body && ((p = kd_lookup(headers, "Content-Type")) != NULL) && !strcasecmp(p, "application/x-dmap-tagged")) {
- struct metadata_s metadata;
- dmap_settings settings = {
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, on_dmap_string, NULL,
- NULL
- };
-
- LOG_INFO("[%p]: received metadata");
- settings.ctx = &metadata;
- memset(&metadata, 0, sizeof(struct metadata_s));
- if (!dmap_parse(&settings, body, len)) {
- LOG_INFO("[%p]: received metadata\n\tartist: %s\n\talbum: %s\n\ttitle: %s",
- ctx, metadata.artist, metadata.album, metadata.title);
- success = ctx->cmd_cb(RAOP_METADATA, metadata.artist, metadata.album, metadata.title);
- free_metadata(&metadata);
- }
- }
- }
-
- // don't need to free "buf" because kd_lookup return a pointer, not a strdup
- kd_add(resp, "Audio-Jack-Status", "connected; type=analog");
- kd_add(resp, "CSeq", kd_lookup(headers, "CSeq"));
-
- if (success) {
- buf = http_send(sock, "RTSP/1.0 200 OK", resp);
- } else {
- buf = http_send(sock, "RTSP/1.0 503 ERROR", NULL);
- closesocket(sock);
- }
-
- if (strcmp(method, "OPTIONS")) {
- LOG_INFO("[%p]: responding:\n%s", ctx, buf ? buf : "");
- }
-
- NFREE(body);
- NFREE(buf);
- kd_free(resp);
- kd_free(headers);
-
- return true;
-}
-
-/*----------------------------------------------------------------------------*/
-void abort_rtsp(raop_ctx_t *ctx) {
- // first stop RTP process
- if (ctx->rtp) {
- rtp_end(ctx->rtp);
- ctx->rtp = NULL;
- LOG_INFO("[%p]: RTP thread aborted", ctx);
- }
-
- if (ctx->active_remote.running) {
- // need to make sure no search is on-going and reclaim task memory
- ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
- ctx->active_remote.running = false;
-
- xSemaphoreTake(ctx->active_remote.destroy_mutex, portMAX_DELAY);
- vTaskDelete(ctx->active_remote.thread);
- vSemaphoreDelete(ctx->active_remote.thread);
-
- heap_caps_free(ctx->active_remote.xTaskBuffer);
- memset(&ctx->active_remote, 0, sizeof(ctx->active_remote));
-
- LOG_INFO("[%p]: Remote search thread aborted", ctx);
- }
-
- NFREE(ctx->rtsp.aeskey);
- NFREE(ctx->rtsp.aesiv);
- NFREE(ctx->rtsp.fmtp);
-}
-
-/*----------------------------------------------------------------------------*/
-#ifdef WIN32
-bool search_remote_cb(mDNSservice_t *slist, void *cookie, bool *stop) {
- mDNSservice_t *s;
- raop_ctx_t *ctx = (raop_ctx_t*) cookie;
-
- // see if we have found an active remote for our ID
- for (s = slist; s; s = s->next) {
- if (strcasestr(s->name, ctx->active_remote.DACPid)) {
- ctx->active_remote.host = s->addr;
- ctx->active_remote.port = s->port;
- LOG_INFO("[%p]: found ActiveRemote for %s at %s:%u", ctx, ctx->active_remote.DACPid,
- inet_ntoa(ctx->active_remote.host), ctx->active_remote.port);
- *stop = true;
- break;
- }
- }
-
- // let caller clear list
- return false;
-}
-
-
-/*----------------------------------------------------------------------------*/
-static void* search_remote(void *args) {
- raop_ctx_t *ctx = (raop_ctx_t*) args;
-
- query_mDNS(ctx->active_remote.handle, "_dacp._tcp.local", 0, 0, &search_remote_cb, (void*) ctx);
-
- return NULL;
-}
-
-#else
-
-/*----------------------------------------------------------------------------*/
-static void* search_remote(void *args) {
- raop_ctx_t *ctx = (raop_ctx_t*) args;
- bool found = false;
-
- LOG_INFO("starting remote search");
-
- while (ctx->active_remote.running && !found) {
- mdns_result_t *results = NULL;
- mdns_result_t *r;
- mdns_ip_addr_t *a;
-
- if (mdns_query_ptr("_dacp", "_tcp", 3000, 32, &results)) {
- LOG_ERROR("mDNS active remote query Failed");
- continue;
- }
-
- for (r = results; r && !strcasestr(r->instance_name, ctx->active_remote.DACPid); r = r->next);
- if (r) {
- for (a = r->addr; a && a->addr.type != IPADDR_TYPE_V4; a = a->next);
- if (a) {
- found = true;
- ctx->active_remote.host.s_addr = a->addr.u_addr.ip4.addr;
- ctx->active_remote.port = r->port;
- LOG_INFO("found remote %s %s:%hu", r->instance_name, inet_ntoa(ctx->active_remote.host), ctx->active_remote.port);
- }
- }
-
- mdns_query_results_free(results);
- }
-
- // can't use xNotifyGive as it seems LWIP is using it as well
- xSemaphoreGive(ctx->active_remote.destroy_mutex);
- vTaskSuspend(NULL);
-
- return NULL;
- }
-#endif
+/*
+ *
+ * (c) Philippe 2019, philippe_44@outlook.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#include
+
+#include "platform.h"
+
+#ifdef WIN32
+#include
+#include
+#include
+#include
+#include
+#include "mdns.h"
+#include "mdnsd.h"
+#include "mdnssd-itf.h"
+#else
+#include "esp_pthread.h"
+#include "mdns.h"
+#include "mbedtls/version.h"
+#include
+#endif
+
+#include "util.h"
+#include "raop.h"
+#include "rtp.h"
+#include "dmap_parser.h"
+#include "log_util.h"
+
+#define RTSP_STACK_SIZE (8*1024)
+#define SEARCH_STACK_SIZE (2*1048)
+
+typedef struct raop_ctx_s {
+#ifdef WIN32
+ struct mdns_service *svc;
+ struct mdnsd *svr;
+#endif
+ struct in_addr host; // IP of bridge
+ short unsigned port; // RTSP port for AirPlay
+ int sock; // socket of the above
+ struct in_addr peer; // IP of the iDevice (airplay sender)
+ bool running;
+#ifdef WIN32
+ pthread_t thread, search_thread;
+#else
+ TaskHandle_t thread, search_thread, joiner;
+ StaticTask_t *xTaskBuffer;
+ StackType_t xStack[RTSP_STACK_SIZE] __attribute__ ((aligned (4)));
+#endif
+ /*
+ Compiler/Execution bug: if this bool is next to 'running', the rtsp_thread
+ loop sees 'running' being set to false from at first execution ...
+ */
+ bool abort;
+ unsigned char mac[6];
+ int latency;
+ struct {
+ char *aesiv, *aeskey;
+ char *fmtp;
+ } rtsp;
+ struct rtp_s *rtp;
+ raop_cmd_cb_t cmd_cb;
+ raop_data_cb_t data_cb;
+ struct {
+ char DACPid[32], id[32];
+ struct in_addr host;
+ u16_t port;
+#ifdef WIN32
+ struct mDNShandle_s *handle;
+#else
+ bool running;
+ TaskHandle_t thread, joiner;
+ StaticTask_t *xTaskBuffer;
+ StackType_t xStack[SEARCH_STACK_SIZE] __attribute__ ((aligned (4)));;
+ SemaphoreHandle_t destroy_mutex;
+#endif
+ } active_remote;
+ void *owner;
+} raop_ctx_t;
+
+extern struct mdnsd* glmDNSServer;
+extern log_level raop_loglevel;
+static log_level *loglevel = &raop_loglevel;
+
+static void* rtsp_thread(void *arg);
+static void abort_rtsp(raop_ctx_t *ctx);
+static bool handle_rtsp(raop_ctx_t *ctx, int sock);
+
+static char* rsa_apply(unsigned char *input, int inlen, int *outlen, int mode);
+static int base64_pad(char *src, char **padded);
+static int base64_encode(const void *data, int size, char **str);
+static int base64_decode(const char *str, void *data);
+static void* search_remote(void *args);
+
+extern char private_key[];
+
enum { RSA_MODE_KEY, RSA_MODE_AUTH };
+
+static void on_dmap_string(void *ctx, const char *code, const char *name, const char *buf, size_t len);
+
+/*----------------------------------------------------------------------------*/
+struct raop_ctx_s *raop_create(struct in_addr host, char *name,
+ unsigned char mac[6], int latency,
+ raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb) {
+ struct raop_ctx_s *ctx = malloc(sizeof(struct raop_ctx_s));
+ struct sockaddr_in addr;
+ char id[64];
+
#ifdef WIN32
+ socklen_t nlen = sizeof(struct sockaddr);
+ char *txt[] = { "am=airesp32", "tp=UDP", "sm=false", "sv=false", "ek=1",
+ "et=0,1", "md=0,1,2", "cn=0,1", "ch=2",
+ "ss=16", "sr=44100", "vn=3", "txtvers=1",
+ NULL };
+#else
+ mdns_txt_item_t txt[] = {
+ {"am", "airesp32"},
+ {"tp", "UDP"},
+ {"sm","false"},
+ {"sv","false"},
+ {"ek","1"},
+ {"et","0,1"},
+ {"md","0,1,2"},
+ {"cn","0,1"},
+ {"ch","2"},
+ {"ss","16"},
+ {"sr","44100"},
+ {"vn","3"},
+ {"txtvers","1"},
+ };
+
+#endif
+
+ if (!ctx) return NULL;
+
+ // make sure we have a clean context
+ memset(ctx, 0, sizeof(raop_ctx_t));
+
+#ifdef WIN32
+ ctx->svr = glmDNSServer;
+#endif
+ ctx->host = host;
+ ctx->sock = socket(AF_INET, SOCK_STREAM, 0);
+ ctx->cmd_cb = cmd_cb;
+ ctx->data_cb = data_cb;
+ ctx->latency = min(latency, 88200);
+ if (ctx->sock == -1) {
+ LOG_ERROR("Cannot create listening socket", NULL);
+ free(ctx);
+ return NULL;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_addr.s_addr = host.s_addr;
+ addr.sin_family = AF_INET;
+#ifdef WIN32
+ ctx->port = 0;
+ addr.sin_port = htons(ctx->port);
+#else
+ ctx->port = 5000;
+ addr.sin_port = htons(ctx->port);
+#endif
+
+ if (bind(ctx->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0 || listen(ctx->sock, 1)) {
+ LOG_ERROR("Cannot bind or listen RTSP listener: %s", strerror(errno));
+ free(ctx);
+ closesocket(ctx->sock);
+ return NULL;
+ }
+
+#ifdef WIN32
+ getsockname(ctx->sock, (struct sockaddr *) &addr, &nlen);
+ ctx->port = ntohs(addr.sin_port);
+#endif
+
ctx->running = true;
+
memcpy(ctx->mac, mac, 6);
+ snprintf(id, 64, "%02X%02X%02X%02X%02X%02X@%s", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], name);
+
#ifdef WIN32
+ // seems that Windows snprintf does not add NULL char if actual size > max
+ id[63] = '\0';
+ ctx->svc = mdnsd_register_svc(ctx->svr, id, "_raop._tcp.local", ctx->port, NULL, (const char**) txt);
+ pthread_create(&ctx->thread, NULL, &rtsp_thread, ctx);
+
#else
+ LOG_INFO("starting mDNS with %s", id);
+ ESP_ERROR_CHECK( mdns_service_add(id, "_raop", "_tcp", ctx->port, txt, sizeof(txt) / sizeof(mdns_txt_item_t)) );
+
+ ctx->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
+ ctx->thread = xTaskCreateStatic( (TaskFunction_t) rtsp_thread, "RTSP_thread", RTSP_STACK_SIZE, ctx, ESP_TASK_PRIO_MIN + 1, ctx->xStack, ctx->xTaskBuffer);
+#endif
+
+ return ctx;
+}
+
+/*----------------------------------------------------------------------------*/
+void raop_abort(struct raop_ctx_s *ctx) {
+ LOG_INFO("[%p]: aborting RTSP session at next select() wakeup", ctx);
+ ctx->abort = true;
+}
+
+/*----------------------------------------------------------------------------*/
+void raop_delete(struct raop_ctx_s *ctx) {
+#ifdef WIN32
+ int sock;
+ struct sockaddr addr;
+ socklen_t nlen = sizeof(struct sockaddr);
+#endif
+
+ if (!ctx) return;
+
+#ifdef WIN32
+ ctx->running = false;
+
+ // wake-up thread by connecting socket, needed for freeBSD
+ sock = socket(AF_INET, SOCK_STREAM, 0);
+ getsockname(ctx->sock, (struct sockaddr *) &addr, &nlen);
+ connect(sock, (struct sockaddr*) &addr, sizeof(addr));
+ closesocket(sock);
+
+ pthread_join(ctx->thread, NULL);
+
+ rtp_end(ctx->rtp);
+
+ shutdown(ctx->sock, SD_BOTH);
+ closesocket(ctx->sock);
+
+ // terminate search, but do not reclaim memory of pthread if never launched
+ if (ctx->active_remote.handle) {
+ close_mDNS(ctx->active_remote.handle);
+ pthread_join(ctx->search_thread, NULL);
+ }
+
+ // stop broadcasting devices
+ mdns_service_remove(ctx->svr, ctx->svc);
+ mdnsd_stop(ctx->svr);
+#else
+ // first stop the search task if any
+ if (ctx->active_remote.running) {
+ ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
+ ctx->active_remote.running = false;
+
+ vTaskResume(ctx->active_remote.thread);
+ ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
+ vTaskDelete(ctx->active_remote.thread);
+
+ heap_caps_free(ctx->active_remote.xTaskBuffer);
+ }
+
+ // then the RTSP task
+ ctx->joiner = xTaskGetCurrentTaskHandle();
+ ctx->running = false;
+
+ ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
+ vTaskDelete(ctx->thread);
+ heap_caps_free(ctx->xTaskBuffer);
+
+ rtp_end(ctx->rtp);
+
+ shutdown(ctx->sock, SHUT_RDWR);
+ closesocket(ctx->sock);
+
+ mdns_service_remove("_raop", "_tcp");
+#endif
+
+ NFREE(ctx->rtsp.aeskey);
+ NFREE(ctx->rtsp.aesiv);
+ NFREE(ctx->rtsp.fmtp);
+
+ free(ctx);
+}
+
+/*----------------------------------------------------------------------------*/
+bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
+ struct sockaddr_in addr;
+ int sock;
+ char *command = NULL;
+
+ // first notify the remote controller (if any)
+ switch(event) {
+ case RAOP_REW:
+ command = strdup("beginrew");
+ break;
+ case RAOP_FWD:
+ command = strdup("beginff");
+ break;
+ case RAOP_PREV:
+ command = strdup("previtem");
+ break;
+ case RAOP_NEXT:
+ command = strdup("nextitem");
+ break;
+ case RAOP_TOGGLE:
+ command = strdup("playpause");
+ break;
+ case RAOP_PAUSE:
+ command = strdup("pause");
+ break;
+ case RAOP_PLAY:
+ command = strdup("play");
+ break;
+ case RAOP_RESUME:
+ command = strdup("playresume");
+ break;
+ case RAOP_STOP:
+ command = strdup("stop");
+ break;
+ case RAOP_VOLUME_UP:
+ command = strdup("volumeup");
+ break;
+ case RAOP_VOLUME_DOWN:
+ command = strdup("volumedown");
+ break;
+ case RAOP_VOLUME: {
+ float Volume = *((float*) param);
+ Volume = Volume ? (Volume - 1) * 30 : -144;
+ asprintf(&command,"setproperty?dmcp.device-volume=%0.4lf", Volume);
+ break;
+ }
+ default:
+ break;
+ }
+
+ // no command to send to remote or no remote found yet
+ if (!command || !ctx->active_remote.port) {
+ NFREE(command);
+ return false;
+ }
+
+ sock = socket(AF_INET, SOCK_STREAM, 0);
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = S_ADDR(ctx->active_remote.host);
+ addr.sin_port = htons(ctx->active_remote.port);
+
+ if (!connect(sock, (struct sockaddr*) &addr, sizeof(addr))) {
+ char *method, *buf, resp[512] = "";
+ int len;
+ key_data_t headers[4] = { {NULL, NULL} };
+
+ asprintf(&method, "GET /ctrl-int/1/%s HTTP/1.0", command);
+ kd_add(headers, "Active-Remote", ctx->active_remote.id);
+ kd_add(headers, "Connection", "close");
+
+ buf = http_send(sock, method, headers);
+ len = recv(sock, resp, 512, 0);
+ if (len > 0) resp[len-1] = '\0';
+ LOG_INFO("[%p]: sending airplay remote\n%s<== received ==>\n%s", ctx, buf, resp);
+
+ NFREE(method);
+ NFREE(buf);
+ kd_free(headers);
+ }
+
+ free(command);
+ closesocket(sock);
+
+ return true;
+}
+
+/*----------------------------------------------------------------------------*/
+static void *rtsp_thread(void *arg) {
+ raop_ctx_t *ctx = (raop_ctx_t*) arg;
+ int sock = -1;
+
+ while (ctx->running) {
+ fd_set rfds;
+ struct timeval timeout = {0, 100*1000};
+ int n;
+ bool res = false;
+
+ if (sock == -1) {
+ struct sockaddr_in peer;
+ socklen_t addrlen = sizeof(struct sockaddr_in);
+
+ sock = accept(ctx->sock, (struct sockaddr*) &peer, &addrlen);
+ ctx->peer.s_addr = peer.sin_addr.s_addr;
+ ctx->abort = false;
+
+ if (sock != -1 && ctx->running) {
+ LOG_INFO("got RTSP connection %u", sock);
+ } else continue;
+ }
+
+ FD_ZERO(&rfds);
+ FD_SET(sock, &rfds);
+
+ n = select(sock + 1, &rfds, NULL, NULL, &timeout);
+
+ if (!n && !ctx->abort) continue;
+
+ if (n > 0) res = handle_rtsp(ctx, sock);
+
+ if (n < 0 || !res || ctx->abort) {
+ abort_rtsp(ctx);
+ closesocket(sock);
+ LOG_INFO("RTSP close %u", sock);
+ sock = -1;
+ }
+ }
+
+ if (sock != -1) closesocket(sock);
+
+#ifndef WIN32
+ xTaskNotifyGive(ctx->joiner);
+ vTaskSuspend(NULL);
+#endif
+
+ return NULL;
+}
+
+
+/*----------------------------------------------------------------------------*/
+static bool handle_rtsp(raop_ctx_t *ctx, int sock)
+{
+ char *buf = NULL, *body = NULL, method[16] = "";
+ key_data_t headers[16], resp[8] = { {NULL, NULL} };
+ int len;
+ bool success = true;
+
+ if (!http_parse(sock, method, headers, &body, &len)) {
+ NFREE(body);
+ kd_free(headers);
+ return false;
+ }
+
+ if (strcmp(method, "OPTIONS")) {
+ LOG_INFO("[%p]: received %s", ctx, method);
+ }
+
+ if ((buf = kd_lookup(headers, "Apple-Challenge")) != NULL) {
+ int n;
+ char *buf_pad, *p, *data_b64 = NULL, data[32];
+
+ LOG_INFO("[%p]: challenge %s", ctx, buf);
+
+ // need to pad the base64 string as apple device don't
+ base64_pad(buf, &buf_pad);
+
+ p = data + min(base64_decode(buf_pad, data), 32-10);
+ p = (char*) memcpy(p, &S_ADDR(ctx->host), 4) + 4;
+ p = (char*) memcpy(p, ctx->mac, 6) + 6;
+ memset(p, 0, 32 - (p - data));
+ p = rsa_apply((unsigned char*) data, 32, &n, RSA_MODE_AUTH);
+ n = base64_encode(p, n, &data_b64);
+
+ // remove padding as well (seems to be optional now)
+ for (n = strlen(data_b64) - 1; n > 0 && data_b64[n] == '='; data_b64[n--] = '\0');
+
+ kd_add(resp, "Apple-Response", data_b64);
+
+ NFREE(p);
+ NFREE(buf_pad);
+ NFREE(data_b64);
+ }
+
+ if (!strcmp(method, "OPTIONS")) {
+
+ kd_add(resp, "Public", "ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER");
+
+ } else if (!strcmp(method, "ANNOUNCE")) {
+ char *padded, *p;
+
+ NFREE(ctx->rtsp.aeskey);
+ NFREE(ctx->rtsp.aesiv);
+ NFREE(ctx->rtsp.fmtp);
+
+ if ((p = strcasestr(body, "rsaaeskey")) != NULL) {
+ unsigned char *aeskey;
+ int len, outlen;
+
+ p = strextract(p, ":", "\r\n");
+ base64_pad(p, &padded);
+ aeskey = malloc(strlen(padded));
+ len = base64_decode(padded, aeskey);
+ ctx->rtsp.aeskey = rsa_apply(aeskey, len, &outlen, RSA_MODE_KEY);
+
+ NFREE(p);
+ NFREE(aeskey);
+ NFREE(padded);
+ }
+
+ if ((p = strcasestr(body, "aesiv")) != NULL) {
+ p = strextract(p, ":", "\r\n");
+ base64_pad(p, &padded);
+ ctx->rtsp.aesiv = malloc(strlen(padded));
+ base64_decode(padded, ctx->rtsp.aesiv);
+
+ NFREE(p);
+ NFREE(padded);
+ }
+
+ if ((p = strcasestr(body, "fmtp")) != NULL) {
+ p = strextract(p, ":", "\r\n");
+ ctx->rtsp.fmtp = strdup(p);
+ NFREE(p);
+ }
+
+ // on announce, search remote
+ if ((buf = kd_lookup(headers, "DACP-ID")) != NULL) strcpy(ctx->active_remote.DACPid, buf);
+ if ((buf = kd_lookup(headers, "Active-Remote")) != NULL) strcpy(ctx->active_remote.id, buf);
+
+#ifdef WIN32
+ ctx->active_remote.handle = init_mDNS(false, ctx->host);
+ pthread_create(&ctx->search_thread, NULL, &search_remote, ctx);
+#else
+ ctx->active_remote.running = true;
+ ctx->active_remote.destroy_mutex = xSemaphoreCreateMutex();
+ ctx->active_remote.xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
+ ctx->active_remote.thread = xTaskCreateStatic( (TaskFunction_t) search_remote, "search_remote", SEARCH_STACK_SIZE, ctx, ESP_TASK_PRIO_MIN + 1, ctx->active_remote.xStack, ctx->active_remote.xTaskBuffer);
+#endif
+
+ } else if (!strcmp(method, "SETUP") && ((buf = kd_lookup(headers, "Transport")) != NULL)) {
+ char *p;
+ rtp_resp_t rtp = { 0 };
+ short unsigned tport = 0, cport = 0;
+
+ // we are about to stream, do something if needed
+ success = ctx->cmd_cb(RAOP_SETUP);
+
+ if ((p = strcasestr(buf, "timing_port")) != NULL) sscanf(p, "%*[^=]=%hu", &tport);
+ if ((p = strcasestr(buf, "control_port")) != NULL) sscanf(p, "%*[^=]=%hu", &cport);
+
+ rtp = rtp_init(ctx->peer, ctx->latency, ctx->rtsp.aeskey, ctx->rtsp.aesiv,
+ ctx->rtsp.fmtp, cport, tport, ctx->cmd_cb, ctx->data_cb);
+
+ ctx->rtp = rtp.ctx;
+
+ if ( (cport * tport * rtp.cport * rtp.tport * rtp.aport) != 0 && rtp.ctx) {
+ char *transport;
+ asprintf(&transport, "RTP/AVP/UDP;unicast;mode=record;control_port=%u;timing_port=%u;server_port=%u", rtp.cport, rtp.tport, rtp.aport);
+ LOG_DEBUG("[%p]: audio=(%hu:%hu), timing=(%hu:%hu), control=(%hu:%hu)", ctx, 0, rtp.aport, tport, rtp.tport, cport, rtp.cport);
+ kd_add(resp, "Transport", transport);
+ kd_add(resp, "Session", "DEADBEEF");
+ free(transport);
+ } else {
+ success = false;
+ LOG_INFO("[%p]: cannot start session, missing ports", ctx);
+ }
+
+ } else if (!strcmp(method, "RECORD")) {
+ unsigned short seqno = 0;
+ unsigned rtptime = 0;
+ char *p;
+
+ if (ctx->latency) {
+ char latency[6];
+ snprintf(latency, 6, "%u", ctx->latency);
+ kd_add(resp, "Audio-Latency", latency);
+ }
+
+ buf = kd_lookup(headers, "RTP-Info");
+ if (buf && (p = strcasestr(buf, "seq")) != NULL) sscanf(p, "%*[^=]=%hu", &seqno);
+ if (buf && (p = strcasestr(buf, "rtptime")) != NULL) sscanf(p, "%*[^=]=%u", &rtptime);
+
+ if (ctx->rtp) rtp_record(ctx->rtp, seqno, rtptime);
+
+ success = ctx->cmd_cb(RAOP_STREAM);
+
+ } else if (!strcmp(method, "FLUSH")) {
+ unsigned short seqno = 0;
+ unsigned rtptime = 0;
+ char *p;
+
+ buf = kd_lookup(headers, "RTP-Info");
+ if ((p = strcasestr(buf, "seq")) != NULL) sscanf(p, "%*[^=]=%hu", &seqno);
+ if ((p = strcasestr(buf, "rtptime")) != NULL) sscanf(p, "%*[^=]=%u", &rtptime);
+
+ // only send FLUSH if useful (discards frames above buffer head and top)
+ if (ctx->rtp && rtp_flush(ctx->rtp, seqno, rtptime))
+ success = ctx->cmd_cb(RAOP_FLUSH);
+
+ } else if (!strcmp(method, "TEARDOWN")) {
+
+ rtp_end(ctx->rtp);
+
+ ctx->rtp = NULL;
+
+ // need to make sure no search is on-going and reclaim pthread memory
+#ifdef WIN32
+ if (ctx->active_remote.handle) close_mDNS(ctx->active_remote.handle);
+ pthread_join(ctx->search_thread, NULL);
+#else
+ ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
+ ctx->active_remote.running = false;
+
+ xSemaphoreTake(ctx->active_remote.destroy_mutex, portMAX_DELAY);
+ vTaskDelete(ctx->active_remote.thread);
+ vSemaphoreDelete(ctx->active_remote.thread);
+
+ heap_caps_free(ctx->active_remote.xTaskBuffer);
+
+ LOG_INFO("[%p]: mDNS search task terminated", ctx);
+#endif
+
+ memset(&ctx->active_remote, 0, sizeof(ctx->active_remote));
+ NFREE(ctx->rtsp.aeskey);
+ NFREE(ctx->rtsp.aesiv);
+ NFREE(ctx->rtsp.fmtp);
+
+ success = ctx->cmd_cb(RAOP_STOP);
+
+ } else if (!strcmp(method, "SET_PARAMETER")) {
+ char *p;
+
+ if (body && (p = strcasestr(body, "volume")) != NULL) {
+ float volume;
+
+ sscanf(p, "%*[^:]:%f", &volume);
+ LOG_INFO("[%p]: SET PARAMETER volume %f", ctx, volume);
+ volume = (volume == -144.0) ? 0 : (1 + volume / 30);
+ success = ctx->cmd_cb(RAOP_VOLUME, volume);
+ } else if (body && (p = strcasestr(body, "progress")) != NULL) {
+ int start, current, stop = 0;
+
+ // we want ms, not s
+ sscanf(p, "%*[^:]:%u/%u/%u", &start, ¤t, &stop);
+ current = ((current - start) / 44100) * 1000;
+ if (stop) stop = ((stop - start) / 44100) * 1000;
+ else stop = -1;
+ LOG_INFO("[%p]: SET PARAMETER progress %u/%u %s", ctx, current, stop, p);
+ success = ctx->cmd_cb(RAOP_PROGRESS, current, stop);
+ }
+
+ if (body && ((p = kd_lookup(headers, "Content-Type")) != NULL) && !strcasecmp(p, "application/x-dmap-tagged")) {
+ struct metadata_s metadata;
+ dmap_settings settings = {
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, on_dmap_string, NULL,
+ NULL
+ };
+
+ LOG_INFO("[%p]: received metadata");
+ settings.ctx = &metadata;
+ memset(&metadata, 0, sizeof(struct metadata_s));
+ if (!dmap_parse(&settings, body, len)) {
+ LOG_INFO("[%p]: received metadata\n\tartist: %s\n\talbum: %s\n\ttitle: %s",
+ ctx, metadata.artist, metadata.album, metadata.title);
+ success = ctx->cmd_cb(RAOP_METADATA, metadata.artist, metadata.album, metadata.title);
+ free_metadata(&metadata);
+ }
+ }
+ }
+
+ // don't need to free "buf" because kd_lookup return a pointer, not a strdup
+ kd_add(resp, "Audio-Jack-Status", "connected; type=analog");
+ kd_add(resp, "CSeq", kd_lookup(headers, "CSeq"));
+
+ if (success) {
+ buf = http_send(sock, "RTSP/1.0 200 OK", resp);
+ } else {
+ buf = http_send(sock, "RTSP/1.0 503 ERROR", NULL);
+ closesocket(sock);
+ }
+
+ if (strcmp(method, "OPTIONS")) {
+ LOG_INFO("[%p]: responding:\n%s", ctx, buf ? buf : "");
+ }
+
+ NFREE(body);
+ NFREE(buf);
+ kd_free(resp);
+ kd_free(headers);
+
+ return true;
+}
+
+/*----------------------------------------------------------------------------*/
+void abort_rtsp(raop_ctx_t *ctx) {
+ // first stop RTP process
+ if (ctx->rtp) {
+ rtp_end(ctx->rtp);
+ ctx->rtp = NULL;
+ LOG_INFO("[%p]: RTP thread aborted", ctx);
+ }
+
+ if (ctx->active_remote.running) {
+ // need to make sure no search is on-going and reclaim task memory
+ ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
+ ctx->active_remote.running = false;
+
+ xSemaphoreTake(ctx->active_remote.destroy_mutex, portMAX_DELAY);
+ vTaskDelete(ctx->active_remote.thread);
+ vSemaphoreDelete(ctx->active_remote.thread);
+
+ heap_caps_free(ctx->active_remote.xTaskBuffer);
+ memset(&ctx->active_remote, 0, sizeof(ctx->active_remote));
+
+ LOG_INFO("[%p]: Remote search thread aborted", ctx);
+ }
+
+ NFREE(ctx->rtsp.aeskey);
+ NFREE(ctx->rtsp.aesiv);
+ NFREE(ctx->rtsp.fmtp);
+}
+
+/*----------------------------------------------------------------------------*/
+#ifdef WIN32
+bool search_remote_cb(mDNSservice_t *slist, void *cookie, bool *stop) {
+ mDNSservice_t *s;
+ raop_ctx_t *ctx = (raop_ctx_t*) cookie;
+
+ // see if we have found an active remote for our ID
+ for (s = slist; s; s = s->next) {
+ if (strcasestr(s->name, ctx->active_remote.DACPid)) {
+ ctx->active_remote.host = s->addr;
+ ctx->active_remote.port = s->port;
+ LOG_INFO("[%p]: found ActiveRemote for %s at %s:%u", ctx, ctx->active_remote.DACPid,
+ inet_ntoa(ctx->active_remote.host), ctx->active_remote.port);
+ *stop = true;
+ break;
+ }
+ }
+
+ // let caller clear list
+ return false;
+}
+
+
+/*----------------------------------------------------------------------------*/
+static void* search_remote(void *args) {
+ raop_ctx_t *ctx = (raop_ctx_t*) args;
+
+ query_mDNS(ctx->active_remote.handle, "_dacp._tcp.local", 0, 0, &search_remote_cb, (void*) ctx);
+
+ return NULL;
+}
+
+#else
+
+/*----------------------------------------------------------------------------*/
+static void* search_remote(void *args) {
+ raop_ctx_t *ctx = (raop_ctx_t*) args;
+ bool found = false;
+
+ LOG_INFO("starting remote search");
+
+ while (ctx->active_remote.running && !found) {
+ mdns_result_t *results = NULL;
+ mdns_result_t *r;
+ mdns_ip_addr_t *a;
+
+ if (mdns_query_ptr("_dacp", "_tcp", 3000, 32, &results)) {
+ LOG_ERROR("mDNS active remote query Failed");
+ continue;
+ }
+
+ for (r = results; r && !strcasestr(r->instance_name, ctx->active_remote.DACPid); r = r->next);
+ if (r) {
+ for (a = r->addr; a && a->addr.type != IPADDR_TYPE_V4; a = a->next);
+ if (a) {
+ found = true;
+ ctx->active_remote.host.s_addr = a->addr.u_addr.ip4.addr;
+ ctx->active_remote.port = r->port;
+ LOG_INFO("found remote %s %s:%hu", r->instance_name, inet_ntoa(ctx->active_remote.host), ctx->active_remote.port);
+ }
+ }
+
+ mdns_query_results_free(results);
+ }
+
+ // can't use xNotifyGive as it seems LWIP is using it as well
+ xSemaphoreGive(ctx->active_remote.destroy_mutex);
+ vTaskSuspend(NULL);
+
+ return NULL;
+ }
+#endif
+
+
/*----------------------------------------------------------------------------*/
+static char *rsa_apply(unsigned char *input, int inlen, int *outlen, int mode)
+{
+ static char super_secret_key[] =
+ "-----BEGIN RSA PRIVATE KEY-----\n"
+ "MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\n"
+ "wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\n"
+ "wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\n"
+ "/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\n"
+ "UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\n"
+ "BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\n"
+ "LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\n"
+ "NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\n"
+ "lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\n"
+ "aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\n"
+ "a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\n"
+ "oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\n"
+ "oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\n"
+ "k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\n"
+ "AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\n"
+ "cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\n"
+ "54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\n"
+ "17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\n"
+ "1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\n"
+ "LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\n"
+ "2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\n"
+ "-----END RSA PRIVATE KEY-----";
+#ifdef WIN32
+ unsigned char *out;
+ RSA *rsa;
+
+ BIO *bmem = BIO_new_mem_buf(super_secret_key, -1);
+ rsa = PEM_read_bio_RSAPrivateKey(bmem, NULL, NULL, NULL);
+ BIO_free(bmem);
+
+ out = malloc(RSA_size(rsa));
+ switch (mode) {
+ case RSA_MODE_AUTH:
+ *outlen = RSA_private_encrypt(inlen, input, out, rsa,
+ RSA_PKCS1_PADDING);
+ break;
+ case RSA_MODE_KEY:
+ *outlen = RSA_private_decrypt(inlen, input, out, rsa,
+ RSA_PKCS1_OAEP_PADDING);
+ break;
+ }
+
+ RSA_free(rsa);
+
+ return (char*) out;
+#else
+ mbedtls_pk_context pkctx;
+ mbedtls_rsa_context *trsa;
+ size_t olen;
+
+ /*
+ we should do entropy initialization & pass a rng function but this
+ consumes a ton of stack and there is no security concern here. Anyway,
+ mbedtls takes a lot of stack, unfortunately ...
+ */
+
+ mbedtls_pk_init(&pkctx);
+ mbedtls_pk_parse_key(&pkctx, (unsigned char *)super_secret_key,
+ sizeof(super_secret_key), NULL, 0);
+
+ uint8_t *outbuf = NULL;
+ trsa = mbedtls_pk_rsa(pkctx);
+
+ switch (mode) {
+ case RSA_MODE_AUTH:
+ mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_NONE);
+ outbuf = malloc(trsa->len);
+ mbedtls_rsa_pkcs1_encrypt(trsa, NULL, NULL, MBEDTLS_RSA_PRIVATE, inlen, input, outbuf);
+ *outlen = trsa->len;
+ break;
+ case RSA_MODE_KEY:
+ mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1);
+ outbuf = malloc(trsa->len);
+ mbedtls_rsa_pkcs1_decrypt(trsa, NULL, NULL, MBEDTLS_RSA_PRIVATE, &olen, input, outbuf, trsa->len);
+ *outlen = olen;
+ break;
+ }
+
+ mbedtls_pk_free(&pkctx);
+
+ return (char*) outbuf;
+#endif
+}
+
+#define DECODE_ERROR 0xffffffff
+
+static char base64_chars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/*----------------------------------------------------------------------------*/
+static int base64_pad(char *src, char **padded)
+{
+ int n;
+
+ n = strlen(src) + strlen(src) % 4;
+ *padded = malloc(n + 1);
+ memset(*padded, '=', n);
+ memcpy(*padded, src, strlen(src));
+ (*padded)[n] = '\0';
+
+ return strlen(*padded);
+}
+
+/*----------------------------------------------------------------------------*/
+static int pos(char c)
+{
+ char *p;
+ for (p = base64_chars; *p; p++)
+ if (*p == c)
+ return p - base64_chars;
+ return -1;
+}
+
+/*----------------------------------------------------------------------------*/
+static int base64_encode(const void *data, int size, char **str)
+{
+ char *s, *p;
+ int i;
+ int c;
+ const unsigned char *q;
+
+ p = s = (char *) malloc(size * 4 / 3 + 4);
+ if (p == NULL) return -1;
+ q = (const unsigned char *) data;
+ i = 0;
+ for (i = 0; i < size;) {
+ c = q[i++];
+ c *= 256;
+ if (i < size) c += q[i];
+ i++;
+ c *= 256;
+ if (i < size) c += q[i];
+ i++;
+ p[0] = base64_chars[(c & 0x00fc0000) >> 18];
+ p[1] = base64_chars[(c & 0x0003f000) >> 12];
+ p[2] = base64_chars[(c & 0x00000fc0) >> 6];
+ p[3] = base64_chars[(c & 0x0000003f) >> 0];
+ if (i > size) p[3] = '=';
+ if (i > size + 1) p[2] = '=';
+ p += 4;
+ }
+ *p = 0;
+ *str = s;
+ return strlen(s);
+}
+
+/*----------------------------------------------------------------------------*/
+static unsigned int token_decode(const char *token)
+{
+ int i;
+ unsigned int val = 0;
+ int marker = 0;
+ if (strlen(token) < 4)
+ return DECODE_ERROR;
+ for (i = 0; i < 4; i++) {
+ val *= 64;
+ if (token[i] == '=')
+ marker++;
+ else if (marker > 0)
+ return DECODE_ERROR;
+ else
+ val += pos(token[i]);
+ }
+ if (marker > 2){
+ return DECODE_ERROR;
+ }
+ return (marker << 24) | val;
+}
+
+/*----------------------------------------------------------------------------*/
+static int base64_decode(const char *str, void *data)
+{
+ const char *p;
+ unsigned char *q;
+
+ q = data;
+ for (p = str; *p && (*p == '=' || strchr(base64_chars, *p)); p += 4) {
+ unsigned int val = token_decode(p);
+ unsigned int marker = (val >> 24) & 0xff;
+ if (val == DECODE_ERROR)
+ return -1;
+ *q++ = (val >> 16) & 0xff;
+ if (marker < 2)
+ *q++ = (val >> 8) & 0xff;
+ if (marker < 1)
+ *q++ = val & 0xff;
+ }
+ return q - (unsigned char *) data;
+}
+
+/*----------------------------------------------------------------------------*/
+static void on_dmap_string(void *ctx, const char *code, const char *name, const char *buf, size_t len) {
+ struct metadata_s *metadata = (struct metadata_s *) ctx;
+
+ if (!strcasecmp(code, "asar")) metadata->artist = strndup(buf, len);
+ else if (!strcasecmp(code, "asal")) metadata->album = strndup(buf, len);
+ else if (!strcasecmp(code, "minm")) metadata->title = strndup(buf, len);
+}
-
/*----------------------------------------------------------------------------*/
-static char *rsa_apply(unsigned char *input, int inlen, int *outlen, int mode)
-{
- static char super_secret_key[] =
- "-----BEGIN RSA PRIVATE KEY-----\n"
- "MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\n"
- "wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\n"
- "wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\n"
- "/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\n"
- "UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\n"
- "BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\n"
- "LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\n"
- "NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\n"
- "lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\n"
- "aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\n"
- "a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\n"
- "oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\n"
- "oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\n"
- "k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\n"
- "AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\n"
- "cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\n"
- "54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\n"
- "17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\n"
- "1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\n"
- "LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\n"
- "2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\n"
- "-----END RSA PRIVATE KEY-----";
-#ifdef WIN32
- unsigned char *out;
- RSA *rsa;
-
- BIO *bmem = BIO_new_mem_buf(super_secret_key, -1);
- rsa = PEM_read_bio_RSAPrivateKey(bmem, NULL, NULL, NULL);
- BIO_free(bmem);
-
- out = malloc(RSA_size(rsa));
- switch (mode) {
- case RSA_MODE_AUTH:
- *outlen = RSA_private_encrypt(inlen, input, out, rsa,
- RSA_PKCS1_PADDING);
- break;
- case RSA_MODE_KEY:
- *outlen = RSA_private_decrypt(inlen, input, out, rsa,
- RSA_PKCS1_OAEP_PADDING);
- break;
- }
-
- RSA_free(rsa);
-
- return (char*) out;
-#else
- mbedtls_pk_context pkctx;
- mbedtls_rsa_context *trsa;
- size_t olen;
-
- /*
- we should do entropy initialization & pass a rng function but this
- consumes a ton of stack and there is no security concern here. Anyway,
- mbedtls takes a lot of stack, unfortunately ...
- */
-
- mbedtls_pk_init(&pkctx);
- mbedtls_pk_parse_key(&pkctx, (unsigned char *)super_secret_key,
- sizeof(super_secret_key), NULL, 0);
-
- uint8_t *outbuf = NULL;
- trsa = mbedtls_pk_rsa(pkctx);
-
- switch (mode) {
- case RSA_MODE_AUTH:
- mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_NONE);
- outbuf = malloc(trsa->len);
- mbedtls_rsa_pkcs1_encrypt(trsa, NULL, NULL, MBEDTLS_RSA_PRIVATE, inlen, input, outbuf);
- *outlen = trsa->len;
- break;
- case RSA_MODE_KEY:
- mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1);
- outbuf = malloc(trsa->len);
- mbedtls_rsa_pkcs1_decrypt(trsa, NULL, NULL, MBEDTLS_RSA_PRIVATE, &olen, input, outbuf, trsa->len);
- *outlen = olen;
- break;
- }
-
- mbedtls_pk_free(&pkctx);
-
- return (char*) outbuf;
-#endif
-}
-
-#define DECODE_ERROR 0xffffffff
-
-static char base64_chars[] =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-
-/*----------------------------------------------------------------------------*/
-static int base64_pad(char *src, char **padded)
-{
- int n;
-
- n = strlen(src) + strlen(src) % 4;
- *padded = malloc(n + 1);
- memset(*padded, '=', n);
- memcpy(*padded, src, strlen(src));
- (*padded)[n] = '\0';
-
- return strlen(*padded);
-}
-
-/*----------------------------------------------------------------------------*/
-static int pos(char c)
-{
- char *p;
- for (p = base64_chars; *p; p++)
- if (*p == c)
- return p - base64_chars;
- return -1;
-}
-
-/*----------------------------------------------------------------------------*/
-static int base64_encode(const void *data, int size, char **str)
-{
- char *s, *p;
- int i;
- int c;
- const unsigned char *q;
-
- p = s = (char *) malloc(size * 4 / 3 + 4);
- if (p == NULL) return -1;
- q = (const unsigned char *) data;
- i = 0;
- for (i = 0; i < size;) {
- c = q[i++];
- c *= 256;
- if (i < size) c += q[i];
- i++;
- c *= 256;
- if (i < size) c += q[i];
- i++;
- p[0] = base64_chars[(c & 0x00fc0000) >> 18];
- p[1] = base64_chars[(c & 0x0003f000) >> 12];
- p[2] = base64_chars[(c & 0x00000fc0) >> 6];
- p[3] = base64_chars[(c & 0x0000003f) >> 0];
- if (i > size) p[3] = '=';
- if (i > size + 1) p[2] = '=';
- p += 4;
- }
- *p = 0;
- *str = s;
- return strlen(s);
-}
-
-/*----------------------------------------------------------------------------*/
-static unsigned int token_decode(const char *token)
-{
- int i;
- unsigned int val = 0;
- int marker = 0;
- if (strlen(token) < 4)
- return DECODE_ERROR;
- for (i = 0; i < 4; i++) {
- val *= 64;
- if (token[i] == '=')
- marker++;
- else if (marker > 0)
- return DECODE_ERROR;
- else
- val += pos(token[i]);
- }
- if (marker > 2)
- return DECODE_ERROR;
- return (marker << 24) | val;
-}
-
-/*----------------------------------------------------------------------------*/
-static int base64_decode(const char *str, void *data)
-{
- const char *p;
- unsigned char *q;
-
- q = data;
- for (p = str; *p && (*p == '=' || strchr(base64_chars, *p)); p += 4) {
- unsigned int val = token_decode(p);
- unsigned int marker = (val >> 24) & 0xff;
- if (val == DECODE_ERROR)
- return -1;
- *q++ = (val >> 16) & 0xff;
- if (marker < 2)
- *q++ = (val >> 8) & 0xff;
- if (marker < 1)
- *q++ = val & 0xff;
- }
- return q - (unsigned char *) data;
-}
-
-/*----------------------------------------------------------------------------*/
-static void on_dmap_string(void *ctx, const char *code, const char *name, const char *buf, size_t len) {
- struct metadata_s *metadata = (struct metadata_s *) ctx;
-
- if (!strcasecmp(code, "asar")) metadata->artist = strndup(buf, len);
- else if (!strcasecmp(code, "asal")) metadata->album = strndup(buf, len);
- else if (!strcasecmp(code, "minm")) metadata->title = strndup(buf, len);
-}
-
diff --git a/components/raop/rtp.c b/components/raop/rtp.c
index beccdda9..08bd1ff7 100644
--- a/components/raop/rtp.c
+++ b/components/raop/rtp.c
@@ -1,786 +1,787 @@
-
-/*
- * HairTunes - RAOP packet handler and slave-clocked replay engine
- * Copyright (c) James Laird 2011
- * All rights reserved.
- *
- * Modularisation: philippe_44@outlook.com, 2019
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use,
- * copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- */
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "platform.h"
-#include "rtp.h"
-#include "raop_sink.h"
-#include "log_util.h"
-#include "util.h"
-
-#ifdef WIN32
-#include
-#include "alac_wrapper.h"
-#else
-#include "esp_pthread.h"
-#include "esp_system.h"
-#include
-#include
-#include "alac_wrapper.h"
-#endif
-
-#define NTP2MS(ntp) ((((ntp) >> 10) * 1000L) >> 22)
-#define MS2NTP(ms) (((((u64_t) (ms)) << 22) / 1000) << 10)
-#define NTP2TS(ntp, rate) ((((ntp) >> 16) * (rate)) >> 16)
-#define TS2NTP(ts, rate) (((((u64_t) (ts)) << 16) / (rate)) << 16)
-#define MS2TS(ms, rate) ((((u64_t) (ms)) * (rate)) / 1000)
-#define TS2MS(ts, rate) NTP2MS(TS2NTP(ts,rate))
-
-
extern log_level raop_loglevel;
-
static log_level *loglevel = &raop_loglevel;
-
-//#define __RTP_STORE
-
-// default buffer size
-#define BUFFER_FRAMES ( (150 * RAOP_SAMPLE_RATE * 2) / (352 * 100) )
-#define MAX_PACKET 1408
-#define MIN_LATENCY 11025
-#define MAX_LATENCY ( (120 * RAOP_SAMPLE_RATE * 2) / 100 )
-
-#define RTP_STACK_SIZE (4*1024)
-
-#define RTP_SYNC (0x01)
-#define NTP_SYNC (0x02)
-
-#define RESEND_TO 200
-
-enum { DATA = 0, CONTROL, TIMING };
-
-static const u8_t silence_frame[MAX_PACKET] = { 0 };
-
-typedef u16_t seq_t;
-typedef struct audio_buffer_entry { // decoded audio packets
- int ready;
- u32_t rtptime, last_resend;
- s16_t *data;
- int len;
-} abuf_t;
-
-typedef struct rtp_s {
-#ifdef __RTP_STORE
- FILE *rtpIN, *rtpOUT;
-#endif
- bool running;
- unsigned char aesiv[16];
-#ifdef WIN32
- AES_KEY aes;
-#else
- mbedtls_aes_context aes;
-#endif
- bool decrypt;
- u8_t *decrypt_buf;
- u32_t frame_size, frame_duration;
- u32_t in_frames, out_frames;
- struct in_addr host;
- struct sockaddr_in rtp_host;
- struct {
- unsigned short rport, lport;
- int sock;
- } rtp_sockets[3]; // data, control, timing
- struct timing_s {
- u64_t local, remote;
- } timing;
- struct {
- u32_t rtp, time;
- u8_t status;
- } synchro;
- struct {
- u32_t time;
- seq_t seqno;
- u32_t rtptime;
- } record;
- int latency; // rtp hold depth in samples
- u32_t resent_req, resent_rec; // total resent + recovered frames
- u32_t silent_frames; // total silence frames
- u32_t discarded;
- abuf_t audio_buffer[BUFFER_FRAMES];
- seq_t ab_read, ab_write;
- pthread_mutex_t ab_mutex;
-#ifdef WIN32
- pthread_t thread;
-#else
- TaskHandle_t thread, joiner;
- StaticTask_t *xTaskBuffer;
- StackType_t xStack[RTP_STACK_SIZE] __attribute__ ((aligned (4)));
-#endif
-
- struct alac_codec_s *alac_codec;
- int flush_seqno;
- bool playing;
- raop_data_cb_t data_cb;
- raop_cmd_cb_t cmd_cb;
-} rtp_t;
-
-
-#define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES)
-static void buffer_alloc(abuf_t *audio_buffer, int size);
-static void buffer_release(abuf_t *audio_buffer);
-static void buffer_reset(abuf_t *audio_buffer);
-static void buffer_push_packet(rtp_t *ctx);
-static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last);
-static bool rtp_request_timing(rtp_t *ctx);
-static void* rtp_thread_func(void *arg);
-static int seq_order(seq_t a, seq_t b);
-
-/*---------------------------------------------------------------------------*/
-static struct alac_codec_s* alac_init(int fmtp[32]) {
- struct alac_codec_s *alac;
- unsigned sample_rate;
- unsigned char sample_size, channels;
- struct {
- uint32_t frameLength;
- uint8_t compatibleVersion;
- uint8_t bitDepth;
- uint8_t pb;
- uint8_t mb;
- uint8_t kb;
- uint8_t numChannels;
- uint16_t maxRun;
- uint32_t maxFrameBytes;
- uint32_t avgBitRate;
- uint32_t sampleRate;
- } config;
-
- config.frameLength = htonl(fmtp[1]);
- config.compatibleVersion = fmtp[2];
- config.bitDepth = fmtp[3];
- config.pb = fmtp[4];
- config.mb = fmtp[5];
- config.kb = fmtp[6];
- config.numChannels = fmtp[7];
- config.maxRun = htons(fmtp[8]);
- config.maxFrameBytes = htonl(fmtp[9]);
- config.avgBitRate = htonl(fmtp[10]);
- config.sampleRate = htonl(fmtp[11]);
-
- alac = alac_create_decoder(sizeof(config), (unsigned char*) &config, &sample_size, &sample_rate, &channels);
- if (!alac) {
- LOG_ERROR("cannot create alac codec", NULL);
- return NULL;
- }
-
- return alac;
-}
-
-/*---------------------------------------------------------------------------*/
-rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv, char *fmtpstr,
- short unsigned pCtrlPort, short unsigned pTimingPort,
- raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb)
-{
- int i = 0;
- char *arg;
- int fmtp[12];
- bool rc = true;
- rtp_t *ctx = calloc(1, sizeof(rtp_t));
- rtp_resp_t resp = { 0, 0, 0, NULL };
-
- if (!ctx) return resp;
-
- ctx->host = host;
- ctx->decrypt = false;
- ctx->cmd_cb = cmd_cb;
- ctx->data_cb = data_cb;
- ctx->rtp_host.sin_family = AF_INET;
- ctx->rtp_host.sin_addr.s_addr = INADDR_ANY;
- pthread_mutex_init(&ctx->ab_mutex, 0);
- ctx->flush_seqno = -1;
- ctx->latency = latency;
- ctx->ab_read = ctx->ab_write;
-
-#ifdef __RTP_STORE
- ctx->rtpIN = fopen("airplay.rtpin", "wb");
- ctx->rtpOUT = fopen("airplay.rtpout", "wb");
-#endif
-
- ctx->rtp_sockets[CONTROL].rport = pCtrlPort;
- ctx->rtp_sockets[TIMING].rport = pTimingPort;
-
- if (aesiv && aeskey) {
- memcpy(ctx->aesiv, aesiv, 16);
-#ifdef WIN32
- AES_set_decrypt_key((unsigned char*) aeskey, 128, &ctx->aes);
-#else
- memset(&ctx->aes, 0, sizeof(mbedtls_aes_context));
- mbedtls_aes_setkey_dec(&ctx->aes, (unsigned char*) aeskey, 128);
-#endif
- ctx->decrypt = true;
- ctx->decrypt_buf = malloc(MAX_PACKET);
- }
-
- memset(fmtp, 0, sizeof(fmtp));
- while ((arg = strsep(&fmtpstr, " \t")) != NULL) fmtp[i++] = atoi(arg);
-
- ctx->frame_size = fmtp[1];
- ctx->frame_duration = (ctx->frame_size * 1000) / RAOP_SAMPLE_RATE;
-
- // alac decoder
- ctx->alac_codec = alac_init(fmtp);
- rc &= ctx->alac_codec != NULL;
-
- buffer_alloc(ctx->audio_buffer, ctx->frame_size*4);
-
- // create rtp ports
- for (i = 0; i < 3; i++) {
- ctx->rtp_sockets[i].sock = bind_socket(&ctx->rtp_sockets[i].lport, SOCK_DGRAM);
- rc &= ctx->rtp_sockets[i].sock > 0;
- }
-
- // create http port and start listening
- resp.cport = ctx->rtp_sockets[CONTROL].lport;
- resp.tport = ctx->rtp_sockets[TIMING].lport;
- resp.aport = ctx->rtp_sockets[DATA].lport;
-
- if (rc) {
- ctx->running = true;
-#ifdef WIN32
- pthread_create(&ctx->thread, NULL, rtp_thread_func, (void *) ctx);
-#else
- // xTaskCreate((TaskFunction_t) rtp_thread_func, "RTP_thread", RTP_TASK_SIZE, ctx, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1 , &ctx->thread);
- ctx->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
- ctx->thread = xTaskCreateStatic( (TaskFunction_t) rtp_thread_func, "RTP_thread", RTP_STACK_SIZE, ctx,
- CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, ctx->xStack, ctx->xTaskBuffer );
-#endif
- } else {
- LOG_ERROR("[%p]: cannot start RTP", ctx);
- rtp_end(ctx);
- ctx = NULL;
- }
-
- resp.ctx = ctx;
-
- return resp;
-}
-
-/*---------------------------------------------------------------------------*/
-void rtp_end(rtp_t *ctx)
-{
- int i;
-
- if (!ctx) return;
-
- if (ctx->running) {
-#if !defined WIN32
- ctx->joiner = xTaskGetCurrentTaskHandle();
-#endif
- ctx->running = false;
-#ifdef WIN32
- pthread_join(ctx->thread, NULL);
-#else
- ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
- vTaskDelete(ctx->thread);
- heap_caps_free(ctx->xTaskBuffer);
-#endif
- }
-
- for (i = 0; i < 3; i++) closesocket(ctx->rtp_sockets[i].sock);
-
- if (ctx->alac_codec) alac_delete_decoder(ctx->alac_codec);
- if (ctx->decrypt_buf) free(ctx->decrypt_buf);
-
- pthread_mutex_destroy(&ctx->ab_mutex);
- buffer_release(ctx->audio_buffer);
-
- free(ctx);
-
-#ifdef __RTP_STORE
- fclose(ctx->rtpIN);
- fclose(ctx->rtpOUT);
-#endif
-}
-
-/*---------------------------------------------------------------------------*/
-bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime)
-{
- bool rc = true;
- u32_t now = gettime_ms();
-
- if (now < ctx->record.time + 250 || (ctx->record.seqno == seqno && ctx->record.rtptime == rtptime)) {
- rc = false;
- LOG_ERROR("[%p]: FLUSH ignored as same as RECORD (%hu - %u)", ctx, seqno, rtptime);
- } else {
- pthread_mutex_lock(&ctx->ab_mutex);
- buffer_reset(ctx->audio_buffer);
- ctx->playing = false;
- ctx->flush_seqno = seqno;
- pthread_mutex_unlock(&ctx->ab_mutex);
- }
-
- LOG_INFO("[%p]: flush %hu %u", ctx, seqno, rtptime);
-
- return rc;
-}
-
-/*---------------------------------------------------------------------------*/
-void rtp_record(rtp_t *ctx, unsigned short seqno, unsigned rtptime)
-{
- ctx->record.seqno = seqno;
- ctx->record.rtptime = rtptime;
- ctx->record.time = gettime_ms();
-
- LOG_INFO("[%p]: record %hu %u", ctx, seqno, rtptime);
-}
-
-/*---------------------------------------------------------------------------*/
-static void buffer_alloc(abuf_t *audio_buffer, int size) {
- int i;
- for (i = 0; i < BUFFER_FRAMES; i++) {
- audio_buffer[i].data = malloc(size);
- audio_buffer[i].ready = 0;
- }
-}
-
-/*---------------------------------------------------------------------------*/
-static void buffer_release(abuf_t *audio_buffer) {
- int i;
- for (i = 0; i < BUFFER_FRAMES; i++) {
- free(audio_buffer[i].data);
- }
-}
-
-/*---------------------------------------------------------------------------*/
-static void buffer_reset(abuf_t *audio_buffer) {
- int i;
- for (i = 0; i < BUFFER_FRAMES; i++) audio_buffer[i].ready = 0;
-}
-
-/*---------------------------------------------------------------------------*/
-// the sequence numbers will wrap pretty often.
-// this returns true if the second arg is after the first
-static int seq_order(seq_t a, seq_t b) {
- s16_t d = b - a;
- return d > 0;
-}
-
-/*---------------------------------------------------------------------------*/
-static void alac_decode(rtp_t *ctx, s16_t *dest, char *buf, int len, int *outsize) {
- unsigned char iv[16];
- int aeslen;
- assert(len<=MAX_PACKET);
-
- if (ctx->decrypt) {
- aeslen = len & ~0xf;
- memcpy(iv, ctx->aesiv, sizeof(iv));
-#ifdef WIN32
- AES_cbc_encrypt((unsigned char*)buf, ctx->decrypt_buf, aeslen, &ctx->aes, iv, AES_DECRYPT);
-#else
- mbedtls_aes_crypt_cbc(&ctx->aes, MBEDTLS_AES_DECRYPT, aeslen, iv, (unsigned char*) buf, ctx->decrypt_buf);
-#endif
- memcpy(ctx->decrypt_buf+aeslen, buf+aeslen, len-aeslen);
- alac_to_pcm(ctx->alac_codec, (unsigned char*) ctx->decrypt_buf, (unsigned char*) dest, 2, (unsigned int*) outsize);
- } else {
- alac_to_pcm(ctx->alac_codec, (unsigned char*) buf, (unsigned char*) dest, 2, (unsigned int*) outsize);
- }
-
- *outsize *= 4;
-}
-
-
-/*---------------------------------------------------------------------------*/
-static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool first, char *data, int len) {
- abuf_t *abuf = NULL;
- u32_t playtime;
-
- pthread_mutex_lock(&ctx->ab_mutex);
-
- if (!ctx->playing) {
- if ((ctx->flush_seqno == -1 || seq_order(ctx->flush_seqno, seqno)) &&
- (ctx->synchro.status & RTP_SYNC) && (ctx->synchro.status & NTP_SYNC)) {
- ctx->ab_write = seqno-1;
- ctx->ab_read = seqno;
- ctx->flush_seqno = -1;
- ctx->playing = true;
- ctx->resent_req = ctx->resent_rec = ctx->silent_frames = ctx->discarded = 0;
- playtime = ctx->synchro.time + (((s32_t)(rtptime - ctx->synchro.rtp)) * 1000) / RAOP_SAMPLE_RATE;
- ctx->cmd_cb(RAOP_PLAY, playtime);
- } else {
- pthread_mutex_unlock(&ctx->ab_mutex);
- return;
- }
- }
-
- if (seqno == (u16_t) (ctx->ab_write+1)) {
- // expected packet
- abuf = ctx->audio_buffer + BUFIDX(seqno);
- ctx->ab_write = seqno;
- LOG_SDEBUG("packet expected seqno:%hu rtptime:%u (W:%hu R:%hu)", seqno, rtptime, ctx->ab_write, ctx->ab_read);
-
- } else if (seq_order(ctx->ab_write, seqno)) {
- // newer than expected
- if (ctx->latency && seq_order(ctx->latency / ctx->frame_size, seqno - ctx->ab_write - 1)) {
- // only get rtp latency-1 frames back (last one is seqno)
- LOG_WARN("[%p] too many missing frames %hu seq: %hu, (W:%hu R:%hu)", ctx, seqno - ctx->ab_write - 1, seqno, ctx->ab_write, ctx->ab_read);
- ctx->ab_write = seqno - ctx->latency / ctx->frame_size;
- }
- if (ctx->latency && seq_order(ctx->latency / ctx->frame_size, seqno - ctx->ab_read)) {
- // if ab_read is lagging more than http latency, advance it
- LOG_WARN("[%p] on hold for too long %hu (W:%hu R:%hu)", ctx, seqno - ctx->ab_read + 1, ctx->ab_write, ctx->ab_read);
- ctx->ab_read = seqno - ctx->latency / ctx->frame_size + 1;
- }
- if (rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1)) {
- seq_t i;
- u32_t now = gettime_ms();
- for (i = ctx->ab_write + 1; seq_order(i, seqno); i++) {
- ctx->audio_buffer[BUFIDX(i)].rtptime = rtptime - (seqno-i)*ctx->frame_size;
- ctx->audio_buffer[BUFIDX(i)].last_resend = now;
- }
- }
- LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
- abuf = ctx->audio_buffer + BUFIDX(seqno);
- ctx->ab_write = seqno;
- } else if (seq_order(ctx->ab_read, seqno + 1)) {
- // recovered packet, not yet sent
- abuf = ctx->audio_buffer + BUFIDX(seqno);
- ctx->resent_rec++;
- LOG_DEBUG("[%p]: packet recovered seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
- } else {
- // too late
- LOG_DEBUG("[%p]: packet too late seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
- }
-
- if (ctx->in_frames++ > 1000) {
- LOG_INFO("[%p]: fill [level:%hu rec:%u] [W:%hu R:%hu]", ctx, ctx->ab_write - ctx->ab_read, ctx->resent_rec, ctx->ab_write, ctx->ab_read);
- ctx->in_frames = 0;
- }
-
- if (abuf) {
- alac_decode(ctx, abuf->data, data, len, &abuf->len);
- abuf->ready = 1;
- // this is the local rtptime when this frame is expected to play
- abuf->rtptime = rtptime;
- buffer_push_packet(ctx);
-
-#ifdef __RTP_STORE
- fwrite(data, len, 1, ctx->rtpIN);
- fwrite(abuf->data, abuf->len, 1, ctx->rtpOUT);
-#endif
- }
-
- pthread_mutex_unlock(&ctx->ab_mutex);
-}
-
-/*---------------------------------------------------------------------------*/
-// push as many frames as possible through callback
-static void buffer_push_packet(rtp_t *ctx) {
- abuf_t *curframe = NULL;
- u32_t now, playtime, hold = max((ctx->latency * 1000) / (8 * RAOP_SAMPLE_RATE), 100);
- int i;
-
- // not ready to play yet
- if (!ctx->playing || ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
-
- // maybe re-evaluate time in loop in case data callback blocks ...
- now = gettime_ms();
-
- // there is always at least one frame in the buffer
- do {
-
- curframe = ctx->audio_buffer + BUFIDX(ctx->ab_read);
- playtime = ctx->synchro.time + (((s32_t)(curframe->rtptime - ctx->synchro.rtp)) * 1000) / RAOP_SAMPLE_RATE;
-
- if (now > playtime) {
- LOG_DEBUG("[%p]: discarded frame now:%u missed by:%d (W:%hu R:%hu)", ctx, now, now - playtime, ctx->ab_write, ctx->ab_read);
- ctx->discarded++;
- curframe->ready = 0;
- } else if (curframe->ready) {
- ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime);
- curframe->ready = 0;
- } else if (playtime - now <= hold) {
- LOG_DEBUG("[%p]: created zero frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read);
- ctx->data_cb(silence_frame, ctx->frame_size * 4, playtime);
- ctx->silent_frames++;
- } else break;
-
- ctx->ab_read++;
- ctx->out_frames++;
-
- } while (seq_order(ctx->ab_read, ctx->ab_write));
-
- if (ctx->out_frames > 1000) {
- LOG_INFO("[%p]: drain [level:%hd head:%d ms] [W:%hu R:%hu] [req:%u sil:%u dis:%u]",
- ctx, ctx->ab_write - ctx->ab_read, playtime - now, ctx->ab_write, ctx->ab_read,
- ctx->resent_req, ctx->silent_frames, ctx->discarded);
- ctx->out_frames = 0;
- }
-
- LOG_SDEBUG("playtime %u %d [W:%hu R:%hu] %d", playtime, playtime - now, ctx->ab_write, ctx->ab_read, curframe->ready);
-
- // each missing packet will be requested up to (latency_frames / 16) times
- for (i = 0; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) {
- abuf_t *frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i);
- if (!frame->ready && now - frame->last_resend > RESEND_TO) {
- rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i);
- frame->last_resend = now;
- }
- }
-
}
-
-
-/*---------------------------------------------------------------------------*/
-static void *rtp_thread_func(void *arg) {
- fd_set fds;
- int i, sock = -1;
- int count = 0;
- bool ntp_sent;
- char *packet = malloc(MAX_PACKET);
- rtp_t *ctx = (rtp_t*) arg;
-
- for (i = 0; i < 3; i++) {
- if (ctx->rtp_sockets[i].sock > sock) sock = ctx->rtp_sockets[i].sock;
- // send synchro request 3 times
- ntp_sent = rtp_request_timing(ctx);
- }
-
- while (ctx->running) {
- ssize_t plen;
- char type;
- socklen_t rtp_client_len = sizeof(struct sockaddr_storage);
- int idx = 0;
- char *pktp = packet;
- struct timeval timeout = {0, 100*1000};
-
- FD_ZERO(&fds);
- for (i = 0; i < 3; i++) { FD_SET(ctx->rtp_sockets[i].sock, &fds); }
-
- if (select(sock + 1, &fds, NULL, NULL, &timeout) <= 0) continue;
-
- for (i = 0; i < 3; i++)
- if (FD_ISSET(ctx->rtp_sockets[i].sock, &fds)) idx = i;
-
- plen = recvfrom(ctx->rtp_sockets[idx].sock, packet, MAX_PACKET, 0, (struct sockaddr*) &ctx->rtp_host, &rtp_client_len);
-
- if (!ntp_sent) {
- LOG_WARN("[%p]: NTP request not send yet", ctx);
- ntp_sent = rtp_request_timing(ctx);
- }
-
- if (plen < 0) continue;
- assert(plen <= MAX_PACKET);
-
- type = packet[1] & ~0x80;
- pktp = packet;
-
- switch (type) {
- seq_t seqno;
- unsigned rtptime;
-
- // re-sent packet
- case 0x56: {
- pktp += 4;
- plen -= 4;
- }
-
- // data packet
- case 0x60: {
- seqno = ntohs(*(u16_t*)(pktp+2));
- rtptime = ntohl(*(u32_t*)(pktp+4));
-
- // adjust pointer and length
- pktp += 12;
- plen -= 12;
-
- LOG_SDEBUG("[%p]: seqno:%hu rtp:%u (type: %x, first: %u)", ctx, seqno, rtptime, type, packet[1] & 0x80);
-
- // check if packet contains enough content to be reasonable
- if (plen < 16) break;
-
- if ((packet[1] & 0x80) && (type != 0x56)) {
- LOG_INFO("[%p]: 1st audio packet received", ctx);
- }
-
- buffer_put_packet(ctx, seqno, rtptime, packet[1] & 0x80, pktp, plen);
-
- break;
- }
-
- // sync packet
- case 0x54: {
- u32_t rtp_now_latency = ntohl(*(u32_t*)(pktp+4));
- u64_t remote = (((u64_t) ntohl(*(u32_t*)(pktp+8))) << 32) + ntohl(*(u32_t*)(pktp+12));
- u32_t rtp_now = ntohl(*(u32_t*)(pktp+16));
- u16_t flags = ntohs(*(u16_t*)(pktp+2));
- u32_t remote_gap = NTP2MS(remote - ctx->timing.remote);
-
- // something is wrong and if we are supposed to be NTP synced, better ask for re-sync
- if (remote_gap > 10000) {
- if (ctx->synchro.status & NTP_SYNC) rtp_request_timing(ctx);
- LOG_WARN("discarding remote timing information %u", remote_gap);
- break;
- }
-
- pthread_mutex_lock(&ctx->ab_mutex);
-
- // re-align timestamp and expected local playback time (and magic 11025 latency)
- ctx->latency = rtp_now - rtp_now_latency;
- if (flags == 7 || flags == 4) ctx->latency += 11025;
- if (ctx->latency < MIN_LATENCY) ctx->latency = MIN_LATENCY;
- else if (ctx->latency > MAX_LATENCY) ctx->latency = MAX_LATENCY;
- ctx->synchro.rtp = rtp_now - ctx->latency;
- ctx->synchro.time = ctx->timing.local + remote_gap;
-
- // now we are synced on RTP frames
- ctx->synchro.status |= RTP_SYNC;
-
- // 1st sync packet received (signals a restart of playback)
- if (packet[0] & 0x10) {
- LOG_INFO("[%p]: 1st sync packet received", ctx);
- }
-
- pthread_mutex_unlock(&ctx->ab_mutex);
-
- LOG_DEBUG("[%p]: sync packet latency:%d rtp_latency:%u rtp:%u remote ntp:%llx, local time:%u local rtp:%u (now:%u)",
- ctx, ctx->latency, rtp_now_latency, rtp_now, remote, ctx->synchro.time, ctx->synchro.rtp, gettime_ms());
-
- if (!count--) {
- rtp_request_timing(ctx);
- count = 3;
- }
-
- if ((ctx->synchro.status & RTP_SYNC) && (ctx->synchro.status & NTP_SYNC)) ctx->cmd_cb(RAOP_TIMING);
-
- break;
- }
-
- // NTP timing packet
- case 0x53: {
- u64_t expected;
- u32_t reference = ntohl(*(u32_t*)(pktp+12)); // only low 32 bits in our case
- u64_t remote =(((u64_t) ntohl(*(u32_t*)(pktp+16))) << 32) + ntohl(*(u32_t*)(pktp+20));
- u32_t roundtrip = gettime_ms() - reference;
-
- // better discard sync packets when roundtrip is suspicious and ask for another one
- if (roundtrip > 100) {
- rtp_request_timing(ctx);
- LOG_WARN("[%p]: discarding NTP roundtrip of %u ms", ctx, roundtrip);
- break;
- }
-
- /*
- The expected elapsed remote time should be exactly the same as
- elapsed local time between the two request, corrected by the
- drifting
- */
- expected = ctx->timing.remote + MS2NTP(reference - ctx->timing.local);
-
- ctx->timing.remote = remote;
- ctx->timing.local = reference;
-
- // now we are synced on NTP (mutex not needed)
- ctx->synchro.status |= NTP_SYNC;
-
- LOG_DEBUG("[%p]: Timing references local:%llu, remote:%llx (delta:%lld, sum:%lld, adjust:%lld, gaps:%d)",
- ctx, ctx->timing.local, ctx->timing.remote);
-
- break;
- }
- }
- }
-
- free(packet);
- LOG_INFO("[%p]: terminating", ctx);
-
-#ifndef WIN32
- xTaskNotifyGive(ctx->joiner);
- vTaskSuspend(NULL);
-#endif
-
- return NULL;
-}
-
-/*---------------------------------------------------------------------------*/
-static bool rtp_request_timing(rtp_t *ctx) {
- unsigned char req[32];
- u32_t now = gettime_ms();
- int i;
- struct sockaddr_in host;
-
- LOG_DEBUG("[%p]: timing request now:%u (port: %hu)", ctx, now, ctx->rtp_sockets[TIMING].rport);
-
- req[0] = 0x80;
- req[1] = 0x52|0x80;
- *(u16_t*)(req+2) = htons(7);
- *(u32_t*)(req+4) = htonl(0); // dummy
- for (i = 0; i < 16; i++) req[i+8] = 0;
- *(u32_t*)(req+24) = 0;
- *(u32_t*)(req+28) = htonl(now); // this is not a real NTP, but a 32 ms counter in the low part of the NTP
-
- if (ctx->host.s_addr != INADDR_ANY) {
- host.sin_family = AF_INET;
- host.sin_addr = ctx->host;
- } else host = ctx->rtp_host;
-
- // no address from sender, need to wait for 1st packet to be received
- if (host.sin_addr.s_addr == INADDR_ANY) return false;
-
- host.sin_port = htons(ctx->rtp_sockets[TIMING].rport);
-
- if (sizeof(req) != sendto(ctx->rtp_sockets[TIMING].sock, req, sizeof(req), 0, (struct sockaddr*) &host, sizeof(host))) {
- LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno));
- }
-
- return true;
-}
-
-/*---------------------------------------------------------------------------*/
-static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last) {
- unsigned char req[8]; // *not* a standard RTCP NACK
-
- // do not request silly ranges (happens in case of network large blackouts)
- if (seq_order(last, first) || last - first > BUFFER_FRAMES / 2) return false;
-
- ctx->resent_req += last - first + 1;
-
- LOG_DEBUG("resend request [W:%hu R:%hu first=%hu last=%hu]", ctx->ab_write, ctx->ab_read, first, last);
-
- req[0] = 0x80;
- req[1] = 0x55|0x80; // Apple 'resend'
- *(u16_t*)(req+2) = htons(1); // our seqnum
- *(u16_t*)(req+4) = htons(first); // missed seqnum
- *(u16_t*)(req+6) = htons(last-first+1); // count
-
- ctx->rtp_host.sin_port = htons(ctx->rtp_sockets[CONTROL].rport);
-
- if (sizeof(req) != sendto(ctx->rtp_sockets[CONTROL].sock, req, sizeof(req), 0, (struct sockaddr*) &ctx->rtp_host, sizeof(ctx->rtp_host))) {
- LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno));
- }
-
- return true;
-}
-
+
+/*
+ * HairTunes - RAOP packet handler and slave-clocked replay engine
+ * Copyright (c) James Laird 2011
+ * All rights reserved.
+ *
+ * Modularisation: philippe_44@outlook.com, 2019
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "platform.h"
+#include "rtp.h"
+#include "raop_sink.h"
+#include "log_util.h"
+#include "util.h"
+
+#ifdef WIN32
+#include
+#include "alac_wrapper.h"
+#include
+#else
+#include "esp_pthread.h"
+#include "esp_system.h"
+#include "assert.h"
+#include
+#include
+#include "alac_wrapper.h"
+#endif
+
+#define NTP2MS(ntp) ((((ntp) >> 10) * 1000L) >> 22)
+#define MS2NTP(ms) (((((u64_t) (ms)) << 22) / 1000) << 10)
+#define NTP2TS(ntp, rate) ((((ntp) >> 16) * (rate)) >> 16)
+#define TS2NTP(ts, rate) (((((u64_t) (ts)) << 16) / (rate)) << 16)
+#define MS2TS(ms, rate) ((((u64_t) (ms)) * (rate)) / 1000)
+#define TS2MS(ts, rate) NTP2MS(TS2NTP(ts,rate))
+
+
extern log_level raop_loglevel;
+
static log_level *loglevel = &raop_loglevel;
+
+//#define __RTP_STORE
+
+// default buffer size
+#define BUFFER_FRAMES ( (150 * RAOP_SAMPLE_RATE * 2) / (352 * 100) )
+#define MAX_PACKET 1408
+#define MIN_LATENCY 11025
+#define MAX_LATENCY ( (120 * RAOP_SAMPLE_RATE * 2) / 100 )
+
+#define RTP_STACK_SIZE (4*1024)
+
+#define RTP_SYNC (0x01)
+#define NTP_SYNC (0x02)
+
+#define RESEND_TO 200
+
+enum { DATA = 0, CONTROL, TIMING };
+
+static const u8_t silence_frame[MAX_PACKET] = { 0 };
+
+typedef u16_t seq_t;
+typedef struct audio_buffer_entry { // decoded audio packets
+ int ready;
+ u32_t rtptime, last_resend;
+ s16_t *data;
+ int len;
+} abuf_t;
+
+typedef struct rtp_s {
+#ifdef __RTP_STORE
+ FILE *rtpIN, *rtpOUT;
+#endif
+ bool running;
+ unsigned char aesiv[16];
+#ifdef WIN32
+ AES_KEY aes;
+#else
+ mbedtls_aes_context aes;
+#endif
+ bool decrypt;
+ u8_t *decrypt_buf;
+ u32_t frame_size, frame_duration;
+ u32_t in_frames, out_frames;
+ struct in_addr host;
+ struct sockaddr_in rtp_host;
+ struct {
+ unsigned short rport, lport;
+ int sock;
+ } rtp_sockets[3]; // data, control, timing
+ struct timing_s {
+ u64_t local, remote;
+ } timing;
+ struct {
+ u32_t rtp, time;
+ u8_t status;
+ } synchro;
+ struct {
+ u32_t time;
+ seq_t seqno;
+ u32_t rtptime;
+ } record;
+ int latency; // rtp hold depth in samples
+ u32_t resent_req, resent_rec; // total resent + recovered frames
+ u32_t silent_frames; // total silence frames
+ u32_t discarded;
+ abuf_t audio_buffer[BUFFER_FRAMES];
+ seq_t ab_read, ab_write;
+ pthread_mutex_t ab_mutex;
+#ifdef WIN32
+ pthread_t thread;
+#else
+ TaskHandle_t thread, joiner;
+ StaticTask_t *xTaskBuffer;
+ StackType_t xStack[RTP_STACK_SIZE] __attribute__ ((aligned (4)));
+#endif
+
+ struct alac_codec_s *alac_codec;
+ int flush_seqno;
+ bool playing;
+ raop_data_cb_t data_cb;
+ raop_cmd_cb_t cmd_cb;
+} rtp_t;
+
+
+#define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES)
+static void buffer_alloc(abuf_t *audio_buffer, int size);
+static void buffer_release(abuf_t *audio_buffer);
+static void buffer_reset(abuf_t *audio_buffer);
+static void buffer_push_packet(rtp_t *ctx);
+static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last);
+static bool rtp_request_timing(rtp_t *ctx);
+static void* rtp_thread_func(void *arg);
+static int seq_order(seq_t a, seq_t b);
+
+/*---------------------------------------------------------------------------*/
+static struct alac_codec_s* alac_init(int fmtp[32]) {
+ struct alac_codec_s *alac;
+ unsigned sample_rate;
+ unsigned char sample_size, channels;
+ struct {
+ uint32_t frameLength;
+ uint8_t compatibleVersion;
+ uint8_t bitDepth;
+ uint8_t pb;
+ uint8_t mb;
+ uint8_t kb;
+ uint8_t numChannels;
+ uint16_t maxRun;
+ uint32_t maxFrameBytes;
+ uint32_t avgBitRate;
+ uint32_t sampleRate;
+ } config;
+
+ config.frameLength = htonl(fmtp[1]);
+ config.compatibleVersion = fmtp[2];
+ config.bitDepth = fmtp[3];
+ config.pb = fmtp[4];
+ config.mb = fmtp[5];
+ config.kb = fmtp[6];
+ config.numChannels = fmtp[7];
+ config.maxRun = htons(fmtp[8]);
+ config.maxFrameBytes = htonl(fmtp[9]);
+ config.avgBitRate = htonl(fmtp[10]);
+ config.sampleRate = htonl(fmtp[11]);
+
+ alac = alac_create_decoder(sizeof(config), (unsigned char*) &config, &sample_size, &sample_rate, &channels);
+ if (!alac) {
+ LOG_ERROR("cannot create alac codec", NULL);
+ return NULL;
+ }
+
+ return alac;
+}
+
+/*---------------------------------------------------------------------------*/
+rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv, char *fmtpstr,
+ short unsigned pCtrlPort, short unsigned pTimingPort,
+ raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb)
+{
+ int i = 0;
+ char *arg;
+ int fmtp[12];
+ bool rc = true;
+ rtp_t *ctx = calloc(1, sizeof(rtp_t));
+ rtp_resp_t resp = { 0, 0, 0, NULL };
+
+ if (!ctx) return resp;
+
+ ctx->host = host;
+ ctx->decrypt = false;
+ ctx->cmd_cb = cmd_cb;
+ ctx->data_cb = data_cb;
+ ctx->rtp_host.sin_family = AF_INET;
+ ctx->rtp_host.sin_addr.s_addr = INADDR_ANY;
+ pthread_mutex_init(&ctx->ab_mutex, 0);
+ ctx->flush_seqno = -1;
+ ctx->latency = latency;
+ ctx->ab_read = ctx->ab_write;
+
+#ifdef __RTP_STORE
+ ctx->rtpIN = fopen("airplay.rtpin", "wb");
+ ctx->rtpOUT = fopen("airplay.rtpout", "wb");
+#endif
+
+ ctx->rtp_sockets[CONTROL].rport = pCtrlPort;
+ ctx->rtp_sockets[TIMING].rport = pTimingPort;
+
+ if (aesiv && aeskey) {
+ memcpy(ctx->aesiv, aesiv, 16);
+#ifdef WIN32
+ AES_set_decrypt_key((unsigned char*) aeskey, 128, &ctx->aes);
+#else
+ memset(&ctx->aes, 0, sizeof(mbedtls_aes_context));
+ mbedtls_aes_setkey_dec(&ctx->aes, (unsigned char*) aeskey, 128);
+#endif
+ ctx->decrypt = true;
+ ctx->decrypt_buf = malloc(MAX_PACKET);
+ }
+
+ memset(fmtp, 0, sizeof(fmtp));
+ while ((arg = strsep(&fmtpstr, " \t")) != NULL) fmtp[i++] = atoi(arg);
+
+ ctx->frame_size = fmtp[1];
+ ctx->frame_duration = (ctx->frame_size * 1000) / RAOP_SAMPLE_RATE;
+
+ // alac decoder
+ ctx->alac_codec = alac_init(fmtp);
+ rc &= ctx->alac_codec != NULL;
+
+ buffer_alloc(ctx->audio_buffer, ctx->frame_size*4);
+
+ // create rtp ports
+ for (i = 0; i < 3; i++) {
+ ctx->rtp_sockets[i].sock = bind_socket(&ctx->rtp_sockets[i].lport, SOCK_DGRAM);
+ rc &= ctx->rtp_sockets[i].sock > 0;
+ }
+
+ // create http port and start listening
+ resp.cport = ctx->rtp_sockets[CONTROL].lport;
+ resp.tport = ctx->rtp_sockets[TIMING].lport;
+ resp.aport = ctx->rtp_sockets[DATA].lport;
+
+ if (rc) {
+ ctx->running = true;
+#ifdef WIN32
+ pthread_create(&ctx->thread, NULL, rtp_thread_func, (void *) ctx);
+#else
+ // xTaskCreate((TaskFunction_t) rtp_thread_func, "RTP_thread", RTP_TASK_SIZE, ctx, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1 , &ctx->thread);
+ ctx->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
+ ctx->thread = xTaskCreateStatic( (TaskFunction_t) rtp_thread_func, "RTP_thread", RTP_STACK_SIZE, ctx,
+ CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, ctx->xStack, ctx->xTaskBuffer );
+#endif
+ } else {
+ LOG_ERROR("[%p]: cannot start RTP", ctx);
+ rtp_end(ctx);
+ ctx = NULL;
+ }
+
+ resp.ctx = ctx;
+
+ return resp;
+}
+
+/*---------------------------------------------------------------------------*/
+void rtp_end(rtp_t *ctx)
+{
+ int i;
+
+ if (!ctx) return;
+
+ if (ctx->running) {
+#if !defined WIN32
+ ctx->joiner = xTaskGetCurrentTaskHandle();
+#endif
+ ctx->running = false;
+#ifdef WIN32
+ pthread_join(ctx->thread, NULL);
+#else
+ ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
+ vTaskDelete(ctx->thread);
+ heap_caps_free(ctx->xTaskBuffer);
+#endif
+ }
+
+ for (i = 0; i < 3; i++) closesocket(ctx->rtp_sockets[i].sock);
+
+ if (ctx->alac_codec) alac_delete_decoder(ctx->alac_codec);
+ if (ctx->decrypt_buf) free(ctx->decrypt_buf);
+
+ pthread_mutex_destroy(&ctx->ab_mutex);
+ buffer_release(ctx->audio_buffer);
+
+ free(ctx);
+
+#ifdef __RTP_STORE
+ fclose(ctx->rtpIN);
+ fclose(ctx->rtpOUT);
+#endif
+}
+
+/*---------------------------------------------------------------------------*/
+bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime)
+{
+ bool rc = true;
+ u32_t now = gettime_ms();
+
+ if (now < ctx->record.time + 250 || (ctx->record.seqno == seqno && ctx->record.rtptime == rtptime)) {
+ rc = false;
+ LOG_ERROR("[%p]: FLUSH ignored as same as RECORD (%hu - %u)", ctx, seqno, rtptime);
+ } else {
+ pthread_mutex_lock(&ctx->ab_mutex);
+ buffer_reset(ctx->audio_buffer);
+ ctx->playing = false;
+ ctx->flush_seqno = seqno;
+ pthread_mutex_unlock(&ctx->ab_mutex);
+ }
+
+ LOG_INFO("[%p]: flush %hu %u", ctx, seqno, rtptime);
+
+ return rc;
+}
+
+/*---------------------------------------------------------------------------*/
+void rtp_record(rtp_t *ctx, unsigned short seqno, unsigned rtptime)
+{
+ ctx->record.seqno = seqno;
+ ctx->record.rtptime = rtptime;
+ ctx->record.time = gettime_ms();
+
+ LOG_INFO("[%p]: record %hu %u", ctx, seqno, rtptime);
+}
+
+/*---------------------------------------------------------------------------*/
+static void buffer_alloc(abuf_t *audio_buffer, int size) {
+ int i;
+ for (i = 0; i < BUFFER_FRAMES; i++) {
+ audio_buffer[i].data = malloc(size);
+ audio_buffer[i].ready = 0;
+ }
+}
+
+/*---------------------------------------------------------------------------*/
+static void buffer_release(abuf_t *audio_buffer) {
+ int i;
+ for (i = 0; i < BUFFER_FRAMES; i++) {
+ free(audio_buffer[i].data);
+ }
+}
+
+/*---------------------------------------------------------------------------*/
+static void buffer_reset(abuf_t *audio_buffer) {
+ int i;
+ for (i = 0; i < BUFFER_FRAMES; i++) audio_buffer[i].ready = 0;
+}
+
+/*---------------------------------------------------------------------------*/
+// the sequence numbers will wrap pretty often.
+// this returns true if the second arg is after the first
+static int seq_order(seq_t a, seq_t b) {
+ s16_t d = b - a;
+ return d > 0;
+}
+
+/*---------------------------------------------------------------------------*/
+static void alac_decode(rtp_t *ctx, s16_t *dest, char *buf, int len, int *outsize) {
+ unsigned char iv[16];
+ int aeslen;
+ assert(len<=MAX_PACKET);
+
+ if (ctx->decrypt) {
+ aeslen = len & ~0xf;
+ memcpy(iv, ctx->aesiv, sizeof(iv));
+#ifdef WIN32
+ AES_cbc_encrypt((unsigned char*)buf, ctx->decrypt_buf, aeslen, &ctx->aes, iv, AES_DECRYPT);
+#else
+ mbedtls_aes_crypt_cbc(&ctx->aes, MBEDTLS_AES_DECRYPT, aeslen, iv, (unsigned char*) buf, ctx->decrypt_buf);
+#endif
+ memcpy(ctx->decrypt_buf+aeslen, buf+aeslen, len-aeslen);
+ alac_to_pcm(ctx->alac_codec, (unsigned char*) ctx->decrypt_buf, (unsigned char*) dest, 2, (unsigned int*) outsize);
+ } else {
+ alac_to_pcm(ctx->alac_codec, (unsigned char*) buf, (unsigned char*) dest, 2, (unsigned int*) outsize);
+ }
+
+ *outsize *= 4;
+}
+
+
+/*---------------------------------------------------------------------------*/
+static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool first, char *data, int len) {
+ abuf_t *abuf = NULL;
+ u32_t playtime;
+
+ pthread_mutex_lock(&ctx->ab_mutex);
+
+ if (!ctx->playing) {
+ if ((ctx->flush_seqno == -1 || seq_order(ctx->flush_seqno, seqno)) &&
+ (ctx->synchro.status & RTP_SYNC) && (ctx->synchro.status & NTP_SYNC)) {
+ ctx->ab_write = seqno-1;
+ ctx->ab_read = seqno;
+ ctx->flush_seqno = -1;
+ ctx->playing = true;
+ ctx->resent_req = ctx->resent_rec = ctx->silent_frames = ctx->discarded = 0;
+ playtime = ctx->synchro.time + (((s32_t)(rtptime - ctx->synchro.rtp)) * 1000) / RAOP_SAMPLE_RATE;
+ ctx->cmd_cb(RAOP_PLAY, playtime);
+ } else {
+ pthread_mutex_unlock(&ctx->ab_mutex);
+ return;
+ }
+ }
+
+ if (seqno == (u16_t) (ctx->ab_write+1)) {
+ // expected packet
+ abuf = ctx->audio_buffer + BUFIDX(seqno);
+ ctx->ab_write = seqno;
+ LOG_SDEBUG("packet expected seqno:%hu rtptime:%u (W:%hu R:%hu)", seqno, rtptime, ctx->ab_write, ctx->ab_read);
+
+ } else if (seq_order(ctx->ab_write, seqno)) {
+ // newer than expected
+ if (ctx->latency && seq_order(ctx->latency / ctx->frame_size, seqno - ctx->ab_write - 1)) {
+ // only get rtp latency-1 frames back (last one is seqno)
+ LOG_WARN("[%p] too many missing frames %hu seq: %hu, (W:%hu R:%hu)", ctx, seqno - ctx->ab_write - 1, seqno, ctx->ab_write, ctx->ab_read);
+ ctx->ab_write = seqno - ctx->latency / ctx->frame_size;
+ }
+ if (ctx->latency && seq_order(ctx->latency / ctx->frame_size, seqno - ctx->ab_read)) {
+ // if ab_read is lagging more than http latency, advance it
+ LOG_WARN("[%p] on hold for too long %hu (W:%hu R:%hu)", ctx, seqno - ctx->ab_read + 1, ctx->ab_write, ctx->ab_read);
+ ctx->ab_read = seqno - ctx->latency / ctx->frame_size + 1;
+ }
+ if (rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1)) {
+ seq_t i;
+ u32_t now = gettime_ms();
+ for (i = ctx->ab_write + 1; seq_order(i, seqno); i++) {
+ ctx->audio_buffer[BUFIDX(i)].rtptime = rtptime - (seqno-i)*ctx->frame_size;
+ ctx->audio_buffer[BUFIDX(i)].last_resend = now;
+ }
+ }
+ LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
+ abuf = ctx->audio_buffer + BUFIDX(seqno);
+ ctx->ab_write = seqno;
+ } else if (seq_order(ctx->ab_read, seqno + 1)) {
+ // recovered packet, not yet sent
+ abuf = ctx->audio_buffer + BUFIDX(seqno);
+ ctx->resent_rec++;
+ LOG_DEBUG("[%p]: packet recovered seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
+ } else {
+ // too late
+ LOG_DEBUG("[%p]: packet too late seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
+ }
+
+ if (ctx->in_frames++ > 1000) {
+ LOG_INFO("[%p]: fill [level:%hu rec:%u] [W:%hu R:%hu]", ctx, ctx->ab_write - ctx->ab_read, ctx->resent_rec, ctx->ab_write, ctx->ab_read);
+ ctx->in_frames = 0;
+ }
+
+ if (abuf) {
+ alac_decode(ctx, abuf->data, data, len, &abuf->len);
+ abuf->ready = 1;
+ // this is the local rtptime when this frame is expected to play
+ abuf->rtptime = rtptime;
+ buffer_push_packet(ctx);
+
+#ifdef __RTP_STORE
+ fwrite(data, len, 1, ctx->rtpIN);
+ fwrite(abuf->data, abuf->len, 1, ctx->rtpOUT);
+#endif
+ }
+
+ pthread_mutex_unlock(&ctx->ab_mutex);
+}
+
+/*---------------------------------------------------------------------------*/
+// push as many frames as possible through callback
+static void buffer_push_packet(rtp_t *ctx) {
+ abuf_t *curframe = NULL;
+ u32_t now, playtime, hold = max((ctx->latency * 1000) / (8 * RAOP_SAMPLE_RATE), 100);
+ int i;
+
+ // not ready to play yet
+ if (!ctx->playing || ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
+
+ // maybe re-evaluate time in loop in case data callback blocks ...
+ now = gettime_ms();
+
+ // there is always at least one frame in the buffer
+ do {
+
+ curframe = ctx->audio_buffer + BUFIDX(ctx->ab_read);
+ playtime = ctx->synchro.time + (((s32_t)(curframe->rtptime - ctx->synchro.rtp)) * 1000) / RAOP_SAMPLE_RATE;
+
+ if (now > playtime) {
+ LOG_DEBUG("[%p]: discarded frame now:%u missed by:%d (W:%hu R:%hu)", ctx, now, now - playtime, ctx->ab_write, ctx->ab_read);
+ ctx->discarded++;
+ curframe->ready = 0;
+ } else if (curframe->ready) {
+ ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime);
+ curframe->ready = 0;
+ } else if (playtime - now <= hold) {
+ LOG_DEBUG("[%p]: created zero frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read);
+ ctx->data_cb(silence_frame, ctx->frame_size * 4, playtime);
+ ctx->silent_frames++;
+ } else break;
+
+ ctx->ab_read++;
+ ctx->out_frames++;
+
+ } while (seq_order(ctx->ab_read, ctx->ab_write));
+
+ if (ctx->out_frames > 1000) {
+ LOG_INFO("[%p]: drain [level:%hd head:%d ms] [W:%hu R:%hu] [req:%u sil:%u dis:%u]",
+ ctx, ctx->ab_write - ctx->ab_read, playtime - now, ctx->ab_write, ctx->ab_read,
+ ctx->resent_req, ctx->silent_frames, ctx->discarded);
+ ctx->out_frames = 0;
+ }
+
+ LOG_SDEBUG("playtime %u %d [W:%hu R:%hu] %d", playtime, playtime - now, ctx->ab_write, ctx->ab_read, curframe->ready);
+
+ // each missing packet will be requested up to (latency_frames / 16) times
+ for (i = 0; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) {
+ abuf_t *frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i);
+ if (!frame->ready && now - frame->last_resend > RESEND_TO) {
+ rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i);
+ frame->last_resend = now;
+ }
+ }
+
}
+
+
+/*---------------------------------------------------------------------------*/
+static void *rtp_thread_func(void *arg) {
+ fd_set fds;
+ int i, sock = -1;
+ int count = 0;
+ bool ntp_sent;
+ char *packet = malloc(MAX_PACKET);
+ rtp_t *ctx = (rtp_t*) arg;
+
+ for (i = 0; i < 3; i++) {
+ if (ctx->rtp_sockets[i].sock > sock) sock = ctx->rtp_sockets[i].sock;
+ // send synchro request 3 times
+ ntp_sent = rtp_request_timing(ctx);
+ }
+
+ while (ctx->running) {
+ ssize_t plen;
+ char type;
+ socklen_t rtp_client_len = sizeof(struct sockaddr_storage);
+ int idx = 0;
+ char *pktp = packet;
+ struct timeval timeout = {0, 100*1000};
+
+ FD_ZERO(&fds);
+ for (i = 0; i < 3; i++) { FD_SET(ctx->rtp_sockets[i].sock, &fds); }
+
+ if (select(sock + 1, &fds, NULL, NULL, &timeout) <= 0) continue;
+
+ for (i = 0; i < 3; i++)
+ if (FD_ISSET(ctx->rtp_sockets[i].sock, &fds)) idx = i;
+
+ plen = recvfrom(ctx->rtp_sockets[idx].sock, packet, MAX_PACKET, 0, (struct sockaddr*) &ctx->rtp_host, &rtp_client_len);
+
+ if (!ntp_sent) {
+ LOG_WARN("[%p]: NTP request not send yet", ctx);
+ ntp_sent = rtp_request_timing(ctx);
+ }
+
+ if (plen < 0) continue;
+ assert(plen <= MAX_PACKET);
+
+ type = packet[1] & ~0x80;
+ pktp = packet;
+
+ switch (type) {
+ seq_t seqno;
+ unsigned rtptime;
+
+ // re-sent packet
+ case 0x56: {
+ pktp += 4;
+ plen -= 4;
+ }
+
+ // data packet
+ case 0x60: {
+ seqno = ntohs(*(u16_t*)(pktp+2));
+ rtptime = ntohl(*(u32_t*)(pktp+4));
+
+ // adjust pointer and length
+ pktp += 12;
+ plen -= 12;
+
+ LOG_SDEBUG("[%p]: seqno:%hu rtp:%u (type: %x, first: %u)", ctx, seqno, rtptime, type, packet[1] & 0x80);
+
+ // check if packet contains enough content to be reasonable
+ if (plen < 16) break;
+
+ if ((packet[1] & 0x80) && (type != 0x56)) {
+ LOG_INFO("[%p]: 1st audio packet received", ctx);
+ }
+
+ buffer_put_packet(ctx, seqno, rtptime, packet[1] & 0x80, pktp, plen);
+
+ break;
+ }
+
+ // sync packet
+ case 0x54: {
+ u32_t rtp_now_latency = ntohl(*(u32_t*)(pktp+4));
+ u64_t remote = (((u64_t) ntohl(*(u32_t*)(pktp+8))) << 32) + ntohl(*(u32_t*)(pktp+12));
+ u32_t rtp_now = ntohl(*(u32_t*)(pktp+16));
+ u16_t flags = ntohs(*(u16_t*)(pktp+2));
+ u32_t remote_gap = NTP2MS(remote - ctx->timing.remote);
+
+ // something is wrong and if we are supposed to be NTP synced, better ask for re-sync
+ if (remote_gap > 10000) {
+ if (ctx->synchro.status & NTP_SYNC) rtp_request_timing(ctx);
+ LOG_WARN("discarding remote timing information %u", remote_gap);
+ break;
+ }
+
+ pthread_mutex_lock(&ctx->ab_mutex);
+
+ // re-align timestamp and expected local playback time (and magic 11025 latency)
+ ctx->latency = rtp_now - rtp_now_latency;
+ if (flags == 7 || flags == 4) ctx->latency += 11025;
+ if (ctx->latency < MIN_LATENCY) ctx->latency = MIN_LATENCY;
+ else if (ctx->latency > MAX_LATENCY) ctx->latency = MAX_LATENCY;
+ ctx->synchro.rtp = rtp_now - ctx->latency;
+ ctx->synchro.time = ctx->timing.local + remote_gap;
+
+ // now we are synced on RTP frames
+ ctx->synchro.status |= RTP_SYNC;
+
+ // 1st sync packet received (signals a restart of playback)
+ if (packet[0] & 0x10) {
+ LOG_INFO("[%p]: 1st sync packet received", ctx);
+ }
+
+ pthread_mutex_unlock(&ctx->ab_mutex);
+
+ LOG_DEBUG("[%p]: sync packet latency:%d rtp_latency:%u rtp:%u remote ntp:%llx, local time:%u local rtp:%u (now:%u)",
+ ctx, ctx->latency, rtp_now_latency, rtp_now, remote, ctx->synchro.time, ctx->synchro.rtp, gettime_ms());
+
+ if (!count--) {
+ rtp_request_timing(ctx);
+ count = 3;
+ }
+
+ if ((ctx->synchro.status & RTP_SYNC) && (ctx->synchro.status & NTP_SYNC)) ctx->cmd_cb(RAOP_TIMING);
+
+ break;
+ }
+
+ // NTP timing packet
+ case 0x53: {
+ u64_t expected;
+ u32_t reference = ntohl(*(u32_t*)(pktp+12)); // only low 32 bits in our case
+ u64_t remote =(((u64_t) ntohl(*(u32_t*)(pktp+16))) << 32) + ntohl(*(u32_t*)(pktp+20));
+ u32_t roundtrip = gettime_ms() - reference;
+
+ // better discard sync packets when roundtrip is suspicious and ask for another one
+ if (roundtrip > 100) {
+ rtp_request_timing(ctx);
+ LOG_WARN("[%p]: discarding NTP roundtrip of %u ms", ctx, roundtrip);
+ break;
+ }
+
+ /*
+ The expected elapsed remote time should be exactly the same as
+ elapsed local time between the two request, corrected by the
+ drifting
+ */
+ expected = ctx->timing.remote + MS2NTP(reference - ctx->timing.local);
+
+ ctx->timing.remote = remote;
+ ctx->timing.local = reference;
+
+ // now we are synced on NTP (mutex not needed)
+ ctx->synchro.status |= NTP_SYNC;
+
+ LOG_DEBUG("[%p]: Timing references local:%llu, remote:%llx (delta:%lld, sum:%lld, adjust:%lld, gaps:%d)",
+ ctx, ctx->timing.local, ctx->timing.remote);
+
+ break;
+ }
+ }
+ }
+
+ free(packet);
+ LOG_INFO("[%p]: terminating", ctx);
+
+#ifndef WIN32
+ xTaskNotifyGive(ctx->joiner);
+ vTaskSuspend(NULL);
+#endif
+
+ return NULL;
+}
+
+/*---------------------------------------------------------------------------*/
+static bool rtp_request_timing(rtp_t *ctx) {
+ unsigned char req[32];
+ u32_t now = gettime_ms();
+ int i;
+ struct sockaddr_in host;
+
+ LOG_DEBUG("[%p]: timing request now:%u (port: %hu)", ctx, now, ctx->rtp_sockets[TIMING].rport);
+
+ req[0] = 0x80;
+ req[1] = 0x52|0x80;
+ *(u16_t*)(req+2) = htons(7);
+ *(u32_t*)(req+4) = htonl(0); // dummy
+ for (i = 0; i < 16; i++) req[i+8] = 0;
+ *(u32_t*)(req+24) = 0;
+ *(u32_t*)(req+28) = htonl(now); // this is not a real NTP, but a 32 ms counter in the low part of the NTP
+
+ if (ctx->host.s_addr != INADDR_ANY) {
+ host.sin_family = AF_INET;
+ host.sin_addr = ctx->host;
+ } else host = ctx->rtp_host;
+
+ // no address from sender, need to wait for 1st packet to be received
+ if (host.sin_addr.s_addr == INADDR_ANY) return false;
+
+ host.sin_port = htons(ctx->rtp_sockets[TIMING].rport);
+
+ if (sizeof(req) != sendto(ctx->rtp_sockets[TIMING].sock, req, sizeof(req), 0, (struct sockaddr*) &host, sizeof(host))) {
+ LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno));
+ }
+
+ return true;
+}
+
+/*---------------------------------------------------------------------------*/
+static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last) {
+ unsigned char req[8]; // *not* a standard RTCP NACK
+
+ // do not request silly ranges (happens in case of network large blackouts)
+ if (seq_order(last, first) || last - first > BUFFER_FRAMES / 2) return false;
+
+ ctx->resent_req += last - first + 1;
+
+ LOG_DEBUG("resend request [W:%hu R:%hu first=%hu last=%hu]", ctx->ab_write, ctx->ab_read, first, last);
+
+ req[0] = 0x80;
+ req[1] = 0x55|0x80; // Apple 'resend'
+ *(u16_t*)(req+2) = htons(1); // our seqnum
+ *(u16_t*)(req+4) = htons(first); // missed seqnum
+ *(u16_t*)(req+6) = htons(last-first+1); // count
+
+ ctx->rtp_host.sin_port = htons(ctx->rtp_sockets[CONTROL].rport);
+
+ if (sizeof(req) != sendto(ctx->rtp_sockets[CONTROL].sock, req, sizeof(req), 0, (struct sockaddr*) &ctx->rtp_host, sizeof(ctx->rtp_host))) {
+ LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno));
+ }
+
+ return true;
+}
+
diff --git a/components/squeezelite/.sc3357753833280144641.c b/components/squeezelite/.sc3357753833280144641.c
new file mode 100644
index 00000000..e69de29b
diff --git a/components/squeezelite/CMakeLists.txt b/components/squeezelite/CMakeLists.txt
new file mode 100644
index 00000000..144640a5
--- /dev/null
+++ b/components/squeezelite/CMakeLists.txt
@@ -0,0 +1,65 @@
+
+idf_component_register(SRCS "alac.c"
+ "buffer.c"
+ "controls.c"
+ "decode_external.c"
+ "decode.c"
+ "display.c"
+ "embedded.c"
+ "flac.c"
+ "helix-aac.c"
+ "mad.c"
+ "main.c"
+ "mpg.c"
+ "opus.c"
+ "output_bt.c"
+ "output_embedded.c"
+ "output_i2s.c"
+ "output_pack.c"
+ "output_visu.c"
+ "output.c"
+ "pcm.c"
+ "process.c"
+ "resample.c"
+ "resample16.c"
+ "slimproto.c"
+ "stream.c"
+ "utils.c"
+ "vorbis.c"
+ "a1s/ac101.c"
+ "tas57xx/dac_57xx.c"
+ "external/dac_external.c"
+ INCLUDE_DIRS . a1s
+ REQUIRES newlib
+ esp_common
+ esp-dsp
+ platform_display
+ platform_config
+ platform_bluetooth
+ codecs
+ services
+ raop
+)
+
+
+
+set_source_files_properties(mad.c
+ PROPERTIES COMPILE_FLAGS
+ -Wno-maybe-uninitialized
+)
+set_source_files_properties(pcm.c
+ PROPERTIES COMPILE_FLAGS
+ -Wno-maybe-uninitialized
+)
+
+set_source_files_properties(flac.c
+ PROPERTIES COMPILE_FLAGS
+ -Wno-maybe-uninitialized
+)
+
+add_definitions(-O3 -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ONLY -DBYTES_PER_FRAME=4)
+#add_library(platform_bluetooth STATIC IMPORTED GLOBAL)
+#set_property(TARGET platform_bt PROPERTY IMPORTED_LOCATION "${COMPONENT_DIR}/../freetype/libfreetype.a")
+#add_library(platform_bluetooth STATIC IMPORTED GLOBAL)
+#set_property(TARGET platform_bluetooth PROPERTY IMPORTED_LOCATION "../platform_bluetooth/platform_bluetooth.a")
+#target_link_libraries(${COMPONENT_TARGET} PUBLIC platform_bluetooth)
\ No newline at end of file
diff --git a/components/squeezelite/component.mk b/components/squeezelite/component.mk
index c30f0ffa..0b83b1b1 100644
--- a/components/squeezelite/component.mk
+++ b/components/squeezelite/component.mk
@@ -13,7 +13,7 @@ CFLAGS += -O3 -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ON
-I$(COMPONENT_PATH)/../tools \
-I$(COMPONENT_PATH)/../codecs/inc/opus \
-I$(COMPONENT_PATH)/../codecs/inc/opusfile \
- -I$(COMPONENT_PATH)/../driver_bt \
+ -I$(COMPONENT_PATH)/../platform_bluetooth \
-I$(COMPONENT_PATH)/../raop \
-I$(COMPONENT_PATH)/../services
diff --git a/components/squeezelite/decode_external.c b/components/squeezelite/decode_external.c
index 6caed665..20785666 100644
--- a/components/squeezelite/decode_external.c
+++ b/components/squeezelite/decode_external.c
@@ -21,9 +21,9 @@
#include "platform_config.h"
#include "squeezelite.h"
-#include "bt_app_sink.h"
#include "raop_sink.h"
#include
+#include
#define LOCK_O mutex_lock(outputbuf->mutex)
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
diff --git a/components/squeezelite/embedded.h b/components/squeezelite/embedded.h
index 90926dca..71127406 100644
--- a/components/squeezelite/embedded.h
+++ b/components/squeezelite/embedded.h
@@ -1,6 +1,6 @@
#ifndef EMBEDDED_H
#define EMBEDDED_H
-
+#include
#include
/* must provide
@@ -20,6 +20,9 @@
#ifndef PTHREAD_STACK_MIN
#define PTHREAD_STACK_MIN 256
#endif
+#ifndef _CONST
+#define _CONST
+#endif
#define STREAM_THREAD_STACK_SIZE 6 * 1024
#define DECODE_THREAD_STACK_SIZE 16 * 1024
diff --git a/components/squeezelite/output_bt.c b/components/squeezelite/output_bt.c
index 3295baa7..c65e8dfc 100644
--- a/components/squeezelite/output_bt.c
+++ b/components/squeezelite/output_bt.c
@@ -18,11 +18,12 @@
* along with this program. If not, see .
*
*/
-
+
#include "driver/gpio.h"
#include "squeezelite.h"
#include "perf_trace.h"
#include "platform_config.h"
+#include
extern struct outputstate output;
extern struct buffer *outputbuf;
@@ -196,3 +197,4 @@ void output_bt_tick(void) {
RESET_ALL_MIN_MAX;
}
}
+
diff --git a/components/telnet/CMakeLists.txt b/components/telnet/CMakeLists.txt
index 36bcf9cb..37afd1c9 100644
--- a/components/telnet/CMakeLists.txt
+++ b/components/telnet/CMakeLists.txt
@@ -1,5 +1,4 @@
-idf_component_register(SRCS "telnet.c"
- INCLUDE_DIRS .
- INCLUDE_DIRS . ../tools/
-
+idf_component_register(SRCS "telnet.c" "libtelnet/libtelnet.c"
+ INCLUDE_DIRS . libtelnet
+ REQUIRES platform_config services
)
diff --git a/components/telnet/telnet.c b/components/telnet/telnet.c
index 23d846a0..552c678e 100644
--- a/components/telnet/telnet.c
+++ b/components/telnet/telnet.c
@@ -18,9 +18,9 @@
* https://github.com/PocketSprite/8bkc-sdk/blob/master/8bkc-components/8bkc-hal/vfs-stdout.c
*
*/
-#include // Required for CONFIG_PTHREAD_TASK_PRIO_DEFAULT.h
+#include // Required for libtelnet.h
#include
-#include "CONFIG_PTHREAD_TASK_PRIO_DEFAULT.h"
+#include "libtelnet.h"
#include "stdbool.h"
#include
#include
@@ -35,7 +35,7 @@
#include "esp_attr.h"
#include "soc/uart_struct.h"
#include "driver/uart.h"
-#include "platform_config.h"
+#include "config.h"
#include "nvs_utilities.h"
#include "platform_esp32.h"
diff --git a/components/wifi-manager/CMakeLists.txt b/components/wifi-manager/CMakeLists.txt
index cad513cb..51fc521b 100644
--- a/components/wifi-manager/CMakeLists.txt
+++ b/components/wifi-manager/CMakeLists.txt
@@ -1,7 +1,6 @@
idf_component_register(SRCS "dns_server.c" "http_server.c" "wifi_manager.c"
INCLUDE_DIRS .
REQUIRES esp_common newlib freertos spi_flash nvs_flash mdns pthread wpa_supplicant platform_console squeezelite-ota
-
EMBED_FILES style.css code.js index.html bootstrap.min.css.gz jquery.min.js.gz popper.min.js.gz bootstrap.min.js.gz
diff --git a/esp-dsp b/esp-dsp
deleted file mode 160000
index de39220f..00000000
--- a/esp-dsp
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit de39220fb482eeaee4db5707358bf6b97b34fe59
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index 495ff884..b9ce81b1 100644
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -1,4 +1,5 @@
idf_component_register(SRCS "esp_app_main.c"
- REQUIRES esp_common pthread squeezelite-ota platform_console
+ REQUIRES esp_common pthread squeezelite-ota platform_console telnet
INCLUDE_DIRS .
+ EMBED_FILES ../server_certs/github.pem
)
\ No newline at end of file
diff --git a/mconf-idf-prefix/tmp/mconf-idf-cfgcmd.txt b/mconf-idf-prefix/tmp/mconf-idf-cfgcmd.txt
new file mode 100644
index 00000000..6a6ed5fd
--- /dev/null
+++ b/mconf-idf-prefix/tmp/mconf-idf-cfgcmd.txt
@@ -0,0 +1 @@
+cmd=''
diff --git a/mconf-idf-prefix/tmp/mconf-idf-cfgcmd.txt.in b/mconf-idf-prefix/tmp/mconf-idf-cfgcmd.txt.in
new file mode 100644
index 00000000..b3f09efc
--- /dev/null
+++ b/mconf-idf-prefix/tmp/mconf-idf-cfgcmd.txt.in
@@ -0,0 +1 @@
+cmd='@cmd@'