Compare commits

..

127 Commits

Author SHA1 Message Date
philippe44
752cfbf3b2 Update .gitattributes 2025-03-30 14:19:09 +02:00
philippe44
cecb7fd876 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2025-02-17 23:26:07 +01:00
philippe44
e92e431b45 add MCK in get_dac_config 2025-02-17 23:26:01 +01:00
philippe44
db792e47bd Update Platform_build.yml 2025-02-17 22:47:05 +01:00
philippe44
a22f75a13a limit display checks 2025-02-17 22:39:48 +01:00
philippe44
1f220895e6 Update README.md 2025-02-17 12:55:44 +01:00
philippe44
769ff99f7d Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-09-28 23:17:23 +02:00
philippe44
424fb93ec4 add 2nd encoder for volume only 2024-09-28 23:17:09 +02:00
philippe44
e270963dbd Update README.md 2024-09-28 23:11:52 +02:00
philippe44
2cae41d29c Update README.md 2024-09-28 23:11:10 +02:00
philippe44
84b95cd79c Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-09-28 18:55:48 +02:00
philippe44
6369f4bd69 another misplaced NVS #ifdef 2024-09-28 18:55:43 +02:00
philippe44
4c1bca3166 Update CHANGELOG 2024-09-28 14:43:23 +02:00
philippe44
3a5163e6f6 Update esp_app_main.c
autoexec default was created at the wrong place!
2024-09-28 14:42:08 +02:00
philippe44
cbe42b56bc Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-09-27 18:53:46 +02:00
philippe44
ab9812cb75 make i2s emergency stop platform independant 2024-09-27 18:52:26 +02:00
philippe44
084caedd7e Update README.md 2024-09-27 16:35:15 +02:00
philippe44
f254bf49af Update README.md 2024-09-27 15:35:59 +02:00
philippe44
66bd26f007 Update README.md 2024-09-27 15:35:03 +02:00
philippe44
dd6c932c39 Update README.md 2024-09-27 15:06:38 +02:00
philippe44
50070378ad Merge pull request #440 from digidocs/loudness_plugin_fix1
SqueezeESP32 plugin loudness control fix
2024-09-12 12:33:31 +02:00
github-actions
b50bc8f376 Update prebuilt objects [skip actions] 2024-09-12 09:32:21 +00:00
philippe44
e6723dfa2f Update CHANGELOG 2024-09-12 11:27:55 +02:00
philippe44
ffaff5ac27 Merge pull request #430 from StefanKrupop/aw9523_expander
Add support for AW9523 GPIO expander
2024-09-12 11:25:31 +02:00
philippe44
33ef4b01e7 Update CHANGELOG 2024-09-12 11:24:23 +02:00
github-actions
302865b167 Update prebuilt objects [skip actions] 2024-09-11 18:05:43 +00:00
philippe44
7f0ae69e81 Merge pull request #439 from digidocs/eq_update_fix2
Fix for ESP32 equalizer settings don't update when expected
2024-09-11 20:02:25 +02:00
David Carr
e21e2cf7f9 Equalizer: change gain to int8 and memcmp-based update check 2024-09-04 15:07:42 -05:00
David Carr
57cd009e4c Revert "Equalizer: check if requested gain has changed before updating"
This reverts commit 78e8d60021.
2024-09-04 14:52:27 -05:00
David Carr
fdd8b0a4c9 Change to //= operator 2024-09-04 14:48:07 -05:00
David Carr
78e8d60021 Equalizer: check if requested gain has changed before updating 2024-09-04 13:49:51 -05:00
digidocs
4068e07a45 Fix for I2S noise burst when ESP32 panic occurs (#437) 2024-09-02 08:51:40 -04:00
David Carr
f8d7ac23e1 Updated SqueezeESP32 plugin and zip file 2024-08-21 12:07:57 -05:00
David Carr
befc81f573 Fix for wrong loudness value being sent when user requests loudness 0 2024-08-21 11:01:18 -05:00
David Carr
a633524936 Fix for ESP32 equalizer settings don't update when expected 2024-08-21 00:08:06 -05:00
Stefan Krupop
9d71b8ee26 Added "aw9523" to list of possible expanders 2024-07-26 21:01:10 +02:00
Stefan Krupop
672aca8258 Fixed resetting interrupt 2024-07-26 20:18:55 +02:00
Stefan Krupop
a2351ba0d5 Add support for AW9523 port expander 2024-07-25 01:13:14 +02:00
wizmo2
40a698e2f1 add checks for grfX handlers to prevent divide-by-zero with slimproto (#424)
Thank you!
2024-06-07 18:19:33 -04:00
github-actions
38d28ae8c4 Update prebuilt objects [skip actions] 2024-05-07 21:41:36 +00:00
Sebastien L
21407e8c1c Fix crash from led_vu when no display - release 2024-05-07 17:39:02 -04:00
github-actions
12aa555ff3 Update prebuilt objects [skip actions] 2024-03-22 03:03:10 +00:00
philippe44
cb47ec855b Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-03-20 22:36:28 -07:00
philippe44
787a5d9a6e spdif glitch at track transition 2024-03-20 22:36:22 -07:00
github-actions
7b9deb795c Update prebuilt objects [skip actions] 2024-01-28 06:33:54 +00:00
philippe44
4b1f8a8d4b see CHANGELOG - release 2024-01-27 22:32:01 -08:00
philippe44
4f8661100b Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-01-19 16:14:12 -08:00
philippe44
6b2eb1b3c0 see CHANGELOG 2024-01-19 16:14:08 -08:00
github-actions
f3593fa2f4 Update prebuilt objects [skip actions] 2024-01-17 02:53:45 +00:00
philippe44
ccc7b86369 See CHANGELOG - release 2024-01-16 18:51:33 -08:00
philippe44
9e3c6dcf30 Update README.md 2024-01-10 21:53:03 -08:00
github-actions
7be81887a6 Update prebuilt objects [skip actions] 2024-01-11 05:41:18 +00:00
philippe44
29f71fc677 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-01-10 21:36:53 -08:00
philippe44
a14a6edc1b reset flac packet flag - release 2024-01-10 21:36:50 -08:00
github-actions
35bf0a3c10 Update prebuilt objects [skip actions] 2024-01-11 03:00:35 +00:00
philippe44
55a8658f3a OggFlac & displayer fixes (see CHANGELOG) - release 2024-01-10 18:58:47 -08:00
github-actions
b0d9e1668c Update prebuilt objects [skip actions] 2024-01-04 08:48:28 +00:00
philippe44
922367baf5 no false alarm on OggS miss + homogeneous use of u32/size_t/int - release 2024-01-04 00:46:36 -08:00
philippe44
01320db007 always copy granule unless it's -1 (not valid) 2024-01-03 23:47:00 -08:00
philippe44
f677695fc7 handle pages with no terminated packet 2024-01-03 23:25:08 -08:00
github-actions
7902af2bf0 Update prebuilt objects [skip actions] 2024-01-03 04:08:04 +00:00
philippe44
5faecb08f6 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-01-02 18:15:43 -08:00
philippe44
8e3d91020f bring ogg parsing context purely inside stream.c - release 2024-01-02 18:15:39 -08:00
github-actions
4f54a47c83 Update prebuilt objects [skip actions] 2024-01-02 08:50:27 +00:00
philippe44
58840f894f Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-01-02 00:44:20 -08:00
philippe44
94109ebf38 fix copy typo - release 2024-01-02 00:43:32 -08:00
github-actions
fc20618fa2 Update prebuilt objects [skip actions] 2024-01-02 08:37:41 +00:00
philippe44
70720d3445 don't allocate segments - release 2024-01-02 00:35:47 -08:00
github-actions
4abe1304e8 Update prebuilt objects [skip actions] 2024-01-01 02:54:46 +00:00
philippe44
be7fb4b669 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-12-31 18:50:29 -08:00
philippe44
3554af1460 add vorbis/ogg live metadata (and fix some ogg issues) - release 2023-12-31 18:50:25 -08:00
Sébastien
02d422c1f4 update workflow dependency 2023-12-28 09:11:46 -05:00
github-actions
343b728323 Update prebuilt objects [skip actions] 2023-12-28 13:56:07 +00:00
philippe44
9679c5c104 fix flush mode 2023-12-27 19:46:16 -08:00
philippe44
5c90086bbd sync with upstream/cspot 2023-11-21 12:52:55 -08:00
philippe44
807a0e547a airplay improvements 2023-11-19 22:57:21 -08:00
philippe44
1d54331224 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-11-16 22:52:23 -08:00
philippe44
e85a3cddf1 GDS optimization 2023-11-16 22:52:18 -08:00
github-actions
d4d97d5a60 Update prebuilt objects [skip actions] 2023-11-17 05:45:59 +00:00
philippe44
2afbee7cb1 add SH1122 - release 2023-11-16 21:43:56 -08:00
philippe44
bbf9a3af70 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-11-13 22:30:51 -08:00
philippe44
a9ca4c4450 attempt to fix cspot track issues - release 2023-11-13 22:30:45 -08:00
github-actions
55bce084eb Update prebuilt objects [skip actions] 2023-11-09 21:49:08 +00:00
philippe44
d31697e7ef force pad_select for gpio use in dac_controlset - release 2023-11-09 13:47:08 -08:00
philippe44
e830b4db73 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-11-09 13:42:11 -08:00
philippe44
8fe21327b8 Update README.md 2023-11-08 16:54:54 -08:00
philippe44
3f487366ee Update README.md 2023-11-08 16:51:47 -08:00
philippe44
b875585aec Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-11-08 16:37:53 -08:00
philippe44
b46fccfc83 execute dac_controlset even w/o i2c (for gpio) - release 2023-11-08 16:37:49 -08:00
philippe44
655c17fb29 Update README.md 2023-11-08 11:36:29 -08:00
philippe44
b8bda0435a Update README.md 2023-11-08 11:34:17 -08:00
github-actions
dc5cb31efb Update prebuilt objects [skip actions] 2023-11-08 06:18:54 +00:00
philippe44
44ccbb49a6 ledvu update 2023-11-07 22:15:54 -08:00
philippe44
6589387cd3 Merge pull request #351 from wizmo2/ledvu-update-clean
Ledvu update (clean)
2023-11-07 22:08:42 -08:00
github-actions
0d3f6a8870 Update prebuilt objects [skip actions] 2023-11-06 01:24:27 +00:00
philippe44
bb6ec4a629 chmod... 2023-11-05 17:12:55 -08:00
philippe44
a029304776 chmod... 2023-11-05 15:52:02 -08:00
Wizmo2
dad8efff8b Merge branch 'ledvu-update-clean' of https://github.com/wizmo2/squeezelite-esp32 into ledvu-update-clean 2023-11-05 14:30:23 -05:00
Wizmo2
e8c6169e09 formating changes 2023-11-05 14:27:45 -05:00
Wizmo2
fc8d15f58d formating changes 2023-11-04 23:07:25 -04:00
Wizmo2
f9d7e15d4b add suspend service 2023-11-04 22:56:37 -04:00
github-actions
67722bef94 Update prebuilt objects [skip actions] 2023-11-04 02:25:17 +00:00
github-actions
8ea777ddff Update prebuilt objects [skip actions] 2023-11-04 01:11:45 +00:00
philippe44
ad8d587e94 no reboot on external active decoder - release 2023-11-03 18:09:39 -07:00
Wizmo2
667b90fafc ledvu-update-clean 2023-10-30 19:24:23 -04:00
philippe44
7be653f722 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-10-30 01:06:41 -07:00
philippe44
f6269a2f7a gap when changing queue workaround 2023-10-30 01:06:37 -07:00
philippe44
4b70514fa3 Update CHANGELOG 2023-10-29 00:08:20 -07:00
philippe44
00a0b8c36e more warnings 2023-10-28 20:54:37 -07:00
philippe44
9f4474a19c remove warnings 2023-10-28 20:51:57 -07:00
github-actions
58fac99e95 Update prebuilt objects [skip actions] 2023-10-29 03:19:48 +00:00
Sebastien L
cb7c8fd6be Merge remote-tracking branch 'origin/master-v4.3' into master-v4.3 2023-10-28 23:13:38 -04:00
Sebastien L
b9a1bf0432 remove bootswatch 2023-10-28 23:13:20 -04:00
github-actions
63658efefe Update prebuilt objects [skip actions] 2023-10-29 02:53:20 +00:00
Sebastien L
c76bbc3524 Fix recovery 2023-10-28 22:48:12 -04:00
philippe44
9619b1d792 Update CHANGELOG 2023-10-28 00:13:39 -07:00
philippe44
0bbd5a064f Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-10-27 18:27:24 -07:00
philippe44
986521fd4a fix vorbis & opus memory leaks 2023-10-27 18:26:57 -07:00
github-actions
406a56a3a3 Update prebuilt objects [skip actions] 2023-10-26 05:59:15 +00:00
philippe44
c9455f70ff Update CHANGELOG 2023-10-25 22:47:14 -07:00
philippe44
b5b76480e4 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-10-25 22:45:37 -07:00
philippe44
e6744deab8 fix vorbis not closing properly - release 2023-10-25 22:45:26 -07:00
github-actions
94baf86989 Update prebuilt objects [skip actions] 2023-10-25 01:48:05 +00:00
philippe44
0c856a37c1 well, fix 16 bits as well... 2023-10-24 16:27:27 -07:00
philippe44
d5f28375ce fix SPDIF miscount 2023-10-24 13:39:56 -07:00
philippe44
7ea5a93647 backport webUI changes as well (-disable) 2023-10-23 17:15:02 -07:00
philippe44
ca38a14420 cspot fixes backport 2023-10-23 17:09:50 -07:00
215 changed files with 3788 additions and 6497 deletions

2
.gitattributes vendored
View File

@@ -1,5 +1,5 @@
# Auto detect text files and perform LF normalization # Auto detect text files and perform LF normalization
* text=auto # * text=auto
# Custom for Visual Studio # Custom for Visual Studio
*.cs diff=csharp *.cs diff=csharp

View File

@@ -88,7 +88,7 @@ jobs:
git commit -m "Update prebuilt objects [skip actions]" git commit -m "Update prebuilt objects [skip actions]"
git push https://${{secrets.github_token}}@github.com/sle118/squeezelite-esp32.git git push https://${{secrets.github_token}}@github.com/sle118/squeezelite-esp32.git
- name: Locally store commonly built objects - name: Locally store commonly built objects
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: prebuilt_objects name: prebuilt_objects
path: | path: |
@@ -130,7 +130,7 @@ jobs:
git status git status
build_tools.py environment --build ${{ needs.bootstrap.outputs.build_number }} --env_file "$GITHUB_ENV" --node "${{matrix.node}}" --depth ${{matrix.depth}} --major 2 --docker sle118/squeezelite-esp32-idfv435 build_tools.py environment --build ${{ needs.bootstrap.outputs.build_number }} --env_file "$GITHUB_ENV" --node "${{matrix.node}}" --depth ${{matrix.depth}} --major 2 --docker sle118/squeezelite-esp32-idfv435
- uses: actions/download-artifact@master - uses: actions/download-artifact@v4
name: Restore common objects name: Restore common objects
with: with:
name: prebuilt_objects name: prebuilt_objects
@@ -171,7 +171,7 @@ jobs:
zip build/${artifact_file_name} partitions*.csv components/ build/*.bin build/bootloader/bootloader.bin build/partition_table/partition-table.bin build/flash_project_args build/size_*.txt zip build/${artifact_file_name} partitions*.csv components/ build/*.bin build/bootloader/bootloader.bin build/partition_table/partition-table.bin build/flash_project_args build/size_*.txt
fi fi
- name: Upload Build Artifacts - name: Upload Build Artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
if: ${{ needs.bootstrap.outputs.mock == 0 }} if: ${{ needs.bootstrap.outputs.mock == 0 }}
with: with:
name: ${{ env.artifact_prefix }} name: ${{ env.artifact_prefix }}

View File

@@ -1,5 +1,81 @@
2023-10-11 2025-02-17
- Reduce the size of binaries (Fixes https://github.com/sle118/squeezelite-esp32/issues/329) - reverse some checks on display not NULL in gds.c. As it is about being fast, I'd prefer the caller to know that there is no display and don't call. I'm sure I have missed something when there is only led_vu and no display, but people will remind me soon enough :-)
2024-09-28
- add dedicated volume encoder
- fix memory leak in rotary config creation
2024-09-28
- create autoexec NVS entry at the right place (not only whne BT is enabled!
- try to make i2s panic mode work for all esp versions
2024-09-12
- add AW9523 GPIO expander credits @Stefan Krupop (https://github.com/sle118/squeezelite-esp32/pull/430
2024-09-10
- Merge pull request # 439 from digidocs/eq_update_fix2 (# 309)
- Fix for I2S noise burst when ESP32 panic occurs (# 437)
2024-05-05
- Fix crash when led_vu is configured without display
2024-01-27
- complete libflac fix and add chaining enablement
- fixed stream Ogg demux issue with unknown granule
2024-01-19
- fixed libflac with OggFlac
- AirPlay missed frame logging
2024-01-16
- catch-up with cspot latest
- refactor airplay flush/first packet
- new libFLAC that supports multi-stream OggFlac
- fix output threshold
- log missed frames
2024-01-10
- add OggFlac to stream metadata
- fix OggFlac deadlock in flac callback when not enough data in streambuf
- fix no displayer due to threadshold too high (use 500ms instead)
- reset outputbuf when cspot starts
2024-01-01
- ogg stream are parsed to foward metadata to LMS
- fix some ogg parsing on multi-stream containers
2023-11-19
- more robust (?) airplay RTP frame recovery
- initialize of scratch string in monitor (trying to figure out random reboot)
2023-11-16
- add SH1122 support
- optimize GDS DrawPixel function
2023-11-09
- force gpio_pad_select_gpio in dac_controlset in case somebody uses UART gpio's (or other pre-programmed)
2023-11-08
- execute dac_controlset even when there is no i2s (for gpio)
2023-11-07
- led-vu gain + misc fixes
- bump plugin version to 0.600
2023-11-03
- don't reboot when external decoder is connected even with a LMS server
2023-10-28
- fix recovery size (remove bootstrap)
- improve NVS initialization structure
2023-10-27
- fix vorbis (and opus) memory leak
2023-10-25
- fix vorbis codec close
2023-10-23
- fix Spotify track insertion
- [WEB] Allow running without LMS with option "Audio/Disable Squeezelite" - [WEB] Allow running without LMS with option "Audio/Disable Squeezelite"
2023-10.07 2023-10.07

View File

@@ -73,7 +73,7 @@ set_target_properties(recovery.elf PROPERTIES LINK_LIBRARIES "${BCA};idf::app_re
# build squeezelite, add app_squeezelite to the link # build squeezelite, add app_squeezelite to the link
add_executable(squeezelite.elf "CMakeLists.txt") add_executable(squeezelite.elf "CMakeLists.txt")
add_dependencies(squeezelite.elf recovery.elf) add_dependencies(squeezelite.elf recovery.elf)
set_target_properties(squeezelite.elf PROPERTIES LINK_LIBRARIES "${BCA};idf::app_squeezelite;-Wl,--Map=${BUILD_DIR}/squeezelite.map") set_target_properties(squeezelite.elf PROPERTIES LINK_LIBRARIES "${BCA};idf::app_squeezelite;-Wl,--Map=${BUILD_DIR}/squeezelite.map,--wrap=esp_panic_handler")
add_custom_command( add_custom_command(
TARGET squeezelite.elf TARGET squeezelite.elf
POST_BUILD POST_BUILD

View File

@@ -25,7 +25,11 @@ ENV GCC_TOOLS_BASE=/opt/esp/tools/xtensa-esp32-elf/esp-2021r2-patch3-8.4.0/xtens
# pushd components/wifi-manager/webapp/ && npm install && npm run-script build && popd # pushd components/wifi-manager/webapp/ && npm install && npm run-script build && popd
# #
# to run the docker with netwotrk port published on the host: # to run the docker with netwotrk port published on the host:
# (windows)
# docker run --rm -p 5000:5000/tcp -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv435 # docker run --rm -p 5000:5000/tcp -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv435
# (linux)
# docker run --rm -p 5000:5000/tcp -v `pwd`:/project -w /project -it sle118/squeezelite-esp32-idfv435
ARG IDF_CLONE_URL=https://github.com/espressif/esp-idf.git ARG IDF_CLONE_URL=https://github.com/espressif/esp-idf.git
ARG IDF_CLONE_BRANCH_OR_TAG=master ARG IDF_CLONE_BRANCH_OR_TAG=master

View File

@@ -15,6 +15,7 @@ Depending on the hardware connected to the esp32, you can send audio to a local
But squeezelite-esp32 is highly extensible and you can add But squeezelite-esp32 is highly extensible and you can add
- [Buttons](#buttons) and [Rotary Encoder](#rotary-encoder) and map/combine them to various functions (play, pause, volume, next ...) - [Buttons](#buttons) and [Rotary Encoder](#rotary-encoder) and map/combine them to various functions (play, pause, volume, next ...)
- [Volume Encoder](#volume-rotary-encoder) for a dedicated volume rotary encoder
- [GPIO expander](#gpio-expanders) (buttons, led and rotary) - [GPIO expander](#gpio-expanders) (buttons, led and rotary)
- [IR receiver](#infrared) (no pullup resistor or capacitor needed, just the 38kHz receiver) - [IR receiver](#infrared) (no pullup resistor or capacitor needed, just the 38kHz receiver)
- [Monochrome, GrayScale or Color displays](#display) using SPI or I2C (supported drivers are SH1106, SSD1306, SSD1322, SSD1326/7, SSD1351, ST7735, ST7789 and ILI9341). - [Monochrome, GrayScale or Color displays](#display) using SPI or I2C (supported drivers are SH1106, SSD1306, SSD1322, SSD1326/7, SSD1351, ST7735, ST7789 and ILI9341).
@@ -189,13 +190,19 @@ if "model" is not set or is not recognized, then default "I2S" is used. The opti
So far, TAS57xx, TAS5713, AC101, WM8978 and ES8388 are recognized models where the proper init sequence/volume/power controls are sent. For other codecs that might require an I2C commands, please use the parameter "dac_controlset" that allows definition of simple commands to be sent over i2c for init, power, speaker and headset on and off using a JSON syntax: So far, TAS57xx, TAS5713, AC101, WM8978 and ES8388 are recognized models where the proper init sequence/volume/power controls are sent. For other codecs that might require an I2C commands, please use the parameter "dac_controlset" that allows definition of simple commands to be sent over i2c for init, power, speaker and headset on and off using a JSON syntax:
```json ```json
{ <command>: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ], { <command>: [ <item1>, <item2>, ... <item3> ],
<command>: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ], <command>: [ <item1>, <item2>, ... <item3> ],
... } ... }
``` ```
Where `<command>` is one of init, poweron, poweroff, speakeron, speakeroff, headseton, headsetoff Where `<command>` is one of init, poweron, poweroff, speakeron, speakeroff, headseton, headsetoff (it **must** be an array even for a single item). Item is any of the following elements
```
{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}
{"gpio":<gpio>,"level":0|1}
{"delay":<ms>}
```
This is standard JSON notation, so if you are not familiar with it, Google is your best friend. Be aware that the '...' means you can have as many entries as you want, it's not part of the syntax. Every section is optional, but it does not make sense to set i2c in the 'dac_config' parameter and not setting anything here.
This is standard JSON notation, so if you are not familiar with it, Google is your best friend. Be aware that the '...' means you can have as many entries as you want, it's not part of the syntax. Every section is optional, but it does not make sense to set i2c in the 'dac_config' parameter and not setting anything here. The parameter 'mode' allows to *or* the register with the value or to *and* it. Don't set 'mode' if you simply want to write. The 'val parameter can be an array [v1, v2,...] to write a serie of bytes in a single i2c burst (in that case 'mode' is ignored). **Note that all values must be decimal**. You can use a validator like [this](https://jsonlint.com) to verify your syntax The `reg` key allow to write registers on i2c bus. The parameter `mode` allows to *or* the register with the value or to *and* it. Don't set `mode` if you simply want to write. The `val` parameter can be an array [v1, v2,...] to write a serie of bytes in a single i2c burst (in that case 'mode' is ignored). **Note that all values must be decimal**. You can use a validator like [this](https://jsonlint.com) to verify your syntax. The `gpio` key is simply to set a gpio as part of DAC action and `delay` allows a pause between elements.
The 'power' command is used when powering on/off the DAC after the idle period (see -C option of squeezelite) and the 'speaker/headset' commands are sent when switching between speakers and headsets (see headset jack detection). The 'power' command is used when powering on/off the DAC after the idle period (see -C option of squeezelite) and the 'speaker/headset' commands are sent when switching between speakers and headsets (see headset jack detection).
@@ -302,7 +309,7 @@ The parameter "gpio_exp_config" is a semicolon (;) separated list with following
``` ```
model=<model>,addr=<addr>,[,port=system|dac][,base=<n>][,count=<n>][,intr=<gpio>][,cs=<gpio>][,speed=<Hz>] model=<model>,addr=<addr>,[,port=system|dac][,base=<n>][,count=<n>][,intr=<gpio>][,cs=<gpio>][,speed=<Hz>]
``` ```
- model: pca9535, pca85xx, mcp23017 and mcp23s17 (SPI version) - model: pca9535, pca85xx, mcp23017, aw9523 and mcp23s17 (SPI version)
- addr: chip i2c/spi address (decimal) - addr: chip i2c/spi address (decimal)
- port (I2C): use either "system" port (shared with display for example) or "dac" port (system is default) - port (I2C): use either "system" port (shared with display for example) or "dac" port (system is default)
- cs (SPI): gpio used for Chip Select - cs (SPI): gpio used for Chip Select
@@ -321,23 +328,23 @@ See [set_GPIO](#set-gpio) for how to set the green and red LEDs (including addre
NB: For named configuration, GPIO affected to green and red LED cannot be changed but brightness option applies NB: For named configuration, GPIO affected to green and red LED cannot be changed but brightness option applies
### LED Strip ### LED Strip
One LED strip with up to 255 addressable LEDs can be configured to offer enhanced visualizations. The LED strip can also be controlled remotely though the LMS server (using the CLI interface). Currently only WS2812B LEDs are supported. Set the LED Strip configuration (or NVS led_vu_config) to `WS2812,length=<n>,gpio=<gpio>, where <n> is the number of leds in the strip (1..255), and <gpio> is the data pin.` One LED strip with up to 255 addressable LEDs can be configured to offer enhanced visualizations. The VU Meter visualizer includes a battery status indicator (see Battery). Currently only WS2812B LEDs are supported. Set the LED Strip hardware configuration, or the NVS led_vu_config syntax is
The latest LMS plugin update is required to set the visualizer mode and brightness, in the ESP32 settings page for the player. The plugin also adds the following CLI command options
``` ```
<playerid> led_visual [<mode>] [brightness(1-255)] type=[WS2812],length=<n>,gpio=<dataPin>[,scale=<gain>]
Toggles or selects the visulaizer mode.
The visualizer brighness can be controled using the optional <brighness> tag.
<playerid> dmx <R,G,B|R,G,B,R,G,B ... R,G,B> [<offset>]
Sets the LED at position "offset" to any RGB color where "R"(red),"G"(green), and "B"(blue) are values from 0(off) to 255(max brightness).
Add additional RGB values to the delimited string to set multiple LEDs.
``` ```
where `<n>` is the number of LEDs in the strip (1..255). A `<scale>` gain value (percentage) can be added to enhance effect responses.
The latest LMS plugin update is required to set the visualizer mode and brightness in the ESP32 Settings page for the player, or a controllable display (see Extra/SqueezeESP32 menus). The plugin adds additional LMS CLI commands.
| Command | Notes |
| -------------------------------------------------- | ----------- |
| \<playerid\> led_visual \[\<mode\>\] \[\<brightness\>\] | Toggles or selects the visualizer "mode".<br />The visualizer brightness(0..255) can be controlled using the "brightness" tag. |
| \<playerid\> dmx \<R,G,B,R,G,B, ... R,G,B\> \[\<offset\>\] | Sets the LED color starting at position "offset"<br /> with "R"(red),"G"(green),and "B"(blue) color sequences.<br />Add additional RGB values to the delimited string to set multiple LEDs.<br /> |
### Rotary Encoder ### Rotary Encoder
One rotary encoder is supported, quadrature shift with press. Such encoders usually have 2 pins for encoders (A and B), and common C that must be set to ground and an optional SW pin for press. A, B and SW must be pulled up, so automatic pull-up is provided by ESP32, but you can add your own resistors. A bit of filtering on A and B (~470nF) helps for debouncing which is not made by software. One general rotary encoder is supported, quadrature shift with press. Such encoders usually have 2 pins for encoders (A and B), and common C that must be set to ground and an optional SW pin for press. A, B and SW must be pulled up, so automatic pull-up is provided by ESP32, but you can add your own resistors. A bit of filtering on A and B (~470nF) helps for debouncing which is not made by software.
Encoder is normally hard-coded to respectively knob left, right and push on LMS and to volume down/up/play toggle on BT and AirPlay. Using the option 'volume' makes it hard-coded to volume down/up/play toggle all the time (even in LMS). The option 'longpress' allows an alternate mode when SW is long-pressed. In that mode, left is previous, right is next and press is toggle. Every long press on SW alternates between modes (the main mode actual behavior depends on 'volume'). Encoder is normally hard-coded to respectively knob left, right and push on LMS and to volume down/up/play toggle on BT, AirPlay and Spotify. Using the option 'volume' makes it hard-coded to volume down/up/play toggle all the time (even in LMS). The option 'longpress' allows an alternate mode when SW is long-pressed. In that mode, left is previous, right is next and press is toggle. Every long press on SW alternates between modes (the main mode actual behavior depends on 'volume').
There is also the possibility to use 'knobonly' option (exclusive with 'volume' and 'longpress'). This mode attempts to offer a single knob full navigation which is a bit contorded due to LMS UI's principles. Left, Right and Press obey to LMS's navigation rules and especially Press always goes to lower submenu item, even when navigating in the Music Library. That causes a challenge as there is no 'Play', 'Back' or 'Pause' button. Workaround are as of below: There is also the possibility to use 'knobonly' option (exclusive with 'volume' and 'longpress'). This mode attempts to offer a single knob full navigation which is a bit contorded due to LMS UI's principles. Left, Right and Press obey to LMS's navigation rules and especially Press always goes to lower submenu item, even when navigating in the Music Library. That causes a challenge as there is no 'Play', 'Back' or 'Pause' button. Workaround are as of below:
- longpress is 'Play' - longpress is 'Play'
@@ -358,7 +365,16 @@ The SW gpio is optional, you can re-affect it to a pure button if you prefer but
See also the "IMPORTANT NOTE" on the "Buttons" section and remember that when 'lms_ctrls_raw' (see below) is activated, none of these knobonly,volume,longpress options apply, raw button codes (not actions) are simply sent to LMS See also the "IMPORTANT NOTE" on the "Buttons" section and remember that when 'lms_ctrls_raw' (see below) is activated, none of these knobonly,volume,longpress options apply, raw button codes (not actions) are simply sent to LMS
**Note that gpio 36 and 39 are input only and cannot use interrupt, so they cannot be set to A or B. When using them for SW, a 100ms polling is used which is expensive** **Note that on esp32, gpio 36 and 39 are input only and cannot use interrupt, so they cannot be set to A or B. When using them for SW, a 100ms polling is used which is expensive**
### Volume Rotary Encoder
One dedicated volume rotary encoder is supported, quadrature shift with press. Encoder is hard-coded to volume-up, down and play toggle for LMS, BT, AirPlay and Spotify (see note above for filtering and HW note as well GPIO 36 and 39 on esp32)
Use parameter volume_rotary with the following syntax:
```
A=<gpio>,B=<gpio>[,SW=gpio>]
```
### Buttons ### Buttons
Buttons are described using a JSON string with the following syntax Buttons are described using a JSON string with the following syntax
@@ -626,12 +642,20 @@ docker run -it -v `pwd`:/workspace/squeezelite-esp32 sle118/squeezelite-esp32-id
The above command will mount this repo into the docker container and start a bash terminal. From there, simply run idf.py build to build, etc. Note that at the time of writing these lines, flashing is not possible for docker running under windows https://github.com/docker/for-win/issues/1018. The above command will mount this repo into the docker container and start a bash terminal. From there, simply run idf.py build to build, etc. Note that at the time of writing these lines, flashing is not possible for docker running under windows https://github.com/docker/for-win/issues/1018.
### Manual Install of ESP-IDF ### Manual Install of ESP-IDF
You can install IDF manually on Linux or Windows (using the Subsystem for Linux) following the instructions at: https://www.instructables.com/id/ESP32-Development-on-Windows-Subsystem-for-Linux/ or see here https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/windows-setup.html for a direct install. You also need a few extra Python libraries for cspot by addingsudo `pip3 install protobuf grpcio-tools` First you need git and python (e.g 3.10.x), install these and let it add to system path.
**Use the esp-idf 4.3.5 https://github.com/espressif/esp-idf/tree/release/v4.3.5 ** or the 4.4.5 (and above version) if you want to build for esp32-s3
**Use the esp-idf 4.3.5 https://github.com/espressif/esp-idf/tree/release/v4.3.5 ** or the 4.4.5 (and above version) if you want to build for esp32-s3. You should clone recursively the whole branch (at the version you need) `git clone -b v4.3.5 https://github.com/espressif/esp-idf --recursive`and run the installer (`install.bat [esp32[,esp32s3]]` from there. Some Windows version (at least) have now a SSL certificate issue. You can workaround this by editing idf-tools.py and adding the following under ìmport ssl`
```
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
```
And because the fun never ends, some Windows installations might fail to build a few files and spit a tons of errors on the output. It seems that the cache of the compile is a problem, so try to disable it by running `idf.py --no-ccache build` (I know...)
## Building SqueezeESP32 ## Building SqueezeESP32
When initially cloning the repo, make sure you do it recursively. For example: `git clone --recursive https://github.com/sle118/squeezelite-esp32.git` When initially cloning the repo, make sure you do it recursively. For example: `git clone --recursive https://github.com/sle118/squeezelite-esp32.git`. You also should install cspot additional components for protobuf use.
```
$ sudo pip3 install protobuf grpcio-tools
```
NB: I need to check on a fresh installation, but you might also require "protoc". You should do that within the esp32 local Python environment.
Don't forget to choose one of the config files in build_scripts/ and rename it sdkconfig.defaults or sdkconfig as many important WiFi/BT options are set there. **The codecs libraries will not be rebuilt by these scripts (it's a tedious process - see below)** Don't forget to choose one of the config files in build_scripts/ and rename it sdkconfig.defaults or sdkconfig as many important WiFi/BT options are set there. **The codecs libraries will not be rebuilt by these scripts (it's a tedious process - see below)**

View File

@@ -1,48 +0,0 @@
param (
[Parameter(Position=0, Mandatory=$false)]
[ValidateSet("t", "u")]
[string]$option
)
# Define the directory to apply changes to
$targetDir = "components\wifi-manager\webapp\dist"
# Get the current directory
$currentDir = Get-Location
# Get list of files from the file system
$fsFiles = Get-ChildItem -Recurse $targetDir -File | ForEach-Object {
$_.FullName.Substring($currentDir.Path.Length + 1).Replace("\", "/")
}
# Get list of files from the Git index
$indexFiles = git ls-files -s $targetDir | ForEach-Object {
($_ -split "\s+")[3]
}
# Combine and remove duplicates
$allFiles = $fsFiles + $indexFiles | Sort-Object -Unique
# Apply the git command based on the option
$allFiles | ForEach-Object {
$relativePath = $_
$isInIndex = $indexFiles -contains $relativePath
if ($null -eq $option) {
$status = if ($isInIndex) { 'tracked' } else { 'not tracked' }
Write-Host "$relativePath is $status"
}
elseif ($isInIndex) {
if ($option -eq "t") {
git update-index --no-skip-worktree $relativePath
Write-Host "Started tracking changes in $relativePath"
}
elseif ($option -eq "u") {
git update-index --skip-worktree $relativePath
Write-Host "Stopped tracking changes in $relativePath"
}
}
else {
Write-Host "File $relativePath is not tracked."
}
}

View File

@@ -1,6 +1,6 @@
/* libFLAC - Free Lossless Audio Codec library /* libFLAC - Free Lossless Audio Codec library
* Copyright (C) 2000-2009 Josh Coalson * Copyright (C) 2000-2009 Josh Coalson
* Copyright (C) 2011-2022 Xiph.Org Foundation * Copyright (C) 2011-2023 Xiph.Org Foundation
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions
@@ -782,6 +782,25 @@ FLAC_API void FLAC__stream_decoder_delete(FLAC__StreamDecoder *decoder);
*/ */
FLAC_API FLAC__bool FLAC__stream_decoder_set_ogg_serial_number(FLAC__StreamDecoder *decoder, long serial_number); FLAC_API FLAC__bool FLAC__stream_decoder_set_ogg_serial_number(FLAC__StreamDecoder *decoder, long serial_number);
/** Set the "allow Ogg chaining" flag. If set, the Ogg decoder will
* prepare to receive a new stream once the last Ogg page arrives for
* the stream encapsulating the FLAC audio data. This can be used to
* support chained Ogg FLAC streams; a new \c STREAMINFO signals the
* beginning of a new stream.
*
* \note
* This function has no effect with native FLAC decoding.
*
* \default \c false
* \param decoder A decoder instance to set.
* \param allow Whether to allow chained streams.
* \assert
* \code decoder != NULL \endcode
* \retval FLAC__bool
* \c false if the decoder is already initialized, else \c true.
*/
FLAC_API FLAC__bool FLAC__stream_decoder_set_ogg_chaining(FLAC__StreamDecoder* decoder, FLAC__bool value);
/** Set the "MD5 signature checking" flag. If \c true, the decoder will /** Set the "MD5 signature checking" flag. If \c true, the decoder will
* compute the MD5 signature of the unencoded audio data while decoding * compute the MD5 signature of the unencoded audio data while decoding
* and compare it to the signature from the STREAMINFO block, if it * and compare it to the signature from the STREAMINFO block, if it
@@ -906,6 +925,17 @@ FLAC_API FLAC__StreamDecoderState FLAC__stream_decoder_get_state(const FLAC__Str
*/ */
FLAC_API const char *FLAC__stream_decoder_get_resolved_state_string(const FLAC__StreamDecoder *decoder); FLAC_API const char *FLAC__stream_decoder_get_resolved_state_string(const FLAC__StreamDecoder *decoder);
/** Get the "allow Ogg chaining" flag as described in
* \code FLAC__stream_decoder_set_ogg_chaining \endcode.
*
* \param decoder A decoder instance to query.
* \assert
* \code decoder != NULL \endcode
* \retval FLAC__bool
* See above.
*/
FLAC_API FLAC__bool FLAC__stream_decoder_get_ogg_chaining(const FLAC__StreamDecoder* decoder);
/** Get the "MD5 signature checking" flag. /** Get the "MD5 signature checking" flag.
* This is the value of the setting, not whether or not the decoder is * This is the value of the setting, not whether or not the decoder is
* currently checking the MD5 (remember, it can be turned off automatically * currently checking the MD5 (remember, it can be turned off automatically

Binary file not shown.

176
components/display/SH1122.c Normal file
View File

@@ -0,0 +1,176 @@
/**
* Copyright (c) 2017-2018 Tara Keeling
* 2020 Philippe G.
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <esp_heap_caps.h>
#include <esp_log.h>
#include "gds.h"
#include "gds_private.h"
#define SHADOW_BUFFER
#define PAGE_BLOCK 1024
#define min(a,b) (((a) < (b)) ? (a) : (b))
static char TAG[] = "SH1122";
struct PrivateSpace {
uint8_t *iRAM, *Shadowbuffer;
uint8_t PageSize;
};
// Functions are not declared to minimize # of lines
static void SetColumnAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) {
Device->WriteCommand( Device, 0x10 | (Start >> 4) );
Device->WriteCommand( Device, 0x00 | (Start & 0x0f) );
}
static void SetRowAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) {
Device->WriteCommand( Device, 0xB0 );
Device->WriteCommand( Device, Start );
}
static void Update( struct GDS_Device* Device ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
// RAM is by columns of 4 pixels ...
SetColumnAddress( Device, 0, Device->Width / 4 - 1);
#ifdef SHADOW_BUFFER
uint16_t *optr = (uint16_t*) Private->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer;
bool dirty = false;
for (int r = 0, page = 0; r < Device->Height; r++) {
// look for change and update shadow (cheap optimization = width always / by 2)
for (int c = Device->Width / 2 / 2; --c >= 0;) {
if (*optr != *iptr) {
dirty = true;
*optr = *iptr;
}
iptr++; optr++;
}
// one line done, check for page boundary
if (++page == Private->PageSize) {
if (dirty) {
SetRowAddress( Device, r - page + 1, r );
if (Private->iRAM) {
memcpy(Private->iRAM, Private->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 );
Device->WriteData( Device, Private->iRAM, Device->Width * page / 2 );
} else {
Device->WriteData( Device, Private->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2);
}
dirty = false;
}
page = 0;
}
}
#else
SetRowAddress( Device, 0, Device->Height - 1 );
for (int r = 0; r < Device->Height; r += Private->PageSize) {
if (Private->iRAM) {
memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
Device->WriteData( Device, Private->iRAM, Private->PageSize * Device->Width / 2 );
} else {
Device->WriteData( Device, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
}
}
#endif
}
static void SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) {
if (Layout->HFlip) {
Device->WriteCommand( Device, 0x40 + 0x20 );
Device->WriteCommand( Device, 0xA1 );
} else {
Device->WriteCommand( Device, 0x40 + 0x00 );
Device->WriteCommand( Device, 0xA0 );
}
Device->WriteCommand( Device, Layout->VFlip ? 0xC8 : 0xC0 );
Device->WriteCommand( Device, Layout->Invert ? 0xA7 : 0xA6 );
}
static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAF ); }
static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAE ); }
static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
Device->WriteCommand( Device, 0x81 );
Device->WriteCommand( Device, Contrast );
}
static bool Init( struct GDS_Device* Device ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
// find a page size that is not too small is an integer of height
Private->PageSize = min(8, PAGE_BLOCK / (Device->Width / 2));
while (Private->PageSize && Device->Height != (Device->Height / Private->PageSize) * Private->PageSize) Private->PageSize--;
#ifdef SHADOW_BUFFER
Private->Shadowbuffer = malloc( Device->FramebufferSize );
memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize);
#endif
// only use iRAM for SPI
if (Device->IF == GDS_IF_SPI) {
Private->iRAM = heap_caps_malloc( Private->PageSize * Device->Width / 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
}
ESP_LOGI(TAG, "SH1122 page %u, iRAM %p", Private->PageSize, Private->iRAM);
// need to be off and disable display RAM
Device->DisplayOff( Device );
Device->WriteCommand( Device, 0xA5 );
// Display Offset
Device->WriteCommand( Device, 0xD3 );
Device->WriteCommand( Device, 0 );
// set flip modes
struct GDS_Layout Layout = { };
Device->SetLayout( Device, &Layout );
// set Clocks => check value
Device->WriteCommand( Device, 0xD5 );
Device->WriteCommand( Device, ( 0x04 << 4 ) | 0x00 );
// MUX Ratio => fixed
Device->WriteCommand( Device, 0xA8 );
Device->WriteCommand( Device, Device->Height - 1);
// no Display Inversion
Device->WriteCommand( Device, 0xA6 );
// gone with the wind
Device->WriteCommand( Device, 0xA4 );
Device->DisplayOn( Device );
Device->Update( Device );
return true;
}
static const struct GDS_Device SH1122 = {
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
.SetLayout = SetLayout,
.Update = Update, .Init = Init,
.Mode = GDS_GRAYSCALE, .Depth = 4,
.HighNibble = true,
};
struct GDS_Device* SH1122_Detect(char *Driver, struct GDS_Device* Device) {
if (!strcasestr(Driver, "SH1122")) return NULL;
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
*Device = SH1122;
return Device;
}

View File

@@ -71,8 +71,8 @@ static void Update( struct GDS_Device* Device ) {
if (dirty) { if (dirty) {
uint16_t *optr = (uint16_t*) Private->iRAM, *iptr = (uint16_t*) (Private->Shadowbuffer + (r - page + 1) * Device->Width / 2); uint16_t *optr = (uint16_t*) Private->iRAM, *iptr = (uint16_t*) (Private->Shadowbuffer + (r - page + 1) * Device->Width / 2);
SetRowAddress( Device, r - page + 1, r ); SetRowAddress( Device, r - page + 1, r );
// need byte swapping
for (int i = page * Device->Width / 2 / 2; --i >= 0; iptr++) *optr++ = (*iptr >> 8) | (*iptr << 8); for (int i = page * Device->Width / 2 / 2; --i >= 0; iptr++) *optr++ = (*iptr >> 8) | (*iptr << 8);
//memcpy(Private->iRAM, Private->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 );
Device->WriteCommand( Device, 0x5c ); Device->WriteCommand( Device, 0x5c );
Device->WriteData( Device, Private->iRAM, Device->Width * page / 2 ); Device->WriteData( Device, Private->iRAM, Device->Width * page / 2 );
dirty = false; dirty = false;
@@ -84,14 +84,10 @@ static void Update( struct GDS_Device* Device ) {
for (int r = 0; r < Device->Height; r += Private->PageSize) { for (int r = 0; r < Device->Height; r += Private->PageSize) {
SetRowAddress( Device, r, r + Private->PageSize - 1 ); SetRowAddress( Device, r, r + Private->PageSize - 1 );
Device->WriteCommand( Device, 0x5c ); Device->WriteCommand( Device, 0x5c );
if (Private->iRAM) { // need byte swapping
uint16_t *optr = (uint16_t*) Private->iRAM, *iptr = (uint16_t*) (Device->Framebuffer + r * Device->Width / 2); uint16_t *optr = (uint16_t*) Private->iRAM, *iptr = (uint16_t*) (Device->Framebuffer + r * Device->Width / 2);
for (int i = Private->PageSize * Device->Width / 2 / 2; --i >= 0; iptr++) *optr++ = (*iptr >> 8) | (*iptr << 8); for (int i = Private->PageSize * Device->Width / 2 / 2; --i >= 0; iptr++) *optr++ = (*iptr >> 8) | (*iptr << 8);
//memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 ); Device->WriteData( Device, Private->iRAM, Private->PageSize * Device->Width / 2 );
Device->WriteData( Device, Private->iRAM, Private->PageSize * Device->Width / 2 );
} else {
Device->WriteData( Device, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
}
} }
#endif #endif
} }

View File

@@ -109,7 +109,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
int c = x1; int c = x1;
// for a row that is not on a boundary, no optimization possible // for a row that is not on a boundary, no optimization possible
while (r & 0x07 && r <= y2) { while (r & 0x07 && r <= y2) {
for (c = x1; c <= x2; c++) DrawPixelFast( Device, c, r, Color ); for (c = x1; c <= x2; c++) Device->DrawPixelFast( Device, c, r, Color );
r++; r++;
} }
// go fast if we have more than 8 lines to write // go fast if we have more than 8 lines to write
@@ -117,7 +117,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
memset(optr + Width * r + x1, _Color, x2 - x1 + 1); memset(optr + Width * r + x1, _Color, x2 - x1 + 1);
r += 8; r += 8;
} else while (r <= y2) { } else while (r <= y2) {
for (c = x1; c <= x2; c++) DrawPixelFast( Device, c, r, Color ); for (c = x1; c <= x2; c++) Device->DrawPixelFast( Device, c, r, Color );
r++; r++;
} }
} }
@@ -133,10 +133,10 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
// try to do byte processing as much as possible // try to do byte processing as much as possible
for (int r = y1; r <= y2; r++) { for (int r = y1; r <= y2; r++) {
int c = x1; int c = x1;
if (c & 0x01) DrawPixelFast( Device, c++, r, Color); if (c & 0x01) Device->DrawPixelFast( Device, c++, r, Color);
int chunk = (x2 - c + 1) >> 1; int chunk = (x2 - c + 1) >> 1;
memset(optr + ((r * Width + c) >> 1), _Color, chunk); memset(optr + ((r * Width + c) >> 1), _Color, chunk);
if (c + chunk <= x2) DrawPixelFast( Device, x2, r, Color); if (c + chunk <= x2) Device->DrawPixelFast( Device, x2, r, Color);
} }
} }
} else if (Device->Depth == 8) { } else if (Device->Depth == 8) {
@@ -148,7 +148,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
} else { } else {
for (int y = y1; y <= y2; y++) { for (int y = y1; y <= y2; y++) {
for (int x = x1; x <= x2; x++) { for (int x = x1; x <= x2; x++) {
DrawPixelFast( Device, x, y, Color); Device->DrawPixelFast( Device, x, y, Color);
} }
} }
} }
@@ -171,11 +171,77 @@ bool GDS_Reset( struct GDS_Device* Device ) {
return true; return true;
} }
static void IRAM_ATTR DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint32_t YBit = ( Y & 0x07 );
uint8_t* FBOffset;
/*
* We only need to modify the Y coordinate since the pitch
* of the screen is the same as the width.
* Dividing Y by 8 gives us which row the pixel is in but not
* the bit position.
*/
Y>>= 3;
FBOffset = Device->Framebuffer + ( ( Y * Device->Width ) + X );
if ( Color == GDS_COLOR_XOR ) {
*FBOffset ^= BIT( YBit );
} else {
*FBOffset = ( Color == GDS_COLOR_BLACK ) ? *FBOffset & ~BIT( YBit ) : *FBOffset | BIT( YBit );
}
}
static void IRAM_ATTR DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f));
}
static void IRAM_ATTR DrawPixel4FastHigh( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
*FBOffset = X & 0x01 ? ((*FBOffset & 0xf0) | (Color & 0x0f)) : (*FBOffset & 0x0f) | ((Color & 0x0f) << 4);
}
static void IRAM_ATTR DrawPixel8Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
Device->Framebuffer[Y * Device->Width + X] = Color;
}
// assumes that Color is 16 bits R..RG..GB..B from MSB to LSB and FB wants 1st serialized byte to start with R
static void IRAM_ATTR DrawPixel16Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint16_t* FBOffset = (uint16_t*) Device->Framebuffer + Y * Device->Width + X;
*FBOffset = __builtin_bswap16(Color);
}
// assumes that Color is 18 bits RGB from MSB to LSB RRRRRRGGGGGGBBBBBB, so byte[0] is B
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = xxRRRRRR xxGGGGGG xxBBBBBB
static void IRAM_ATTR DrawPixel18Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
*FBOffset++ = Color >> 12; *FBOffset++ = (Color >> 6) & 0x3f; *FBOffset = Color & 0x3f;
}
// assumes that Color is 24 bits RGB from MSB to LSB RRRRRRRRGGGGGGGGBBBBBBBB, so byte[0] is B
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = RRRRRRRR GGGGGGGG BBBBBBBB
static void IRAM_ATTR DrawPixel24Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
*FBOffset++ = Color >> 16; *FBOffset++ = Color >> 8; *FBOffset = Color;
}
bool GDS_Init( struct GDS_Device* Device ) { bool GDS_Init( struct GDS_Device* Device ) {
if (Device->Depth > 8) Device->FramebufferSize = Device->Width * Device->Height * ((8 + Device->Depth - 1) / 8); if (Device->Depth > 8) Device->FramebufferSize = Device->Width * Device->Height * ((8 + Device->Depth - 1) / 8);
else Device->FramebufferSize = (Device->Width * Device->Height) / (8 / Device->Depth); else Device->FramebufferSize = (Device->Width * Device->Height) / (8 / Device->Depth);
// set the proper DrawPixel function if not already set by driver
if (!Device->DrawPixelFast) {
if (Device->Depth == 1) Device->DrawPixelFast = DrawPixel1Fast;
else if (Device->Depth == 4 && Device->HighNibble) Device->DrawPixelFast = DrawPixel4FastHigh;
else if (Device->Depth == 4) Device->DrawPixelFast = DrawPixel4Fast;
else if (Device->Depth == 8) Device->DrawPixelFast = DrawPixel8Fast;
else if (Device->Depth == 16) Device->DrawPixelFast = DrawPixel16Fast;
else if (Device->Depth == 24 && Device->Mode == GDS_RGB666) Device->DrawPixelFast = DrawPixel18Fast;
else if (Device->Depth == 24 && Device->Mode == GDS_RGB888) Device->DrawPixelFast = DrawPixel24Fast;
}
// allocate FB unless explicitely asked not to // allocate FB unless explicitely asked not to
if (!(Device->Alloc & GDS_ALLOC_NONE)) { if (!(Device->Alloc & GDS_ALLOC_NONE)) {
if ((Device->Alloc & GDS_ALLOC_IRAM) || ((Device->Alloc & GDS_ALLOC_IRAM_SPI) && Device->IF == GDS_IF_SPI)) { if ((Device->Alloc & GDS_ALLOC_IRAM) || ((Device->Alloc & GDS_ALLOC_IRAM_SPI) && Device->IF == GDS_IF_SPI)) {

View File

@@ -45,7 +45,7 @@ __attribute__( ( always_inline ) ) static inline void SwapInt( int* a, int* b )
} }
void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) { void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
DrawPixelFast( Device, X, Y, Color ); Device->DrawPixelFast( Device, X, Y, Color );
} }
void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color ) { void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color ) {
@@ -63,7 +63,7 @@ void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Colo
if (y < 0) y = 0; if (y < 0) y = 0;
else if (y >= Device->Height) y = Device->Height - 1; else if (y >= Device->Height) y = Device->Height - 1;
for ( ; x < XEnd; x++ ) DrawPixelFast( Device, x, y, Color ); for ( ; x < XEnd; x++ ) Device->DrawPixelFast( Device, x, y, Color );
} }
void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Color ) { void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Color ) {
@@ -97,7 +97,7 @@ static inline void DrawWideLine( struct GDS_Device* Device, int x0, int y0, int
for ( ; x < x1; x++ ) { for ( ; x < x1; x++ ) {
if ( IsPixelVisible( Device, x, y ) == true ) { if ( IsPixelVisible( Device, x, y ) == true ) {
DrawPixelFast( Device, x, y, Color ); Device->DrawPixelFast( Device, x, y, Color );
} }
if ( Error > 0 ) { if ( Error > 0 ) {
@@ -126,7 +126,7 @@ static inline void DrawTallLine( struct GDS_Device* Device, int x0, int y0, int
for ( ; y < y1; y++ ) { for ( ; y < y1; y++ ) {
if ( IsPixelVisible( Device, x, y ) == true ) { if ( IsPixelVisible( Device, x, y ) == true ) {
DrawPixelFast( Device, x, y, Color ); Device->DrawPixelFast( Device, x, y, Color );
} }
if ( Error > 0 ) { if ( Error > 0 ) {
@@ -213,37 +213,65 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
iptr += Height; iptr += Height;
} }
} }
} else if (Device->Depth == 4) { } else if (Device->Depth == 4) {
uint8_t *optr = Device->Framebuffer; uint8_t *optr = Device->Framebuffer;
int LineLen = Device->Width >> 1; int LineLen = Device->Width >> 1;
Height >>= 3; Height >>= 3;
Color &= 0x0f; Color &= 0x0f;
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) { if (Device->HighNibble) {
uint8_t Byte = BitReverseTable256[*Data++]; for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
// we need to linearize code to let compiler better optimize uint8_t Byte = BitReverseTable256[*Data++];
if (c & 0x01) { // we need to linearize code to let compiler better optimize
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1; if (c & 0x01) {
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1; *optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1; *optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1; *optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1; *optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1; *optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1; *optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; *optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
} else { *optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1; } else {
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1; *optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1; *optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1; *optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1; *optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1; *optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1; *optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; *optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
} *optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen;
// end of a column, move to next one }
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); } // end of a column, move to next one
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }
}
} else {
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
uint8_t Byte = BitReverseTable256[*Data++];
// we need to linearize code to let compiler better optimize
if (c & 0x01) {
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen;
} else {
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen;
}
// end of a column, move to next one
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }
}
} }
} else if (Device->Depth == 8) { } else if (Device->Depth == 8) {
uint8_t *optr = Device->Framebuffer; uint8_t *optr = Device->Framebuffer;
@@ -320,14 +348,14 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
// don't know bitdepth, use brute-force solution // don't know bitdepth, use brute-force solution
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) { for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
uint8_t Byte = *Data++; uint8_t Byte = *Data++;
DrawPixelFast( Device, c, (r << 3) + 7, (Byte & 0x01) * Color ); Byte >>= 1; Device->DrawPixelFast( Device, c, (r << 3) + 7, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 6, (Byte & 0x01) * Color ); Byte >>= 1; Device->DrawPixelFast( Device, c, (r << 3) + 6, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 5, (Byte & 0x01) * Color ); Byte >>= 1; Device->DrawPixelFast( Device, c, (r << 3) + 5, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 4, (Byte & 0x01) * Color ); Byte >>= 1; Device->DrawPixelFast( Device, c, (r << 3) + 4, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 3, (Byte & 0x01) * Color ); Byte >>= 1; Device->DrawPixelFast( Device, c, (r << 3) + 3, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 2, (Byte & 0x01) * Color ); Byte >>= 1; Device->DrawPixelFast( Device, c, (r << 3) + 2, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 1, (Byte & 0x01) * Color ); Byte >>= 1; Device->DrawPixelFast( Device, c, (r << 3) + 1, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 0, (Byte & 0x01) * Color ); Device->DrawPixelFast( Device, c, (r << 3) + 0, (Byte & 0x01) * Color );
if (++r == Height) { c++; r = 0; } if (++r == Height) { c++; r = 0; }
} }
/* for better understanding, here is the mundane version /* for better understanding, here is the mundane version

View File

@@ -98,6 +98,7 @@ struct GDS_Device {
uint16_t Width, TextWidth; uint16_t Width, TextWidth;
uint16_t Height; uint16_t Height;
uint8_t Depth, Mode; uint8_t Depth, Mode;
bool HighNibble;
uint8_t Alloc; uint8_t Alloc;
uint8_t* Framebuffer; uint8_t* Framebuffer;
@@ -155,69 +156,9 @@ static inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) {
return Result; return Result;
} }
static inline void DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint32_t YBit = ( Y & 0x07 );
uint8_t* FBOffset;
/*
* We only need to modify the Y coordinate since the pitch
* of the screen is the same as the width.
* Dividing Y by 8 gives us which row the pixel is in but not
* the bit position.
*/
Y>>= 3;
FBOffset = Device->Framebuffer + ( ( Y * Device->Width ) + X );
if ( Color == GDS_COLOR_XOR ) {
*FBOffset ^= BIT( YBit );
} else {
*FBOffset = ( Color == GDS_COLOR_BLACK ) ? *FBOffset & ~BIT( YBit ) : *FBOffset | BIT( YBit );
}
}
static inline void DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f));
}
static inline void DrawPixel8Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
Device->Framebuffer[Y * Device->Width + X] = Color;
}
// assumes that Color is 16 bits R..RG..GB..B from MSB to LSB and FB wants 1st serialized byte to start with R
static inline void DrawPixel16Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint16_t* FBOffset = (uint16_t*) Device->Framebuffer + Y * Device->Width + X;
*FBOffset = __builtin_bswap16(Color);
}
// assumes that Color is 18 bits RGB from MSB to LSB RRRRRRGGGGGGBBBBBB, so byte[0] is B
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = xxRRRRRR xxGGGGGG xxBBBBBB
static inline void DrawPixel18Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
*FBOffset++ = Color >> 12; *FBOffset++ = (Color >> 6) & 0x3f; *FBOffset = Color & 0x3f;
}
// assumes that Color is 24 bits RGB from MSB to LSB RRRRRRRRGGGGGGGGBBBBBBBB, so byte[0] is B
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = RRRRRRRR GGGGGGGG BBBBBBBB
static inline void DrawPixel24Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
*FBOffset++ = Color >> 16; *FBOffset++ = Color >> 8; *FBOffset = Color;
}
static inline void IRAM_ATTR 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) DrawPixel4Fast( Device, X, Y, Color );
else if (Device->Depth == 1) DrawPixel1Fast( Device, X, Y, Color );
else if (Device->Depth == 16) DrawPixel16Fast( Device, X, Y, Color );
else if (Device->Depth == 24 && Device->Mode == GDS_RGB666) DrawPixel18Fast( Device, X, Y, Color );
else if (Device->Depth == 24 && Device->Mode == GDS_RGB888) DrawPixel24Fast( Device, X, Y, Color );
else if (Device->Depth == 8) DrawPixel8Fast( Device, X, Y, Color );
}
static inline void IRAM_ATTR DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) { static inline void IRAM_ATTR DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) {
if ( IsPixelVisible( Device, x, y ) == true ) { if ( IsPixelVisible( Device, x, y ) == true ) {
DrawPixelFast( Device, x, y, Color ); Device->DrawPixelFast( Device, x, y, Color );
} }
} }

View File

@@ -108,7 +108,7 @@ bool GDS_TextLine(struct GDS_Device* Device, int N, int Pos, int Attr, char *Tex
int Y_min = max(0, Device->Lines[N].Y), Y_max = max(0, Device->Lines[N].Y + Device->Lines[N].Font->Height); int Y_min = max(0, Device->Lines[N].Y), Y_max = max(0, Device->Lines[N].Y + Device->Lines[N].Font->Height);
for (int c = (Attr & GDS_TEXT_CLEAR_EOL) ? X : 0; c < Device->TextWidth; c++) for (int c = (Attr & GDS_TEXT_CLEAR_EOL) ? X : 0; c < Device->TextWidth; c++)
for (int y = Y_min; y < Y_max; y++) for (int y = Y_min; y < Y_max; y++)
DrawPixelFast( Device, c, y, GDS_COLOR_BLACK ); Device->DrawPixelFast( Device, c, y, GDS_COLOR_BLACK );
} }
GDS_FontDrawString( Device, X, Device->Lines[N].Y, Text, GDS_COLOR_WHITE ); GDS_FontDrawString( Device, X, Device->Lines[N].Y, Text, GDS_COLOR_WHITE );

View File

@@ -63,6 +63,7 @@ static EXT_RAM_ATTR struct {
} displayer; } displayer;
static const char *known_drivers[] = {"SH1106", static const char *known_drivers[] = {"SH1106",
"SH1122",
"SSD1306", "SSD1306",
"SSD1322", "SSD1322",
"SSD1326", "SSD1326",
@@ -79,8 +80,8 @@ static void displayer_task(void *args);
static void display_sleep(void); static void display_sleep(void);
struct GDS_Device *display; struct GDS_Device *display;
extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect; extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SH1122_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect;
GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect, NULL }; GDS_DetectFunc *drivers[] = { SH1106_Detect, SH1122_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect, NULL };
/**************************************************************************************** /****************************************************************************************
* *

View File

@@ -293,11 +293,8 @@ bool led_strip_init(struct led_strip_t *led_strip)
static EXT_RAM_ATTR StackType_t xStack[LED_STRIP_TASK_SIZE] __attribute__ ((aligned (4))); static EXT_RAM_ATTR StackType_t xStack[LED_STRIP_TASK_SIZE] __attribute__ ((aligned (4)));
if ((led_strip == NULL) || if ((led_strip == NULL) ||
(led_strip->rmt_channel >= RMT_CHANNEL_MAX) ||
(led_strip->gpio > GPIO_NUM_33) ||
(led_strip->led_strip_working == NULL) || (led_strip->led_strip_working == NULL) ||
(led_strip->led_strip_showing == NULL) || (led_strip->led_strip_showing == NULL) ||
(led_strip->led_strip_length == 0) ||
(led_strip->access_semaphore == NULL)) { (led_strip->access_semaphore == NULL)) {
return false; return false;
} }

View File

@@ -24,6 +24,7 @@
#include "monitor.h" #include "monitor.h"
#include "led_strip.h" #include "led_strip.h"
#include "platform_config.h" #include "platform_config.h"
#include "services.h"
#include "led_vu.h" #include "led_vu.h"
static const char *TAG = "led_vu"; static const char *TAG = "led_vu";
@@ -55,6 +56,7 @@ static EXT_RAM_ATTR struct {
int vu_start_l; int vu_start_l;
int vu_start_r; int vu_start_r;
int vu_status; int vu_status;
int vu_scale;
} strip; } strip;
static int led_addr(int pos ) { static int led_addr(int pos ) {
@@ -70,31 +72,31 @@ static void battery_svc(float value, int cells) {
if (battery_handler_chain) battery_handler_chain(value, cells); if (battery_handler_chain) battery_handler_chain(value, cells);
} }
/****************************************************************************************
* Suspend.
*
*/
static void led_vu_sleep(void) {
led_vu_clear(led_display);
}
/**************************************************************************************** /****************************************************************************************
* Initialize the led vu strip if configured. * Initialize the led vu strip if configured.
* *
*/ */
void led_vu_init() void led_vu_init()
{ {
char* p;
char* config = config_alloc_get_str("led_vu_config", NULL, "N/A"); char* config = config_alloc_get_str("led_vu_config", NULL, "N/A");
// Initialize led VU strip PARSE_PARAM(config, "length",'=', strip.length);
char* drivername = strcasestr(config, "WS2812"); PARSE_PARAM(config, "gpio",'=', strip.gpio);
if ((p = strcasestr(config, "length")) != NULL) {
strip.length = atoi(strchr(p, '=') + 1);
} // else 0
if ((p = strcasestr(config, "gpio")) != NULL) {
strip.gpio = atoi(strchr(p, '=') + 1);
} else {
strip.gpio = LED_VU_DEFAULT_GPIO;
}
// check for valid configuration // check for valid configuration
if (!drivername || !strip.gpio) { if (!strip.gpio) {
ESP_LOGI(TAG, "led_vu configuration invalid"); ESP_LOGI(TAG, "led_vu configuration invalid");
goto done; goto done;
} }
strip.vu_scale = 100;
PARSE_PARAM(config, "scale",'=',strip.vu_scale);
battery_handler_chain = battery_handler_svc; battery_handler_chain = battery_handler_svc;
battery_handler_svc = battery_svc; battery_handler_svc = battery_svc;
@@ -114,7 +116,7 @@ void led_vu_init()
strip.vu_start_r = strip.vu_length + 1; strip.vu_start_r = strip.vu_length + 1;
strip.vu_status = strip.vu_length; strip.vu_status = strip.vu_length;
} }
ESP_LOGI(TAG, "vu meter using length:%d left:%d right:%d status:%d", strip.vu_length, strip.vu_start_l, strip.vu_start_r, strip.vu_status); ESP_LOGI(TAG, "vu meter using length:%d left:%d right:%d status:%d scale:%d", strip.vu_length, strip.vu_start_l, strip.vu_start_r, strip.vu_status, strip.vu_scale);
// create driver configuration // create driver configuration
led_strip_config.rgb_led_type = RGB_LED_TYPE_WS2812; led_strip_config.rgb_led_type = RGB_LED_TYPE_WS2812;
@@ -138,6 +140,8 @@ void led_vu_init()
// reserver max memory for remote management systems // reserver max memory for remote management systems
rmt_set_mem_block_num(led_strip_config.rmt_channel, 7); rmt_set_mem_block_num(led_strip_config.rmt_channel, 7);
services_sleep_setsuspend(led_vu_sleep);
led_vu_clear(led_display); led_vu_clear(led_display);
done: done:
@@ -157,6 +161,14 @@ uint16_t led_vu_string_length() {
return (uint16_t)strip.length; return (uint16_t)strip.length;
} }
/****************************************************************************************
* Returns a user defined scale (percent)
*/
uint16_t led_vu_scale() {
if (!led_display) return 0;
return (uint16_t)strip.vu_scale;
}
/**************************************************************************************** /****************************************************************************************
* Turns all LEDs off (Black) * Turns all LEDs off (Black)
*/ */

View File

@@ -21,6 +21,7 @@
extern struct led_strip_t* led_display; extern struct led_strip_t* led_display;
uint16_t led_vu_string_length(); uint16_t led_vu_string_length();
uint16_t led_vu_scale();
void led_vu_progress_bar(int pct, int bright); void led_vu_progress_bar(int pct, int bright);
void led_vu_display(int vu_l, int vu_r, int bright, bool comet); void led_vu_display(int vu_l, int vu_r, int bright, bool comet);
void led_vu_spin_dial(int gain, int rate, int speed, bool comet); void led_vu_spin_dial(int gain, int rate, int speed, bool comet);

View File

@@ -1,124 +0,0 @@
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
#include "Batch.h"
#include "esp_event.h"
#include "esp_http_client.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_ota_ops.h"
#include "esp_tls.h"
#include "nvs_flash.h"
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
#include "esp_crt_bundle.h"
#endif
#include "esp_system.h"
#include "http_handlers.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "nvs_utilities.h"
#include "tools.h"
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>
#include <sys/param.h>
#if CONFIG_WITH_METRICS
static const char* const TAG = "MetricsBatch";
static const char* const feature_evt_name = "$feature_flag_called";
static const char* const feature_flag_name = "$feature_flag";
static const char* const feature_flag_response_name = "$feature_flag_response";
namespace Metrics {
Event& Batch::add_feature_event() { return add_event(feature_evt_name); }
void Batch::add_remove_feature_event(const char* name, bool active) {
if (!active) {
remove_feature_event(name);
} else {
add_event(feature_evt_name).add_property(feature_flag_name, name);
}
}
Event& Batch::add_feature_variant_event(const char* const name, const char* const value) {
return add_event(feature_evt_name)
.add_property(feature_flag_name, name)
.add_property(feature_flag_response_name, value);
}
void Batch::remove_feature_event(const char* name) {
for (Metrics::Event& e : _events) {
if (strcmp(e.get_name(), feature_evt_name) == 0) {
e.remove_property(feature_flag_name, name);
return;
}
}
}
cJSON* Batch::to_json() {
cJSON* batch_json = cJSON_CreateArray();
for (Metrics::Event& e : _events) {
cJSON_AddItemToArray(batch_json, e.to_json(_metrics_uid.c_str()));
}
cJSON* message = cJSON_CreateObject();
cJSON_AddItemToObject(message, "batch", batch_json);
cJSON_AddStringToObject(message, "api_key", _api_key);
return batch_json;
}
char* Batch::to_json_str() {
cJSON* json = to_json();
char* json_str = cJSON_PrintUnformatted(json);
cJSON_Delete(json);
return json_str;
}
void Batch::push() {
int status_code = 0;
if (_metrics_uid.empty() && !_warned) {
ESP_LOGW(TAG, "Metrics disabled; no CID found");
_warned = true;
return;
}
char* json_str = to_json_str();
ESP_LOGV(TAG, "Metrics payload: %s", json_str);
uint32_t start_time = gettime_ms();
status_code = metrics_http_post_request(json_str, _url);
if (status_code == 200 || status_code == 204) {
_events.clear();
}
FREE_AND_NULL(json_str)
ESP_LOGD(TAG, "Total duration for metrics call: %lu. ", gettime_ms() - start_time);
}
void Batch::build_guid() {
uint8_t raw[16];
std::ostringstream oss;
esp_fill_random(raw, 16);
std::for_each(std::begin(raw), std::end(raw), [&oss](const uint8_t& byte) {
oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(byte);
});
_metrics_uid = oss.str();
}
void Batch::assign_id() {
size_t size = 0;
esp_err_t esp_err = ESP_OK;
_metrics_uid = std::string((char*)get_nvs_value_alloc_for_partition(
NVS_DEFAULT_PART_NAME, TAG, NVS_TYPE_BLOB, "cid", &size));
if (_metrics_uid[0] == 'G') {
ESP_LOGW(TAG, "Invalid ID. %s", _metrics_uid.c_str());
_metrics_uid.clear();
}
if (_metrics_uid.empty()) {
build_guid();
if (_metrics_uid.empty()) {
ESP_LOGE(TAG, "ID Failed");
return;
}
ESP_LOGW(TAG, "Metrics ID: %s", _metrics_uid.c_str());
esp_err = store_nvs_value_len_for_partition(NVS_DEFAULT_PART_NAME, TAG, NVS_TYPE_BLOB,
"cid", _metrics_uid.c_str(), _metrics_uid.length() + 1);
if (esp_err != ESP_OK) {
ESP_LOGE(TAG, "Store ID failed: %s", esp_err_to_name(esp_err));
}
}
}
} // namespace Metrics
#endif

View File

@@ -1,46 +0,0 @@
#pragma once
#include "Events.h"
#include <string>
#ifdef __cplusplus
namespace Metrics {
extern "C" {
#endif
#ifdef __cplusplus
class Batch {
private:
std::list<Event> _events;
bool _warned = false;
std::string _metrics_uid = nullptr;
const char* _api_key = nullptr;
const char* _url = nullptr;
void build_guid();
void assign_id();
public:
Batch() = default;
void configure(const char* api_key, const char* url) {
_api_key = api_key;
_url = url;
assign_id();
}
Event& add_feature_event();
void add_remove_feature_event(const char* name, bool active);
Event& add_feature_variant_event(const char* const name, const char* const value);
Event& add_event(const char* name) {
_events.emplace_back(name);
return _events.back();
}
bool has_events() const { return !_events.empty(); }
void remove_feature_event(const char* name);
cJSON* to_json();
char* to_json_str();
void push();
};
}
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -1,5 +0,0 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES json tools platform_config wifi-manager esp-tls platform_config
PRIV_REQUIRES esp32 freertos
)

View File

@@ -1,98 +0,0 @@
#include "Events.h"
#include <algorithm>
#include "esp_app_format.h"
#include "esp_ota_ops.h"
#if CONFIG_WITH_METRICS
static const char* const TAG = "MetricsEvent";
namespace Metrics {
Event& Event::add_property(const char* name, const char* value) {
ESP_LOGV(TAG, "Adding property %s:%s to event %s",name,value,_name);
char* mutable_name = strdup_psram(name); // Cast away const-ness, be careful with this
auto elem = properties.find(mutable_name);
FREE_AND_NULL(mutable_name)
if (elem == properties.end()) {
ESP_LOGV(TAG, "Adding property %s:%s to event %s",name,value,_name);
properties.insert({strdup_psram(name), strdup_psram(value)});
} else {
ESP_LOGV(TAG, "Replacing value for property %s. Old: %s New: %s, Event: %s",name,elem->second,value,name);
FREE_AND_NULL(elem->second)
elem->second = strdup_psram(value);
}
return *this;
}
bool Event::has_property_value(const char* name, const char* value) const {
ESP_LOGV(TAG, "Checking if event %s property %s has value %s",_name, name,value);
return std::any_of(properties.begin(), properties.end(),
[name, value](const std::pair<const char* const, char*>& kv) {
ESP_LOGV(TAG, "Found property %s=%s", name,value);
return strcmp(kv.first, name) == 0 && strcmp(kv.second, value) == 0;
});
}
void Event::remove_property(const char* name, const char* value) {
auto it = properties.begin();
ESP_LOGV(TAG, "Removing event %s property %s=%s",_name, name,value);
while (it != properties.end()) {
if (strcmp(it->first, name) == 0 && strcmp(it->second, value)) {
properties.erase(it);
return;
}
}
ESP_LOGV(TAG, "Property %s=%s not found.", name,value);
}
cJSON* Event::properties_to_json() {
ESP_LOGV(TAG, "Event %s properties to json.",_name);
const esp_app_desc_t* desc = esp_ota_get_app_description();
#ifdef CONFIG_FW_PLATFORM_NAME
const char* platform = CONFIG_FW_PLATFORM_NAME;
#else
const char* platform = desc->project_name;
#endif
cJSON* prop_json = cJSON_CreateObject();
auto it = properties.begin();
while (it != properties.end()) {
cJSON_AddStringToObject(prop_json, it->first, it->second);
++it;
}
cJSON_AddStringToObject(prop_json, "platform", platform);
cJSON_AddStringToObject(prop_json, "build", desc->version);
dump_json_content("User properties for event:", prop_json, ESP_LOG_VERBOSE);
return prop_json;
}
cJSON* Event::to_json(const char* distinct_id) {
// The target structure looks like this
// {
// "event": "batched_event_name_1",
// "properties": {
// "distinct_id": "user distinct id",
// "account_type": "pro"
// },
// "timestamp": "[optional timestamp in ISO 8601 format]"
// }
ESP_LOGV(TAG,"Event %s to json",_name);
free_json();
_json = cJSON_CreateObject();
cJSON_AddStringToObject(_json, "name", _name);
cJSON_AddItemToObject(_json, "properties", properties_to_json());
char buf[26] = {};
strftime(buf, sizeof(buf), "%FT%TZ", gmtime(&_time));
// this will work too, if your compiler doesn't support %F or %T:
// strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
cJSON_AddStringToObject(_json, "timestamp", buf);
cJSON* prop_json = properties_to_json();
cJSON_AddStringToObject(prop_json, "distinct_id", distinct_id);
dump_json_content("Full Event:", _json, ESP_LOG_VERBOSE);
return _json;
}
void Event::free_json() { cJSON_Delete(_json); }
void Event::update_time() {
if (_time == 0) {
_time = time(nullptr);
}
}
} // namespace Metrics
#endif

View File

@@ -1,53 +0,0 @@
#pragma once
#ifdef __cplusplus
#include "esp_log.h"
#include "tools.h"
#include <cJSON.h>
#include <ctime>
#include <list>
#include <map>
#include <stdio.h>
#include <string.h>
#include <string>
namespace Metrics {
struct StrCompare {
bool operator()(const char* a, const char* b) const { return strcmp(a, b) < 0; }
};
class Event {
public:
std::map<char*, char*, StrCompare> properties;
Event& add_property(const char* name, const char* value);
bool has_property_value(const char* name, const char* value) const;
void remove_property(const char* name, const char* value);
cJSON* properties_to_json();
cJSON* to_json(const char* distinct_id);
void free_json();
void update_time();
explicit Event(const char* name) {
_name = strdup_psram(name);
memset(&_time, 0x00, sizeof(_time));
}
const char* get_name() const { return _name; }
~Event() {
FREE_AND_NULL(_name);
// Iterate through the map and free the elements
for (auto& kv : properties) {
free((void*)kv.first);
free(kv.second);
}
properties.clear(); // Clear the map after freeing memory
FREE_AND_NULL(_json);
}
private:
char* _name = nullptr;
uint32_t _time;
cJSON* _json = nullptr;
};
} // namespace Metrics
#endif

View File

@@ -1,148 +0,0 @@
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
#include "Metrics.h"
#include "Batch.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_ota_ops.h"
#include "esp_system.h"
#include "esp_tls.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "tools.h"
#include <cstdarg>
#include <cstdio>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include "cJSON.h"
#include "freertos/timers.h"
#include "network_manager.h"
#include "platform_config.h"
static const char* TAG = "metrics";
#if CONFIG_WITH_METRICS
extern bool is_network_connected();
#define METRICS_CLIENT_ID_LEN 50
#define MAX_HTTP_RECV_BUFFER 512
static bool metrics_usage_gen = false;
static uint32_t metrics_usage_gen_time = 0;
#ifndef METRICS_API_KEY
#pragma message "Metrics API key needs to be passed from the environment"
#define METRICS_API_KEY "ZZZ"
#endif
static const char* metrics_api_key =
static const char* parms_str = "params";
static const char* properties_str = "properties";
static const char* user_properties_str = "user_properties";
static const char* items_str = "items";
static const char* quantity_str = "quantity";
static const char* metrics_url = "https://app.posthog.com";
static TimerHandle_t timer;
extern cJSON* get_cmd_list();
Metrics::Batch batch;
static void metrics_timer_cb(void* timer_id) {
if (batch.has_events()) {
if (!is_network_connected()) {
ESP_LOGV(TAG, "Network not connected. can't flush");
} else {
ESP_LOGV(TAG, "Pushing events");
batch.push();
}
}
if (gettime_ms() > metrics_usage_gen_time && !metrics_usage_gen) {
metrics_usage_gen = true;
ESP_LOGV(TAG, "Generate command list to pull features");
cJSON* cmdlist = get_cmd_list();
dump_json_content("generated cmd list", cmdlist, ESP_LOG_VERBOSE);
cJSON_Delete(cmdlist);
}
}
void metrics_init() {
ESP_LOGV(TAG, "Initializing metrics");
batch.configure(metrics_api_key, metrics_url);
if (!timer) {
ESP_LOGE(TAG, "Metrics Timer failure");
} else {
ESP_LOGV(TAG, "Starting timer");
xTimerStart(timer, portMAX_DELAY);
}
// set a 20 seconds delay before generating the
// features so the system has time to boot
metrics_usage_gen_time = gettime_ms() + 20000;
}
void metrics_event_playback(const char* source) {
ESP_LOGV(TAG, "Playback event: %s", source);
auto event = batch.add_event("play").add_property("source", source);
}
void metrics_event_boot(const char* partition) {
ESP_LOGV(TAG, "Boot event %s", partition);
auto event = batch.add_event("start");
event.add_property("partition", partition);
}
void metrics_add_feature_variant(const char* name, const char* format, ...) {
va_list args;
ESP_LOGV(TAG, "Feature %s", name);
va_start(args, format);
// Determine the required buffer size
int size = vsnprintf(nullptr, 0, format, args);
va_end(args); // Reset the va_list
// Allocate buffer and format the string
std::vector<char> buffer(size + 1); // +1 for the null-terminator
va_start(args, format);
vsnprintf(buffer.data(), buffer.size(), format, args);
va_end(args);
// Now buffer.data() contains the formatted string
batch.add_feature_variant_event(name, buffer.data());
}
void metrics_add_feature(const char* name, bool active) {
ESP_LOGV(TAG, "Adding feature %s: %s", name, active ? "ACTIVE" : "INACTIVE");
batch.add_remove_feature_event(name, active);
}
void metrics_event(const char* name) {
ESP_LOGV(TAG, "Adding Event %s", name);
batch.add_event(name);
}
#else
static const char * not_enabled = " - (metrics not enabled, this is just marking where the call happens)";
void metrics_init(){
#pragma message("Metrics disabled")
ESP_LOGD(TAG,"Metrics init%s",not_enabled);
}
void metrics_event_boot(const char* partition){
ESP_LOGD(TAG,"Metrics Event Boot from partition %s%s",partition,not_enabled);
}
void metrics_event(const char* name){
ESP_LOGD(TAG,"Metrics Event %s%s",name,not_enabled);
}
void metrics_add_feature(const char* name, bool active) {
ESP_LOGD(TAG,"Metrics add feature %s%s%s",name,active?"ACTIVE":"INACTIVE",not_enabled);
}
void metrics_add_feature_variant(const char* name, const char* format, ...){
va_list args;
ESP_LOGV(TAG, "Feature %s", name);
va_start(args, format);
// Determine the required buffer size
int size = vsnprintf(nullptr, 0, format, args);
va_end(args); // Reset the va_list
// Allocate buffer and format the string
std::vector<char> buffer(size + 1); // +1 for the null-terminator
va_start(args, format);
vsnprintf(buffer.data(), buffer.size(), format, args);
va_end(args);
ESP_LOGD(TAG,"Metrics add feature %s variant %s%s",name,buffer.data(),not_enabled);
}
#endif

View File

@@ -1,18 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
void metrics_event_playback(const char* source);
void metrics_event_boot(const char* partition);
void metrics_event(const char* name);
void metrics_add_feature(const char* name, bool active);
void metrics_add_feature_variant(const char* name, const char* format, ...);
void metrics_init();
void metrics_flush();
#ifdef __cplusplus
}
#endif

View File

@@ -1,163 +0,0 @@
#include "http_handlers.h"
#include "esp_http_client.h"
#include "esp_log.h"
#include "esp_tls.h"
#include "tools.h"
#include <sys/param.h>
#if CONFIG_WITH_METRICS
static const char* TAG = "metrics_http";
static char* output_buffer; // Buffer to store response of http request from
// event handler
static int output_len = 0; // Stores number of bytes read
#define MAX_HTTP_OUTPUT_BUFFER 2048
// Common function signature for event handlers
typedef void (*HttpEventHandler)(esp_http_client_event_t* evt);
static void handle_http_error(esp_http_client_event_t* evt) { ESP_LOGV(TAG, "ERROR"); }
static void handle_http_connected(esp_http_client_event_t* evt) {
ESP_LOGV(TAG, "ON_CONNECTED");
}
static void handle_http_header_sent(esp_http_client_event_t* evt) {
ESP_LOGV(TAG, "HEADER_SENT");
}
static void handle_http_on_header(esp_http_client_event_t* evt) {
ESP_LOGV(TAG, "ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
}
static void handle_http_on_data(esp_http_client_event_t* evt) {
ESP_LOGV(TAG, "ON_DATA, len=%d", evt->data_len);
ESP_LOGV(TAG, "ON_DATA, len=%d", evt->data_len);
// Clean the buffer in case of a new request
if (output_len == 0 && evt->user_data) {
// we are just starting to copy the output data into the use
ESP_LOGV(TAG, "Resetting buffer");
memset(evt->user_data, 0, MAX_HTTP_OUTPUT_BUFFER);
}
/*
* Check for chunked encoding is added as the URL for chunked encoding used in this example
* returns binary data. However, event handler can also be used in case chunked encoding is
* used.
*/
// If user_data buffer is configured, copy the response into the buffer
int copy_len = 0;
if (evt->user_data) {
ESP_LOGV(TAG, "Not Chunked response, with user data");
// The last byte in evt->user_data is kept for the NULL character in
// case of out-of-bound access.
copy_len = MIN(evt->data_len, (MAX_HTTP_OUTPUT_BUFFER - output_len));
if (copy_len) {
memcpy(evt->user_data + output_len, evt->data, copy_len);
}
} else {
int content_len = esp_http_client_get_content_length(evt->client);
if (esp_http_client_is_chunked_response(evt->client)) {
esp_http_client_get_chunk_length(evt->client, &content_len);
}
if (output_buffer == NULL) {
// We initialize output_buffer with 0 because it is used by
// strlen() and similar functions therefore should be null
// terminated.
size_t len=(content_len + 1) * sizeof(char);
ESP_LOGV(TAG, "Init buffer %d",len);
output_buffer = (char*)malloc_init_external(len);
output_len = 0;
if (output_buffer == NULL) {
ESP_LOGE(TAG, "Buffer alloc failed.");
return;
}
}
copy_len = MIN(evt->data_len, (content_len - output_len));
if (copy_len) {
memcpy(output_buffer + output_len, evt->data, copy_len);
}
}
output_len += copy_len;
}
static void handle_http_on_finish(esp_http_client_event_t* evt) {
ESP_LOGD(TAG, "ON_FINISH");
if (output_buffer != NULL) {
ESP_LOGV(TAG, "Response: %s", output_buffer);
free(output_buffer);
output_buffer = NULL;
}
output_len = 0;
}
static void handle_http_disconnected(esp_http_client_event_t* evt) {
ESP_LOGI(TAG, "DISCONNECTED");
int mbedtls_err = 0;
esp_err_t err =
esp_tls_get_and_clear_last_error((esp_tls_error_handle_t)evt->data, &mbedtls_err, NULL);
if (err != 0) {
ESP_LOGI(TAG, "Last error : %s", esp_err_to_name(err));
ESP_LOGI(TAG, "Last mbedtls err 0x%x", mbedtls_err);
}
if (output_buffer != NULL) {
free(output_buffer);
output_buffer = NULL;
}
output_len = 0;
}
static const HttpEventHandler eventHandlers[] = {
handle_http_error, // HTTP_EVENT_ERROR
handle_http_connected, // HTTP_EVENT_ON_CONNECTED
handle_http_header_sent, // HTTP_EVENT_HEADERS_SENT
handle_http_header_sent, // HTTP_EVENT_HEADER_SENT (alias for HTTP_EVENT_HEADERS_SENT)
handle_http_on_header, // HTTP_EVENT_ON_HEADER
handle_http_on_data, // HTTP_EVENT_ON_DATA
handle_http_on_finish, // HTTP_EVENT_ON_FINISH
handle_http_disconnected // HTTP_EVENT_DISCONNECTED
};
esp_err_t metrics_http_event_handler(esp_http_client_event_t* evt) {
if (evt->event_id < 0 || evt->event_id >= sizeof(eventHandlers) / sizeof(eventHandlers[0])) {
ESP_LOGE(TAG, "Invalid event ID: %d", evt->event_id);
return ESP_FAIL;
}
eventHandlers[evt->event_id](evt);
return ESP_OK;
}
int metrics_http_post_request(const char* payload, const char* url) {
int status_code = 0;
esp_http_client_config_t config = {.url = url,
.disable_auto_redirect = false,
.event_handler = metrics_http_event_handler,
.transport_type = HTTP_TRANSPORT_OVER_SSL,
.user_data = NULL, // local_response_buffer, // Pass address of
// local buffer to get response
.skip_cert_common_name_check = true
};
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_err_t err = esp_http_client_set_method(client, HTTP_METHOD_POST);
if (err == ESP_OK) {
err = esp_http_client_set_header(client, "Content-Type", "application/json");
}
if (err == ESP_OK) {
ESP_LOGV(TAG, "Setting payload: %s", payload);
err = esp_http_client_set_post_field(client, payload, strlen(payload));
}
if (err == ESP_OK) {
err = esp_http_client_perform(client);
}
if (err == ESP_OK) {
status_code = esp_http_client_get_status_code(client);
ESP_LOGD(TAG, "metrics call Status = %d, content_length = %d",
esp_http_client_get_status_code(client), esp_http_client_get_content_length(client));
} else {
status_code = 500;
ESP_LOGW(TAG, "metrics call Status failed: %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
return status_code;
}
#endif

View File

@@ -1,11 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
int metrics_http_post_request(const char* payload, const char* url);
#ifdef __cplusplus
}
#endif

View File

@@ -13,14 +13,14 @@ esp_err_t store_nvs_value_len(nvs_type_t type, const char *key, void * data, siz
esp_err_t store_nvs_value(nvs_type_t type, const char *key, void * data); esp_err_t store_nvs_value(nvs_type_t type, const char *key, void * data);
esp_err_t get_nvs_value(nvs_type_t type, const char *key, void*value, const uint8_t buf_size); esp_err_t get_nvs_value(nvs_type_t type, const char *key, void*value, const uint8_t buf_size);
void * get_nvs_value_alloc(nvs_type_t type, const char *key); void * get_nvs_value_alloc(nvs_type_t type, const char *key);
void * get_nvs_value_alloc_for_partition(const char * partition,const char * name_space,nvs_type_t type, const char *key, size_t * size); void * get_nvs_value_alloc_for_partition(const char * partition,const char * ns,nvs_type_t type, const char *key, size_t * size);
esp_err_t erase_nvs_for_partition(const char * partition, const char * name_space,const char *key); esp_err_t erase_nvs_for_partition(const char * partition, const char * ns,const char *key);
esp_err_t store_nvs_value_len_for_partition(const char * partition,const char * name_space,nvs_type_t type, const char *key, const void * data,size_t data_len); esp_err_t store_nvs_value_len_for_partition(const char * partition,const char * ns,nvs_type_t type, const char *key, const void * data,size_t data_len);
esp_err_t erase_nvs(const char *key); esp_err_t erase_nvs(const char *key);
void print_blob(const char *blob, size_t len); void print_blob(const char *blob, size_t len);
const char *type_to_str(nvs_type_t type); const char *type_to_str(nvs_type_t type);
nvs_type_t str_to_type(const char *type); nvs_type_t str_to_type(const char *type);
esp_err_t erase_nvs_partition(const char * partition, const char * name_space); esp_err_t erase_nvs_partition(const char * partition, const char * ns);
void erase_settings_partition(); void erase_settings_partition();
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@@ -634,7 +634,7 @@ cJSON * config_alloc_get_cjson(const char *key){
} }
return conf_json; return conf_json;
} }
esp_err_t config_set_cjson(const char *key, cJSON *value, bool free_cjson){ esp_err_t config_set_cjson_str_and_free(const char *key, cJSON *value){
char * value_str = cJSON_PrintUnformatted(value); char * value_str = cJSON_PrintUnformatted(value);
if(value_str==NULL){ if(value_str==NULL){
ESP_LOGE(TAG, "Unable to print cJSON for key [%s]", key); ESP_LOGE(TAG, "Unable to print cJSON for key [%s]", key);
@@ -642,14 +642,9 @@ esp_err_t config_set_cjson(const char *key, cJSON *value, bool free_cjson){
} }
esp_err_t err = config_set_value(NVS_TYPE_STR,key, value_str); esp_err_t err = config_set_value(NVS_TYPE_STR,key, value_str);
free(value_str); free(value_str);
if(free_cjson){ cJSON_Delete(value);
cJSON_Delete(value);
}
return err; return err;
} }
esp_err_t config_set_cjson_str_and_free(const char *key, cJSON *value){
return config_set_cjson(key, value, true);
}
void config_get_uint16t_from_str(const char *key, uint16_t *value, uint16_t default_value){ void config_get_uint16t_from_str(const char *key, uint16_t *value, uint16_t default_value){
char * str_value = config_alloc_get(NVS_TYPE_STR, key); char * str_value = config_alloc_get(NVS_TYPE_STR, key);
if(str_value == NULL){ if(str_value == NULL){
@@ -791,7 +786,6 @@ cJSON* cjson_update_number(cJSON** root, const char* key, int value) {
} }
return *root; return *root;
} }
IMPLEMENT_SET_DEFAULT(uint8_t,NVS_TYPE_U8); IMPLEMENT_SET_DEFAULT(uint8_t,NVS_TYPE_U8);
IMPLEMENT_SET_DEFAULT(int8_t,NVS_TYPE_I8); IMPLEMENT_SET_DEFAULT(int8_t,NVS_TYPE_I8);
IMPLEMENT_SET_DEFAULT(uint16_t,NVS_TYPE_U16); IMPLEMENT_SET_DEFAULT(uint16_t,NVS_TYPE_U16);

View File

@@ -9,23 +9,23 @@
extern "C" { extern "C" {
#endif #endif
#define PARSE_PARAM(S,P,C,V) do { \ #define PARSE_PARAM(S,P,C,V) do { \
char *__p; \ char *__p; \
if ((__p = strcasestr(S, P)) && (__p = strchr(__p, C))) V = atoi(__p+1); \ if ((__p = strcasestr(S, P)) && (__p = strchr(__p, C))) V = atoi(__p+1); \
} while (0) } while (0)
#define PARSE_PARAM_FLOAT(S,P,C,V) do { \ #define PARSE_PARAM_FLOAT(S,P,C,V) do { \
char *__p; \ char *__p; \
if ((__p = strcasestr(S, P)) && (__p = strchr(__p, C))) V = atof(__p+1); \ if ((__p = strcasestr(S, P)) && (__p = strchr(__p, C))) V = atof(__p+1); \
} while (0) } while (0)
#define PARSE_PARAM_STR(S,P,C,V,I) do { \ #define PARSE_PARAM_STR(S,P,C,V,I) do { \
char *__p; \ char *__p; \
if ((__p = strstr(S, P)) && (__p = strchr(__p, C))) { \ if ((__p = strstr(S, P)) && (__p = strchr(__p, C))) { \
while (*++__p == ' '); \ while (*++__p == ' '); \
sscanf(__p,"%" #I "[^,]", V); \ sscanf(__p,"%" #I "[^,]", V); \
} \ } \
} while (0) } while (0)
#define DECLARE_SET_DEFAULT(t) void config_set_default_## t (const char *key, t value); #define DECLARE_SET_DEFAULT(t) void config_set_default_## t (const char *key, t value);
#define DECLARE_GET_NUM(t) esp_err_t config_get_## t (const char *key, t * value); #define DECLARE_GET_NUM(t) esp_err_t config_get_## t (const char *key, t * value);
@@ -54,7 +54,6 @@ void * config_alloc_get_default(nvs_type_t type, const char *key, void * default
void * config_alloc_get_str(const char *key, char *lead, char *fallback); void * config_alloc_get_str(const char *key, char *lead, char *fallback);
cJSON * config_alloc_get_cjson(const char *key); cJSON * config_alloc_get_cjson(const char *key);
esp_err_t config_set_cjson_str_and_free(const char *key, cJSON *value); esp_err_t config_set_cjson_str_and_free(const char *key, cJSON *value);
esp_err_t config_set_cjson(const char *key, cJSON *value, bool free_cjson);
void config_get_uint16t_from_str(const char *key, uint16_t *value, uint16_t default_value); void config_get_uint16t_from_str(const char *key, uint16_t *value, uint16_t default_value);
void config_delete_key(const char *key); void config_delete_key(const char *key);
void config_set_default(nvs_type_t type, const char *key, const void * default_value, size_t blob_size); void config_set_default(nvs_type_t type, const char *key, const void * default_value, size_t blob_size);

View File

@@ -8,7 +8,7 @@ idf_component_register( SRCS
cmd_config.c cmd_config.c
INCLUDE_DIRS . INCLUDE_DIRS .
REQUIRES nvs_flash REQUIRES nvs_flash
PRIV_REQUIRES console app_update tools services spi_flash platform_config vfs pthread wifi-manager platform_config newlib telnet display squeezelite tools metrics) PRIV_REQUIRES console app_update tools services spi_flash platform_config vfs pthread wifi-manager platform_config newlib telnet display squeezelite tools)
set_source_files_properties(cmd_config.c set_source_files_properties(cmd_config.c
PROPERTIES COMPILE_FLAGS PROPERTIES COMPILE_FLAGS

View File

@@ -1,6 +1,6 @@
idf_component_register( SRC_DIRS . idf_component_register( SRC_DIRS .
INCLUDE_DIRS . INCLUDE_DIRS .
PRIV_REQUIRES bootloader_support json PRIV_REQUIRES bootloader_support
) )
target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--undefined=esp_app_desc") target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--undefined=esp_app_desc")

View File

@@ -3,10 +3,9 @@
#include "application_name.h" #include "application_name.h"
#include "esp_err.h" #include "esp_err.h"
#include "esp_app_format.h" #include "esp_app_format.h"
#include "cJSON.h"
#include "stdbool.h"
extern esp_err_t process_recovery_ota(const char * bin_url, char * bin_buffer, uint32_t length); extern esp_err_t process_recovery_ota(const char * bin_url, char * bin_buffer, uint32_t length);
extern cJSON * gpio_list;
const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = { const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = {
.magic_word = ESP_APP_DESC_MAGIC_WORD, .magic_word = ESP_APP_DESC_MAGIC_WORD,
.version = PROJECT_VER, .version = PROJECT_VER,
@@ -27,12 +26,7 @@ const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = {
.date = "", .date = "",
#endif #endif
}; };
cJSON * get_gpio_list(bool refresh){
if(!gpio_list){
gpio_list = cJSON_CreateArray();
}
return gpio_list;
}
void register_optional_cmd(void) { void register_optional_cmd(void) {
} }

View File

@@ -44,24 +44,15 @@ extern void register_audio_config(void);
extern void register_rotary_config(void); extern void register_rotary_config(void);
extern void register_ledvu_config(void); extern void register_ledvu_config(void);
extern void register_nvs(); extern void register_nvs();
extern cJSON * get_gpio_list_handler(bool refresh);
void register_optional_cmd(void) { void register_optional_cmd(void) {
#if CONFIG_WITH_CONFIG_UI #if CONFIG_WITH_CONFIG_UI
register_rotary_config(); register_rotary_config();
#endif #endif
register_audio_config(); register_audio_config();
register_ledvu_config(); register_ledvu_config();
register_nvs(); register_nvs();
} }
cJSON * get_gpio_list(bool refresh){
#if CONFIG_WITH_CONFIG_UI
return get_gpio_list_handler(refresh);
#else
return cJSON_CreateArray();
#endif
}
extern int squeezelite_main(int argc, char **argv); extern int squeezelite_main(int argc, char **argv);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -589,7 +589,6 @@ void register_nvs()
.func = &list_entries, .func = &list_entries,
.argtable = &list_args .argtable = &list_args
}; };
MEMTRACE_PRINT_DELTA_MESSAGE("registering list_entries_cmd"); MEMTRACE_PRINT_DELTA_MESSAGE("registering list_entries_cmd");
ESP_ERROR_CHECK(esp_console_cmd_register(&list_entries_cmd)); ESP_ERROR_CHECK(esp_console_cmd_register(&list_entries_cmd));
MEMTRACE_PRINT_DELTA_MESSAGE("registering set_cmd"); MEMTRACE_PRINT_DELTA_MESSAGE("registering set_cmd");

View File

@@ -62,7 +62,7 @@ static int perform_ota_update(int argc, char **argv)
const esp_console_cmd_t cmd = { const esp_console_cmd_t cmd = {
.command = "update", .command = "update",
.help = "Update from URL", .help = "Updates the application binary from the provided URL",
.hint = NULL, .hint = NULL,
.func = &perform_ota_update, .func = &perform_ota_update,
.argtable = &ota_args .argtable = &ota_args

View File

@@ -31,9 +31,6 @@
#include "messaging.h" #include "messaging.h"
#include "platform_console.h" #include "platform_console.h"
#include "tools.h" #include "tools.h"
#if defined(CONFIG_WITH_METRICS)
#include "Metrics.h"
#endif
#ifdef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS #ifdef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS
#pragma message("Runtime stats enabled") #pragma message("Runtime stats enabled")
@@ -65,6 +62,7 @@ static void register_free();
static void register_setdevicename(); static void register_setdevicename();
static void register_heap(); static void register_heap();
static void register_dump_heap(); static void register_dump_heap();
static void register_abort();
static void register_version(); static void register_version();
static void register_restart(); static void register_restart();
#if CONFIG_WITH_CONFIG_UI #if CONFIG_WITH_CONFIG_UI
@@ -88,11 +86,12 @@ FILE * system_open_memstream(const char * cmdname,char **buf,size_t *buf_size){
void register_system() void register_system()
{ {
register_set_services();
register_setdevicename(); register_setdevicename();
register_set_services();
register_free(); register_free();
register_heap(); register_heap();
register_dump_heap(); register_dump_heap();
register_abort();
register_version(); register_version();
register_restart(); register_restart();
register_factory_boot(); register_factory_boot();
@@ -147,6 +146,27 @@ static void register_version()
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) ); ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
} }
/* 'abort' command */
static int cmd_abort(int argc, char **argv)
{
cmd_send_messaging(argv[0],MESSAGING_INFO,"ABORT!\r\n");
abort();
return 0;
}
static void register_abort()
{
const esp_console_cmd_t cmd = {
.command = "abort",
.help = "Crash now!",
.hint = NULL,
.func = &cmd_abort,
};
cmd_to_json(&cmd);
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
esp_err_t guided_boot(esp_partition_subtype_t partition_subtype) esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
{ {
if(is_recovery_running){ if(is_recovery_running){
@@ -557,7 +577,6 @@ static void register_tasks()
/** 'deep_sleep' command puts the chip into deep sleep mode */ /** 'deep_sleep' command puts the chip into deep sleep mode */
#if CONFIG_WITH_CONFIG_UI #if CONFIG_WITH_CONFIG_UI
static struct { static struct {
struct arg_int *wakeup_time; struct arg_int *wakeup_time;
@@ -712,9 +731,6 @@ cJSON * set_services_cb(){
else { else {
cJSON_AddStringToObject(values,set_services_args.telnet->hdr.longopts,"Disabled"); cJSON_AddStringToObject(values,set_services_args.telnet->hdr.longopts,"Disabled");
} }
#if defined(CONFIG_WITH_METRICS)
metrics_add_feature_variant("telnet",p);
#endif
FREE_AND_NULL(p); FREE_AND_NULL(p);
} }

View File

@@ -18,6 +18,7 @@ esp_err_t guided_factory();
esp_err_t guided_restart_ota(); esp_err_t guided_restart_ota();
void simple_restart(); void simple_restart();
FILE * system_open_memstream(const char * cmdname,char **buf,size_t *buf_size); FILE * system_open_memstream(const char * cmdname,char **buf,size_t *buf_size);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -37,9 +37,6 @@ extern bool bypass_network_manager;
#define JOIN_TIMEOUT_MS (10000) #define JOIN_TIMEOUT_MS (10000)
#include "platform_console.h" #include "platform_console.h"
// To enable wifi configuration from the command line, uncomment the line below
// define WIFI_CMDLINE 1
extern EventGroupHandle_t network_event_group; extern EventGroupHandle_t network_event_group;
extern const int CONNECTED_BIT; extern const int CONNECTED_BIT;
@@ -56,6 +53,13 @@ static struct {
// todo: implement access point config - cmd_to_json(&i2cdetect_cmd); // todo: implement access point config - cmd_to_json(&i2cdetect_cmd);
///** Arguments used by 'join' function */
//static struct {
// struct arg_int *autoconnect;
// struct arg_end *end;
//} auto_connect_args;
static void event_handler(void* arg, esp_event_base_t event_base, static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) int32_t event_id, void* event_data)
{ {
@@ -68,7 +72,27 @@ static void event_handler(void* arg, esp_event_base_t event_base,
xEventGroupSetBits(network_event_group, CONNECTED_BIT); xEventGroupSetBits(network_event_group, CONNECTED_BIT);
} }
} }
//bool wait_for_wifi(){
//
// bool connected=(xEventGroupGetBits(wifi_event_group) & CONNECTED_BIT)!=0;
//
// if(!connected){
// ESP_LOGD(TAG,"Waiting for WiFi...");
// connected = (xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
// pdFALSE, pdTRUE, JOIN_TIMEOUT_MS / portTICK_PERIOD_MS)& CONNECTED_BIT)!=0;
// if(!connected){
// ESP_LOGD(TAG,"wifi timeout.");
// }
// else
// {
// ESP_LOGI(TAG,"WiFi Connected!");
// }
// }
//
//
// return connected;
//
//}
static void initialise_wifi(void) static void initialise_wifi(void)
{ {
static bool initialized = false; static bool initialized = false;
@@ -180,10 +204,10 @@ void register_wifi_join()
void register_wifi() void register_wifi()
{ {
#ifdef WIFI_CMDLINE #ifdef WIFI_CMDLINE
register_wifi_join(); register_wifi_join();
if(bypass_network_manager){ if(bypass_network_manager){
initialise_wifi(); initialise_wifi();
} }
#endif #endif
} }

View File

@@ -27,9 +27,7 @@
#include "platform_config.h" #include "platform_config.h"
#include "telnet.h" #include "telnet.h"
#include "tools.h" #include "tools.h"
#if defined(CONFIG_WITH_METRICS)
#include "metrics.h"
#endif
#include "messaging.h" #include "messaging.h"
#include "config.h" #include "config.h"
@@ -87,22 +85,14 @@ cJSON * get_cmd_list(){
} }
void console_set_bool_parameter(cJSON * root,char * nvs_name, struct arg_lit *arg){ void console_set_bool_parameter(cJSON * root,char * nvs_name, struct arg_lit *arg){
char * p=NULL; char * p=NULL;
bool enabled = false; if(!root) {
if(!root) {
ESP_LOGE(TAG,"Invalid json parameter. Cannot set %s from %s",arg->hdr.longopts?arg->hdr.longopts:arg->hdr.glossary,nvs_name); ESP_LOGE(TAG,"Invalid json parameter. Cannot set %s from %s",arg->hdr.longopts?arg->hdr.longopts:arg->hdr.glossary,nvs_name);
return; return;
} }
if ((p = config_alloc_get(NVS_TYPE_STR, nvs_name)) != NULL) { if ((p = config_alloc_get(NVS_TYPE_STR, nvs_name)) != NULL) {
enabled = strcmp(p,"1") == 0 || strcasecmp(p,"y") == 0; cJSON_AddBoolToObject(root,arg->hdr.longopts,strcmp(p,"1") == 0 || strcasecmp(p,"y") == 0);
cJSON_AddBoolToObject(root,arg->hdr.longopts,enabled);
FREE_AND_NULL(p); FREE_AND_NULL(p);
} }
#if defined(CONFIG_WITH_METRICS)
if(enabled){
metrics_add_feature(nvs_name,"enabled");
}
#endif
} }
struct arg_end *getParmsEnd(struct arg_hdr * * argtable){ struct arg_end *getParmsEnd(struct arg_hdr * * argtable){
if(!argtable) return NULL; if(!argtable) return NULL;
@@ -370,6 +360,8 @@ void console_start() {
register_system(); register_system();
MEMTRACE_PRINT_DELTA_MESSAGE("Registering config commands"); MEMTRACE_PRINT_DELTA_MESSAGE("Registering config commands");
register_config_cmd(); register_config_cmd();
MEMTRACE_PRINT_DELTA_MESSAGE("Registering nvs commands");
register_nvs();
MEMTRACE_PRINT_DELTA_MESSAGE("Registering wifi commands"); MEMTRACE_PRINT_DELTA_MESSAGE("Registering wifi commands");
register_wifi(); register_wifi();

View File

@@ -96,6 +96,7 @@ typedef struct __attribute__((__packed__)) audio_buffer_entry { // decoded aud
u16_t len; u16_t len;
u8_t ready; u8_t ready;
u8_t allocated; u8_t allocated;
u8_t missed;
} abuf_t; } abuf_t;
typedef struct rtp_s { typedef struct rtp_s {
@@ -126,11 +127,6 @@ typedef struct rtp_s {
u32_t rtp, time; u32_t rtp, time;
u8_t status; u8_t status;
} synchro; } synchro;
struct {
u32_t time;
seq_t seqno;
u32_t rtptime;
} record;
int latency; // rtp hold depth in samples int latency; // rtp hold depth in samples
u32_t resent_req, resent_rec; // total resent + recovered frames u32_t resent_req, resent_rec; // total resent + recovered frames
u32_t silent_frames; // total silence frames u32_t silent_frames; // total silence frames
@@ -147,8 +143,8 @@ typedef struct rtp_s {
#endif #endif
struct alac_codec_s *alac_codec; struct alac_codec_s *alac_codec;
int flush_seqno; int first_seqno;
bool playing; enum { RTP_WAIT, RTP_STREAM, RTP_PLAY } state;
int stalled; int stalled;
raop_data_cb_t data_cb; raop_data_cb_t data_cb;
raop_cmd_cb_t cmd_cb; raop_cmd_cb_t cmd_cb;
@@ -231,7 +227,7 @@ rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv,
ctx->rtp_host.sin_family = AF_INET; ctx->rtp_host.sin_family = AF_INET;
ctx->rtp_host.sin_addr.s_addr = INADDR_ANY; ctx->rtp_host.sin_addr.s_addr = INADDR_ANY;
pthread_mutex_init(&ctx->ab_mutex, 0); pthread_mutex_init(&ctx->ab_mutex, 0);
ctx->flush_seqno = -1; ctx->first_seqno = -1;
ctx->latency = latency; ctx->latency = latency;
ctx->ab_read = ctx->ab_write; ctx->ab_read = ctx->ab_write;
@@ -340,23 +336,22 @@ void rtp_end(rtp_t *ctx)
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime, bool exit_locked) bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime, bool exit_locked)
{ {
bool rc = true; pthread_mutex_lock(&ctx->ab_mutex);
u32_t now = gettime_ms();
if (now < ctx->record.time + 250 || (ctx->record.seqno == seqno && ctx->record.rtptime == rtptime)) { // always store flush seqno as we only want stricly above it, even when equal to RECORD
rc = false; ctx->first_seqno = seqno;
LOG_ERROR("[%p]: FLUSH ignored as same as RECORD (%hu - %u)", ctx, seqno, rtptime); bool flushed = false;
} else {
pthread_mutex_lock(&ctx->ab_mutex); // no need to stop playing if recent or equal to record - but first_seqno is needed
buffer_reset(ctx->audio_buffer); if (ctx->state == RTP_PLAY) {
ctx->playing = false; buffer_reset(ctx->audio_buffer);
ctx->flush_seqno = seqno; ctx->state = RTP_WAIT;
if (!exit_locked) pthread_mutex_unlock(&ctx->ab_mutex); flushed = true;
LOG_INFO("[%p]: FLUSH packets below %hu - %u", ctx, seqno, rtptime);
} }
LOG_INFO("[%p]: flush %hu %u", ctx, seqno, rtptime); if (!exit_locked || !flushed) pthread_mutex_unlock(&ctx->ab_mutex);
return flushed;
return rc;
} }
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
@@ -367,11 +362,9 @@ void rtp_flush_release(rtp_t *ctx) {
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
void rtp_record(rtp_t *ctx, unsigned short seqno, unsigned rtptime) { void rtp_record(rtp_t *ctx, unsigned short seqno, unsigned rtptime) {
ctx->record.seqno = seqno; ctx->first_seqno = (seqno || rtptime) ? seqno : -1;
ctx->record.rtptime = rtptime; ctx->state = RTP_WAIT;
ctx->record.time = gettime_ms(); LOG_INFO("[%p]: record %hu - %u", ctx, seqno, rtptime);
LOG_INFO("[%p]: record %hu %u", ctx, seqno, rtptime);
} }
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
@@ -442,29 +435,54 @@ static void alac_decode(rtp_t *ctx, s16_t *dest, char *buf, int len, u16_t *outs
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool first, char *data, int len) { static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool first, char *data, int len) {
abuf_t *abuf = NULL; abuf_t *abuf = NULL;
u32_t playtime;
pthread_mutex_lock(&ctx->ab_mutex); pthread_mutex_lock(&ctx->ab_mutex);
if (!ctx->playing) { /* if we have received a RECORD with a seqno, then this is the first allowed rtp sequence number
if ((ctx->flush_seqno == -1 || seq_order(ctx->flush_seqno, seqno)) && * and we are in RTP_WAIT state. If seqno was 0, then we are waiting for a flush that will tell
(ctx->synchro.status & RTP_SYNC) && (ctx->synchro.status & NTP_SYNC)) { * us what should be our first allowed packet but we must accept everything, wait and clean when
ctx->ab_write = seqno-1; * we the it arrives. This means that first packet moves us to RTP_STREAM state where we accept
ctx->ab_read = seqno; * frames but wait for the FLUSH. If this was a FLUSH while playing, then we are also in RTP_WAIT
ctx->flush_seqno = -1; * state but we do have an allowed seqno and we should not accept any frame before we have it */
ctx->playing = true;
ctx->resent_req = ctx->resent_rec = ctx->silent_frames = ctx->discarded = 0; // if we have a pending first seqno and we are below, always ignore it
playtime = ctx->synchro.time + ((rtptime - ctx->synchro.rtp) * 10) / (RAOP_SAMPLE_RATE / 100); if (ctx->first_seqno != -1 && seq_order(seqno, ctx->first_seqno)) {
ctx->cmd_cb(RAOP_PLAY, playtime); pthread_mutex_unlock(&ctx->ab_mutex);
} else { return;
pthread_mutex_unlock(&ctx->ab_mutex);
return;
}
} }
if (ctx->state == RTP_WAIT) {
ctx->ab_write = seqno - 1;
ctx->ab_read = ctx->ab_write + 1;
ctx->resent_req = ctx->resent_rec = ctx->silent_frames = ctx->discarded = 0;
if (ctx->first_seqno != -1) {
LOG_INFO("[%p]: 1st accepted packet:%d, now playing", ctx, seqno);
ctx->state = RTP_PLAY;
ctx->first_seqno = -1;
u32_t playtime = ctx->synchro.time + ((rtptime - ctx->synchro.rtp) * 10) / (RAOP_SAMPLE_RATE / 100);
ctx->cmd_cb(RAOP_PLAY, playtime);
} else {
ctx->state = RTP_STREAM;
LOG_INFO("[%p]: 1st accepted packet:%hu, waiting for FLUSH", ctx, seqno);
}
} else if (ctx->state == RTP_STREAM && ctx->first_seqno != -1 && seq_order(ctx->first_seqno, seqno + 1)) {
// now we're talking, but first discard all packets with a seqno below first_seqno AND not ready
while (seq_order(ctx->ab_read, ctx->first_seqno) ||
!ctx->audio_buffer[BUFIDX(ctx->ab_read)].ready) {
ctx->audio_buffer[BUFIDX(ctx->ab_read)].ready = false;
ctx->ab_read++;
}
LOG_INFO("[%p]: done waiting for FLUSH with packet:%d, now playing starting:%hu", ctx, seqno, ctx->ab_read);
ctx->state = RTP_PLAY;
ctx->first_seqno = -1;
u32_t playtime = ctx->synchro.time + ((rtptime - ctx->synchro.rtp) * 10) / (RAOP_SAMPLE_RATE / 100);
ctx->cmd_cb(RAOP_PLAY, playtime);
}
abuf = ctx->audio_buffer + BUFIDX(seqno);
if (seqno == (u16_t) (ctx->ab_write+1)) { if (seqno == (u16_t) (ctx->ab_write+1)) {
// expected packet // expected packet
abuf = ctx->audio_buffer + BUFIDX(seqno);
ctx->ab_write = 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); 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)) { } else if (seq_order(ctx->ab_write, seqno)) {
@@ -475,7 +493,7 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
ctx->ab_read = seqno; ctx->ab_read = seqno;
} else { } else {
// request re-send missed frames and evaluate resent date as a whole *after* // request re-send missed frames and evaluate resent date as a whole *after*
rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1); if (ctx->state == RTP_PLAY) rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1);
// resend date is after all requests have been sent // resend date is after all requests have been sent
u32_t now = gettime_ms(); u32_t now = gettime_ms();
@@ -488,16 +506,15 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read); 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; ctx->ab_write = seqno;
} else if (seq_order(ctx->ab_read, seqno + 1)) { } else if (seq_order(ctx->ab_read, seqno + 1)) {
// recovered packet, not yet sent // recovered packet, not yet sent
abuf = ctx->audio_buffer + BUFIDX(seqno);
ctx->resent_rec++; 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); LOG_DEBUG("[%p]: packet recovered seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
} else { } else {
// too late // 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 (abuf->missed) LOG_INFO("[%p]: packet too late seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
abuf = NULL;
} }
if (ctx->in_frames++ > 1000) { if (ctx->in_frames++ > 1000) {
@@ -508,6 +525,7 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
if (abuf) { if (abuf) {
alac_decode(ctx, abuf->data, data, len, &abuf->len); alac_decode(ctx, abuf->data, data, len, &abuf->len);
abuf->ready = 1; abuf->ready = 1;
abuf->missed = 0;
// this is the local rtptime when this frame is expected to play // this is the local rtptime when this frame is expected to play
abuf->rtptime = rtptime; abuf->rtptime = rtptime;
buffer_push_packet(ctx); buffer_push_packet(ctx);
@@ -528,7 +546,7 @@ static void buffer_push_packet(rtp_t *ctx) {
u32_t now, playtime, hold = max((ctx->latency * 1000) / (8 * RAOP_SAMPLE_RATE), 100); u32_t now, playtime, hold = max((ctx->latency * 1000) / (8 * RAOP_SAMPLE_RATE), 100);
// not ready to play yet // not ready to play yet
if (!ctx->playing || ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return; if (ctx->state != RTP_PLAY || ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
// there is always at least one frame in the buffer // there is always at least one frame in the buffer
do { do {
@@ -551,6 +569,7 @@ static void buffer_push_packet(rtp_t *ctx) {
LOG_DEBUG("[%p]: created zero frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read); 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->data_cb(silence_frame, ctx->frame_size * 4, playtime);
ctx->silent_frames++; ctx->silent_frames++;
curframe->missed = 1;
} }
} else if (curframe->ready) { } else if (curframe->ready) {
ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime); ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime);
@@ -573,16 +592,24 @@ static void buffer_push_packet(rtp_t *ctx) {
LOG_SDEBUG("playtime %u %d [W:%hu R:%hu] %d", playtime, playtime - now, ctx->ab_write, ctx->ab_read, curframe->ready); 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 // try to request resend missing packet in order, explore up to 32 frames
for (int i = 0; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) { for (int step = max((ctx->ab_write - ctx->ab_read + 1) / 32, 1),
abuf_t *frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i); i = 0, first = 0;
if (!frame->ready && now - frame->last_resend > RESEND_TO) { seq_order(ctx->ab_read + i, ctx->ab_write); i += step) {
// stop if one fails
if (!rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i)) break; abuf_t* frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i);
frame->last_resend = now;
} // stop when we reach a ready frame or a recent pending resend
} if (first && (frame->ready || now - frame->last_resend <= RESEND_TO)) {
if (!rtp_request_resend(ctx, first, ctx->ab_read + i - 1)) break;
first = 0;
i += step - 1;
} else if (!frame->ready && now - frame->last_resend > RESEND_TO) {
if (!first) first = ctx->ab_read + i;
frame->last_resend = now;
}
}
}
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/

View File

@@ -1,5 +1,5 @@
idf_component_register(SRC_DIRS . idf_component_register(SRC_DIRS .
INCLUDE_DIRS . INCLUDE_DIRS .
REQUIRES json tools platform_config display wifi-manager esp-tls platform_config REQUIRES json tools platform_config display wifi-manager
PRIV_REQUIRES soc esp32 PRIV_REQUIRES soc esp32
) )

View File

@@ -47,7 +47,6 @@ cJSON * gpio_list=NULL;
#define STR(macro) QUOTE(macro) #define STR(macro) QUOTE(macro)
#endif #endif
extern cJSON * get_gpio_list(bool refresh);
bool are_statistics_enabled(){ bool are_statistics_enabled(){
#if defined(CONFIG_FREERTOS_USE_TRACE_FACILITY) && defined (CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS) #if defined(CONFIG_FREERTOS_USE_TRACE_FACILITY) && defined (CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS)
return true; return true;
@@ -71,7 +70,7 @@ static char * get_dac_config_string(){
return config_alloc_get_str("dac_config", CONFIG_DAC_CONFIG, "model=i2s,bck=" STR(CONFIG_I2S_BCK_IO) return config_alloc_get_str("dac_config", CONFIG_DAC_CONFIG, "model=i2s,bck=" STR(CONFIG_I2S_BCK_IO)
",ws=" STR(CONFIG_I2S_WS_IO) ",do=" STR(CONFIG_I2S_DO_IO) ",ws=" STR(CONFIG_I2S_WS_IO) ",do=" STR(CONFIG_I2S_DO_IO)
",sda=" STR(CONFIG_I2C_SDA) ",scl=" STR(CONFIG_I2C_SCL) ",sda=" STR(CONFIG_I2C_SDA) ",scl=" STR(CONFIG_I2C_SCL)
",mute=" STR(CONFIG_MUTE_GPIO)); ",mute=" STR(CONFIG_MUTE_GPIO) ",mck=" STR(CONFIG_I2S_MCK_IO));
} }
/**************************************************************************************** /****************************************************************************************
@@ -330,7 +329,7 @@ esp_err_t config_ledvu_set(ledvu_struct_t * config){
esp_err_t err=ESP_OK; esp_err_t err=ESP_OK;
char * config_buffer=malloc_init_external(buffer_size); char * config_buffer=malloc_init_external(buffer_size);
if(config_buffer) { if(config_buffer) {
snprintf(config_buffer,buffer_size,"%s,length=%i,gpio=%i",config->type, config->length, config->gpio); snprintf(config_buffer,buffer_size,"%s,length=%i,gpio=%i,scale=%i",config->type, config->length, config->gpio, config->scale);
log_send_messaging(MESSAGING_INFO,"Updating ledvu configuration to %s",config_buffer); log_send_messaging(MESSAGING_INFO,"Updating ledvu configuration to %s",config_buffer);
err = config_set_value(NVS_TYPE_STR, "led_vu_config", config_buffer); err = config_set_value(NVS_TYPE_STR, "led_vu_config", config_buffer);
if(err!=ESP_OK){ if(err!=ESP_OK){
@@ -593,7 +592,7 @@ const gpio_exp_config_t* config_gpio_exp_get(int index) {
PARSE_PARAM(item, "intr", '=', config.intr); PARSE_PARAM(item, "intr", '=', config.intr);
PARSE_PARAM(item, "base", '=', config.base); PARSE_PARAM(item, "base", '=', config.base);
PARSE_PARAM(item, "count", '=', config.count); PARSE_PARAM(item, "count", '=', config.count);
PARSE_PARAM_STR(item, "model", '=', config.model, sizeof(config.model)-1); PARSE_PARAM_STR(item, "model", '=', config.model, 31);
if ((p = strcasestr(item, "port")) != NULL) { if ((p = strcasestr(item, "port")) != NULL) {
char port[8] = ""; char port[8] = "";
@@ -647,12 +646,6 @@ const set_GPIO_struct_t * get_gpio_struct(){
#endif #endif
#ifdef CONFIG_LED_RED_GPIO #ifdef CONFIG_LED_RED_GPIO
gpio_struct.red.gpio = CONFIG_LED_RED_GPIO; gpio_struct.red.gpio = CONFIG_LED_RED_GPIO;
#endif
#if defined(CONFIG_POWER_GPIO) && CONFIG_POWER_GPIO != -1
gpio_struct.power.gpio = CONFIG_POWER_GPIO;
#endif
#ifdef CONFIG_POWER_GPIO_LEVEL
gpio_struct.power.level = CONFIG_POWER_GPIO_LEVEL;
#endif #endif
if(nvs_item){ if(nvs_item){
HANDLE_GPIO_STRUCT_MEMBER(amp,false); HANDLE_GPIO_STRUCT_MEMBER(amp,false);
@@ -665,7 +658,6 @@ const set_GPIO_struct_t * get_gpio_struct(){
HANDLE_GPIO_STRUCT_MEMBER(vcc,false); HANDLE_GPIO_STRUCT_MEMBER(vcc,false);
HANDLE_GPIO_STRUCT_MEMBER(gnd,false); HANDLE_GPIO_STRUCT_MEMBER(gnd,false);
HANDLE_GPIO_STRUCT_MEMBER(ir,false); HANDLE_GPIO_STRUCT_MEMBER(ir,false);
HANDLE_GPIO_STRUCT_MEMBER(power,false);
free(nvs_item); free(nvs_item);
} }
@@ -769,14 +761,13 @@ const rotary_struct_t * config_rotary_get() {
*/ */
const ledvu_struct_t * config_ledvu_get() { const ledvu_struct_t * config_ledvu_get() {
static ledvu_struct_t ledvu={ .type = "WS2812", .gpio = -1, .length = 0}; static ledvu_struct_t ledvu={ .type = "WS2812", .gpio = -1, .length = 0, .scale= 100 };
char *config = config_alloc_get_default(NVS_TYPE_STR, "led_vu_config", NULL, 0); char *config = config_alloc_get_default(NVS_TYPE_STR, "led_vu_config", NULL, 0);
if (config && *config) { if (config && *config) {
char *p; PARSE_PARAM_STR(config, "type", '=', ledvu.type, 15);
PARSE_PARAM(config, "gpio", '=', ledvu.gpio);
// ToDo: Add code for future support of alternate led types PARSE_PARAM(config, "length", '=', ledvu.length);
if ((p = strcasestr(config, "gpio")) != NULL) ledvu.gpio = atoi(strchr(p, '=') + 1); PARSE_PARAM(config, "scale", '=', ledvu.scale);
if ((p = strcasestr(config, "length")) != NULL) ledvu.length = atoi(strchr(p, '=') + 1);
free(config); free(config);
} }
return &ledvu; return &ledvu;
@@ -831,7 +822,6 @@ cJSON * get_GPIO_nvs_list(cJSON * list) {
ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,jack,"other"); ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,jack,"other");
ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,green,"other"); ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,green,"other");
ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,red,"other"); ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,red,"other");
ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,power,"other");
ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,spkfault,"other"); ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,spkfault,"other");
return ilist; return ilist;
} }
@@ -1178,7 +1168,7 @@ cJSON * get_psram_gpio_list(cJSON * list){
/**************************************************************************************** /****************************************************************************************
* *
*/ */
cJSON * get_gpio_list_handler(bool refresh) { cJSON * get_gpio_list(bool refresh) {
gpio_num_t gpio_num; gpio_num_t gpio_num;
if(gpio_list && !refresh){ if(gpio_list && !refresh){
return gpio_list; return gpio_list;

View File

@@ -13,7 +13,7 @@
#include "driver/i2s.h" #include "driver/i2s.h"
#include "driver/spi_master.h" #include "driver/spi_master.h"
#include "gpio_exp.h" #include "gpio_exp.h"
#include "cJSON.h"
extern const char *i2c_name_type; extern const char *i2c_name_type;
extern const char *spi_name_type; extern const char *spi_name_type;
@@ -73,7 +73,6 @@ typedef struct {
gpio_with_level_t green; gpio_with_level_t green;
gpio_with_level_t red; gpio_with_level_t red;
gpio_with_level_t spkfault; gpio_with_level_t spkfault;
gpio_with_level_t power;
} set_GPIO_struct_t; } set_GPIO_struct_t;
typedef struct { typedef struct {
@@ -90,6 +89,7 @@ typedef struct {
char type[16]; char type[16];
int length; int length;
int gpio; int gpio;
int scale;
} ledvu_struct_t; } ledvu_struct_t;
typedef struct { typedef struct {
@@ -118,7 +118,7 @@ bool is_spdif_config_locked();
esp_err_t free_gpio_entry( gpio_entry_t ** gpio); esp_err_t free_gpio_entry( gpio_entry_t ** gpio);
gpio_entry_t * get_gpio_by_name(char * name,char * group, bool refresh); gpio_entry_t * get_gpio_by_name(char * name,char * group, bool refresh);
gpio_entry_t * get_gpio_by_no(int gpionum, bool refresh); gpio_entry_t * get_gpio_by_no(int gpionum, bool refresh);
cJSON * get_gpio_list(bool refresh);
bool is_dac_config_locked(); bool is_dac_config_locked();
bool are_statistics_enabled(); bool are_statistics_enabled();
const rotary_struct_t * config_rotary_get(); const rotary_struct_t * config_rotary_get();

View File

@@ -38,6 +38,7 @@ static esp_err_t actrls_process_action (const cJSON * member, actrls_config_t *c
static esp_err_t actrls_init_json(const char *profile_name, bool create); static esp_err_t actrls_init_json(const char *profile_name, bool create);
static void control_rotary_handler(void *client, rotary_event_e event, bool long_press); static void control_rotary_handler(void *client, rotary_event_e event, bool long_press);
static void volume_rotary_handler(void *client, rotary_event_e event, bool long_press);
static void rotary_timer( TimerHandle_t xTimer ); static void rotary_timer( TimerHandle_t xTimer );
static const actrls_config_map_t actrls_config_map[] = static const actrls_config_map_t actrls_config_map[] =
@@ -157,6 +158,24 @@ esp_err_t actrls_init(const char *profile_name) {
err = create_rotary(NULL, A, B, SW, longpress, control_rotary_handler) ? ESP_OK : ESP_FAIL; err = create_rotary(NULL, A, B, SW, longpress, control_rotary_handler) ? ESP_OK : ESP_FAIL;
} }
free(config);
config = config_alloc_get_default(NVS_TYPE_STR, "volume_rotary", NULL, 0);
// now see if we have a dedicated volume rotary
if (config && *config) {
int A = -1, B = -1, SW = -1;
// parse config
PARSE_PARAM(config, "A", '=', A);
PARSE_PARAM(config, "B", '=', B);
PARSE_PARAM(config, "SW", '=', SW);
// create rotary (no handling of long press)
err |= create_volume_rotary(NULL, A, B, SW, volume_rotary_handler) ? ESP_OK : ESP_FAIL;
}
free(config);
// set infrared GPIO if any // set infrared GPIO if any
parse_set_GPIO(set_ir_gpio); parse_set_GPIO(set_ir_gpio);
@@ -290,6 +309,29 @@ static void control_rotary_handler(void *client, rotary_event_e event, bool long
if (action != ACTRLS_NONE) (*current_controls[action])(pressed); if (action != ACTRLS_NONE) (*current_controls[action])(pressed);
} }
/****************************************************************************************
*
*/
static void volume_rotary_handler(void *client, rotary_event_e event, bool long_press) {
actrls_action_e action = ACTRLS_NONE;
bool pressed = true;
switch(event) {
case ROTARY_LEFT:
action = ACTRLS_VOLDOWN;
break;
case ROTARY_RIGHT:
action = ACTRLS_VOLUP;
break;
case ROTARY_PRESSED:
action = ACTRLS_TOGGLE;
default:
break;
}
if (action != ACTRLS_NONE) (*current_controls[action])(pressed);
}
/**************************************************************************************** /****************************************************************************************
* *
*/ */
@@ -568,6 +610,13 @@ exit:
return err; return err;
} }
/****************************************************************************************
*
*/
actrls_handler get_ctrl_handler(actrls_action_e action) {
return current_controls[action];
}
/**************************************************************************************** /****************************************************************************************
* *
*/ */

View File

@@ -53,3 +53,9 @@ void actrls_set_default(const actrls_t controls, bool raw_controls, actrls_hook_
void actrls_set(const actrls_t controls, bool raw_controls, actrls_hook_t *hook, actrls_ir_handler_t *ir_handler); void actrls_set(const actrls_t controls, bool raw_controls, actrls_hook_t *hook, actrls_ir_handler_t *ir_handler);
void actrls_unset(void); void actrls_unset(void);
bool actrls_ir_action(uint16_t addr, uint16_t code); bool actrls_ir_action(uint16_t addr, uint16_t code);
/* Call this to get the handler for any of the audio actions. It will map to the control specific
to the current mode (LMS, AirPlay, Spotify). This is useful if you have a custom way to create
buttons (like analogue buttons)
*/
actrls_handler get_ctrl_handler(actrls_action_e);

View File

@@ -58,13 +58,13 @@ static struct {
static TimerHandle_t polled_timer; static TimerHandle_t polled_timer;
static EXT_RAM_ATTR struct { static EXT_RAM_ATTR struct encoder {
QueueHandle_t queue; QueueHandle_t queue;
void *client; void *client;
rotary_encoder_info_t info; rotary_encoder_info_t info;
int A, B, SW; int A, B, SW;
rotary_handler handler; rotary_handler handler;
} rotary; } rotary, volume;
static EXT_RAM_ATTR struct { static EXT_RAM_ATTR struct {
RingbufHandle_t rb; RingbufHandle_t rb;
@@ -227,11 +227,22 @@ static void buttons_task(void* arg) {
// received a rotary event // received a rotary event
xQueueReceive(rotary.queue, &event, 0); xQueueReceive(rotary.queue, &event, 0);
ESP_LOGD(TAG, "Event: position %d, direction %s", event.state.position, ESP_LOGD(TAG, "Rotary event: position %d, direction %s", event.state.position,
event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET"); event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET");
rotary.handler(rotary.client, event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? rotary.handler(rotary.client, event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ?
ROTARY_RIGHT : ROTARY_LEFT, false); ROTARY_RIGHT : ROTARY_LEFT, false);
} else if (xActivatedMember == volume.queue) {
rotary_encoder_event_t event = { 0 };
// received a volume rotary event
xQueueReceive(volume.queue, &event, 0);
ESP_LOGD(TAG, "Volume event: position %d, direction %s", event.state.position,
event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET");
volume.handler(volume.client, event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ?
ROTARY_RIGHT : ROTARY_LEFT, false);
} else { } else {
// this is IR // this is IR
active = infrared_receive(infrared.rb, infrared.handler); active = infrared_receive(infrared.rb, infrared.handler);
@@ -395,7 +406,55 @@ void *button_remap(void *client, int gpio, button_handler handler, int long_pres
} }
/**************************************************************************************** /****************************************************************************************
* Rotary encoder handler * Create rotary encoder
*/
static bool create_rotary_encoder(struct encoder *encoder, void *id, int A, int B, int SW, int long_press, rotary_handler handler, button_handler button) {
// nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
if (A == -1 || B == -1 || A == 36 || A == 39 || B == 36 || B == 39) {
ESP_LOGI(TAG, "Cannot create rotary %d %d", A, B);
return false;
}
encoder->A = A;
encoder->B = B;
encoder->SW = SW;
encoder->client = id;
encoder->handler = handler;
// Initialise the rotary encoder device with the GPIOs for A and B signals
rotary_encoder_init(&encoder->info, A, B);
// Create a queue for events from the rotary encoder driver.
encoder->queue = rotary_encoder_create_queue();
rotary_encoder_set_queue(&encoder->info, encoder->queue);
common_task_init();
xQueueAddToSet( encoder->queue, common_queue_set );
// create companion button if rotary has a switch
if (SW != -1) button_create(id, SW, BUTTON_LOW, true, 0, button, long_press, -1);
return true;
}
/****************************************************************************************
* Volume button encoder handler
*/
static void volume_button_handler(void *id, button_event_e event, button_press_e mode, bool long_press) {
ESP_LOGI(TAG, "Volume encoder push-button %d", event);
volume.handler(id, event == BUTTON_PRESSED ? ROTARY_PRESSED : ROTARY_RELEASED, long_press);
}
/****************************************************************************************
* Create volume encoder
*/
bool create_volume_rotary(void *id, int A, int B, int SW, rotary_handler handler) {
ESP_LOGI(TAG, "Created volume encoder A:%d B:%d, SW:%d", A, B, SW);
return create_rotary_encoder(&volume, id, A, B, SW, false, handler, volume_button_handler);
}
/****************************************************************************************
* Rotary button encoder handler
*/ */
static void rotary_button_handler(void *id, button_event_e event, button_press_e mode, bool long_press) { static void rotary_button_handler(void *id, button_event_e event, button_press_e mode, bool long_press) {
ESP_LOGI(TAG, "Rotary push-button %d", event); ESP_LOGI(TAG, "Rotary push-button %d", event);
@@ -406,34 +465,8 @@ static void rotary_button_handler(void *id, button_event_e event, button_press_e
* Create rotary encoder * Create rotary encoder
*/ */
bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler) { bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler) {
// nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
if (A == -1 || B == -1 || A == 36 || A == 39 || B == 36 || B == 39) {
ESP_LOGI(TAG, "Cannot create rotary %d %d", A, B);
return false;
}
rotary.A = A;
rotary.B = B;
rotary.SW = SW;
rotary.client = id;
rotary.handler = handler;
// Initialise the rotary encoder device with the GPIOs for A and B signals
rotary_encoder_init(&rotary.info, A, B);
// Create a queue for events from the rotary encoder driver.
rotary.queue = rotary_encoder_create_queue();
rotary_encoder_set_queue(&rotary.info, rotary.queue);
common_task_init();
xQueueAddToSet( rotary.queue, common_queue_set );
// create companion button if rotary has a switch
if (SW != -1) button_create(id, SW, BUTTON_LOW, true, 0, rotary_button_handler, long_press, -1);
ESP_LOGI(TAG, "Created rotary encoder A:%d B:%d, SW:%d", A, B, SW); ESP_LOGI(TAG, "Created rotary encoder A:%d B:%d, SW:%d", A, B, SW);
return create_rotary_encoder(&rotary, id, A, B, SW, long_press, handler, rotary_button_handler);
return true;
} }
/**************************************************************************************** /****************************************************************************************

View File

@@ -34,5 +34,5 @@ typedef enum { ROTARY_LEFT, ROTARY_RIGHT, ROTARY_PRESSED, ROTARY_RELEASED } rota
typedef void (*rotary_handler)(void *id, rotary_event_e event, bool long_press); typedef void (*rotary_handler)(void *id, rotary_event_e event, bool long_press);
bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler); bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler);
bool create_volume_rotary(void *id, int A, int B, int SW, rotary_handler handler);
bool create_infrared(int gpio, infrared_handler handler, infrared_mode_t mode); bool create_infrared(int gpio, infrared_handler handler, infrared_mode_t mode);

View File

@@ -83,6 +83,10 @@ static void mcp23s17_set_direction(gpio_exp_t* self);
static uint32_t mcp23s17_read(gpio_exp_t* self); static uint32_t mcp23s17_read(gpio_exp_t* self);
static void mcp23s17_write(gpio_exp_t* self); static void mcp23s17_write(gpio_exp_t* self);
static void aw9523_set_direction(gpio_exp_t* self);
static uint32_t aw9523_read(gpio_exp_t* self);
static void aw9523_write(gpio_exp_t* self);
static void service_handler(void *arg); static void service_handler(void *arg);
static void debounce_handler( TimerHandle_t xTimer ); static void debounce_handler( TimerHandle_t xTimer );
@@ -130,6 +134,11 @@ static const struct gpio_exp_model_s {
.set_pull_mode = mcp23s17_set_pull_mode, .set_pull_mode = mcp23s17_set_pull_mode,
.read = mcp23s17_read, .read = mcp23s17_read,
.write = mcp23s17_write, }, .write = mcp23s17_write, },
{ .model = "aw9523",
.trigger = GPIO_INTR_LOW_LEVEL,
.set_direction = aw9523_set_direction,
.read = aw9523_read,
.write = aw9523_write, },
}; };
static EXT_RAM_ATTR uint8_t n_expanders; static EXT_RAM_ATTR uint8_t n_expanders;
@@ -671,6 +680,24 @@ static void mcp23s17_write(gpio_exp_t* self) {
spi_write(self->spi_handle, self->phy.addr, 0x12, self->shadow, 2); spi_write(self->spi_handle, self->phy.addr, 0x12, self->shadow, 2);
} }
/****************************************************************************************
* AW9523 family : direction, read and write
*/
static void aw9523_set_direction(gpio_exp_t* self) {
i2c_write(self->phy.port, self->phy.addr, 0x04, self->r_mask, 2);
i2c_write(self->phy.port, self->phy.addr, 0x06, ~self->r_mask, 2);
}
static uint32_t aw9523_read(gpio_exp_t* self) {
// Reading both registers in one go does not seem to reset IRQ correctly
uint8_t port1 = i2c_read(self->phy.port, self->phy.addr, 0x00, 1);
return (i2c_read(self->phy.port, self->phy.addr, 0x01, 1) << 8) | port1;
}
static void aw9523_write(gpio_exp_t* self) {
i2c_write(self->phy.port, self->phy.addr, 0x02, self->shadow, 2);
}
/*************************************************************************************** /***************************************************************************************
I2C low level I2C low level
***************************************************************************************/ ***************************************************************************************/

View File

@@ -62,7 +62,7 @@ static void task_stats( cJSON* top ) {
current.n = uxTaskGetSystemState( current.tasks, current.n, &current.total ); current.n = uxTaskGetSystemState( current.tasks, current.n, &current.total );
cJSON_AddNumberToObject(top,"ntasks",current.n); cJSON_AddNumberToObject(top,"ntasks",current.n);
char scratch[SCRATCH_SIZE] = { }; char scratch[SCRATCH_SIZE] = {0};
#ifdef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS #ifdef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS
#pragma message("Compiled with runtime stats") #pragma message("Compiled with runtime stats")
@@ -143,11 +143,13 @@ static void monitor_trace(uint32_t now) {
heap_caps_get_minimum_free_size(MALLOC_CAP_DMA)); heap_caps_get_minimum_free_size(MALLOC_CAP_DMA));
task_stats(top); task_stats(top);
char * top_a= cJSON_PrintUnformatted(top); char * top_a= cJSON_PrintUnformatted(top);
if(top_a){ if(top_a){
messaging_post_message(MESSAGING_INFO, MESSAGING_CLASS_STATS,top_a); messaging_post_message(MESSAGING_INFO, MESSAGING_CLASS_STATS,top_a);
FREE_AND_NULL(top_a); FREE_AND_NULL(top_a);
} }
cJSON_Delete(top); cJSON_Delete(top);
} }

View File

@@ -361,7 +361,7 @@ void services_init(void) {
} }
#endif #endif
// set potential power GPIO on chip first in case expanders are power using these // set potential power GPIO on chip first in case expanders are powered using these
parse_set_GPIO(set_chip_power_gpio); parse_set_GPIO(set_chip_power_gpio);
// shared I2C bus // shared I2C bus

View File

@@ -22482,9 +22482,13 @@ mg_init_library(unsigned features)
file_mutex_init = file_mutex_init =
pthread_mutex_init(&global_log_file_lock, &pthread_mutex_attr); pthread_mutex_init(&global_log_file_lock, &pthread_mutex_attr);
if (file_mutex_init == 0) { if (file_mutex_init == 0) {
#ifdef WINSOCK_START
/* Start WinSock */ /* Start WinSock */
WSADATA data; WSADATA data;
failed = wsa = WSAStartup(MAKEWORD(2, 2), &data); failed = wsa = WSAStartup(MAKEWORD(2, 2), &data);
#else
failed = wsa = 0;
#endif
} }
#else #else
mutexattr_init = pthread_mutexattr_init(&pthread_mutex_attr); mutexattr_init = pthread_mutexattr_init(&pthread_mutex_attr);
@@ -22498,7 +22502,9 @@ mg_init_library(unsigned features)
if (failed) { if (failed) {
#if defined(_WIN32) #if defined(_WIN32)
if (wsa == 0) { if (wsa == 0) {
#ifdef WINSOCK_START
(void)WSACleanup(); (void)WSACleanup();
#endif
} }
if (file_mutex_init == 0) { if (file_mutex_init == 0) {
(void)pthread_mutex_destroy(&global_log_file_lock); (void)pthread_mutex_destroy(&global_log_file_lock);
@@ -22598,7 +22604,9 @@ mg_exit_library(void)
#endif #endif
#if defined(_WIN32) #if defined(_WIN32)
#ifdef WINSOCK_START
(void)WSACleanup(); (void)WSACleanup();
#endif
(void)pthread_mutex_destroy(&global_log_file_lock); (void)pthread_mutex_destroy(&global_log_file_lock);
#else #else
(void)pthread_mutexattr_destroy(&pthread_mutex_attr); (void)pthread_mutexattr_destroy(&pthread_mutex_attr);

View File

@@ -227,7 +227,7 @@ int main(int argc, char *argv[]) {
} else if (!strcasecmp(arg, "-i")) { } else if (!strcasecmp(arg, "-i")) {
identity = *++argv; identity = *++argv;
} else { } else {
// nothing let's try to be smart and handle legacy crappy // nothing let's try to be smart and handle legacy crap
if (!identity) identity = *argv; if (!identity) identity = *argv;
else if (!type) (void) !asprintf(&type, "%s.local", *argv); else if (!type) (void) !asprintf(&type, "%s.local", *argv);
else if (!port) port = atoi(*argv); else if (!port) port = atoi(*argv);
@@ -235,6 +235,7 @@ int main(int argc, char *argv[]) {
txt = (const char**) malloc((argc + 1) * sizeof(char**)); txt = (const char**) malloc((argc + 1) * sizeof(char**));
memcpy(txt, argv, argc * sizeof(char**)); memcpy(txt, argv, argc * sizeof(char**));
txt[argc] = NULL; txt[argc] = NULL;
break;
} }
argc--; argc--;
} }
@@ -250,13 +251,14 @@ int main(int argc, char *argv[]) {
mdnsd_set_hostname(svr, hostname, host); mdnsd_set_hostname(svr, hostname, host);
svc = mdnsd_register_svc(svr, identity, type, port, NULL, txt); svc = mdnsd_register_svc(svr, identity, type, port, NULL, txt);
mdns_service_destroy(svc); // mdns_service_destroy(svc);
#ifdef _WIN32 #ifdef _WIN32
Sleep(INFINITE); Sleep(INFINITE);
#else #else
pause(); pause();
#endif #endif
mdns_service_remove(svr, svc);
mdnsd_stop(svr); mdnsd_stop(svr);
} else { } else {
printf("Can't start server"); printf("Can't start server");
@@ -264,7 +266,7 @@ int main(int argc, char *argv[]) {
} }
free(type); free(type);
free(txt); if (txt) free(txt);
#ifdef _WIN32 #ifdef _WIN32
winsock_close(); winsock_close();

View File

@@ -144,11 +144,10 @@ static int create_recv_sock(uint32_t host) {
} }
#if !defined(_WIN32) #if !defined(_WIN32)
on = sizeof(on); socklen_t len = sizeof(on);
socklen_t len; if (!getsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &on, &len)) {
if (!getsockopt(sd, SOL_SOCKET, SO_REUSEPORT,(char*) &on, &len)) {
on = 1; on = 1;
if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEPORT,(char*) &on, sizeof(on))) < 0) { if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on))) < 0) {
log_message(LOG_ERR, "recv setsockopt(SO_REUSEPORT): %m\n", r); log_message(LOG_ERR, "recv setsockopt(SO_REUSEPORT): %m\n", r);
} }
} }

View File

View File

0
components/spotify/cspot/bell/external/nanopb/generator/protoc vendored Normal file → Executable file
View File

View File

View File

@@ -12,6 +12,8 @@
using namespace bell; using namespace bell;
std::mutex BellHTTPServer::initMutex;
class WebSocketHandler : public CivetWebSocketHandler { class WebSocketHandler : public CivetWebSocketHandler {
public: public:
BellHTTPServer::WSDataHandler dataHandler; BellHTTPServer::WSDataHandler dataHandler;
@@ -187,6 +189,7 @@ bool BellHTTPServer::handlePost(CivetServer* server,
} }
BellHTTPServer::BellHTTPServer(int serverPort) { BellHTTPServer::BellHTTPServer(int serverPort) {
std::lock_guard lock(initMutex);
mg_init_library(0); mg_init_library(0);
BELL_LOG(info, "HttpServer", "Server listening on port %d", serverPort); BELL_LOG(info, "HttpServer", "Server listening on port %d", serverPort);
this->serverPort = serverPort; this->serverPort = serverPort;
@@ -197,6 +200,11 @@ BellHTTPServer::BellHTTPServer(int serverPort) {
server = std::make_unique<CivetServer>(civetWebOptions); server = std::make_unique<CivetServer>(civetWebOptions);
} }
BellHTTPServer::~BellHTTPServer() {
std::lock_guard lock(initMutex);
mg_exit_library();
}
std::unique_ptr<BellHTTPServer::HTTPResponse> BellHTTPServer::makeJsonResponse( std::unique_ptr<BellHTTPServer::HTTPResponse> BellHTTPServer::makeJsonResponse(
const std::string& json, int status) { const std::string& json, int status) {
auto response = std::make_unique<BellHTTPServer::HTTPResponse>(); auto response = std::make_unique<BellHTTPServer::HTTPResponse>();

View File

@@ -19,6 +19,7 @@ namespace bell {
class BellHTTPServer : public CivetHandler { class BellHTTPServer : public CivetHandler {
public: public:
BellHTTPServer(int serverPort); BellHTTPServer(int serverPort);
~BellHTTPServer();
enum class WSState { CONNECTED, READY, CLOSED }; enum class WSState { CONNECTED, READY, CLOSED };
@@ -100,6 +101,8 @@ class BellHTTPServer : public CivetHandler {
std::mutex responseMutex; std::mutex responseMutex;
HTTPHandler notFoundHandler; HTTPHandler notFoundHandler;
static std::mutex initMutex;
bool handleGet(CivetServer* server, struct mg_connection* conn); bool handleGet(CivetServer* server, struct mg_connection* conn);
bool handlePost(CivetServer* server, struct mg_connection* conn); bool handlePost(CivetServer* server, struct mg_connection* conn);
}; };

View File

@@ -5,6 +5,8 @@
#include <unistd.h> #include <unistd.h>
#include <cstring> #include <cstring>
#include <vector> #include <vector>
#include <mutex>
#include <atomic>
#if __has_include("avahi-client/client.h") #if __has_include("avahi-client/client.h")
#include <avahi-client/client.h> #include <avahi-client/client.h>
@@ -40,8 +42,9 @@ class implMDNSService : public MDNSService {
#endif #endif
static struct mdnsd* mdnsServer; static struct mdnsd* mdnsServer;
static in_addr_t host; static in_addr_t host;
static std::atomic<size_t> instances;
implMDNSService(struct mdns_service* service) : service(service){}; implMDNSService(struct mdns_service* service) : service(service){ instances++; };
#ifndef BELL_DISABLE_AVAHI #ifndef BELL_DISABLE_AVAHI
implMDNSService(AvahiEntryGroup* avahiGroup) : avahiGroup(avahiGroup){}; implMDNSService(AvahiEntryGroup* avahiGroup) : avahiGroup(avahiGroup){};
#endif #endif
@@ -50,6 +53,7 @@ class implMDNSService : public MDNSService {
struct mdnsd* implMDNSService::mdnsServer = NULL; struct mdnsd* implMDNSService::mdnsServer = NULL;
in_addr_t implMDNSService::host = INADDR_ANY; in_addr_t implMDNSService::host = INADDR_ANY;
std::atomic<size_t> implMDNSService::instances = 0;
static std::mutex registerMutex; static std::mutex registerMutex;
#ifndef BELL_DISABLE_AVAHI #ifndef BELL_DISABLE_AVAHI
AvahiClient* implMDNSService::avahiClient = NULL; AvahiClient* implMDNSService::avahiClient = NULL;
@@ -65,10 +69,20 @@ void implMDNSService::unregisterService() {
#ifndef BELL_DISABLE_AVAHI #ifndef BELL_DISABLE_AVAHI
if (avahiGroup) { if (avahiGroup) {
avahi_entry_group_free(avahiGroup); avahi_entry_group_free(avahiGroup);
if (!--instances && implMDNSService::avahiClient) {
avahi_client_free(implMDNSService::avahiClient);
avahi_simple_poll_free(implMDNSService::avahiPoll);
implMDNSService::avahiClient = nullptr;
implMDNSService::avahiPoll = nullptr;
}
} else } else
#endif #endif
{ {
mdns_service_remove(implMDNSService::mdnsServer, service); mdns_service_remove(implMDNSService::mdnsServer, service);
if (!--instances && implMDNSService::mdnsServer) {
mdnsd_stop(implMDNSService::mdnsServer);
implMDNSService::mdnsServer = nullptr;
}
} }
} }
@@ -179,19 +193,14 @@ std::unique_ptr<MDNSService> MDNSService::registerService(
std::string type(serviceType + "." + serviceProto + ".local"); std::string type(serviceType + "." + serviceProto + ".local");
BELL_LOG(info, "MDNS", "using built-in mDNS for %s", serviceName.c_str()); BELL_LOG(info, "MDNS", "using built-in mDNS for %s", serviceName.c_str());
struct mdns_service* mdnsService = auto service =
mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(), mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
type.c_str(), servicePort, NULL, txt.data());
if (mdnsService) {
auto service =
mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
type.c_str(), servicePort, NULL, txt.data()); type.c_str(), servicePort, NULL, txt.data());
return std::make_unique<implMDNSService>(service); if (service) return std::make_unique<implMDNSService>(service);
}
} }
BELL_LOG(error, "MDNS", "cannot start any mDNS listener for %s", BELL_LOG(error, "MDNS", "cannot start any mDNS listener for %s",
serviceName.c_str()); serviceName.c_str());
return NULL; return nullptr;
} }

View File

@@ -19,13 +19,12 @@ using namespace bell;
class implMDNSService : public MDNSService { class implMDNSService : public MDNSService {
private: private:
struct mdns_service* service; struct mdns_service* service;
void unregisterService(void) { void unregisterService(void);
mdns_service_remove(implMDNSService::mdnsServer, service);
};
public: public:
static struct mdnsd* mdnsServer; static struct mdnsd* mdnsServer;
implMDNSService(struct mdns_service* service) : service(service){}; static std::atomic<size_t> instances;
implMDNSService(struct mdns_service* service) : service(service) { instances++; };
}; };
/** /**
@@ -33,8 +32,18 @@ class implMDNSService : public MDNSService {
**/ **/
struct mdnsd* implMDNSService::mdnsServer = NULL; struct mdnsd* implMDNSService::mdnsServer = NULL;
std::atomic<size_t> implMDNSService::instances = 0;
static std::mutex registerMutex; static std::mutex registerMutex;
void implMDNSService::unregisterService() {
mdns_service_remove(implMDNSService::mdnsServer, service);
if (!--instances && implMDNSService::mdnsServer) {
mdnsd_stop(implMDNSService::mdnsServer);
implMDNSService::mdnsServer = nullptr;
}
}
std::unique_ptr<MDNSService> MDNSService::registerService( std::unique_ptr<MDNSService> MDNSService::registerService(
const std::string& serviceName, const std::string& serviceType, const std::string& serviceName, const std::string& serviceType,
const std::string& serviceProto, const std::string& serviceHost, const std::string& serviceProto, const std::string& serviceHost,
@@ -94,5 +103,5 @@ std::unique_ptr<MDNSService> MDNSService::registerService(
mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(), mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
type.c_str(), servicePort, NULL, txt.data()); type.c_str(), servicePort, NULL, txt.data());
return std::make_unique<implMDNSService>(service); return service ? std::make_unique<implMDNSService>(service) : nullptr;
} }

View File

@@ -72,7 +72,11 @@ class Task {
(LPTHREAD_START_ROUTINE)taskEntryFunc, this, 0, NULL); (LPTHREAD_START_ROUTINE)taskEntryFunc, this, 0, NULL);
return thread != NULL; return thread != NULL;
#else #else
return (pthread_create(&thread, NULL, taskEntryFunc, this) == 0); if (!pthread_create(&thread, NULL, taskEntryFunc, this)) {
pthread_detach(thread);
return true;
}
return false;
#endif #endif
} }
@@ -108,13 +112,7 @@ class Task {
#endif #endif
static void* taskEntryFunc(void* This) { static void* taskEntryFunc(void* This) {
Task* self = (Task*)This; ((Task*)This)->runTask();
self->runTask();
#if _WIN32
WaitForSingleObject(self->thread, INFINITE);
#else
pthread_join(self->thread, NULL);
#endif
return NULL; return NULL;
} }
}; };

View File

@@ -37,7 +37,6 @@ class TrackPlayer : bell::Task {
typedef std::function<size_t(uint8_t*, size_t, std::string_view)> typedef std::function<size_t(uint8_t*, size_t, std::string_view)>
DataCallback; DataCallback;
typedef std::function<void()> EOFCallback; typedef std::function<void()> EOFCallback;
typedef std::function<bool()> isAiringCallback;
TrackPlayer(std::shared_ptr<cspot::Context> ctx, TrackPlayer(std::shared_ptr<cspot::Context> ctx,
std::shared_ptr<cspot::TrackQueue> trackQueue, std::shared_ptr<cspot::TrackQueue> trackQueue,

View File

@@ -24,7 +24,7 @@ class CDNAudioFile;
// Used in got track info event // Used in got track info event
struct TrackInfo { struct TrackInfo {
std::string name, album, artist, imageUrl, trackId; std::string name, album, artist, imageUrl, trackId;
uint32_t duration; uint32_t duration, number, discNumber;
void loadPbTrack(Track* pbTrack, const std::vector<uint8_t>& gid); void loadPbTrack(Track* pbTrack, const std::vector<uint8_t>& gid);
void loadPbEpisode(Episode* pbEpisode, const std::vector<uint8_t>& gid); void loadPbEpisode(Episode* pbEpisode, const std::vector<uint8_t>& gid);
@@ -54,6 +54,7 @@ class QueuedTrack {
uint32_t requestedPosition; uint32_t requestedPosition;
std::string identifier; std::string identifier;
bool loading = false;
// Will return nullptr if the track is not ready // Will return nullptr if the track is not ready
std::shared_ptr<cspot::CDNAudioFile> getAudioFile(); std::shared_ptr<cspot::CDNAudioFile> getAudioFile();
@@ -100,7 +101,7 @@ class TrackQueue : public bell::Task {
bool hasTracks(); bool hasTracks();
bool isFinished(); bool isFinished();
bool skipTrack(SkipDirection dir, bool expectNotify = true); bool skipTrack(SkipDirection dir, bool expectNotify = true);
void updateTracks(uint32_t requestedPosition = 0, bool initial = false); bool updateTracks(uint32_t requestedPosition = 0, bool initial = false);
TrackInfo getTrackInfo(std::string_view identifier); TrackInfo getTrackInfo(std::string_view identifier);
std::shared_ptr<QueuedTrack> consumeTrack( std::shared_ptr<QueuedTrack> consumeTrack(
std::shared_ptr<QueuedTrack> prevSong, int& offset); std::shared_ptr<QueuedTrack> prevSong, int& offset);

View File

@@ -32,6 +32,8 @@ message Artist {
message Track { message Track {
optional bytes gid = 1; optional bytes gid = 1;
optional string name = 2; optional string name = 2;
optional sint32 number = 5;
optional sint32 disc_number = 6;
optional sint32 duration = 0x7; optional sint32 duration = 0x7;
optional Album album = 0x3; optional Album album = 0x3;
repeated Artist artist = 0x4; repeated Artist artist = 0x4;
@@ -44,6 +46,7 @@ message Episode {
optional bytes gid = 1; optional bytes gid = 1;
optional string name = 2; optional string name = 2;
optional sint32 duration = 7; optional sint32 duration = 7;
optional sint32 number = 65;
repeated AudioFile file = 12; repeated AudioFile file = 12;
repeated Restriction restriction = 0x4B; repeated Restriction restriction = 0x4B;
optional ImageGroup covers = 0x44; optional ImageGroup covers = 0x44;

View File

@@ -201,11 +201,12 @@ void SpircHandler::handleFrame(std::vector<uint8_t>& data) {
break; break;
} }
case MessageType_kMessageTypeReplace: { case MessageType_kMessageTypeReplace: {
CSPOT_LOG(debug, "Got replace frame"); CSPOT_LOG(debug, "Got replace frame %d",
playbackState->remoteTracks.size());
playbackState->syncWithRemote(); playbackState->syncWithRemote();
// 1st track is the current one, but update the position // 1st track is the current one, but update the position
trackQueue->updateTracks( bool cleared = trackQueue->updateTracks(
playbackState->remoteFrame.state.position_ms + playbackState->remoteFrame.state.position_ms +
ctx->timeProvider->getSyncedTimestamp() - ctx->timeProvider->getSyncedTimestamp() -
playbackState->innerFrame.state.position_measured_at, playbackState->innerFrame.state.position_measured_at,
@@ -213,8 +214,11 @@ void SpircHandler::handleFrame(std::vector<uint8_t>& data) {
this->notify(); this->notify();
sendEvent(EventType::FLUSH); // need to re-load all if streaming track is completed
trackPlayer->resetState(); if (cleared) {
sendEvent(EventType::FLUSH);
trackPlayer->resetState();
}
break; break;
} }
case MessageType_kMessageTypeShuffle: { case MessageType_kMessageTypeShuffle: {

View File

@@ -201,6 +201,7 @@ void TrackPlayer::runTask() {
} }
eof = false; eof = false;
track->loading = true;
CSPOT_LOG(info, "Playing"); CSPOT_LOG(info, "Playing");
@@ -255,6 +256,7 @@ void TrackPlayer::runTask() {
// always move back to LOADING (ensure proper seeking after last track has been loaded) // always move back to LOADING (ensure proper seeking after last track has been loaded)
currentTrackStream = nullptr; currentTrackStream = nullptr;
track->loading = false;
} }
if (eof) { if (eof) {

View File

@@ -97,6 +97,8 @@ void TrackInfo::loadPbTrack(Track* pbTrack, const std::vector<uint8_t>& gid) {
} }
} }
number = pbTrack->has_number ? pbTrack->number : 0;
discNumber = pbTrack->has_disc_number ? pbTrack->disc_number : 0;
duration = pbTrack->duration; duration = pbTrack->duration;
} }
@@ -113,6 +115,8 @@ void TrackInfo::loadPbEpisode(Episode* pbEpisode,
imageUrl = "https://i.scdn.co/image/" + bytesToHexString(imageId); imageUrl = "https://i.scdn.co/image/" + bytesToHexString(imageId);
} }
number = pbEpisode->has_number ? pbEpisode->number : 0;
discNumber = 0;
duration = pbEpisode->duration; duration = pbEpisode->duration;
} }
@@ -587,18 +591,18 @@ bool TrackQueue::isFinished() {
return currentTracksIndex >= currentTracks.size() - 1; return currentTracksIndex >= currentTracks.size() - 1;
} }
void TrackQueue::updateTracks(uint32_t requestedPosition, bool initial) { bool TrackQueue::updateTracks(uint32_t requestedPosition, bool initial) {
std::scoped_lock lock(tracksMutex); std::scoped_lock lock(tracksMutex);
bool cleared = true;
// Copy requested track list
currentTracks = playbackState->remoteTracks;
currentTracksIndex = playbackState->innerFrame.state.playing_track_index;
if (initial) { if (initial) {
// Clear preloaded tracks // Clear preloaded tracks
preloadedTracks.clear(); preloadedTracks.clear();
// Copy requested track list
currentTracks = playbackState->remoteTracks;
currentTracksIndex = playbackState->innerFrame.state.playing_track_index;
if (currentTracksIndex < currentTracks.size()) { if (currentTracksIndex < currentTracks.size()) {
// Push a song on the preloaded queue // Push a song on the preloaded queue
queueNextTrack(0, requestedPosition); queueNextTrack(0, requestedPosition);
@@ -608,14 +612,25 @@ void TrackQueue::updateTracks(uint32_t requestedPosition, bool initial) {
notifyPending = true; notifyPending = true;
playableSemaphore->give(); playableSemaphore->give();
} else if (preloadedTracks[0]->loading) {
// try to not re-load track if we are still loading it
// remove everything except first track
preloadedTracks.erase(preloadedTracks.begin() + 1, preloadedTracks.end());
// Push a song on the preloaded queue
CSPOT_LOG(info, "Keeping current track %d", currentTracksIndex);
queueNextTrack(1);
cleared = false;
} else { } else {
// Clear preloaded tracks // Clear preloaded tracks
preloadedTracks.clear(); preloadedTracks.clear();
// Copy requested track list
currentTracks = playbackState->remoteTracks;
// Push a song on the preloaded queue // Push a song on the preloaded queue
CSPOT_LOG(info, "Re-loading current track");
queueNextTrack(0, requestedPosition); queueNextTrack(0, requestedPosition);
} }
return cleared;
} }

View File

@@ -288,6 +288,7 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
output.frames_played = 0; output.frames_played = 0;
output.external = DECODE_RAOP; output.external = DECODE_RAOP;
output.state = OUTPUT_STOPPED; output.state = OUTPUT_STOPPED;
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR; if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
LOG_INFO("resizing buffer %u", outputbuf->size); LOG_INFO("resizing buffer %u", outputbuf->size);
break; break;
@@ -377,6 +378,7 @@ static bool cspot_cmd_handler(cspot_event_t cmd, va_list args)
output.state = OUTPUT_STOPPED; output.state = OUTPUT_STOPPED;
sink_state = SINK_ABORT; sink_state = SINK_ABORT;
_buf_flush(outputbuf); _buf_flush(outputbuf);
_buf_limit(outputbuf, 0);
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR; if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
LOG_INFO("CSpot start track"); LOG_INFO("CSpot start track");
break; break;

View File

@@ -216,7 +216,7 @@ static EXT_RAM_ATTR struct {
static EXT_RAM_ATTR struct { static EXT_RAM_ATTR struct {
int mode; int mode;
int n, style, max; int n, style, max, gain;
u16_t config; u16_t config;
struct bar_s bars[MAX_BARS] ; struct bar_s bars[MAX_BARS] ;
} led_visu; } led_visu;
@@ -687,6 +687,8 @@ void draw_VU(struct GDS_Device * display, int level, int x, int y, int width, bo
static void grfe_handler( u8_t *data, int len) { static void grfe_handler( u8_t *data, int len) {
struct grfe_packet *pkt = (struct grfe_packet*) data; struct grfe_packet *pkt = (struct grfe_packet*) data;
if (!display) return;
// we don't support transition, simply claim we're done // we don't support transition, simply claim we're done
if (pkt->transition != 'c') { if (pkt->transition != 'c') {
LOG_INFO("Transition %c requested with offset %hu, param %d", pkt->transition, pkt->offset, pkt->param); LOG_INFO("Transition %c requested with offset %hu, param %d", pkt->transition, pkt->offset, pkt->param);
@@ -763,6 +765,8 @@ static void grfs_handler(u8_t *data, int len) {
int size = len - sizeof(struct grfs_packet); int size = len - sizeof(struct grfs_packet);
int offset = htons(pkt->offset); int offset = htons(pkt->offset);
if (!display) return;
LOG_DEBUG("grfs s:%u d:%u p:%u sp:%u by:%hu m:%hu w:%hu o:%hu", LOG_DEBUG("grfs s:%u d:%u p:%u sp:%u by:%hu m:%hu w:%hu o:%hu",
(int) pkt->screen, (int) pkt->screen,
(int) pkt->direction, // 1=left, 2=right (int) pkt->direction, // 1=left, 2=right
@@ -818,6 +822,8 @@ static void grfs_handler(u8_t *data, int len) {
static void grfg_handler(u8_t *data, int len) { static void grfg_handler(u8_t *data, int len) {
struct grfg_packet *pkt = (struct grfg_packet*) data; struct grfg_packet *pkt = (struct grfg_packet*) data;
if (!display) return;
LOG_DEBUG("gfrg s:%hu w:%hu (len:%u)", htons(pkt->screen), htons(pkt->width), len); LOG_DEBUG("gfrg s:%hu w:%hu (len:%u)", htons(pkt->screen), htons(pkt->width), len);
// full screen artwork or for small screen, visu has priority when full screen // full screen artwork or for small screen, visu has priority when full screen
@@ -864,6 +870,8 @@ static void grfa_handler(u8_t *data, int len) {
int offset = htonl(pkt->offset); int offset = htonl(pkt->offset);
int length = htonl(pkt->length); int length = htonl(pkt->length);
if (!display) return;
// when using full screen visualizer on small screen there is a brief overlay // when using full screen visualizer on small screen there is a brief overlay
artwork.enable = (length != 0); artwork.enable = (length != 0);
@@ -1089,10 +1097,10 @@ static void displayer_update(void) {
if (led_display && led_visu.mode) { if (led_display && led_visu.mode) {
// run built in visualizer effects // run built in visualizer effects
if (led_visu.mode == VISU_VUMETER) { if (led_visu.mode == VISU_VUMETER) {
vu_scale(led_visu.bars, led_visu.max, meters.levels); vu_scale(led_visu.bars, led_visu.gain, meters.levels);
led_vu_display(led_visu.bars[0].current, led_visu.bars[1].current, led_visu.max, led_visu.style); led_vu_display(led_visu.bars[0].current, led_visu.bars[1].current, led_visu.max, led_visu.style);
} else if (led_visu.mode == VISU_SPECTRUM) { } else if (led_visu.mode == VISU_SPECTRUM) {
spectrum_scale(led_visu.n, led_visu.bars, led_visu.max, meters.samples); spectrum_scale(led_visu.n, led_visu.bars, led_visu.gain, meters.samples);
uint8_t* p = (uint8_t*) led_data; uint8_t* p = (uint8_t*) led_data;
for (int i = 0; i < led_visu.n; i++) { for (int i = 0; i < led_visu.n; i++) {
*p = led_visu.bars[i].current; *p = led_visu.bars[i].current;
@@ -1100,7 +1108,7 @@ static void displayer_update(void) {
} }
led_vu_spectrum(led_data, led_visu.max, led_visu.n, led_visu.style); led_vu_spectrum(led_data, led_visu.max, led_visu.n, led_visu.style);
} else if (led_visu.mode == VISU_WAVEFORM) { } else if (led_visu.mode == VISU_WAVEFORM) {
spectrum_scale(led_visu.n, led_visu.bars, led_visu.max, meters.samples); spectrum_scale(led_visu.n, led_visu.bars, led_visu.gain, meters.samples);
led_vu_spin_dial( led_vu_spin_dial(
led_visu.bars[led_visu.n-2].current, led_visu.bars[led_visu.n-2].current,
led_visu.bars[(led_visu.n/2)+1].current * 50 / led_visu.max, led_visu.bars[(led_visu.n/2)+1].current * 50 / led_visu.max,
@@ -1277,8 +1285,8 @@ static void ledv_handler( u8_t *data, int len) {
led_visu.mode = pkt->which; led_visu.mode = pkt->which;
led_visu.style = pkt->style; led_visu.style = pkt->style;
led_visu.max = pkt->bright; led_visu.max = pkt->bright;
led_visu.gain = led_visu.max * led_vu_scale() / 100;
led_vu_clear();
if (led_visu.mode) { if (led_visu.mode) {
if (led_visu.mode == VISU_SPECTRUM) { if (led_visu.mode == VISU_SPECTRUM) {
led_visu.n = (led_visu.config < MAX_BARS) ? led_visu.config : MAX_BARS; led_visu.n = (led_visu.config < MAX_BARS) ? led_visu.config : MAX_BARS;
@@ -1293,8 +1301,10 @@ static void ledv_handler( u8_t *data, int len) {
// reset bars maximum // reset bars maximum
for (int i = led_visu.n; --i >= 0;) led_visu.bars[i].max = 0; for (int i = led_visu.n; --i >= 0;) led_visu.bars[i].max = 0;
LOG_INFO("LED Visualizer mode %u with bars:%u max:%u style:%d", led_visu.mode, led_visu.n, led_visu.max, led_visu.style); LOG_INFO("LED Visualizer mode %u with bars:%u max:%u style:%d gain:%u", led_visu.mode, led_visu.n, led_visu.max, led_visu.style, led_visu.gain);
} else { } else {
led_vu_clear();
LOG_INFO("Stopping led visualizer"); LOG_INFO("Stopping led visualizer");
} }

View File

@@ -22,7 +22,8 @@ static EXT_RAM_ATTR struct {
void *handle; void *handle;
float loudness, volume; float loudness, volume;
uint32_t samplerate; uint32_t samplerate;
float gain[EQ_BANDS], loudness_gain[EQ_BANDS]; int8_t gain[EQ_BANDS];
float loudness_gain[EQ_BANDS];
bool update; bool update;
} equalizer; } equalizer;
@@ -151,6 +152,8 @@ void equalizer_set_gain(int8_t *gain) {
char config[EQ_BANDS * 4 + 1] = { }; char config[EQ_BANDS * 4 + 1] = { };
int n = 0; int n = 0;
if (memcmp(equalizer.gain, gain, EQ_BANDS) != 0) equalizer.update = true;
for (int i = 0; i < EQ_BANDS; i++) { for (int i = 0; i < EQ_BANDS; i++) {
equalizer.gain[i] = gain[i]; equalizer.gain[i] = gain[i];
n += sprintf(config + n, "%d,", gain[i]); n += sprintf(config + n, "%d,", gain[i]);
@@ -159,9 +162,6 @@ void equalizer_set_gain(int8_t *gain) {
config[n-1] = '\0'; config[n-1] = '\0';
config_set_value(NVS_TYPE_STR, "equalizer", config); config_set_value(NVS_TYPE_STR, "equalizer", config);
// update only if something changed
if (!memcmp(equalizer.gain, gain, EQ_BANDS)) equalizer.update = true;
LOG_INFO("equalizer gain %s", config); LOG_INFO("equalizer gain %s", config);
#else #else
LOG_INFO("no equalizer with 32 bits samples"); LOG_INFO("no equalizer with 32 bits samples");

View File

@@ -61,7 +61,6 @@ static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool
char *p; char *p;
i2c_addr = adac_init(config, i2c_port_num); i2c_addr = adac_init(config, i2c_port_num);
if (!i2c_addr) return true;
ESP_LOGI(TAG, "DAC on I2C @%d", i2c_addr); ESP_LOGI(TAG, "DAC on I2C @%d", i2c_addr);
@@ -137,6 +136,7 @@ bool i2c_json_execute(char *set) {
if ((action = cJSON_GetObjectItemCaseSensitive(item, "gpio")) != NULL) { if ((action = cJSON_GetObjectItemCaseSensitive(item, "gpio")) != NULL) {
cJSON *level = cJSON_GetObjectItemCaseSensitive(item, "level"); cJSON *level = cJSON_GetObjectItemCaseSensitive(item, "level");
ESP_LOGI(TAG, "set GPIO %d at %d", action->valueint, level->valueint); ESP_LOGI(TAG, "set GPIO %d at %d", action->valueint, level->valueint);
if (action->valueint < GPIO_NUM_MAX) gpio_pad_select_gpio(action->valueint);
gpio_set_direction_x(action->valueint, GPIO_MODE_OUTPUT); gpio_set_direction_x(action->valueint, GPIO_MODE_OUTPUT);
gpio_set_level_x(action->valueint, level->valueint); gpio_set_level_x(action->valueint, level->valueint);
continue; continue;

View File

@@ -70,6 +70,7 @@ struct flac {
); );
FLAC__bool (* FLAC__stream_decoder_process_single)(FLAC__StreamDecoder *decoder); FLAC__bool (* FLAC__stream_decoder_process_single)(FLAC__StreamDecoder *decoder);
FLAC__StreamDecoderState (* FLAC__stream_decoder_get_state)(const FLAC__StreamDecoder *decoder); FLAC__StreamDecoderState (* FLAC__stream_decoder_get_state)(const FLAC__StreamDecoder *decoder);
FLAC__bool (*FLAC__stream_decoder_set_ogg_chaining)(FLAC__StreamDecoder* decoder, FLAC__bool allow);
#endif #endif
}; };
@@ -121,6 +122,9 @@ static FLAC__StreamDecoderReadStatus read_cb(const FLAC__StreamDecoder *decoder,
_buf_inc_readp(streambuf, bytes); _buf_inc_readp(streambuf, bytes);
UNLOCK_S; UNLOCK_S;
// give some time for stream to acquire data otherwise flac will hammer us
if (!end && !bytes) usleep(100 * 1000);
*want = bytes; *want = bytes;
return end ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; return end ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
@@ -137,8 +141,8 @@ static FLAC__StreamDecoderWriteStatus write_cb(const FLAC__StreamDecoder *decode
FLAC__int32 *rptr = (FLAC__int32 *)buffer[channels > 1 ? 1 : 0]; FLAC__int32 *rptr = (FLAC__int32 *)buffer[channels > 1 ? 1 : 0];
if (decode.new_stream) { if (decode.new_stream) {
LOCK_O;
LOG_INFO("setting track_start"); LOG_INFO("setting track_start");
LOCK_O;
output.track_start = outputbuf->writep; output.track_start = outputbuf->writep;
decode.new_stream = false; decode.new_stream = false;
@@ -253,6 +257,7 @@ static void flac_open(u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t en
if ( f->container == 'o' ) { if ( f->container == 'o' ) {
LOG_INFO("ogg/flac container - using init_ogg_stream"); LOG_INFO("ogg/flac container - using init_ogg_stream");
FLAC(f, stream_decoder_set_ogg_chaining, f->decoder, true);
FLAC(f, stream_decoder_init_ogg_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL); FLAC(f, stream_decoder_init_ogg_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL);
} else { } else {
FLAC(f, stream_decoder_init_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL); FLAC(f, stream_decoder_init_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL);
@@ -295,6 +300,7 @@ static bool load_flac() {
f->FLAC__stream_decoder_init_ogg_stream = dlsym(handle, "FLAC__stream_decoder_init_ogg_stream"); f->FLAC__stream_decoder_init_ogg_stream = dlsym(handle, "FLAC__stream_decoder_init_ogg_stream");
f->FLAC__stream_decoder_process_single = dlsym(handle, "FLAC__stream_decoder_process_single"); f->FLAC__stream_decoder_process_single = dlsym(handle, "FLAC__stream_decoder_process_single");
f->FLAC__stream_decoder_get_state = dlsym(handle, "FLAC__stream_decoder_get_state"); f->FLAC__stream_decoder_get_state = dlsym(handle, "FLAC__stream_decoder_get_state");
f->FLAC__stream_decoder_set_ogg_chaining = dlsym(handle, "FLAC__stream_decoder_set_ogg_chaining");
if ((err = dlerror()) != NULL) { if ((err = dlerror()) != NULL) {
LOG_INFO("dlerror: %s", err); LOG_INFO("dlerror: %s", err);

View File

@@ -44,7 +44,7 @@
#define MAX_OPUS_FRAMES 5760 #define MAX_OPUS_FRAMES 5760
struct opus { struct opus {
enum {OGG_SYNC, OGG_ID_HEADER, OGG_COMMENT_HEADER} status; enum { OGG_ID_HEADER, OGG_COMMENT_HEADER } status;
ogg_stream_state state; ogg_stream_state state;
ogg_packet packet; ogg_packet packet;
ogg_sync_state sync; ogg_sync_state sync;
@@ -131,7 +131,7 @@ static opus_uint32 parse_uint32(const unsigned char* _data) {
(opus_uint32)_data[2] << 16 | (opus_uint32)_data[3] << 24; (opus_uint32)_data[2] << 16 | (opus_uint32)_data[3] << 24;
} }
static int get_opus_packet(void) { static int get_audio_packet(void) {
int status, packet = -1; int status, packet = -1;
LOCK_S; LOCK_S;
@@ -139,87 +139,101 @@ static int get_opus_packet(void) {
while (!(status = OG(&go, stream_packetout, &u->state, &u->packet)) && bytes) { while (!(status = OG(&go, stream_packetout, &u->state, &u->packet)) && bytes) {
// if sync_pageout (or sync_pageseek) is not called here, sync builds ups // if sync_pageout (or sync_pageseek) is not called here, sync buffers build up
while (!(status = OG(&go, sync_pageout, &u->sync, &u->page)) && bytes) { while (!(status = OG(&go, sync_pageout, &u->sync, &u->page)) && bytes) {
size_t consumed = min(bytes, 4096); size_t consumed = min(bytes, 4096);
char* buffer = OG(&gu, sync_buffer, &u->sync, consumed); char* buffer = OG(&go, sync_buffer, &u->sync, consumed);
memcpy(buffer, streambuf->readp, consumed); memcpy(buffer, streambuf->readp, consumed);
OG(&gu, sync_wrote, &u->sync, consumed); OG(&go, sync_wrote, &u->sync, consumed);
_buf_inc_readp(streambuf, consumed); _buf_inc_readp(streambuf, consumed);
bytes -= consumed; bytes -= consumed;
} }
// if we have a new page, put it in // if we have a new page, put it in and reset serialno at BoS
if (status) OG(&go, stream_pagein, &u->state, &u->page); if (status) {
OG(&go, stream_pagein, &u->state, &u->page);
if (OG(&go, page_bos, &u->page)) OG(&go, stream_reset_serialno, &u->state, OG(&go, page_serialno, &u->page));
}
} }
// only return a negative value when true end of streaming is reached /* discard header packets. With no packet, we return a negative value
if (status > 0) packet = status; * when there is really nothing more to proceed */
else if (stream.state > DISCONNECT || _buf_used(streambuf)) packet = 0; if (status > 0 && memcmp(u->packet.packet, "OpusHead", 8) && memcmp(u->packet.packet, "OpusTags", 8)) packet = status;
else if (stream.state > DISCONNECT || _buf_used(streambuf)) packet = 0;
UNLOCK_S; UNLOCK_S;
return packet; return packet;
} }
static int read_opus_header(void) { static int read_opus_header(void) {
int status = 0; int done = 0;
bool fetch = true; bool fetch = true;
LOCK_S; LOCK_S;
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
while (bytes && !status) { while (bytes && !done) {
// first fetch a page if we need one int status;
if (fetch) {
// get aligned to a page and ready to bring it in
do {
size_t consumed = min(bytes, 4096); size_t consumed = min(bytes, 4096);
char* buffer = OG(&gu, sync_buffer, &u->sync, consumed);
char* buffer = OG(&go, sync_buffer, &u->sync, consumed);
memcpy(buffer, streambuf->readp, consumed); memcpy(buffer, streambuf->readp, consumed);
OG(&gu, sync_wrote, &u->sync, consumed); OG(&go, sync_wrote, &u->sync, consumed);
_buf_inc_readp(streambuf, consumed); _buf_inc_readp(streambuf, consumed);
bytes -= consumed; bytes -= consumed;
if (!OG(&gu, sync_pageseek, &u->sync, &u->page)) continue; status = fetch ? OG(&go, sync_pageout, &u->sync, &u->page) :
} OG(&go, sync_pageseek, &u->sync, &u->page);
} while (bytes && status <= 0);
switch (u->status) { // nothing has been found and we have no more bytes, come back later
case OGG_SYNC: if (status <= 0) break;
u->status = OGG_ID_HEADER;
OG(&gu, stream_init, &u->state, OG(&gu, page_serialno, &u->page)); // always set stream serialno if we have a new one (no multiplexed streams)
fetch = false; if (OG(&go, page_bos, &u->page)) OG(&go, stream_reset_serialno, &u->state, OG(&go, page_serialno, &u->page));
break;
case OGG_ID_HEADER: // bring new page in if we want it (otherwise we're just skipping)
status = OG(&gu, stream_pagein, &u->state, &u->page); if (fetch) OG(&go, stream_pagein, &u->state, &u->page);
if (OG(&gu, stream_packetout, &u->state, &u->packet)) {
if (u->packet.bytes < 19 || memcmp(u->packet.packet, "OpusHead", 8)) { // no need for a switch...case
LOG_ERROR("wrong opus header packet (size:%u)", u->packet.bytes); if (u->status == OGG_ID_HEADER) {
status = -100; // we need the id packet, get more pages if we don't
break; if (OG(&go, stream_packetout, &u->state, &u->packet) <= 0) continue;
}
// make sure this is a valid packet
if (u->packet.bytes < 19 || memcmp(u->packet.packet, "OpusHead", 8)) {
LOG_ERROR("wrong header packet (size:%u)", u->packet.bytes);
done = -100;
} else {
u->status = OGG_COMMENT_HEADER; u->status = OGG_COMMENT_HEADER;
u->channels = u->packet.packet[9]; u->channels = u->packet.packet[9];
u->pre_skip = parse_uint16(u->packet.packet + 10); u->pre_skip = parse_uint16(u->packet.packet + 10);
u->rate = parse_uint32(u->packet.packet + 12); u->rate = parse_uint32(u->packet.packet + 12);
u->gain = parse_int16(u->packet.packet + 16); u->gain = parse_int16(u->packet.packet + 16);
u->decoder = OP(&gu, decoder_create, 48000, u->channels, &status); u->decoder = OP(&gu, decoder_create, 48000, u->channels, &status);
fetch = false;
if (!u->decoder || status != OPUS_OK) { if (!u->decoder || status != OPUS_OK) {
LOG_ERROR("can't create decoder %d (channels:%u)", status, u->channels); LOG_ERROR("can't create decoder %d (channels:%u)", status, u->channels);
} }
else {
LOG_INFO("codec up and running");
}
} }
fetch = true; } else if (u->status == OGG_COMMENT_HEADER) {
break; // don't consume VorbisComment which could be a huge packet, just skip it
case OGG_COMMENT_HEADER: if (!OG(&go, page_packets, &u->page)) continue;
// skip packets to consume VorbisComment. With opus, header packets align on pages LOG_INFO("comment skipped successfully");
status = OG(&gu, page_packets, &u->page); done = 1;
break;
default:
break;
} }
} }
UNLOCK_S; UNLOCK_S;
return status; return done;
} }
static decode_state opus_decompress(void) { static decode_state opus_decompress(void) {
@@ -271,7 +285,7 @@ static decode_state opus_decompress(void) {
memcpy(write_buf, u->overbuf, u->overframes * BYTES_PER_FRAME); memcpy(write_buf, u->overbuf, u->overframes * BYTES_PER_FRAME);
n = u->overframes; n = u->overframes;
u->overframes = 0; u->overframes = 0;
} else if ((packet = get_opus_packet()) > 0) { } else if ((packet = get_audio_packet()) > 0) {
if (frames < MAX_OPUS_FRAMES) { if (frames < MAX_OPUS_FRAMES) {
// don't have enough contiguous space, use the overflow buffer // don't have enough contiguous space, use the overflow buffer
n = OP(&gu, decode, u->decoder, u->packet.packet, u->packet.bytes, (opus_int16*) u->overbuf, MAX_OPUS_FRAMES, 0); n = OP(&gu, decode, u->decoder, u->packet.packet, u->packet.bytes, (opus_int16*) u->overbuf, MAX_OPUS_FRAMES, 0);
@@ -286,7 +300,7 @@ static decode_state opus_decompress(void) {
* outputbuf and streambuf for maybe a long time while we process it all, so don't do that */ * outputbuf and streambuf for maybe a long time while we process it all, so don't do that */
n = OP(&gu, decode, u->decoder, u->packet.packet, u->packet.bytes, (opus_int16*) write_buf, frames, 0); n = OP(&gu, decode, u->decoder, u->packet.packet, u->packet.bytes, (opus_int16*) write_buf, frames, 0);
} }
} else if (!packet && !OG(&go, page_eos, &u->page)) { } else if (!packet) {
UNLOCK_O_direct; UNLOCK_O_direct;
return DECODE_RUNNING; return DECODE_RUNNING;
} }
@@ -342,7 +356,7 @@ static decode_state opus_decompress(void) {
} else { } else {
LOG_INFO("opus decode error: %d", n); LOG_INFO("decode error: %d", n);
UNLOCK_O_direct; UNLOCK_O_direct;
return DECODE_COMPLETE; return DECODE_COMPLETE;
} }
@@ -357,12 +371,12 @@ static void opus_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
if (!u->overbuf) u->overbuf = malloc(MAX_OPUS_FRAMES * BYTES_PER_FRAME); if (!u->overbuf) u->overbuf = malloc(MAX_OPUS_FRAMES * BYTES_PER_FRAME);
u->status = OGG_SYNC; u->status = OGG_ID_HEADER;
u->overframes = 0; u->overframes = 0;
OG(&gu, sync_clear, &u->sync); OG(&go, stream_clear, &u->state);
OG(&gu, stream_clear, &u->state); OG(&go, sync_clear, &u->sync);
OG(&gu, stream_init, &u->state, -1); OG(&go, stream_init, &u->state, -1);
} }
static void opus_close(void) { static void opus_close(void) {
@@ -372,8 +386,8 @@ static void opus_close(void) {
free(u->overbuf); free(u->overbuf);
u->overbuf = NULL; u->overbuf = NULL;
OG(&gu, stream_clear, &u->state); OG(&go, stream_clear, &u->state);
OG(&gu, sync_clear, &u->sync); OG(&go, sync_clear, &u->sync);
} }
static bool load_opus(void) { static bool load_opus(void) {
@@ -394,7 +408,7 @@ static bool load_opus(void) {
} }
g_handle->ogg_stream_clear = dlsym(g_handle->handle, "ogg_stream_clear"); g_handle->ogg_stream_clear = dlsym(g_handle->handle, "ogg_stream_clear");
g_handle->.ogg_stream_reset = dlsym(g_handle->handle, "ogg_stream_reset"); g_handle->ogg_stream_reset = dlsym(g_handle->handle, "ogg_stream_reset");
g_handle->ogg_stream_eos = dlsym(g_handle->handle, "ogg_stream_eos"); g_handle->ogg_stream_eos = dlsym(g_handle->handle, "ogg_stream_eos");
g_handle->ogg_stream_reset_serialno = dlsym(g_handle->handle, "ogg_stream_reset_serialno"); g_handle->ogg_stream_reset_serialno = dlsym(g_handle->handle, "ogg_stream_reset_serialno");
g_handle->ogg_sync_clear = dlsym(g_handle->handle, "ogg_sync_clear"); g_handle->ogg_sync_clear = dlsym(g_handle->handle, "ogg_sync_clear");

View File

@@ -60,7 +60,7 @@ frames_t _output_frames(frames_t avail) {
silence = false; silence = false;
// start when threshold met // start when threshold met
if (output.state == OUTPUT_BUFFER && (frames * BYTES_PER_FRAME) > output.threshold * output.next_sample_rate / 10 && frames > output.start_frames) { if (output.state == OUTPUT_BUFFER && frames > output.threshold * output.next_sample_rate / 10 && frames > output.start_frames) {
output.state = OUTPUT_RUNNING; output.state = OUTPUT_RUNNING;
LOG_INFO("start buffer frames: %u", frames); LOG_INFO("start buffer frames: %u", frames);
wake_controller(); wake_controller();
@@ -443,7 +443,7 @@ void output_close_common(void) {
} }
void output_flush(void) { void output_flush(void) {
LOG_INFO("flush output buffer"); LOG_INFO("flush output buffer (full)");
buf_flush(outputbuf); buf_flush(outputbuf);
LOCK; LOCK;
output.fade = FADE_INACTIVE; output.fade = FADE_INACTIVE;
@@ -457,3 +457,15 @@ void output_flush(void) {
output.frames_played = 0; output.frames_played = 0;
UNLOCK; UNLOCK;
} }
bool output_flush_streaming(void) {
LOG_INFO("flush output buffer (streaming)");
LOCK;
bool flushed = output.track_start != NULL;
if (output.track_start) {
outputbuf->writep = output.track_start;
output.track_start = NULL;
}
UNLOCK;
return flushed;
}

View File

@@ -140,7 +140,10 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g
memcpy(btout + oframes * BYTES_PER_FRAME, buf, out_frames * BYTES_PER_FRAME); memcpy(btout + oframes * BYTES_PER_FRAME, buf, out_frames * BYTES_PER_FRAME);
} }
output_visu_export(btout + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2); // don't update visu if we don't have enough data in buffer (500 ms)
if (silence || _buf_used(outputbuf) > BYTES_PER_FRAME * output.current_sample_rate / 2) {
output_visu_export(btout + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2);
}
oframes += out_frames; oframes += out_frames;

View File

@@ -222,6 +222,25 @@ static void set_i2s_pin(char *config, i2s_pin_config_t *pin_config) {
#endif #endif
} }
/* When a panic occurs during playback, the I2S interface can produce a loud noise burst.
* This code runs just before the system panic handler to "emergency stop" the I2S iterface
* to prevent the noise burst from happening. Note that when this code is called the system
* has already crashed, so no need to disable interrupts, acquire locks, or otherwise be nice.
*
* This code makes use of the linker --wrap feature to intercept the call to esp_panic_handler.
*/
void __real_esp_panic_handler(void*);
void __wrap_esp_panic_handler (void* info) {
esp_rom_printf("I2S abort!\r\n");
i2s_stop(CONFIG_I2S_NUM);
/* Call the original panic handler function to finish processing this error */
__real_esp_panic_handler(info);
}
/**************************************************************************************** /****************************************************************************************
* Initialize the DAC output * Initialize the DAC output
*/ */
@@ -439,7 +458,7 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4))); static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
static EXT_RAM_ATTR StackType_t xStack[OUTPUT_THREAD_STACK_SIZE] __attribute__ ((aligned (4))); static EXT_RAM_ATTR StackType_t xStack[OUTPUT_THREAD_STACK_SIZE] __attribute__ ((aligned (4)));
output_i2s_task = xTaskCreateStaticPinnedToCore( (TaskFunction_t) output_thread_i2s, "output_i2s", OUTPUT_THREAD_STACK_SIZE, output_i2s_task = xTaskCreateStaticPinnedToCore( (TaskFunction_t) output_thread_i2s, "output_i2s", OUTPUT_THREAD_STACK_SIZE,
NULL, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, xStack, &xTaskBuffer, 0 ); NULL, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 10, xStack, &xTaskBuffer, 0 );
} }
} }
@@ -485,8 +504,8 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32
memcpy(obuf + oframes * BYTES_PER_FRAME, silencebuf, out_frames * BYTES_PER_FRAME); memcpy(obuf + oframes * BYTES_PER_FRAME, silencebuf, out_frames * BYTES_PER_FRAME);
} }
// don't update visu if we don't have enough data in buffer // don't update visu if we don't have enough data in buffer (500 ms)
if (silence || output.external || _buf_used(outputbuf) > outputbuf->size >> 2 ) { if (silence || _buf_used(outputbuf) > BYTES_PER_FRAME * output.current_sample_rate / 2) {
output_visu_export(obuf + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2); output_visu_export(obuf + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2);
} }
@@ -744,7 +763,7 @@ static void IRAM_ATTR spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst) {
// we assume frame == 0 as well... // we assume frame == 0 as well...
if (!src) { if (!src) {
count = 192; count = 0;
vu = VUCP24[0]; vu = VUCP24[0];
} }
@@ -757,7 +776,7 @@ static void IRAM_ATTR spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst) {
if (!count--) { if (!count--) {
*dst++ = (vu << 24) | (PREAMBLE_B << 16) | 0xCCCC; *dst++ = (vu << 24) | (PREAMBLE_B << 16) | 0xCCCC;
count = 192; count = 191;
} else { } else {
*dst++ = (vu << 24) | (PREAMBLE_M << 16) | 0xCCCC; *dst++ = (vu << 24) | (PREAMBLE_M << 16) | 0xCCCC;
} }
@@ -771,7 +790,7 @@ static void IRAM_ATTR spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst) {
if (!count--) { if (!count--) {
*dst++ = (vu << 24) | (PREAMBLE_B << 16) | aux; *dst++ = (vu << 24) | (PREAMBLE_B << 16) | aux;
count = 192; count = 191;
} else { } else {
*dst++ = (vu << 24) | (PREAMBLE_M << 16) | aux; *dst++ = (vu << 24) | (PREAMBLE_M << 16) | aux;
} }

View File

@@ -305,8 +305,18 @@ static void process_strm(u8_t *pkt, int len) {
sendSTAT("STMt", strm->replay_gain); // STMt replay_gain is no longer used to track latency, but support it sendSTAT("STMt", strm->replay_gain); // STMt replay_gain is no longer used to track latency, but support it
break; break;
case 'f': case 'f':
{
decode_flush(false);
bool flushed = false;
if (!output.external) flushed |= output_flush_streaming();
// we can have fully finished the current streaming, that's still a flush
if (stream_disconnect() || flushed) sendSTAT("STMf", 0);
buf_flush(streambuf);
output.stop_time = gettime_ms();
break;
}
case 'q': case 'q':
decode_flush(strm->command == 'q'); decode_flush(true);
if (!output.external) output_flush(); if (!output.external) output_flush();
status.frames_played = 0; status.frames_played = 0;
if (stream_disconnect() && strm->command == 'f') sendSTAT("STMf", 0); if (stream_disconnect() && strm->command == 'f') sendSTAT("STMf", 0);
@@ -383,7 +393,9 @@ static void process_strm(u8_t *pkt, int len) {
stream_file(header, header_len, strm->threshold * 1024); stream_file(header, header_len, strm->threshold * 1024);
autostart -= 2; autostart -= 2;
} else { } else {
stream_sock(ip, port, header, header_len, strm->threshold * 1024, autostart >= 2); stream_sock(ip, port, strm->flags & 0x20,
strm->format == 'o' || strm->format == 'u' || (strm->format == 'f' && strm->pcm_sample_size == 'o'),
header, header_len, strm->threshold * 1024, autostart >= 2);
} }
sendSTAT("STMc", 0); sendSTAT("STMc", 0);
sentSTMu = sentSTMo = sentSTMl = false; sentSTMu = sentSTMo = sentSTMl = false;
@@ -402,8 +414,8 @@ static void process_strm(u8_t *pkt, int len) {
output.fade_secs = strm->transition_period; output.fade_secs = strm->transition_period;
output.invert = (strm->flags & 0x03) == 0x03; output.invert = (strm->flags & 0x03) == 0x03;
output.channels = (strm->flags & 0x0c) >> 2; output.channels = (strm->flags & 0x0c) >> 2;
LOG_DEBUG("set fade: %u, channels: %u, invert: %u", output.fade_mode, output.channels, output.invert);
UNLOCK_O; UNLOCK_O;
LOG_DEBUG("set fade: %u, channels: %u, invert: %u", output.fade_mode, output.channels, output.invert);
} }
break; break;
default: default:
@@ -982,8 +994,8 @@ void slimproto(log_level level, char *server, u8_t mac[6], const char *name, con
// in embedded we give up after a while no matter what // in embedded we give up after a while no matter what
if (++failed_connect > MAX_SERVER_RETRIES && !server) { if (++failed_connect > MAX_SERVER_RETRIES && !server) {
slimproto_ip = serv_addr.sin_addr.s_addr = discover_server(NULL, MAX_SERVER_RETRIES); slimproto_ip = serv_addr.sin_addr.s_addr = discover_server(NULL, MAX_SERVER_RETRIES);
if (!slimproto_ip) return; if (!slimproto_ip && !output.external) return;
} else if (reconnect && MAX_SERVER_RETRIES && failed_connect > 5 * MAX_SERVER_RETRIES) return; } else if (reconnect && MAX_SERVER_RETRIES && failed_connect > 5 * MAX_SERVER_RETRIES && !output.external) return;
#else #else
// rediscover server if it was not set at startup or exit // rediscover server if it was not set at startup or exit
if (!server && ++failed_connect > 5) { if (!server && ++failed_connect > 5) {

View File

@@ -585,7 +585,7 @@ struct streamstate {
void stream_init(log_level level, unsigned stream_buf_size); void stream_init(log_level level, unsigned stream_buf_size);
void stream_close(void); void stream_close(void);
void stream_file(const char *header, size_t header_len, unsigned threshold); void stream_file(const char *header, size_t header_len, unsigned threshold);
void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait); void stream_sock(u32_t ip, u16_t port, bool use_ssl, bool use_ogg, const char *header, size_t header_len, unsigned threshold, bool cont_wait);
bool stream_disconnect(void); bool stream_disconnect(void);
// decode.c // decode.c
@@ -727,6 +727,7 @@ struct outputstate {
void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[], unsigned idle); void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[], unsigned idle);
void output_close_common(void); void output_close_common(void);
void output_flush(void); void output_flush(void);
bool output_flush_streaming(void);
// _* called with mutex locked // _* called with mutex locked
frames_t _output_frames(frames_t avail); frames_t _output_frames(frames_t avail);
void _checkfade(bool); void _checkfade(bool);

View File

@@ -59,7 +59,24 @@ is enough and much faster than a mutex
static bool polling; static bool polling;
static sockfd fd; static sockfd fd;
struct streamstate stream; struct EXT_RAM_ATTR streamstate stream;
static EXT_RAM_ATTR struct {
bool flac;
u64_t serial;
enum { OGG_OFF, OGG_SYNC, OGG_HEADER, OGG_SEGMENTS, OGG_PAGE } state;
size_t want, miss, match;
u8_t* data, segments[255];
#pragma pack(push, 1)
struct {
char pattern[4];
u8_t version, type;
u64_t granule;
u32_t serial, page, checksum;
u8_t count;
} header;
#pragma pack(pop)
} ogg;
#if USE_SSL #if USE_SSL
static SSL_CTX *SSLctx; static SSL_CTX *SSLctx;
@@ -113,7 +130,6 @@ static int _poll(SSL *ssl, struct pollfd *pollinfo, int timeout) {
} }
#endif #endif
static bool send_header(void) { static bool send_header(void) {
char *ptr = stream.header; char *ptr = stream.header;
int len = stream.header_len; int len = stream.header_len;
@@ -148,6 +164,8 @@ static bool running = true;
static void _disconnect(stream_state state, disconnect_code disconnect) { static void _disconnect(stream_state state, disconnect_code disconnect) {
stream.state = state; stream.state = state;
stream.disconnect = disconnect; stream.disconnect = disconnect;
if (ogg.state == OGG_PAGE && ogg.data) free(ogg.data);
ogg.data = NULL;
#if USE_SSL #if USE_SSL
if (ssl) { if (ssl) {
SSL_shutdown(ssl); SSL_shutdown(ssl);
@@ -160,6 +178,139 @@ static void _disconnect(stream_state state, disconnect_code disconnect) {
wake_controller(); wake_controller();
} }
static size_t memfind(const u8_t* haystack, size_t n, const char* needle, size_t len, size_t* offset) {
size_t i;
for (i = 0; i < n && *offset != len; i++) *offset = (haystack[i] == needle[*offset]) ? *offset + 1 : 0;
return i;
}
/* https://xiph.org/ogg/doc/framing.html
* https://xiph.org/flac/ogg_mapping.html
* https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-610004.2 */
static void stream_ogg(size_t n) {
if (ogg.state == OGG_OFF) return;
u8_t* p = streambuf->writep;
while (n) {
size_t consumed = min(ogg.miss, n);
// copy as many bytes as possible and come back later if we do'nt have enough
if (ogg.data) {
memcpy(ogg.data + ogg.want - ogg.miss, p, consumed);
ogg.miss -= consumed;
if (ogg.miss) return;
}
// we have what we want, let's parse
switch (ogg.state) {
case OGG_SYNC: {
ogg.miss -= consumed;
if (consumed) break;
// we have to memorize position in case any of last 3 bytes match...
size_t pos = memfind(p, n, "OggS", 4, &ogg.match);
if (ogg.match == 4) {
consumed = pos - ogg.match;
ogg.state = OGG_HEADER;
ogg.miss = ogg.want = sizeof(ogg.header);
ogg.data = (u8_t*) &ogg.header;
ogg.match = 0;
} else {
if (!ogg.match) LOG_INFO("OggS not at expected position %zu/%zu", pos, n);
LOG_INFO("OggS not at expected position %zu/%zu", pos, n);
return;
}
break;
}
case OGG_HEADER:
if (!memcmp(ogg.header.pattern, "OggS", 4)) {
ogg.miss = ogg.want = ogg.header.count;
ogg.data = ogg.segments;
ogg.state = OGG_SEGMENTS;
} else {
ogg.state = OGG_SYNC;
ogg.data = NULL;
}
break;
case OGG_SEGMENTS:
// calculate size of page using lacing values
for (size_t i = 0; i < ogg.want; i++) ogg.miss += ogg.data[i];
ogg.want = ogg.miss;
// acquire serial number when we are looking for headers and hit a bos
if (ogg.serial == ULLONG_MAX && (ogg.header.type & 0x02)) ogg.serial = ogg.header.serial;
// we have overshot and missed header, reset serial number to restart search (O and -1 are le/be)
if (ogg.header.serial == ogg.serial && ogg.header.granule && ogg.header.granule != -1) ogg.serial = ULLONG_MAX;
// not our serial (the above protected us from granule > 0)
if (ogg.header.serial != ogg.serial) {
// otherwise, jump over data
ogg.state = OGG_SYNC;
ogg.data = NULL;
} else {
ogg.state = OGG_PAGE;
ogg.data = malloc(ogg.want);
}
break;
case OGG_PAGE: {
char** tag = (char* []){ "\x3vorbis", "OpusTags", NULL };
size_t ofs = 0;
/* with OggFlac, we need the next page (packet) - VorbisComment is wrapped into a FLAC_METADATA
* and except with vorbis, comment packet starts a new page but even in vorbis, it won't span
* accross multiple pages */
if (ogg.flac) ofs = 4;
else if (!memcmp(ogg.data, "\x7f""FLAC", 5)) ogg.flac = true;
else for (size_t n = 0; *tag; tag++, ofs = 0) if ((ofs = memfind(ogg.data, ogg.want, *tag, strlen(*tag), &n)) && n == strlen(*tag)) break;
if (ofs) {
// u32:len,char[]:vendorId, u32:N, N x (u32:len,char[]:comment)
char* p = (char*) ogg.data + ofs;
p += *p + 4;
u32_t count = *p;
p += 4;
// LMS metadata format for Ogg is "Ogg", N x (u16:len,char[]:comment)
memcpy(stream.header, "Ogg", 3);
stream.header_len = 3;
for (u32_t len; count--; p += len) {
len = *p;
p += 4;
// only report what we use and don't overflow (network byte order)
if (!strncasecmp(p, "TITLE=", 6) || !strncasecmp(p, "ARTIST=", 7) || !strncasecmp(p, "ALBUM=", 6)) {
if (stream.header_len + len > MAX_HEADER) break;
stream.header[stream.header_len++] = len >> 8;
stream.header[stream.header_len++] = len;
memcpy(stream.header + stream.header_len, p, len);
stream.header_len += len;
LOG_INFO("metadata: %.*s", len, p);
}
}
ogg.flac = false;
ogg.serial = ULLONG_MAX;
stream.meta_send = true;
wake_controller();
LOG_INFO("Ogg metadata length: %u", stream.header_len - 3);
}
free(ogg.data);
ogg.data = NULL;
ogg.state = OGG_SYNC;
break;
}
default:
break;
}
p += consumed;
n -= consumed;
}
}
static void *stream_thread() { static void *stream_thread() {
while (running) { while (running) {
@@ -343,6 +494,7 @@ static void *stream_thread() {
} }
if (n > 0) { if (n > 0) {
stream_ogg(n);
_buf_inc_writep(streambuf, n); _buf_inc_writep(streambuf, n);
stream.bytes += n; stream.bytes += n;
if (stream.meta_interval) { if (stream.meta_interval) {
@@ -485,7 +637,7 @@ void stream_file(const char *header, size_t header_len, unsigned threshold) {
UNLOCK; UNLOCK;
} }
void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait) { void stream_sock(u32_t ip, u16_t port, bool use_ssl, bool use_ogg, const char *header, size_t header_len, unsigned threshold, bool cont_wait) {
struct sockaddr_in addr; struct sockaddr_in addr;
#if EMBEDDED #if EMBEDDED
@@ -585,6 +737,11 @@ void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, un
stream.bytes = 0; stream.bytes = 0;
stream.threshold = threshold; stream.threshold = threshold;
ogg.miss = ogg.match = 0;
ogg.state = use_ogg ? OGG_SYNC : OGG_OFF;
ogg.flac = false;
ogg.serial = ULLONG_MAX;
UNLOCK; UNLOCK;
} }
@@ -604,6 +761,8 @@ bool stream_disconnect(void) {
disc = true; disc = true;
} }
stream.state = STOPPED; stream.state = STOPPED;
if (ogg.state == OGG_PAGE && ogg.data) free(ogg.data);
ogg.data = NULL;
UNLOCK; UNLOCK;
return disc; return disc;
} }

View File

@@ -50,7 +50,7 @@ static inline int32_t clip15(int32_t x) {
struct vorbis { struct vorbis {
bool opened; bool opened;
enum { OGG_SYNC, OGG_ID_HEADER, OGG_COMMENT_HEADER, OGG_SETUP_HEADER } status; enum { OGG_ID_HEADER, OGG_COMMENT_HEADER, OGG_SETUP_HEADER } status;
struct { struct {
ogg_stream_state state; ogg_stream_state state;
ogg_packet packet; ogg_packet packet;
@@ -72,6 +72,13 @@ static struct vorbis {
// vorbis symbols to be dynamically loaded - from either vorbisfile or vorbisidec (tremor) version of library // vorbis symbols to be dynamically loaded - from either vorbisfile or vorbisidec (tremor) version of library
vorbis_info *(* ov_info)(OggVorbis_File *vf, int link); vorbis_info *(* ov_info)(OggVorbis_File *vf, int link);
int (* ov_clear)(OggVorbis_File *vf); int (* ov_clear)(OggVorbis_File *vf);
void (* ov_info_init)(vorbis_info* vi);
void (* ov_info_clear)(vorbis_info* vi);
void (* ov_comment_init)(vorbis_comment* vc);
void (* ov_comment_clear(vorbis_comment *vc);
int (* ov_block_init)(vorbis_dsp_state* v, vorbis_block* vb);
int (* ov_block_clear)(vorbis_block* vb);
void (* ov_vorbis_dsp_clear)(vorbis_dsp_state* v);
long (* ov_read)(OggVorbis_File *vf, char *buffer, int length, int bigendianp, int word, int sgned, int *bitstream); long (* ov_read)(OggVorbis_File *vf, char *buffer, int length, int bigendianp, int word, int sgned, int *bitstream);
long (* ov_read_tremor)(OggVorbis_File *vf, char *buffer, int length, int *bitstream); long (* ov_read_tremor)(OggVorbis_File *vf, char *buffer, int length, int *bitstream);
int (* ov_open_callbacks)(void *datasource, OggVorbis_File *vf, const char *initial, long ibytes, ov_callbacks callbacks); int (* ov_open_callbacks)(void *datasource, OggVorbis_File *vf, const char *initial, long ibytes, ov_callbacks callbacks);
@@ -131,7 +138,7 @@ extern struct processstate process;
#define OG(h, fn, ...) (h)->ogg_ ## fn(__VA_ARGS__) #define OG(h, fn, ...) (h)->ogg_ ## fn(__VA_ARGS__)
#endif #endif
static int get_ogg_packet(void) { static int get_audio_packet(void) {
int status, packet = -1; int status, packet = -1;
LOCK_S; LOCK_S;
@@ -139,40 +146,8 @@ static int get_ogg_packet(void) {
while (!(status = OG(&go, stream_packetout, &v->state, &v->packet)) && bytes) { while (!(status = OG(&go, stream_packetout, &v->state, &v->packet)) && bytes) {
// if sync_pageout (or sync_pageseek) is not called first, sync buffers build ups // if sync_pageout (or sync_pageseek) is not called here, sync buffers build up
while (!(status = OG(&go, sync_pageout, &v->sync, &v->page)) && bytes) { while (!(status = OG(&go, sync_pageout, &v->sync, &v->page)) && bytes) {
size_t consumed = min(bytes, 4096);
char* buffer = OG(&gv, sync_buffer, &v->sync, consumed);
memcpy(buffer, streambuf->readp, consumed);
OG(&gv, sync_wrote, &v->sync, consumed);
_buf_inc_readp(streambuf, consumed);
bytes -= consumed;
}
// if we have a new page, put it in
if (status) OG(&go, stream_pagein, &v->state, &v->page);
}
// only return a negative value when true end of streaming is reached
if (status > 0) packet = status;
else if (stream.state > DISCONNECT || _buf_used(streambuf)) packet = 0;
UNLOCK_S;
return packet;
}
static int read_vorbis_header(void) {
int status = 0;
bool fetch = true;
LOCK_S;
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
while (bytes && !status) {
// first fetch a page if we need one
if (fetch) {
size_t consumed = min(bytes, 4096); size_t consumed = min(bytes, 4096);
char* buffer = OG(&go, sync_buffer, &v->sync, consumed); char* buffer = OG(&go, sync_buffer, &v->sync, consumed);
memcpy(buffer, streambuf->readp, consumed); memcpy(buffer, streambuf->readp, consumed);
@@ -180,81 +155,122 @@ static int read_vorbis_header(void) {
_buf_inc_readp(streambuf, consumed); _buf_inc_readp(streambuf, consumed);
bytes -= consumed; bytes -= consumed;
if (!OG(&go, sync_pageseek, &v->sync, &v->page)) continue;
} }
switch (v->status) { // if we have a new page, put it in and reset serialno at BoS
case OGG_SYNC: if (status) {
v->status = OGG_ID_HEADER; OG(&go, stream_pagein, &v->state, &v->page);
OG(&go, stream_reset_serialno, &v->state, OG(&go, page_serialno, &v->page)); if (OG(&go, page_bos, &v->page)) OG(&go, stream_reset_serialno, &v->state, OG(&go, page_serialno, &v->page));
fetch = false; }
break; }
case OGG_ID_HEADER:
status = OG(&go, stream_pagein, &v->state, &v->page); /* odd packets are not audio and should be discarded. With no packet, we
if (!OG(&go, stream_packetout, &v->state, &v->packet)) break; * return a negative value when there is really nothing more to proceed */
if (status > 0 && (v->packet.packet[0] & 0x01) == 0) packet = status;
else if (stream.state > DISCONNECT || _buf_used(streambuf)) packet = 0;
UNLOCK_S;
return packet;
}
static int read_vorbis_header(void) {
int done = 0;
bool fetch = true;
LOCK_S;
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
while (bytes && !done) {
int status;
// get aligned to a page and ready to bring it in
do {
size_t consumed = min(bytes, 4096);
char* buffer = OG(&go, sync_buffer, &v->sync, consumed);
memcpy(buffer, streambuf->readp, consumed);
OG(&go, sync_wrote, &v->sync, consumed);
_buf_inc_readp(streambuf, consumed);
bytes -= consumed;
status = fetch ? OG(&go, sync_pageout, &v->sync, &v->page) :
OG(&go, sync_pageseek, &v->sync, &v->page);
} while (bytes && status <= 0);
// nothing has been found and we have no more bytes, come back later
if (status <= 0) break;
// always set stream serialno if we have a new one (no multiplexed streams)
if (OG(&go, page_bos, &v->page)) OG(&go, stream_reset_serialno, &v->state, OG(&go, page_serialno, &v->page));
// bring new page in if we want it (otherwise we're just skipping)
if (fetch) OG(&go, stream_pagein, &v->state, &v->page);
// not a switch...case b/c we might have multiple packets in a page in vorbis
if (v->status == OGG_ID_HEADER) {
// we need the id packet, get more pages if we don't
if (!OG(&go, stream_packetout, &v->state, &v->packet)) continue;
OV(&gv, info_init, &v->info); OV(&gv, info_init, &v->info);
status = OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet); status = OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet);
if (status) { if (status) {
LOG_ERROR("vorbis id header packet error %d", status); LOG_ERROR("id header packet error %d", status);
status = -1; done = -1;
} else { } else {
v->channels = v->info.channels; v->channels = v->info.channels;
v->rate = v->info.rate; v->rate = v->info.rate;
v->status = OGG_COMMENT_HEADER; v->status = OGG_COMMENT_HEADER;
fetch = false;
// only fetch if no other packet already in (they should not)
fetch = OG(&go, page_packets, &v->page) <= 1;
if (!fetch) LOG_INFO("id packet should terminate page");
LOG_INFO("id acquired"); LOG_INFO("id acquired");
// we should only have one packet, so get next pages
if (OG(&go, page_packets, &v->page) == 1) continue;
} }
break; }
case OGG_SETUP_HEADER:
// header packets don't align with pages on Vorbis (contrary to Opus) if (v->status == OGG_COMMENT_HEADER) {
if (fetch) OG(&go, stream_pagein, &v->state, &v->page); // don't consume VorbisComment which could be a huge packet, just skip it
int packets = OG(&go, page_packets, &v->page);
if (!packets) continue;
// we have a "fake" comment packet that is just has the last page...
v->status = OGG_SETUP_HEADER;
OG(&go, stream_pagein, &v->state, &v->page);
OG(&go, stream_packetout, &v->state, &v->packet);
OV(&gv, comment_init, &v->comment);
v->comment.vendor = "N/A";
fetch = true;
LOG_INFO("comment skipped successfully");
// because of lack of page alignment, we might have the setup page already fully in
if (packets == 1) continue;
}
if (v->status == OGG_SETUP_HEADER) {
// we need the setup packet, get more pages if we don't
if (OG(&go, stream_packetout, &v->state, &v->packet) <= 0) continue;
// finally build a codec if we have the packet // finally build a codec if we have the packet
status = OG(&go, stream_packetout, &v->state, &v->packet); if (OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet) ||
if (status && ((status = OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet)) || OV(&gv, synthesis_init, &v->decoder, &v->info)) {
(status = OV(&gv, synthesis_init, &v->decoder, &v->info)))) { LOG_ERROR("setup header packet error %d", status);
LOG_ERROR("vorbis setup header packet error %d", status);
// no need to free comment, it's fake // no need to free comment, it's fake
OV(&gv, info_clear, &v->info); OV(&gv, info_clear, &v->info);
status = -1; done = -1;
} else { } else {
OV(&gv, block_init, &v->decoder, &v->block); OV(&gv, block_init, &v->decoder, &v->block);
v->opened = true; v->opened = true;
LOG_INFO("codec up and running (rate: %d, channels:%d)", v->rate, v->channels); LOG_INFO("codec up and running");
status = 1; done = 1;
} }
//@FIXME: can we have audio on that page as well?
break;
case OGG_COMMENT_HEADER: {
// don't consume VorbisComment, just skip it
int packets = OG(&go, page_packets, &v->page);
if (packets) {
v->status = OGG_SETUP_HEADER;
OG(&go, stream_pagein, &v->state, &v->page);
OG(&go, stream_packetout, &v->state, &v->packet);
OV(&gv, comment_init, &v->comment);
v->comment.vendor = "N/A";
// because of lack of page alignment, we might have the setup page already fully in
if (packets > 1) fetch = false;
LOG_INFO("comment skipped succesfully");
}
break;
}
default:
break; break;
} }
} }
UNLOCK_S; UNLOCK_S;
return status; return done;
} }
inline int pcm_out(vorbis_dsp_state* decoder, void*** pcm) { inline int pcm_out(vorbis_dsp_state* decoder, void*** pcm) {
@@ -310,12 +326,12 @@ static decode_state vorbis_decode(void) {
if (v->overflow) { if (v->overflow) {
n = pcm_out(&v->decoder, &pcm); n = pcm_out(&v->decoder, &pcm);
v->overflow = n - min(n, frames); v->overflow = n - min(n, frames);
} else if ((packet = get_ogg_packet()) > 0) { } else if ((packet = get_audio_packet()) > 0) {
n = OV(&gv, synthesis, &v->block, &v->packet); n = OV(&gv, synthesis, &v->block, &v->packet);
if (n == 0) n = OV(&gv, synthesis_blockin, &v->decoder, &v->block); if (n == 0) n = OV(&gv, synthesis_blockin, &v->decoder, &v->block);
if (n == 0) n = pcm_out(&v->decoder, &pcm); if (n == 0) n = pcm_out(&v->decoder, &pcm);
v->overflow = n - min(n, frames); v->overflow = n - min(n, frames);
} else if (!packet && !OG(&go, page_eos, &v->page)) { } else if (!packet) {
UNLOCK_O_direct; UNLOCK_O_direct;
return DECODE_RUNNING; return DECODE_RUNNING;
} }
@@ -396,29 +412,28 @@ static decode_state vorbis_decode(void) {
} }
static void vorbis_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { static void vorbis_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
LOG_INFO("OPENING CODEC");
if (v->opened) { if (v->opened) {
OV(&go, block_clear, &v->block); OV(&gv, block_clear, &v->block);
OV(&go, info_clear, &v->info); OV(&gv, dsp_clear, &v->decoder);
OV(&go, dsp_clear, &v->decoder); OV(&gv, info_clear, &v->info);
} }
v->opened = false; v->opened = false;
v->status = OGG_SYNC; v->status = OGG_ID_HEADER;
v->overflow = 0; v->overflow = 0;
OG(&gu, sync_clear, &v->sync); OG(&go, stream_clear, &v->state);
OG(&gu, stream_clear, &v->state); OG(&go, sync_clear, &v->sync);
OG(&gu, stream_init, &v->state, -1); OG(&go, stream_init, &v->state, -1);
} }
static void vorbis_close() { static void vorbis_close() {
return;
LOG_INFO("CLOSING CODEC");
if (v->opened) { if (v->opened) {
OV(&go, block_clear, &v->block); OV(&gv, block_clear, &v->block);
OV(&go, info_clear, &v->info); OV(&gv, dsp_clear, &v->decoder);
OV(&go, dsp_clear, &v->decoder); // info must be last otherwise there is memory leak (where is it said... nowhere)
OV(&gv, info_clear, &v->info);
// we don' t have comments to free
} }
v->opened = false; v->opened = false;
@@ -469,6 +484,11 @@ static bool load_vorbis() {
v_handle.ov_read = dlsym(handle, "ov_read"); v_handle.ov_read = dlsym(handle, "ov_read");
v_handle.ov_info = dlsym(handle, "ov_info"); v_handle.ov_info = dlsym(handle, "ov_info");
v_handle.ov_clear = dlsym(handle, "ov_clear"); v_handle.ov_clear = dlsym(handle, "ov_clear");
v.handle.ov_info_clear = dlsym(gv.handle, "vorbis_info_clear");
v.handle.ov_comment_init = dlsym(gv.handle, "vorbis_comment_init");
v.handle.ov_comment_clear = dlsym(gv.handle, "vorbis_comment_clear");
v.handle.ov_block_init = dlsym(gv.handle, "vorbis_block_init");
v.handle.ov_block_clear = dlsym(gv.handle, "vorbis_block_clear");
v_handle.ov_open_callbacks = dlsym(handle, "ov_open_callbacks"); v_handle.ov_open_callbacks = dlsym(handle, "ov_open_callbacks");
if ((err = dlerror()) != NULL) { if ((err = dlerror()) != NULL) {

View File

@@ -1,6 +1,6 @@
idf_component_register( SRCS operator.cpp tools.c trace.c idf_component_register( SRCS operator.cpp tools.c trace.c
REQUIRES esp_common pthread REQUIRES esp_common pthread
PRIV_REQUIRES esp_http_client esp-tls json PRIV_REQUIRES esp_http_client esp-tls
INCLUDE_DIRS . INCLUDE_DIRS .
) )

Some files were not shown because too many files have changed in this diff Show More