Compare commits

..

43 Commits

Author SHA1 Message Date
philippe44
e0e7e718ba fix vorbis & opus never ending - release 2023-04-20 00:23:23 +02:00
philippe44
f0527f70ac fix compile error 2023-04-19 12:39:48 +02:00
philippe44
3ecc09b989 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-04-19 00:43:11 +02:00
philippe44
7dbed7a67b trying to follow cspot... 2023-04-19 00:43:06 +02:00
github-actions
043d73dd6e Update prebuilt objects [skip actions] 2023-04-18 16:23:07 +00:00
philippe44
dc62afd788 backport typo - release 2023-04-18 18:19:28 +02:00
github-actions
d51df83981 Update prebuilt objects [skip actions] 2023-04-17 17:37:39 +00:00
philippe44
f4388a8c0a proper solution to track "plop" - release 2023-04-17 19:34:49 +02:00
philippe44
4ee9878a6f fix no reboot on squeezelite connection loss 2023-04-16 17:16:07 +02:00
Sébastien
025b5d8c75 Update README.md 2023-04-14 20:55:31 -04:00
Sebastien L
0bebaccf57 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-04-12 14:44:09 -04:00
Sebastien L
7e8313baf3 Merge readme with master 2023-04-12 14:44:02 -04:00
philippe44
ce4cebd994 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-04-09 10:31:01 -07:00
philippe44
ba81c2ecd5 fix plop when switchign track (still need to fix it when seeking) - release 2023-04-09 10:30:56 -07:00
github-actions
131b1d36b0 Update prebuilt objects [skip actions] 2023-04-09 13:48:46 +00:00
Sebastien L
0d37c270e2 more fixing 2023-04-09 09:43:54 -04:00
Sebastien L
6054affb81 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-04-09 09:38:05 -04:00
Sebastien L
196a1d179a Attempt to fix build [skip actions] 2023-04-08 12:36:16 -04:00
Sebastien L
7574054e22 Attempt to fix build [skip actions] 2023-04-08 12:34:19 -04:00
Sébastien
cd088d2500 Update Platform_build.yml [skip actions] 2023-04-08 12:23:37 -04:00
github-actions
d16ce964d6 Update prebuilt objects [skip actions] 2023-04-08 14:44:47 +00:00
github-actions
84d22cce07 Update prebuilt objects [skip actions] 2023-04-07 19:17:01 +00:00
philippe44
0495b7ea7a release 2023-04-07 12:13:43 -07:00
Sebastien L
b98ddd76cb Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-04-07 10:53:29 -04:00
Sebastien L
7ac628a29d Improved audio ui, bug fixes 2023-04-07 10:53:22 -04:00
philippe44
d2b8edce60 fix vorbis as well 2023-04-06 22:45:40 -07:00
philippe44
b4af1e7bef commit BT buttons 2023-04-05 16:33:39 -07:00
philippe44
f91392e044 preset buttons is 0..9 2023-04-05 15:29:48 -07:00
philippe44
1e0fce53c7 and decoder ... 2023-04-04 22:30:38 -07:00
philippe44
3372aaea69 NULL the overflow buffer 2023-04-04 22:21:33 -07:00
philippe44
8d1888a198 new opus decoder 2023-04-04 22:13:31 -07:00
philippe44
18b830eaa3 add preset 7..10 2023-04-02 13:40:07 -07:00
philippe44
cbc1ab38fb gain some .rodata from DMAP decoder (unused strings) 2023-04-01 21:03:10 -07:00
philippe44
27a0d2a4d3 fix Spotify volume normalization 2023-04-01 14:24:56 -07:00
philippe44
02fc039bbe maximize number of AirPlay RTP buffers 2023-04-01 12:37:52 -07:00
philippe44
6fad1f8251 fix empty seek table for good 2023-04-01 00:39:19 -07:00
philippe44
42621561df codec optimizations 2023-03-30 22:22:48 -07:00
philippe44
5ecb371fb0 pretty things up a bit 2023-03-29 23:41:35 -07:00
philippe44
cad286c8d7 provide squeezelite error log in UI 2023-03-29 23:02:16 -07:00
philippe44
a9a9018794 fix country code in cspot 2023-03-29 12:06:20 -07:00
github-actions
236cfef05d Update prebuilt objects [skip actions] 2023-03-29 07:18:33 +00:00
philippe44
02b61e0ab6 release 2023-03-29 00:14:42 -07:00
github-actions
226c483b0b Update prebuilt objects [skip actions] 2023-03-29 06:37:55 +00:00
132 changed files with 2956 additions and 1443 deletions

View File

@@ -13,7 +13,6 @@ on:
description: 'Force a Release build. When not forced, the system will check for release word in the last commit message to trigger a release'
required: true
type: boolean
jobs:
bootstrap:
name: Global setup
@@ -228,5 +227,7 @@ jobs:
update_web_installer:
name: Update Web Installer After Release
needs: [ bootstrap, build ]
if: ${{( always() && !cancelled() ) && needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }}
if: ${{ always() && !cancelled() && needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }}
uses: ./.github/workflows/web_deploy.yml
secrets:
WEB_INSTALLER: ${{ secrets.WEB_INSTALLER }}

View File

@@ -1,6 +1,9 @@
name: Update Web Installer
on:
workflow_call:
secrets:
WEB_INSTALLER:
required: true
workflow_dispatch:
jobs:
update_web_installer:

216
README.md
View File

@@ -1,21 +1,23 @@
![Cross-Build](https://github.com/sle118/squeezelite-esp32/workflows/Cross-Build/badge.svg?branch=master-cmake)
![ESP-IDF v4.3.1](https://github.com/sle118/squeezelite-esp32/actions/workflows/esp-idf-v4.3-build.yml/badge.svg?branch=master-v4.3)
[![Platform Build](https://github.com/sle118/squeezelite-esp32/actions/workflows/Platform_build.yml/badge.svg)](https://github.com/sle118/squeezelite-esp32/actions/workflows/Platform_build.yml)
# Squeezelite-esp32
## What is this
## What is this?
Squeezelite-esp32 is an audio software suite made to run on espressif's ESP32 wifi (b/g/n) and bluetooth chipset. It offers the following capabilities
- Stream your local music and connect to all major on-line music providers (Spotify, Deezer, Tidal, Qobuz) using [Logitech Media Server - a.k.a LMS](https://forums.slimdevices.com/) and enjoy multi-room audio synchronization. LMS can be extended by numerous plugins and can be controlled using a Web browser or dedicated applications (iPhone, Android). It can also send audio to UPnP, Sonos, ChromeCast and AirPlay speakers/devices.
- Stream from a Bluetooth device (iPhone, Android)
- Stream from an AirPlay controller (iPhone, iTunes ...) and enjoy synchronization multiroom as well (although it's AirPlay 1 only)
- Stream from a **Bluetooth** device (iPhone, Android)
- Stream from an **AirPlay** controller (iPhone, iTunes ...) and enjoy synchronization multiroom as well (although it's AirPlay 1 only)
- Stream direcly from **Spotify** using SpotifyConnect (thanks to [cspot](https://github.com/feelfreelinux/cspot)
Depending on the hardware connected to the ESP32, you can send audio to a local DAC, to SPDIF or to a Bluetooth speaker. The bare minimum required hardware is a WROVER module with 4MB of Flash and 4MB of PSRAM (https://www.espressif.com/en/products/modules/esp32). With that module standalone, just apply power and you can stream to a Bluetooth speaker. You can also send audio to most I2S DAC as well as to SPDIF receivers using just a cable or an optical transducer.
But squeezelite-esp32 is highly extensible and you can add
- Buttons and Rotary Encoder and map/combine them to various functions (play, pause, volume, next ...)
- IR receiver (no pullup resistor or capacitor needed, just the 38kHz receiver)
- Monochrome, GrayScale or Color displays using SPI or I2C (supported drivers are SH1106, SSD1306, SSD1322, SSD1326/7, SSD1351, ST7735, ST7789 and ILI9341).
- Ethernet using a Microchip LAN8720 with RMII interface or Davicom DM9051 over SPI.
- [Buttons](#buttons) and [Rotary Encoder](#rotary-encoder) and map/combine them to various functions (play, pause, volume, next ...)
- [GPIO expander](#gpio-expanders) (buttons, led and rotary)
- [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).
- [Ethernet](#ethernet-required-unpublished-version-43) using a Microchip LAN8720 with RMII interface or Davicom DM9051/W5500 over SPI.
Other features include
@@ -50,29 +52,44 @@ The esp32 must run at 240 MHz, with Quad-SPI I/O at 80 MHz and a clock of 40 Mhz
In 16 bits mode, although 192 kHz is reported as max rate, it's highly recommended to limit reported sampling rate to 96k (-Z 96000). Note that some high-speed 24/96k on-line streams might stutter because of TCP/IP stack performances. It is usually due to the fact that the server sends small packets of data and the esp32 cannot receive encoded audio fast enough, regardless of task priority settings (I've tried to tweak that a fair bit). The best option in that case is to let LMS proxy the stream as it will provide larger chunks and a "smoother" stream that can then be handled.
Note as well that some codecs consume more CPU than others or have not been optimized as much. I've done my best to tweak these, but that level of optimization includes writing some assembly which is painful. One very demanding codec is AAC when files are encoded with SBR. It allows reconstruction of upper part of spectrum and thus higher sampling rate, but the codec spec is such that this is optional, you can decode simply lower band and accept lower sampling rate - See the AAC_DISABLE_SBR option below.
## Supported Hardware
Any esp32-based hardware with at least 4MB of flash and 4MB of PSRAM will be capable of running squeezelite-esp32 and there are various boards that include such chip. A few are mentionned below, but any should work. You can find various help & instructions [here](https://forums.slimdevices.com/showthread.php?112697-ANNOUNCE-Squeezelite-ESP32-(dedicated-thread))
**For the sake of clarity, WROOM modules DO NOT work as they don't include PSRAM. Some designs might add it externally, but it's (very) unlikely.**
### Raw WROVER module
Per above description, a [WROVER module](https://www.espressif.com/en/products/modules/esp32) is enough to run Squeezelite-esp32, but that requires a bit of tinkering to extend it to have analogue audio or hardware buttons (e.g.)
Please note that when sending to a Bluetooth speaker (source), only 44.1 kHz can be used, so you either let LMS do the resampling, but you must make sure it only sends 44.1kHz tracks or enable internal resampling (using -R) option. If you connect a DAC, choice of sample rates will depends on its capabilities. See below for more details.
Most DAC will work out-of-the-box with simply an I2S connection, but some require specific commands to be sent using I2C. See DAC option below to understand how to send these dedicated commands. There is build-in support for TAS575x, TAS5780, TAS5713 and AC101 DAC.
### SqueezeAMP
This is the main hardware companion of Squeezelite-esp32 and has been developped together. Details on capabilities can be found [here](https://forums.slimdevices.com/showthread.php?110926-pre-ANNOUNCE-SqueezeAMP-and-SqueezeliteESP32) and [here](https://github.com/philippe44/SqueezeAMP).
if you want to rebuild, use the `squeezelite-esp32-SqueezeAmp-sdkconfig.defaults` configuration file.
If you want to rebuild, use the `squeezelite-esp32-SqueezeAmp-sdkconfig.defaults` configuration file.
NB: You can use the pre-build binaries SqueezeAMP4MBFlash which has all the hardware I/O set properly. You can also use the generic binary I2S4MBFlash in which case the NVS parameters shall be set to get the exact same behavior
- set_GPIO: 12=green,13=red,34=jack,2=spkfault
- batt_config: channel=7,scale=20.24
- dac_config: model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0
- spdif_config: bck=33,ws=25,do=15
- set_GPIO: `12=green,13=red,34=jack,2=spkfault`
- bat_config: `channel=7,scale=20.24`
- dac_config: `model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0`
- spdif_config: `bck=33,ws=25,do=15`
### MuseLuxe
This portable battery-powered [speaker](https://raspiaudio.com/produit/esp-muse-luxe) is compatible with squeezelite-esp32 for which there is a dedicated build supplied with every update. If you want to rebuild, use the `squeezelite-esp32-Muse-sdkconfig.defaults` configuration file.
NB: You can use the pre-build binaries Muse4MBFlash which has all the hardware I/O set properly. You can also use the generic binary I2S4MBFlash in which case the NVS parameters shall be set to get the exact same behavior
- target: `muse`
- bat_config: `channel=5,scale=7.48,atten=3,cells=1`
- spi_config: `"mosi=15,miso=2,clk=14` *(this one is probably optional)*
- dac_config: `model=I2S,bck=5,ws=25,do=26,di=35,i2c=16,sda=18,scl=23,mck`
- dac_controlset: `{"init":[ {"reg":0,"val":128}, {"reg":0,"val":0}, {"reg":25,"val":4}, {"reg":1,"val":80}, {"reg":2,"val":0}, {"reg":8,"val":0}, {"reg":4,"val":192}, {"reg":0,"val":18}, {"reg":1,"val":0}, {"reg":23,"val":24}, {"reg":24,"val":2}, {"reg":38,"val":9}, {"reg":39,"val":144}, {"reg":42,"val":144}, {"reg":43,"val":128}, {"reg":45,"val":128}, {"reg":27,"val":0}, {"reg":26,"val":0}, {"reg":2,"val":240}, {"reg":2,"val":0}, {"reg":29,"val":28}, {"reg":4,"val":48}, {"reg":25,"val":0}, {"reg":46,"val":33}, {"reg":47,"val":33} ]}`
- actrls_config: buttons
- define a "buttons" variable with: `[{"gpio":32, "pull":true, "debounce":10, "normal":{"pressed":"ACTRLS_VOLDOWN"}}, {"gpio":19, "pull":true, "debounce":40, "normal":{"pressed":"ACTRLS_VOLUP"}}, {"gpio":12, "pull":true, "debounce":40, "long_press":1000, "normal":{"pressed":"ACTRLS_TOGGLE"},"longpress":{"pressed":"ACTRLS_POWER"}}]`
### ESP32-A1S
Works with [ESP32-A1S](https://docs.ai-thinker.com/esp32-a1s) module that includes audio codec and headset output. You still need to use a demo board like [this](https://www.aliexpress.com/item/4001060963585.html) or an external amplifier if you want direct speaker connection. Note that there is a version with AC101 codec and another one with ES8388 (see below)
Works with [ESP32-A1S](https://docs.ai-thinker.com/esp32-a1s) module that includes audio codec and headset output. You still need to use a demo board like [this](https://www.aliexpress.com/item/4001060963585.html) or an external amplifier if you want direct speaker connection. Note that there is a version with AC101 codec and another one with ES8388 with probably two variants - these boards are a mess (see below)
The board shown above has the following IO set
- amplifier: GPIO21
@@ -88,23 +105,30 @@ The board shown above has the following IO set
(note that some GPIO need pullups)
So a possible config would be
- set_GPIO: 21=amp,22=green:0,39=jack:0
- set_GPIO: `21=amp,22=green:0,39=jack:0`
- a button mapping:
```
[{"gpio":5,"normal":{"pressed":"ACTRLS_TOGGLE"}},{"gpio":18,"pull":true,"shifter_gpio":5,"normal":{"pressed":"ACTRLS_VOLUP"}, "shifted":{"pressed":"ACTRLS_NEXT"}}, {"gpio":23,"pull":true,"shifter_gpio":5,"normal":{"pressed":"ACTRLS_VOLDOWN"},"shifted":{"pressed":"ACTRLS_PREV"}}]
```
for AC101
- dac_config: model=AC101,bck=27,ws=26,do=25,di=35,sda=33,scl=32
```json
[{"gpio":5,"normal":{"pressed":"ACTRLS_TOGGLE"}},{"gpio":18,"pull":true,"shifter_gpio":5,"normal":{"pressed":"ACTRLS_VOLUP"}, "shifted":{"pressed":"ACTRLS_NEXT"}}, {"gpio":23,"pull":true,"shifter_gpio":5,"normal":{"pressed":"ACTRLS_VOLDOWN"},"shifted":{"pressed":"ACTRLS_PREV"}}]
```
for **AC101**
- dac_config: `model=AC101,bck=27,ws=26,do=25,di=35,sda=33,scl=32`
for ES8388
- dac_config: model=ES8388,bck=5,ws=25,do=26,sda=18,scl=23,i2c=16
for **ES8388** (it seems that there are variants with same version number - a total mess)
- dac_config: `model=ES8388,bck=5,ws=25,do=26,sda=18,scl=23,i2c=16`
or
- dac_config: `model=ES8388,bck=27,ws=25,do=26,sda=33,scl=32,i2c=16`
### T-WATCH2020 by LilyGo
This is a fun [smartwatch](http://www.lilygo.cn/prod_view.aspx?TypeId=50036&Id=1290&FId=t3:50036:3) based on ESP32. It has a 240x240 ST7789 screen and onboard audio. Not very useful to listen to anything but it works. This is an example of a device that requires an I2C set of commands for its dac (see below). There is a build-option if you decide to rebuild everything by yourself, otherwise the I2S default option works with the following parameters
- dac_config: model=I2S,bck=26,ws=25,do=33,i2c=106,sda=21,scl=22
- dac_controlset: { "init": [ {"reg":41, "val":128}, {"reg":18, "val":255} ], "poweron": [ {"reg":18, "val":64, "mode":"or"} ], "poweroff": [ {"reg":18, "val":191, "mode":"and"} ] }
- spi_config: dc=27,data=19,clk=18
- display_config: SPI,driver=ST7789,width=240,height=240,cs=5,back=12,speed=16000000,HFlip,VFlip
- dac_config: `model=I2S,bck=26,ws=25,do=33,i2c=106,sda=21,scl=22`
- dac_controlset:
```json
{ "init": [ {"reg":41, "val":128}, {"reg":18, "val":255} ], "poweron": [ {"reg":18, "val":64, "mode":"or"} ], "poweroff": [ {"reg":18, "val":191, "mode":"and"} ] }
```
- spi_config: `dc=27,data=19,clk=18`
- display_config: `SPI,driver=ST7789,width=240,height=240,cs=5,back=12,speed=16000000,HFlip,VFlip`
### ESP32-WROVER + I2S DAC
Squeezelite-esp32 requires esp32 chipset and 4MB PSRAM. ESP32-WROVER meets these requirements. To get an audio output an I2S DAC can be used. Cheap PCM5102 I2S DACs work others may also work. PCM5012 DACs can be hooked up via:
@@ -130,18 +154,23 @@ And the super cool project https://github.com/rochuck/squeeze-amp-too
## Configuration
To access NVS, in the webUI, go to credits and select "shows nvs editor". Go into the NVS editor tab to change NFS parameters. In syntax description below \<\> means a value while \[\] describe optional parameters.
As mentionned above, there are a few dedicated builds that are provided today: SqueezeAMP and Muse but if you build it yourself, you can also create a build for T-WATCH2020. The default build is a generic firmware named I2S which can be configured through NVS to produce *exactly* the same results than dedicated builds. The difference is that parameters must be entered and can accidently be erased. The GUI provides a great help to load "known config sets" as well.
By design choice, there is no code that is only embedded for a given version, all code is always there. The philosophy is to minimize as much as possible platform-specific code and use of specific `#ifdef` is prohibited, no matter what. So if you want to add your own platfrom, please look **very hard** at the `main\KConfig.projbuild` to see how you can, using parameters below, make your device purely a configuration-based solution. When there is really no other option, look at `targets\<target>` to add your own code. I will not accept PR for code that can avoid creating such dedicated code whenever possible. The NVS "target" will be used to call target-specific code then, but again this is purely runtime, not compile-time.
### I2C
The NVS parameter "i2c_config" set the i2c's gpio used for generic purpose (e.g. display). Leave it blank to disable I2C usage. Note that on SqueezeAMP, port must be 1. Default speed is 400000 but some display can do up to 800000 or more. Syntax is
```
sda=<gpio>,scl=<gpio>[,port=0|1][,speed=<speed>]
```
<strong>Please note that you can not use the same GPIO or port as the DAC</strong>
**Please note that you can not use the same GPIO or port as the DAC.**
### SPI
The esp32 has 4 SPI sub-systems, one is unaccessible so numbering is 0..2 and SPI0 is reserved for Flash/PSRAM. The NVS parameter "spi_config" set the spi's gpio used for generic purpose (e.g. display). Leave it blank to disable SPI usage. The DC parameter is needed for displays. Syntax is
```
data|mosi=<gpio>,clk=<gpio>[,dc=<gpio>][,host=1|2][,miso=<gpio>]
```
Default "host" is 1. The "miso" parameter is only used when SPI bus is to be shared with other peripheral (e.g. ethernet, see below), otherwise it can be omitted. Note that "data" can also be named "mosi".
Default and only "host" is 1 as others are used already by flash and spiram. The optional "miso" (MasterInSlaveOut) parameter is only used when SPI bus is bi-directional and shared with other peripheral like ethernet, gpio expander. Note that "data" can also be named "mosi" (MasterOutSlaveIn).
### DAC/I2S
The NVS parameter "dac_config" set the gpio used for i2s communication with your DAC. You can define the defaults at compile time but nvs parameter takes precedence except for SqueezeAMP and A1S where these are forced at runtime. Syntax is
```
@@ -149,17 +178,20 @@ bck=<gpio>,ws=<gpio>,do=<gpio>[,mck][,mute=<gpio>[:0|1][,model=TAS57xx|TAS5713|A
```
if "model" is not set or is not recognized, then default "I2S" is used. The option "mck" is used for some codecs that require a master clock (although they should not). Only GPIO0 can be used as MCLK and be aware that this cannot coexit with RMII Ethernet (see ethernet section below). I2C parameters are optional and only needed if your DAC requires an I2C control (See 'dac_controlset' below). Note that "i2c" parameters are decimal, hex notation is not allowed.
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 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, speakder and headset on and off using a JSON syntax:
```json
{ <command>: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
<command>: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
... }
```
{ init: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
poweron: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
poweroff: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ] }
```
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. **Note that all values must be decimal**. You can use a validator like [this](https://jsonlint.com) to verify your syntax
Where `<command>` is one of init, poweron, poweroff, speakeron, speakeroff, headseton, headsetoff
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
NB: For specific builds (all except I2S), all this is ignored. For know codecs, the built-in sequences can be overwritten using dac_controlset
<strong>Please note that you can not use the same GPIO or port as the I2C</strong>
**Please note that you can not use the same GPIO or port as the I2C.**
### SPDIF
The NVS parameter "spdif_config" sets the i2s's gpio needed for SPDIF.
@@ -184,26 +216,26 @@ GPIO ----210ohm-----------||---- coax S/PDIF signal out
|
Ground -------------------------- coax signal ground
```
### Display
The NVS parameter "display_config" sets the parameters for an optional display. Syntax is
The NVS parameter "display_config" sets the parameters for an optional display. It can be I2C (see [here](#i2c) for shared bus) or SPI (see [here](#spi) for shared bus) Syntax is
```
I2C,width=<pixels>,height=<pixels>[address=<i2c_address>][,reset=<gpio>][,HFlip][,VFlip][driver=SSD1306|SSD1326[:1|4]|SSD1327|SH1106]
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,reset=<gpio>][,speed=<speed>][,HFlip][,VFlip][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735|ST7789|ILI9341[:16|18][,rotate]][,mode=<mode>]
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,reset=<gpio>][,speed=<speed>][,HFlip][,VFlip][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735[:x=<offset>][:y=<offset>]|ST7789|ILI9341[:16|18][,rotate]]
```
- back: a LED backlight used by some older devices (ST7735). It is PWM controlled for brightness
- reset: some display have a reset pin that is should normally be pulled up if unused
- reset: some display have a reset pin that is should normally be pulled up if unused. Most displays require reset and will not initialize well otherwise.
- VFlip and HFlip are optional can be used to change display orientation
- rotate: for non-square *drivers*, move to portrait mode. Note that *width* and *height* must be inverted then
- Default speed is 8000000 (8MHz) but SPI can work up to 26MHz or even 40MHz
- mode: Default mode = 0. Some display modules use different transaction line timings. Check the module documentation if a non-standard mode is required.
- SH1106 is 128x64 monochrome I2C/SPI [here]((https://www.waveshare.com/wiki/1.3inch_OLED_HAT))
- SH1106 is 128x64 monochrome I2C/SPI [here](https://www.waveshare.com/wiki/1.3inch_OLED_HAT)
- SSD1306 is 128x32 monochrome I2C/SPI [here](https://www.buydisplay.com/i2c-blue-0-91-inch-oled-display-module-128x32-arduino-raspberry-pi)
- SSD1322 is 256x64 grayscale 16-levels SPI in multiple sizes [here](https://www.buydisplay.com/oled-display/oled-display-module?resolution=159) - it is very nice
- SSD1326 is 256x32 monochrome or grayscale 16-levels SPI [here](https://www.aliexpress.com/item/32833603664.html?spm=a2g0o.productlist.0.0.2d19776cyQvsBi&algo_pvid=c7a3db92-e019-4095-8a28-dfdf0a087f98&algo_expid=c7a3db92-e019-4095-8a28-dfdf0a087f98-1&btsid=0ab6f81e15955375483301352e4208&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_)
- SSD1327 is 128x128 16-level grayscale SPI [here](https://www.amazon.com/gp/product/B079N1LLG8/ref=ox_sc_act_title_1?smid=A1N6DLY3NQK2VM&psc=1) - artwork can be up to 96x96 with vertical vu-meter/spectrum
- SSD1351 is 128x128 65k/262k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.5inch-rgb-oled-module.htm)
- SSD1675 is an e-ink paper and is experimental as e-ink is really not suitable for LMS du to its very low refresh rate
- ST7735 is a 128x160 65k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.8inch-lcd-module.htm). This needs a backlight control
- ST7735 is a 128x160 65k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.8inch-lcd-module.htm). This needs a backlight control. Some have X/Y offsets betwen the driver and the glass (green/black/red models) that can be added using "x" and "y" options (case sensitive!)
- ST7789 is a 240x320 65k (262k not enabled) color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/2inch-lcd-module.htm). It also exist with 240x240 displays. See **rotate** for use in portrait mode
- ILI9341 is another 240x320 65k (262k capable) color SPI. I've not used it much, the driver it has been provided by one external contributor to the project
@@ -211,13 +243,12 @@ You can tweak how the vu-meter and spectrum analyzer are displayed, as well as s
The NVS parameter "metadata_config" sets how metadata is displayed for AirPlay and Bluetooth. Syntax is
```
[format=<display_content>][,speed=<speed>][,pause=<pause>]
[format=<display_content>][,speed=<speed>][,pause=<pause>][,artwork[:0|1]]
```
- 'speed' is the scrolling speed in ms (default is 33ms)
- 'pause' is the pause time between scrolls in ms (default is 3600ms)
- 'format' can contain free text and any of the 3 keywords %artist%, %album%, %title%. Using that format string, the keywords are replaced by their value to build the string to be displayed. Note that the plain text following a keyword that happens to be empty during playback of a track will be removed. For example, if you have set format=%artist% - %title% and there is no artist in the metadata then only <title> will be displayed not " - <title>".
- 'format' can contain free text and any of the 3 keywords `%artist%`, `%album%`, `%title%`. Using that format string, the keywords are replaced by their value to build the string to be displayed. Note that the plain text following a keyword that happens to be empty during playback of a track will be removed. For example, if you have set format=`%artist% - %title%` and there is no artist in the metadata then only `<title>` will be displayed not ` - <title>`.
- 'artwork' enables coverart display, if available (does not work for Bluetooth). The optional parameter indicates if the artwork should be resized (1) to fit the available space. Note that the built-in resizer can only do 2,4 and 8 downsizing, so fit is not optimal. The artwork will be placed at the right of the display for landscape displays and underneath the two information lines for others (there is no user option to tweak that).
### Infrared
You can use any IR receiver compatible with NEC protocol (38KHz). Vcc, GND and output are the only pins that need to be connected, no pullup, no filtering capacitor, it's a straight connection.
@@ -233,13 +264,13 @@ The parameter "set_GPIO" is used to assign GPIO to various functions.
GPIO can be set to GND provide or Vcc at boot. This is convenient to power devices that consume less than 40mA from the side connector. Be careful because there is no conflict checks being made wrt which GPIO you're changing, so you might damage your board or create a conflict here.
The \<amp\> parameter can use used to assign a GPIO that will be set to active level (default 1) when playback starts. It will be reset when squeezelite becomes idle. The idle timeout is set on the squeezelite command line through -C \<timeout\>
The `<amp>` parameter can use used to assign a GPIO that will be set to active level (default 1) when playback starts. It will be reset when squeezelite becomes idle. The idle timeout is set on the squeezelite command line through `-C <timeout>`
If you have an audio jack that supports insertion (use :0 or :1 to set the level when inserted), you can specify which GPIO it's connected to. Using the parameter jack_mutes_amp allows to mute the amp when headset (e.g.) is inserted.
You can set the Green and Red status led as well with their respective active state (:0 or :1)
The \<ir\> parameter set the GPIO associated to an IR receiver. No need to add pullup or capacitor
The `<ir>` parameter set the GPIO associated to an IR receiver. No need to add pullup or capacitor
Syntax is:
@@ -248,12 +279,34 @@ Syntax is:
```
You can define the defaults for jack, spkfault leds at compile time but nvs parameter takes precedence except for well-known configurations where these are forced at runtime.
**Note that gpio 36 and 39 are input only and cannot use interrupt. When set to jack or speaker fault, a 100ms polling checks their value but that's expensive**
### GPIO expanders
It is possible to add GPIO expanders using I2C or SPI bus. They should mainly be used for buttons but they can support generic-purpose outputs as well. These additional GPIOs can be numbered starting from an arbitrary value (40 and above as esp32 has GPIO 0..39). Then these new "virtual" GPIOs from (e.g) 100 to 115 can be used in [button](#Buttons) configuration, [set_GPIO](#set-gpio) or other config settings.
Each expander can support up to 32 GPIO. To use an expander for buttons, an interrupt must be provided, polling mode is not acceptable. An expander w/o interruption can still be configured, but only output will be usable. Note that the same interrupt can be shared accross expanders, as long as they are using open drain or open collectors (which they probably all do)
The parameter "gpio_exp_config" is a semicolon (;) separated list with following syntax for each expander
```
model=<model>,addr=<addr>,[,port=system|dac][,base=<n>|100][,count=<n>|16][,intr=<gpio>][,cs=<gpio>][,speed=<Hz>]
```
- model: pca9535, pca85xx, mcp23017 and mcp23s17 (SPI version)
- addr: chip i2c/spi address (decimal)
- port (I2C): use either "system" port (shared with display for example) or "dac" port (system is default)
- cs (SPI): gpio used for Chip Select
- speed (SPI): speed of the SPI bus for that device (in Hz)
- base: GPIO numbering offset to use everywhere else (default 40)
- count: number of GPIO of expander (default 16 - might be obsolted if model if sufficient to decide)
- intr: real GPIO to use as interrupt.
Note that PWM ("led_brightness" below) is not supported for expanded GPIOs and they cannot be used for high speed or precise timing signals like CS, D/C, Reset and Ready. Buttons, rotary encoder, amplifier control and power are supported. Depending on the actual chipset, pullup or pulldown might be supported so you might have to add external resistors (only MCP23x17 does pullup). The pca8575 is not a great chip, it generate a fair bit of spurious interrupts when used for GPIO out. When using a SPI expander, the bus must be configured using shared [SPI](#SPI) bus
### LED
See §**set_GPIO** for how to set the green and red LEDs. In addition, their brightness can be controlled using the "led_brigthness" parameter. The syntax is
See [set_GPIO](#set-gpio) for how to set the green and red LEDs. In addition, their brightness can be controlled using the "led_brigthness" parameter. The syntax is
```
[green=0..100][,red=0..100]
```
NB: For well-known configuration, this is ignored
### 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.
@@ -279,14 +332,15 @@ 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
**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**
### Buttons
Buttons are described using a JSON string with the following syntax
```
```json
[
{"gpio":<num>,
"type":"BUTTON_LOW | BUTTON_HIGH",
{"gpio":<num>,
"type":"BUTTON_LOW | BUTTON_HIGH",
"pull":[true|false],
"long_press":<ms>,
"long_press":<ms>,
"debounce":<ms>,
"shifter_gpio":<-1|num>,
"normal": {"pressed":"<action>","released":"<action>"},
@@ -310,7 +364,7 @@ Where (all parameters are optionals except gpio)
- "shifted": action to take when a button is pressed/released and shifted (see above/below)
- "longshifted": action to take when a button is long-pressed/released and shifted (see above/below)
Where \<action\> is either the name of another configuration to load (remap) or one amongst
Where `<action>` is either the name of another configuration to load (remap) or one amongst
```
ACTRLS_NONE, ACTRLS_POWER, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY,
@@ -323,7 +377,7 @@ KNOB_LEFT, KNOB_RIGHT, KNOB_PUSH,
One you've created such a string, use it to fill a new NVS parameter with any name below 16(?) characters. You can have as many of these configs as you can. Then set the config parameter "actrls_config" with the name of your default config
For example a config named "buttons" :
```
```json
[{"gpio":4,"type":"BUTTON_LOW","pull":true,"long_press":1000,"normal":{"pressed":"ACTRLS_VOLDOWN"},"longpress":{"pressed":"buttons_remap"}},
{"gpio":5,"type":"BUTTON_LOW","pull":true,"shifter_gpio":4,"normal":{"pressed":"ACTRLS_VOLUP"}, "shifted":{"pressed":"ACTRLS_TOGGLE"}}]
```
@@ -332,7 +386,7 @@ Defines two buttons
- second on GPIO 5, active low. When pressed it triggers a volume up command. If first button is pressed together with this button, then a play/pause toggle command is generated.
While the config named "buttons_remap"
```
```json
[{"gpio":4,"type":"BUTTON_LOW","pull":true,"long_press":1000,"normal":{"pressed":"BCTRLS_DOWN"},"longpress":{"pressed":"buttons"}},
{"gpio":5,"type":"BUTTON_LOW","pull":true,"shifter_gpio":4,"normal":{"pressed":"BCTRLS_UP"}}]
```
@@ -340,10 +394,14 @@ Defines two buttons
- first on GPIO 4, active low. When pressed, it triggers a navigation down command. When pressed more than 1000ms, it changes the button configuration for the one described above
- second on GPIO 5, active low. When pressed it triggers a navigation up command. That button, in that configuration, has no shift option
Below is a difficult but functional 2-buttons interface for your decoding pleasure
Below is a difficult but functional 2-buttons interface for your decoding pleasure:
*buttons*
`actrls_config`:
```
buttons
```
`buttons`:
```json
[{"gpio":4,"type":"BUTTON_LOW","pull":true,"long_press":1000,
"normal":{"pressed":"ACTRLS_VOLDOWN"},
"longpress":{"pressed":"buttons_remap"}},
@@ -353,8 +411,8 @@ Below is a difficult but functional 2-buttons interface for your decoding pleasu
"longpress":{"pressed":"ACTRLS_NEXT"}}
]
```
*buttons_remap*
```
`buttons_remap`:
```json
[{"gpio":4,"type":"BUTTON_LOW","pull":true,"long_press":1000,
"normal":{"pressed":"BCTRLS_DOWN"},
"longpress":{"pressed":"buttons"}},
@@ -365,18 +423,20 @@ Below is a difficult but functional 2-buttons interface for your decoding pleasu
"longshifted":{"pressed":"BCTRLS_LEFT"}}
]
```
<strong>IMPORTANT NOTE</strong>: LMS also supports the possibility to send 'raw' button codes. It's a bit complicated, so bear with me. Buttons can either be processed by SqueezeESP32 and mapped to a "function" like play/pause or they can be just sent to LMS as plain (raw) code and the full logic of press/release/longpress is handled by LMS, you don't have any control on that.
**IMPORTANT NOTE**: LMS also supports the possibility to send 'raw' button codes. It's a bit complicated, so bear with me. Buttons can either be processed by SqueezeESP32 and mapped to a "function" like play/pause or they can be just sent to LMS as plain (raw) code and the full logic of press/release/longpress is handled by LMS, you don't have any control on that.
The benefit of the "raw" mode is that you can build a player which is as close as possible to a Boom (e.g.) but you can't use the remapping function nor longress or shift logics to do your own mapping when you have a limited set of buttons. In 'raw' mode, all you really need to define is the mapping between the gpio and the button. As far as LMS is concerned, any other option in these JSON payloads does not matter. Now, when you use BT or AirPlay, the full JSON construct described above fully applies, so the shift, longpress, remapping options still work.
The benefit of the "raw" mode is that you can build a player which is as close as possible to a Boom (e.g.) but you can't use the remapping function nor longress or shift logics to do your own mapping when you have a limited set of buttons. In 'raw' mode, all you really need to define is the mapping between the gpio and the button. As far as LMS is concerned, any other option in these JSON payloads does not matter. Now, when you use BT or AirPlay, the full JSON construct described above fully applies, so the shift, longpress, remapping options still work.
**Be aware that when using non "raw" mode, the CLI (Command Line Interface) of LMS is used and *must* be available without password**
There is no good or bad option, it's your choice. Use the NVS parameter "lms_ctrls_raw" to change that option
**Note that gpio 36 and 39 are input only and cannot use interrupt. When using them for a button, a 100ms polling is started which is expensive. Long press is also likely to not work very well**
### Ethernet (coming soon)
Wired ethernet is supported by esp32 with various options but squeezelite is only supporting a Microchip LAN8720 with a RMII interface like [this](https://www.aliexpress.com/item/32858432526.html) or Davicom DM9051 over SPI like [that](https://www.amazon.com/dp/B08JLFWX9Z).
### Ethernet
Wired ethernet is supported by esp32 with various options but squeezelite is only supporting a Microchip LAN8720 with a RMII interface like [this](https://www.aliexpress.com/item/32858432526.html) or SPI-ethernet bridges like Davicom DM9051 [that](https://www.amazon.com/dp/B08JLFWX9Z) or W5500 like [this](https://www.aliexpress.com/item/32312441357.html).
**Note:** Touch buttons that can be find on some board like the LyraT V4.3 are not supported currently.
#### RMII (LAN8720)
- RMII PHY wiring is fixed and can not be changed
@@ -388,6 +448,7 @@ Wired ethernet is supported by esp32 with various options but squeezelite is onl
| GPIO25 | RX0 | EMAC_RXD0 |
| GPIO26 | RX1 | EMAC_RXD1 |
| GPIO27 | CRS_DV | EMAC_RX_DRV |
| GPIO0 | REF_CLK | 50MHz clock |
- SMI (Serial Management Interface) wiring is not fixed and you can change it either in the configuration or using "eth_config" parameter with the following syntax:
```
@@ -397,30 +458,31 @@ Connecting a reset pin for the LAN8720 is optional but recommended to avoid that
- Clock
The APLL of the esp32 is required for the audio codec, so we **need** a LAN8720 that provides a 50MHz clock. That clock **must** be connected to GPIO0, there is no alternative. This means that if your DAC requires an MCLK, then you are out of luck. It is not possible to have both to work together. There might be some workaround using CLK_OUT2 and GPIO3, but I don't have time for this.
#### SPI (DM9051)
Ethernet over SPI is supported as well and requires less GPIOs but is obvsiously slower. Another benefit is that the SPI bus can be shared with the display, but it's also possible to have a dedicated SPI interface. The esp32 has 4 SPI sub-systems, one is unaccessible so numbering is 0..2 and SPI0 is reserved for Flash/PSRAM. The "eth_config" parameter syntax becomes:
#### SPI (DM9051 or W5500)
Ethernet over SPI is supported as well and requires less GPIOs but is obvsiously slower. SPI is the shared bus set with [spi_config](#spi). The "eth_config" parameter syntax becomes:
```
model=dm9051,cs=<gpio>,speed=<clk_in_Hz>,intr=<gpio>[,host=<-1|1|2>][,rst=<gpio>][,mosi=<gpio>,miso=<gpio>,clk=<gpio>]
model=dm9051|w5500,cs=<gpio>,speed=<clk_in_Hz>,intr=<gpio>[,rst=<gpio>]
```
- To use the system SPI, shared with display (see spi_config) "host" must be set to -1. Any other value will reserve the SPI interface (careful of conflict with spi_config). The default "host" is 2 to avoid conflicting wiht default "spi_config" settings.
- When not using system SPI, "mosi" for data out, "miso" for data in and "clk" **must** be set
- The esp32 has a special I/O multiplexer for faster speed (up to 80 MHz) but that requires using specific GPIOs, which depends on SPI bus (See [here](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_master.html) for more details)
| Pin Name | SPI2 | SPI3 |
| Pin Name | SPI1 | SPI2 |
| -------- | ---- | ---- |
| CS0* | 15 | 5 |
| CS | 15 | 5 |
| SCLK | 14 | 18 |
| MISO | 12 | 19 |
| MOSI | 13 | 23 |
** THIS IS NOT AVAILABLE YET, SO MORE TO COME ON HOW TO USE WIRED ETHERNET***
### Battery / ADC
The NVS parameter "bat_config" sets the ADC1 channel used to measure battery/DC voltage. The "atten" value attenuates the input voltage to the ADC input (the read value maintains a 0-1V rage) where: 0=no attenuation(0..800mV), 1=2.5dB attenuation(0..1.1V), 2=6dB attenuation(0..1.35V), 3=11dB attenuation(0..2.6V). Scale is a float ratio applied to every sample of the 12 bits ADC. A measure is taken every 10s and an average is made every 5 minutes (not a sliding window). Syntax is
```
channel=0..7,scale=<scale>,cells=<2|3>[,atten=<0|1|2|3>]
```
NB: Set parameter to empty to disable battery reading. For well-known configuration, this is ignored (except for SqueezeAMP where number of cells is required)
# Configuration
## Setup WiFi
- Boot the esp, look for a new wifi access point showing up and connect to it. Default build ssid and passwords are "squeezelite"/"squeezelite".
- Once connected, navigate to 192.168.4.1
@@ -429,7 +491,6 @@ NB: Set parameter to empty to disable battery reading. For well-known configurat
- Once connection is established, note down the address the device received; this is the address you will use to configure it going forward
## Setup squeezelite command line (optional)
At this point, the device should have disabled its built-in access point and should be connected to a known WiFi network.
- navigate to the address that was noted in step #1
- Using the list of predefined options, choose the mode in which you want squeezelite to start
@@ -442,7 +503,6 @@ At this point, the device should have disabled its built-in access point and sho
- You can enable accessto NVS parameters under 'credits'
## Monitor
In addition of the esp-idf serial link monitor option, you can also enable a telnet server (see NVS parameters) where you'll have access to a ton of logs of what's happening inside the WROVER.
## Update Squeezelite
@@ -474,20 +534,23 @@ See squeezlite command line, but keys options are
- r "<minrate>-<maxrate>"
- C <sec> : set timeout to switch off amp gpio
- W : activate WAV and AIFF header parsing
# Building everything yourself
## Setting up ESP-IDF
### Docker
A simple alternative to building the project's binaries is to leverage the same docker image that is being used on the GitHub Actions to build our releases. The instructions below assume that you have cloned the squeezelite-esp32 code that you want to build locally and that you have opened a command line/bash session in the folder that contains the code.
Pull the most recent docker image for the environment:
```
docker pull sle118/squeezelite-esp32-idfv4-master
docker pull sle118/squeezelite-esp32-idfv43
```
Then run the container interactively :
```
for windows:
docker run -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv4-master
docker run -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv43
for linux:
docker run -it -v `pwd`:/workspace/squeezelite-esp32 sle118/squeezelite-esp32-idfv4-master
docker run -it -v `pwd`:/workspace/squeezelite-esp32 sle118/squeezelite-esp32-idfv43
```
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.
@@ -510,6 +573,7 @@ Use `idf.py monitor` to monitor the application (see esp-idf documentation)
Note: You can use `idf.py build -DDEPTH=32` to build the 32 bits version and add the `-DVERSION=<your_version>` to add a custom version name (it will be 0.0-<your_version>). If you want to change the whole version string, see squeezelite.h. You can also disable the SBR extension of AAC codecs as it consumes a lot of CPU and might overload the esp32. Use `-DAAC_DISABLE_SBR=1` for that
If you have already cloned the repository and you are getting compile errors on one of the submodules (e.g. telnet), run the following git command in the root of the repository location: `git submodule update --init --recursive`
### Rebuild codecs (highly recommended to NOT try that)
- for codecs libraries, add -mlongcalls if you want to rebuild them, but you should not (use the provided ones in codecs/lib). if you really want to rebuild them, open an issue
- libmad, libflac (no esp's version), libvorbis (tremor - not esp's version), alac work

View File

@@ -1,5 +1,5 @@
idf_component_register(
INCLUDE_DIRS . ./inc inc/alac inc/helix-aac inc/mad inc/resample16 inc/soxr inc/vorbis inc/opus inc/opusfile
INCLUDE_DIRS . ./inc inc/alac inc/helix-aac inc/mad inc/resample16 inc/soxr inc/vorbis inc/opus
)
if (DEFINED AAC_DISABLE_SBR)
@@ -14,7 +14,6 @@ add_prebuilt_library(libvorbisidec lib/libvorbisidec.a )
add_prebuilt_library(libogg lib/libogg.a )
add_prebuilt_library(libalac lib/libalac.a )
add_prebuilt_library(libresample16 lib/libresample16.a )
add_prebuilt_library(libopusfile lib/libopusfile.a )
add_prebuilt_library(libopus lib/libopus.a )
target_link_libraries(${COMPONENT_LIB} INTERFACE libmad)
@@ -24,5 +23,4 @@ target_link_libraries(${COMPONENT_LIB} INTERFACE libvorbisidec)
target_link_libraries(${COMPONENT_LIB} INTERFACE libogg)
target_link_libraries(${COMPONENT_LIB} INTERFACE libalac)
target_link_libraries(${COMPONENT_LIB} INTERFACE libresample16)
target_link_libraries(${COMPONENT_LIB} INTERFACE libopusfile)
target_link_libraries(${COMPONENT_LIB} INTERFACE libopus)

View File

@@ -1,7 +1,7 @@
#ifndef __CONFIG_TYPES_H__
#define __CONFIG_TYPES_H__
/* these are filled in by configure */
/* these are filled in by configure or cmake*/
#define INCLUDE_INTTYPES_H 1
#define INCLUDE_STDINT_H 1
#define INCLUDE_SYS_TYPES_H 1
@@ -21,5 +21,6 @@ typedef uint16_t ogg_uint16_t;
typedef int32_t ogg_int32_t;
typedef uint32_t ogg_uint32_t;
typedef int64_t ogg_int64_t;
typedef uint64_t ogg_uint64_t;
#endif

View File

@@ -11,7 +11,6 @@
********************************************************************
function: toplevel libogg include
last mod: $Id$
********************************************************************/
#ifndef _OGG_H

View File

@@ -10,8 +10,7 @@
* *
********************************************************************
function: #ifdef jail to whip a few platforms into the UNIX ideal.
last mod: $Id$
function: Define a consistent set of types on each platform.
********************************************************************/
#ifndef _OS_TYPES_H
@@ -44,6 +43,7 @@
typedef unsigned long long ogg_uint64_t;
# elif defined(__MWERKS__)
typedef long long ogg_int64_t;
typedef unsigned long long ogg_uint64_t;
typedef int ogg_int32_t;
typedef unsigned int ogg_uint32_t;
typedef short ogg_int16_t;
@@ -62,6 +62,7 @@
typedef __int64 ogg_int64_t;
typedef __int32 ogg_int32_t;
typedef unsigned __int32 ogg_uint32_t;
typedef unsigned __int64 ogg_uint64_t;
typedef __int16 ogg_int16_t;
typedef unsigned __int16 ogg_uint16_t;
# endif
@@ -69,12 +70,13 @@
#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */
# include <inttypes.h>
# include <sys/types.h>
typedef int16_t ogg_int16_t;
typedef uint16_t ogg_uint16_t;
typedef u_int16_t ogg_uint16_t;
typedef int32_t ogg_int32_t;
typedef uint32_t ogg_uint32_t;
typedef u_int32_t ogg_uint32_t;
typedef int64_t ogg_int64_t;
typedef u_int64_t ogg_uint64_t;
#elif defined(__HAIKU__)
@@ -85,6 +87,7 @@
typedef int ogg_int32_t;
typedef unsigned int ogg_uint32_t;
typedef long long ogg_int64_t;
typedef unsigned long long ogg_uint64_t;
#elif defined(__BEOS__)
@@ -95,6 +98,7 @@
typedef int32_t ogg_int32_t;
typedef uint32_t ogg_uint32_t;
typedef int64_t ogg_int64_t;
typedef uint64_t ogg_uint64_t;
#elif defined (__EMX__)
@@ -104,6 +108,8 @@
typedef int ogg_int32_t;
typedef unsigned int ogg_uint32_t;
typedef long long ogg_int64_t;
typedef unsigned long long ogg_uint64_t;
#elif defined (DJGPP)
@@ -112,11 +118,13 @@
typedef int ogg_int32_t;
typedef unsigned int ogg_uint32_t;
typedef long long ogg_int64_t;
typedef unsigned long long ogg_uint64_t;
#elif defined(R5900)
/* PS2 EE */
typedef long ogg_int64_t;
typedef unsigned long ogg_uint64_t;
typedef int ogg_int32_t;
typedef unsigned ogg_uint32_t;
typedef short ogg_int16_t;
@@ -129,6 +137,7 @@
typedef signed int ogg_int32_t;
typedef unsigned int ogg_uint32_t;
typedef long long int ogg_int64_t;
typedef unsigned long long int ogg_uint64_t;
#elif defined(__TMS320C6X__)
@@ -138,6 +147,7 @@
typedef signed int ogg_int32_t;
typedef unsigned int ogg_uint32_t;
typedef long long int ogg_int64_t;
typedef unsigned long long int ogg_uint64_t;
#else

View File

@@ -6,7 +6,7 @@
* IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. *
* *
* THE libopusfile SOURCE CODE IS (C) COPYRIGHT 1994-2012 *
* by the Xiph.Org Foundation and contributors http://www.xiph.org/ *
* by the Xiph.Org Foundation and contributors https://xiph.org/ *
* *
********************************************************************
@@ -28,7 +28,7 @@
reference
<tt><a href="https://www.xiph.org/ogg/doc/libogg/reference.html">libogg</a></tt>
and
<tt><a href="https://mf4.xiph.org/jenkins/view/opus/job/opus/ws/doc/html/index.html">libopus</a></tt>
<tt><a href="https://opus-codec.org/docs/opus_api-1.3.1/">libopus</a></tt>
libraries.
<tt>libopusfile</tt> provides several sets of built-in routines for
@@ -58,7 +58,7 @@
it is stored in the header to allow you to resample to it after decoding
(the <tt>libopusfile</tt> API does not currently provide a resampler,
but the
<a href="http://www.speex.org/docs/manual/speex-manual/node7.html#SECTION00760000000000000000">the
<a href="https://www.speex.org/docs/manual/speex-manual/node7.html#SECTION00760000000000000000">the
Speex resampler</a> is a good choice if you need one).
In general, if you are playing back the audio, you should leave it at
48&nbsp;kHz, provided your audio hardware supports it.
@@ -68,7 +68,7 @@
Opus files can contain anywhere from 1 to 255 channels of audio.
The channel mappings for up to 8 channels are the same as the
<a href="http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-800004.3.9">Vorbis
<a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810004.3.9">Vorbis
mappings</a>.
A special stereo API can convert everything to 2 channels, making it simple
to support multichannel files in an application which only has stereo
@@ -147,18 +147,18 @@ typedef struct OggOpusFile OggOpusFile;
/**@endcond*/
/**\defgroup error_codes Error Codes*/
/*@{*/
/**@{*/
/**\name List of possible error codes
Many of the functions in this library return a negative error code when a
function fails.
This list provides a brief explanation of the common errors.
See each individual function for more details on what a specific error code
means in that context.*/
/*@{*/
/**@{*/
/**A request did not succeed.*/
#define OP_FALSE (-1)
/*Currently not used externally.*/
/**Currently not used externally.**/
#define OP_EOF (-2)
/**There was a hole in the page sequence numbers (e.g., a page was corrupt or
missing).*/
@@ -185,7 +185,7 @@ typedef struct OggOpusFile OggOpusFile;
#define OP_EBADHEADER (-133)
/**The ID header contained an unrecognized version number.*/
#define OP_EVERSION (-134)
/*Currently not used at all.*/
/**Currently not used at all.**/
#define OP_ENOTAUDIO (-135)
/**An audio packet failed to decode properly.
This is usually caused by a multistream Ogg packet where the durations of
@@ -200,11 +200,11 @@ typedef struct OggOpusFile OggOpusFile;
/**The first or last granule position of a link failed basic validity checks.*/
#define OP_EBADTIMESTAMP (-139)
/*@}*/
/*@}*/
/**@}*/
/**@}*/
/**\defgroup header_info Header Information*/
/*@{*/
/**@{*/
/**The maximum number of channels in an Ogg Opus stream.*/
#define OPUS_CHANNEL_COUNT_MAX (255)
@@ -284,7 +284,7 @@ struct OpusHead{
A particular tag may occur more than once, and order is significant.
The character set encoding for the strings is always UTF-8, but the tag
names are limited to ASCII, and treated as case-insensitive.
See <a href="http://www.xiph.org/vorbis/doc/v-comment.html">the Vorbis
See <a href="https://www.xiph.org/vorbis/doc/v-comment.html">the Vorbis
comment header specification</a> for details.
In filling in this structure, <tt>libopusfile</tt> will null-terminate the
@@ -311,7 +311,7 @@ struct OpusTags{
};
/**\name Picture tag image formats*/
/*@{*/
/**@{*/
/**The MIME type was not recognized, or the image data did not match the
declared MIME type.*/
@@ -325,7 +325,7 @@ struct OpusTags{
/**The image is a GIF.*/
#define OP_PIC_FORMAT_GIF (3)
/*@}*/
/**@}*/
/**The contents of a METADATA_BLOCK_PICTURE tag.*/
struct OpusPictureTag{
@@ -398,7 +398,7 @@ struct OpusPictureTag{
These can be used to query the headers returned by <tt>libopusfile</tt>, or
to parse Opus headers from sources other than an Ogg Opus stream, provided
they use the same format.*/
/*@{*/
/**@{*/
/**Parses the contents of the ID header packet of an Ogg Opus stream.
\param[out] _head Returns the contents of the parsed packet.
@@ -671,12 +671,12 @@ void opus_picture_tag_init(OpusPictureTag *_pic) OP_ARG_NONNULL(1);
\param _pic The #OpusPictureTag structure to clear.*/
void opus_picture_tag_clear(OpusPictureTag *_pic) OP_ARG_NONNULL(1);
/*@}*/
/**@}*/
/*@}*/
/**@}*/
/**\defgroup url_options URL Reading Options*/
/*@{*/
/**@{*/
/**\name URL reading options
Options for op_url_stream_create() and associated functions.
These allow you to provide proxy configuration parameters, skip SSL
@@ -685,7 +685,7 @@ void opus_picture_tag_clear(OpusPictureTag *_pic) OP_ARG_NONNULL(1);
times, only the value specified by the last occurrence has an effect
(unless otherwise specified).
They may be expanded in the future.*/
/*@{*/
/**@{*/
/**@cond PRIVATE*/
@@ -698,7 +698,7 @@ void opus_picture_tag_clear(OpusPictureTag *_pic) OP_ARG_NONNULL(1);
#define OP_HTTP_PROXY_PASS_REQUEST (6720)
#define OP_GET_SERVER_INFO_REQUEST (6784)
#define OP_URL_OPT(_request) ((_request)+(char *)0)
#define OP_URL_OPT(_request) ((char *)(_request))
/*These macros trigger compilation errors or warnings if the wrong types are
provided to one of the URL options.*/
@@ -843,11 +843,11 @@ void opus_server_info_clear(OpusServerInfo *_info) OP_ARG_NONNULL(1);
#define OP_GET_SERVER_INFO(_info) \
OP_URL_OPT(OP_GET_SERVER_INFO_REQUEST),OP_CHECK_SERVER_INFO_PTR(_info)
/*@}*/
/*@}*/
/**@}*/
/**@}*/
/**\defgroup stream_callbacks Abstract Stream Reading Interface*/
/*@{*/
/**@{*/
/**\name Functions for reading from streams
These functions define the interface used to read from and seek in a stream
of data.
@@ -856,7 +856,7 @@ void opus_server_info_clear(OpusServerInfo *_info) OP_ARG_NONNULL(1);
These functions also include some convenience routines for working with
standard <code>FILE</code> pointers, complete streams stored in a single
block of memory, or URLs.*/
/*@{*/
/**@{*/
/**Reads up to \a _nbytes bytes of data from \a _stream.
\param _stream The stream to read from.
@@ -1034,18 +1034,18 @@ OP_WARN_UNUSED_RESULT void *op_url_stream_vcreate(OpusFileCallbacks *_cb,
OP_WARN_UNUSED_RESULT void *op_url_stream_create(OpusFileCallbacks *_cb,
const char *_url,...) OP_ARG_NONNULL(1) OP_ARG_NONNULL(2);
/*@}*/
/*@}*/
/**@}*/
/**@}*/
/**\defgroup stream_open_close Opening and Closing*/
/*@{*/
/**@{*/
/**\name Functions for opening and closing streams
These functions allow you to test a stream to see if it is Opus, open it,
and close it.
Several flavors are provided for each of the built-in stream types, plus a
more general version which takes a set of application-provided callbacks.*/
/*@{*/
/**@{*/
/**Test to see if this is an Opus stream.
For good results, you will need at least 57 bytes (for a pure Opus-only
@@ -1159,20 +1159,16 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_open_url(const char *_url,
This value will be passed verbatim as the first
argument to all of the callbacks.
\param _cb The callbacks with which to access the stream.
<code><a href="#op_read_func">read()</a></code> must
be implemented.
<code><a href="#op_seek_func">seek()</a></code> and
<code><a href="#op_tell_func">tell()</a></code> may
be <code>NULL</code>, or may always return -1 to
indicate a stream is unseekable, but if
<code><a href="#op_seek_func">seek()</a></code> is
implemented and succeeds on a particular stream, then
<code><a href="#op_tell_func">tell()</a></code> must
also.
<code><a href="#op_close_func">close()</a></code> may
be <code>NULL</code>, but if it is not, it will be
called when the \c OggOpusFile is destroyed by
op_free().
\ref op_read_func "read()" must be implemented.
\ref op_seek_func "seek()" and \ref op_tell_func
"tell()" may be <code>NULL</code>, or may always
return -1 to indicate a stream is unseekable, but if
\ref op_seek_func "seek()" is implemented and
succeeds on a particular stream, then \ref
op_tell_func "tell()" must also.
\ref op_close_func "close()" may be <code>NULL</code>,
but if it is not, it will be called when the \c
OggOpusFile is destroyed by op_free().
It will not be called if op_open_callbacks() fails
with an error.
\param _initial_data An initial buffer of data from the start of the
@@ -1183,10 +1179,8 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_open_url(const char *_url,
stream to be opened, even if it is unseekable.
\param _initial_bytes The number of bytes in \a _initial_data.
If the stream is seekable, its current position (as
reported by
<code><a href="#opus_tell_func">tell()</a></code>
at the start of this function) must be equal to
\a _initial_bytes.
reported by \ref op_tell_func "tell()" at the start
of this function) must be equal to \a _initial_bytes.
Otherwise, seeking to absolute positions will
generate inconsistent results.
\param[out] _error Returns 0 on success, or a failure code on error.
@@ -1206,11 +1200,10 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_open_url(const char *_url,
implemented, such as an unsupported channel
family.</dd>
<dt>#OP_EINVAL</dt>
<dd><code><a href="#op_seek_func">seek()</a></code>
was implemented and succeeded on this source, but
<code><a href="#op_tell_func">tell()</a></code>
did not, or the starting position indicator was
not equal to \a _initial_bytes.</dd>
<dd>\ref op_seek_func "seek()" was implemented and
succeeded on this source, but \ref op_tell_func
"tell()" did not, or the starting position
indicator was not equal to \a _initial_bytes.</dd>
<dt>#OP_ENOTFORMAT</dt>
<dd>The stream contained a link that did not have
any logical Opus streams in it.</dd>
@@ -1341,20 +1334,16 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_test_url(const char *_url,
This value will be passed verbatim as the first
argument to all of the callbacks.
\param _cb The callbacks with which to access the stream.
<code><a href="#op_read_func">read()</a></code> must
be implemented.
<code><a href="#op_seek_func">seek()</a></code> and
<code><a href="#op_tell_func">tell()</a></code> may
be <code>NULL</code>, or may always return -1 to
indicate a stream is unseekable, but if
<code><a href="#op_seek_func">seek()</a></code> is
implemented and succeeds on a particular stream, then
<code><a href="#op_tell_func">tell()</a></code> must
also.
<code><a href="#op_close_func">close()</a></code> may
be <code>NULL</code>, but if it is not, it will be
called when the \c OggOpusFile is destroyed by
op_free().
\ref op_read_func "read()" must be implemented.
\ref op_seek_func "seek()" and \ref op_tell_func
"tell()" may be <code>NULL</code>, or may always
return -1 to indicate a stream is unseekable, but if
\ref op_seek_func "seek()" is implemented and
succeeds on a particular stream, then \ref
op_tell_func "tell()" must also.
\ref op_close_func "close()" may be <code>NULL</code>,
but if it is not, it will be called when the \c
OggOpusFile is destroyed by op_free().
It will not be called if op_open_callbacks() fails
with an error.
\param _initial_data An initial buffer of data from the start of the
@@ -1367,9 +1356,8 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_test_url(const char *_url,
\param _initial_bytes The number of bytes in \a _initial_data.
If the stream is seekable, its current position (as
reported by
<code><a href="#opus_tell_func">tell()</a></code>
at the start of this function) must be equal to
\a _initial_bytes.
\ref op_tell_func "tell()" at the start of this
function) must be equal to \a _initial_bytes.
Otherwise, seeking to absolute positions will
generate inconsistent results.
\param[out] _error Returns 0 on success, or a failure code on error.
@@ -1418,11 +1406,11 @@ int op_test_open(OggOpusFile *_of) OP_ARG_NONNULL(1);
\param _of The \c OggOpusFile to free.*/
void op_free(OggOpusFile *_of);
/*@}*/
/*@}*/
/**@}*/
/**@}*/
/**\defgroup stream_info Stream Information*/
/*@{*/
/**@{*/
/**\name Functions for obtaining information about streams
These functions allow you to get basic information about a stream, including
@@ -1437,18 +1425,17 @@ void op_free(OggOpusFile *_of);
streams returned by op_test_callbacks() or one of the associated
convenience functions.
Their documention will indicate so explicitly.*/
/*@{*/
/**@{*/
/**Returns whether or not the stream being read is seekable.
This is true if
<ol>
<li>The <code><a href="#op_seek_func">seek()</a></code> and
<code><a href="#op_tell_func">tell()</a></code> callbacks are both
non-<code>NULL</code>,</li>
<li>The <code><a href="#op_seek_func">seek()</a></code> callback was
successfully executed at least once, and</li>
<li>The <code><a href="#op_tell_func">tell()</a></code> callback was
successfully able to report the position indicator afterwards.</li>
<li>The \ref op_seek_func "seek()" and \ref op_tell_func "tell()"
callbacks are both non-<code>NULL</code>,</li>
<li>The \ref op_seek_func "seek()" callback was successfully executed at
least once, and</li>
<li>The \ref op_tell_func "tell()" callback was successfully able to report
the position indicator afterwards.</li>
</ol>
This function may be called on partially-opened streams.
\param _of The \c OggOpusFile whose seekable status is to be returned.
@@ -1638,11 +1625,11 @@ opus_int64 op_raw_tell(const OggOpusFile *_of) OP_ARG_NONNULL(1);
\retval #OP_EINVAL The stream was only partially open.*/
ogg_int64_t op_pcm_tell(const OggOpusFile *_of) OP_ARG_NONNULL(1);
/*@}*/
/*@}*/
/**@}*/
/**@}*/
/**\defgroup stream_seeking Seeking*/
/*@{*/
/**@{*/
/**\name Functions for seeking in Opus streams
These functions let you seek in Opus streams, if the underlying stream
@@ -1667,7 +1654,7 @@ ogg_int64_t op_pcm_tell(const OggOpusFile *_of) OP_ARG_NONNULL(1);
values as would be obtained by decoding the stream straight through.
However, such differences are expected to be smaller than the loss
introduced by Opus's lossy compression.*/
/*@{*/
/**@{*/
/**Seek to a byte offset relative to the <b>compressed</b> data.
This also scans packets to update the PCM cursor.
@@ -1702,11 +1689,11 @@ int op_raw_seek(OggOpusFile *_of,opus_int64 _byte_offset) OP_ARG_NONNULL(1);
seeking to the target destination was impossible.*/
int op_pcm_seek(OggOpusFile *_of,ogg_int64_t _pcm_offset) OP_ARG_NONNULL(1);
/*@}*/
/*@}*/
/**@}*/
/**@}*/
/**\defgroup stream_decoding Decoding*/
/*@{*/
/**@{*/
/**\name Functions for decoding audio data
These functions retrieve actual decoded audio data from the stream.
@@ -1744,7 +1731,7 @@ int op_pcm_seek(OggOpusFile *_of,ogg_int64_t _pcm_offset) OP_ARG_NONNULL(1);
If you are reading from an <https:> URL (particularly if seeking is not
supported), you should make sure to check for this error and warn the user
appropriately.*/
/*@{*/
/**@{*/
/**Indicates that the decoding callback should produce signed 16-bit
native-endian output samples.*/
@@ -1890,7 +1877,7 @@ void op_set_dither_enabled(OggOpusFile *_of,int _enabled) OP_ARG_NONNULL(1);
signed native-endian 16-bit values at 48&nbsp;kHz
with a nominal range of <code>[-32768,32767)</code>.
Multiple channels are interleaved using the
<a href="http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-800004.3.9">Vorbis
<a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810004.3.9">Vorbis
channel ordering</a>.
This must have room for at least \a _buf_size values.
\param _buf_size The number of values that can be stored in \a _pcm.
@@ -1972,7 +1959,7 @@ OP_WARN_UNUSED_RESULT int op_read(OggOpusFile *_of,
signed floats at 48&nbsp;kHz with a nominal range of
<code>[-1.0,1.0]</code>.
Multiple channels are interleaved using the
<a href="http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-800004.3.9">Vorbis
<a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810004.3.9">Vorbis
channel ordering</a>.
This must have room for at least \a _buf_size floats.
\param _buf_size The number of floats that can be stored in \a _pcm.
@@ -2150,8 +2137,8 @@ OP_WARN_UNUSED_RESULT int op_read_stereo(OggOpusFile *_of,
OP_WARN_UNUSED_RESULT int op_read_float_stereo(OggOpusFile *_of,
float *_pcm,int _buf_size) OP_ARG_NONNULL(1);
/*@}*/
/*@}*/
/**@}*/
/**@}*/
# if OP_GNUC_PREREQ(4,0)
# pragma GCC visibility pop

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -136,7 +136,7 @@ const static actrls_t controls = {
NULL, NULL, // rew, fwd
bt_prev, bt_next, // prev, next
NULL, NULL, NULL, NULL, // left, right, up, down
NULL, NULL, NULL, NULL, NULL, NULL, // pre1-6
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // pre1-10
bt_volume_down, bt_volume_up, bt_toggle// knob left, knob_right, knob push
};

View File

@@ -121,7 +121,7 @@ void config_start_timer(){
nvs_type_t config_get_item_type(cJSON * entry){
if(entry==NULL){
ESP_LOGE(TAG,"null pointer received!");
return true;
return 0;
}
cJSON * item_type = cJSON_GetObjectItemCaseSensitive(entry, "type");
if(item_type ==NULL ) {
@@ -142,7 +142,7 @@ cJSON * config_set_value_safe(nvs_type_t nvs_type, const char *key, const void
return NULL;
}
cJSON * existing = cJSON_GetObjectItemCaseSensitive(nvs_json, key);
cJSON * existing = cJSON_GetObjectItemCaseSensitive(nvs_json, key);
if(existing !=NULL && nvs_type == NVS_TYPE_STR && config_get_item_type(existing) != NVS_TYPE_STR ) {
ESP_LOGW(TAG, "Storing numeric value from string");
numvalue = atof((char *)value);

View File

@@ -40,7 +40,8 @@ const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = {
#endif
};
extern int main(int argc, char **argv);
extern int squeezelite_main(int argc, char **argv);
static int launchsqueezelite(int argc, char **argv);
/** Arguments used by 'squeezelite' function */
@@ -54,39 +55,32 @@ static struct {
} thread_parms ;
#define ADDITIONAL_SQUEEZELITE_ARGS 5
static void squeezelite_thread(void *arg){
static void squeezelite_thread(void *arg){
ESP_LOGV(TAG ,"Number of args received: %u",thread_parms.argc );
ESP_LOGV(TAG ,"Values:");
for(int i = 0;i<thread_parms.argc; i++){
ESP_LOGV(TAG ," %s",thread_parms.argv[i]);
}
ESP_LOGI(TAG ,"Calling squeezelite");
int ret = squeezelite_main(thread_parms.argc, thread_parms.argv);
cmd_send_messaging("cfg-audio-tmpl",ret > 1 ? MESSAGING_ERROR : MESSAGING_WARNING,"squeezelite exited with error code %d\n", ret);
ESP_LOGI(TAG ,"Calling squeezelite");
int ret = main(thread_parms.argc,thread_parms.argv);
ESP_LOGV(TAG ,"Exited from squeezelite's main(). Freeing argv structure.");
for(int i=0;i<thread_parms.argc;i++){
ESP_LOGV(TAG ,"Freeing char buffer for parameter %u", i+1);
free(thread_parms.argv[i]);
}
ESP_LOGV(TAG ,"Freeing argv pointer");
free(thread_parms.argv);
if(!wait_for_commit()){
ESP_LOGW(TAG,"Unable to commit configuration. ");
}
messaging_post_message(MESSAGING_ERROR, MESSAGING_CLASS_SYSTEM, "squeezelite exited with error code %d", ret);
if (ret == 1) {
if (ret <= 1) {
int wait = 60;
messaging_post_message(MESSAGING_ERROR, MESSAGING_CLASS_SYSTEM, "Rebooting in %d sec", wait);
wait_for_commit();
cmd_send_messaging("cfg-audio-tmpl",MESSAGING_ERROR,"Rebooting in %d sec\n", wait);
vTaskDelay( pdMS_TO_TICKS(wait * 1000));
esp_restart();
} else {
messaging_post_message(MESSAGING_ERROR, MESSAGING_CLASS_SYSTEM, "Correct command line and reboot");
cmd_send_messaging("cfg-audio-tmpl",MESSAGING_ERROR,"Correct command line and reboot\n");
vTaskSuspend(NULL);
}
ESP_LOGV(TAG, "Exited from squeezelite's main(). Freeing argv structure.");
for(int i=0;i<thread_parms.argc;i++) free(thread_parms.argv[i]);
free(thread_parms.argv);
}
static int launchsqueezelite(int argc, char **argv) {

View File

@@ -50,6 +50,7 @@ static const dmap_field dmap_fields[] = {
{ "abar", DMAP_DICT, DMAP_STR, "daap.browseartistlisting" },
{ "abcp", DMAP_DICT, DMAP_STR, "daap.browsecomposerlisting" },
{ "abgn", DMAP_DICT, DMAP_STR, "daap.browsegenrelisting" },
#ifdef DMAP_FULL
{ "abpl", DMAP_UINT, 0, "daap.baseplaylist" },
{ "abro", DMAP_DICT, 0, "daap.databasebrowse" },
{ "adbs", DMAP_DICT, 0, "daap.databasesongs" },
@@ -256,10 +257,12 @@ static const dmap_field dmap_fields[] = {
{ "meia", DMAP_UINT, 0, "dmap.itemdateadded" },
{ "meip", DMAP_UINT, 0, "dmap.itemdateplayed" },
{ "mext", DMAP_UINT, 0, "dmap.objectextradata" },
#endif
{ "miid", DMAP_UINT, 0, "dmap.itemid" },
{ "mikd", DMAP_UINT, 0, "dmap.itemkind" },
{ "mimc", DMAP_UINT, 0, "dmap.itemcount" },
{ "minm", DMAP_STR, 0, "dmap.itemname" },
#ifdef DMAP_FULL
{ "mlcl", DMAP_DICT, DMAP_DICT, "dmap.listing" },
{ "mlid", DMAP_UINT, 0, "dmap.sessionid" },
{ "mlit", DMAP_ITEM, 0, "dmap.listingitem" },
@@ -314,6 +317,7 @@ static const dmap_field dmap_fields[] = {
{ "prat", DMAP_UINT, 0, "dpap.imagerating" },
{ "pret", DMAP_DICT, 0, "dpap.retryids" },
{ "pwth", DMAP_UINT, 0, "dpap.imagepixelwidth" }
#endif
};
static const size_t dmap_field_count = sizeof(dmap_fields) / sizeof(dmap_field);

View File

@@ -959,7 +959,7 @@ static int base64_decode(const char *str, void *data)
*q++ = (val >> 8) & 0xff;
if (marker < 1)
*q++ = val & 0xff;
}
}
return q - (unsigned char *) data;
}

View File

@@ -90,7 +90,7 @@ const static actrls_t controls = {
NULL, NULL, // rew, fwd
raop_prev, raop_next, // prev, next
NULL, NULL, NULL, NULL, // left, right, up, down
NULL, NULL, NULL, NULL, NULL, NULL, // pre1-6
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // pre1-10
raop_volume_down, raop_volume_up, raop_toggle// knob left, knob_right, knob push
};

View File

@@ -71,7 +71,8 @@ static log_level *loglevel = &raop_loglevel;
//#define __RTP_STORE
// default buffer size
#define BUFFER_FRAMES ( (150 * RAOP_SAMPLE_RATE * 2) / (352 * 100) )
#define BUFFER_FRAMES_MAX ((RAOP_SAMPLE_RATE * 10) / 352 )
#define BUFFER_FRAMES_MIN ( (150 * RAOP_SAMPLE_RATE * 2) / (352 * 100) )
#define MAX_PACKET 1408
#define MIN_LATENCY 11025
#define MAX_LATENCY ( (120 * RAOP_SAMPLE_RATE * 2) / 100 )
@@ -86,14 +87,15 @@ static log_level *loglevel = &raop_loglevel;
enum { DATA = 0, CONTROL, TIMING };
static const u8_t silence_frame[MAX_PACKET] = { 0 };
uint32_t buffer_frames = ((150 * RAOP_SAMPLE_RATE * 2) / (352 * 100));
typedef u16_t seq_t;
typedef struct audio_buffer_entry { // decoded audio packets
int ready;
typedef struct __attribute__((__packed__)) audio_buffer_entry { // decoded audio packets
u32_t rtptime, last_resend;
s16_t *data;
int len;
bool allocated;
u16_t len;
u8_t ready;
u8_t allocated;
} abuf_t;
typedef struct rtp_s {
@@ -133,7 +135,7 @@ typedef struct rtp_s {
u32_t resent_req, resent_rec; // total resent + recovered frames
u32_t silent_frames; // total silence frames
u32_t discarded;
abuf_t audio_buffer[BUFFER_FRAMES];
abuf_t audio_buffer[BUFFER_FRAMES_MAX];
seq_t ab_read, ab_write;
pthread_mutex_t ab_mutex;
#ifdef WIN32
@@ -152,7 +154,7 @@ typedef struct rtp_s {
} rtp_t;
#define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES)
#define BUFIDX(seqno) ((seq_t)(seqno) % buffer_frames)
static void buffer_alloc(abuf_t *audio_buffer, int size, uint8_t *buf, size_t buf_size);
static void buffer_release(abuf_t *audio_buffer);
static void buffer_reset(abuf_t *audio_buffer);
@@ -373,25 +375,27 @@ void rtp_record(rtp_t *ctx, unsigned short seqno, unsigned rtptime) {
/*---------------------------------------------------------------------------*/
static void buffer_alloc(abuf_t *audio_buffer, int size, uint8_t *buf, size_t buf_size) {
int i;
for (i = 0; i < BUFFER_FRAMES; i++) {
if (buf && buf_size >= size) {
audio_buffer[i].data = (s16_t*) buf;
audio_buffer[i].allocated = false;
buf += size;
buf_size -= size;
} else {
audio_buffer[i].allocated = true;
audio_buffer[i].data = malloc(size);
}
audio_buffer[i].ready = 0;
for (buffer_frames = 0; buf && buf_size >= size && buffer_frames < BUFFER_FRAMES_MAX; buffer_frames++) {
audio_buffer[buffer_frames].data = (s16_t*) buf;
audio_buffer[buffer_frames].allocated = 0;
audio_buffer[buffer_frames].ready = 0;
buf += size;
buf_size -= size;
}
LOG_INFO("allocated %d buffers (min=%d) from buffer of %zu bytes", buffer_frames, BUFFER_FRAMES_MIN, buf_size + buffer_frames * size);
for(; buffer_frames < BUFFER_FRAMES_MIN; buffer_frames++) {
audio_buffer[buffer_frames].data = malloc(size);
audio_buffer[buffer_frames].allocated = 1;
audio_buffer[buffer_frames].ready = 0;
}
}
/*---------------------------------------------------------------------------*/
static void buffer_release(abuf_t *audio_buffer) {
int i;
for (i = 0; i < BUFFER_FRAMES; i++) {
for (i = 0; i < buffer_frames; i++) {
if (audio_buffer[i].allocated) free(audio_buffer[i].data);
}
}
@@ -399,7 +403,7 @@ static void buffer_release(abuf_t *audio_buffer) {
/*---------------------------------------------------------------------------*/
static void buffer_reset(abuf_t *audio_buffer) {
int i;
for (i = 0; i < BUFFER_FRAMES; i++) audio_buffer[i].ready = 0;
for (i = 0; i < buffer_frames; i++) audio_buffer[i].ready = 0;
}
/*---------------------------------------------------------------------------*/
@@ -411,7 +415,7 @@ static int seq_order(seq_t a, seq_t b) {
}
/*---------------------------------------------------------------------------*/
static void alac_decode(rtp_t *ctx, s16_t *dest, char *buf, int len, int *outsize) {
static void alac_decode(rtp_t *ctx, s16_t *dest, char *buf, int len, u16_t *outsize) {
unsigned char iv[16];
int aeslen;
assert(len<=MAX_PACKET);
@@ -803,7 +807,7 @@ static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last) {
static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last) {
unsigned char req[8]; // *not* a standard RTCP NACK
// do not request silly ranges (happens in case of network large blackouts)
// do not request silly ranges (happens in case of network large blackouts)
if (seq_order(last, first) || last - first > buffer_frames / 2) return false;
ctx->resent_req += (seq_t) (last - first) + 1;

View File

@@ -60,7 +60,7 @@ static const actrls_config_map_t actrls_config_map[] =
static const char * actrls_action_s[ ] = { EP(ACTRLS_POWER),EP(ACTRLS_VOLUP),EP(ACTRLS_VOLDOWN),EP(ACTRLS_TOGGLE),EP(ACTRLS_PLAY),
EP(ACTRLS_PAUSE),EP(ACTRLS_STOP),EP(ACTRLS_REW),EP(ACTRLS_FWD),EP(ACTRLS_PREV),EP(ACTRLS_NEXT),
EP(BCTRLS_UP),EP(BCTRLS_DOWN),EP(BCTRLS_LEFT),EP(BCTRLS_RIGHT),
EP(BCTRLS_PS1),EP(BCTRLS_PS2),EP(BCTRLS_PS3),EP(BCTRLS_PS4),EP(BCTRLS_PS5),EP(BCTRLS_PS6),
EP(BCTRLS_PS0),EP(BCTRLS_PS1),EP(BCTRLS_PS2),EP(BCTRLS_PS3),EP(BCTRLS_PS4),EP(BCTRLS_PS5),EP(BCTRLS_PS6),EP(BCTRLS_PS7),EP(BCTRLS_PS8),EP(BCTRLS_PS9),
EP(KNOB_LEFT),EP(KNOB_RIGHT),EP(KNOB_PUSH),
""} ;

View File

@@ -14,7 +14,7 @@
typedef enum { ACTRLS_NONE = -1, ACTRLS_POWER,ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY,
ACTRLS_PAUSE, ACTRLS_STOP, ACTRLS_REW, ACTRLS_FWD, ACTRLS_PREV, ACTRLS_NEXT,
BCTRLS_UP, BCTRLS_DOWN, BCTRLS_LEFT, BCTRLS_RIGHT,
BCTRLS_PS1,BCTRLS_PS2,BCTRLS_PS3,BCTRLS_PS4,BCTRLS_PS5,BCTRLS_PS6,
BCTRLS_PS0,BCTRLS_PS1,BCTRLS_PS2,BCTRLS_PS3,BCTRLS_PS4,BCTRLS_PS5,BCTRLS_PS6,BCTRLS_PS7,BCTRLS_PS8,BCTRLS_PS9,
KNOB_LEFT, KNOB_RIGHT, KNOB_PUSH,
ACTRLS_REMAP, ACTRLS_MAX
} actrls_action_e;

View File

@@ -219,18 +219,23 @@ esp_err_t messaging_post_to_queue(messaging_handle_t subscriber_handle, single_m
}
return ESP_LOG_DEBUG;
}
void messaging_post_message(messaging_types type,messaging_classes msg_class, const char *fmt, ...){
va_list va;
va_start(va, fmt);
vmessaging_post_message(type, msg_class, fmt, va);
va_end(va);
}
void vmessaging_post_message(messaging_types type,messaging_classes msg_class, const char *fmt, va_list va){
single_message_t * message=NULL;
size_t msg_size=0;
size_t ln =0;
messaging_list_t * cur=&top;
va_list va;
va_start(va, fmt);
ln = vsnprintf(NULL, 0, fmt, va)+1;
msg_size = sizeof(single_message_t)+ln;
message = (single_message_t *)malloc_init_external(msg_size);
vsprintf(message->message, fmt, va);
va_end(va);
message->msg_size = msg_size;
message->type = type;
message->msg_class = msg_class;

View File

@@ -34,6 +34,7 @@ cJSON * messaging_retrieve_messages(RingbufHandle_t buf_handle);
messaging_handle_t messaging_register_subscriber(uint8_t max_count, char * name);
esp_err_t messaging_post_to_queue(messaging_handle_t subscriber_handle, single_message_t * message, size_t message_size);
void messaging_post_message(messaging_types type,messaging_classes msg_class, const char * fmt, ...);
void vmessaging_post_message(messaging_types type,messaging_classes msg_class, const char *fmt, va_list va);
cJSON * messaging_retrieve_messages(RingbufHandle_t buf_handle);
single_message_t * messaging_retrieve_message(RingbufHandle_t buf_handle);
void log_send_messaging(messaging_types msgtype,const char *fmt, ...);

View File

@@ -16,7 +16,10 @@
#include <stdarg.h>
#include <ApResolve.h>
#include "BellTask.h"
#include "MDNSService.h"
#include "TrackPlayer.h"
#include "CSpotContext.h"
#include "SpircHandler.h"
#include "LoginBlob.h"
#include "CentralAudioBuffer.h"
@@ -39,12 +42,13 @@ class chunkManager : public bell::Task {
public:
std::atomic<bool> isRunning = true;
std::atomic<bool> isPaused = true;
chunkManager(std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer, std::function<void()> trackHandler,
std::function<void(const uint8_t*, size_t)> dataHandler);
chunkManager(std::function<void()> trackHandler, std::function<void(const uint8_t*, size_t)> dataHandler);
size_t writePCM(uint8_t* data, size_t bytes, std::string_view trackId, size_t sequence);
void flush();
void teardown();
private:
std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer;
std::unique_ptr<bell::CentralAudioBuffer> centralAudioBuffer;
std::function<void()> trackHandler;
std::function<void(const uint8_t*, size_t)> dataHandler;
std::mutex runningMutex;
@@ -52,20 +56,27 @@ private:
void runTask() override;
};
chunkManager::chunkManager(std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer,
std::function<void()> trackHandler, std::function<void(const uint8_t*, size_t)> dataHandler)
chunkManager::chunkManager(std::function<void()> trackHandler, std::function<void(const uint8_t*, size_t)> dataHandler)
: bell::Task("chunker", 4 * 1024, 0, 0) {
this->centralAudioBuffer = centralAudioBuffer;
this->centralAudioBuffer = std::make_unique<bell::CentralAudioBuffer>(32);
this->trackHandler = trackHandler;
this->dataHandler = dataHandler;
startTask();
}
size_t chunkManager::writePCM(uint8_t* data, size_t bytes, std::string_view trackId, size_t sequence) {
return centralAudioBuffer->writePCM(data, bytes, sequence);
}
void chunkManager::teardown() {
isRunning = false;
std::scoped_lock lock(runningMutex);
}
void chunkManager::flush() {
centralAudioBuffer->clearBuffer();
}
void chunkManager::runTask() {
std::scoped_lock lock(runningMutex);
size_t lastHash = 0;
@@ -103,7 +114,6 @@ class cspotPlayer : public bell::Task {
private:
std::string name;
bell::WrappedSemaphore clientConnected;
std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer;
int startOffset, volume = 0, bitrate = 160;
httpd_handle_t serverHandle;
@@ -223,7 +233,7 @@ esp_err_t cspotPlayer::handlePOST(httpd_req_t *request) {
void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event) {
switch (event->eventType) {
case cspot::SpircHandler::EventType::PLAYBACK_START: {
centralAudioBuffer->clearBuffer();
chunker->flush();
// we are not playing anymore
trackStatus = TRACK_INIT;
@@ -254,17 +264,17 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
case cspot::SpircHandler::EventType::PREV:
case cspot::SpircHandler::EventType::FLUSH: {
// FLUSH is sent when there is no next, just clean everything
centralAudioBuffer->clearBuffer();
chunker->flush();
cmdHandler(CSPOT_FLUSH);
break;
}
case cspot::SpircHandler::EventType::DISC:
centralAudioBuffer->clearBuffer();
chunker->flush();
cmdHandler(CSPOT_DISC);
chunker->teardown();
break;
case cspot::SpircHandler::EventType::SEEK: {
centralAudioBuffer->clearBuffer();
chunker->flush();
cmdHandler(CSPOT_SEEK, std::get<int>(event->data));
break;
}
@@ -369,7 +379,6 @@ void cspotPlayer::runTask() {
CSPOT_LOG(info, "Spotify client connected for %s", name.c_str());
centralAudioBuffer = std::make_shared<bell::CentralAudioBuffer>(32);
auto ctx = cspot::Context::createFromBlob(blob);
if (bitrate == 320) ctx->config.audioFormat = AudioFormat_OGG_VORBIS_320;
@@ -382,11 +391,20 @@ void cspotPlayer::runTask() {
// Auth successful
if (token.size() > 0) {
spirc = std::make_unique<cspot::SpircHandler>(ctx);
// Create a player, pass the track handler
chunker = std::make_unique<chunkManager>(
[this](void) {
return trackHandler();
},
[this](const uint8_t* data, size_t bytes) {
return dataHandler(data, bytes);
});
// set call back to calculate a hash on trackId
spirc->getTrackPlayer()->setDataCallback(
[this](uint8_t* data, size_t bytes, std::string_view trackId, size_t sequence) {
return centralAudioBuffer->writePCM(data, bytes, sequence);
return chunker->writePCM(data, bytes, trackId, sequence);
});
// set event (PLAY, VOLUME...) handler
@@ -398,15 +416,6 @@ void cspotPlayer::runTask() {
// Start handling mercury messages
ctx->session->startTask();
// Create a player, pass the tack handler
chunker = std::make_unique<chunkManager>(centralAudioBuffer,
[this](void) {
return trackHandler();
},
[this](const uint8_t* data, size_t bytes) {
return dataHandler(data, bytes);
});
// set volume at connection
cmdHandler(CSPOT_VOLUME, volume);
@@ -443,11 +452,10 @@ void cspotPlayer::runTask() {
CSPOT_LOG(info, "disconnecting player %s", name.c_str());
}
// we want to release memory ASAP and fore sure
centralAudioBuffer.reset();
// we want to release memory ASAP and for sure
ctx.reset();
token.clear();
// update volume when we disconnect
cJSON *config = config_alloc_get_cjson("cspot_config");
cJSON_DeleteItemFromObject(config, "volume");

View File

@@ -71,6 +71,7 @@ class CentralAudioBuffer {
void clearBuffer() {
std::scoped_lock lock(this->dataAccessMutex);
//size_t exceptSize = currentSampleRate + (sizeof(AudioChunk) - (currentSampleRate % sizeof(AudioChunk)));
hasChunk = false;
audioBuffer->emptyBuffer();
}

View File

@@ -1,12 +1,12 @@
#pragma once
#include <functional>
#include <memory>
#include "CSpotContext.h"
#include "Utils.h"
#include <functional> // for function
#include <memory> // for shared_ptr
#include <string> // for string
namespace cspot {
struct Context;
class AccessKeyFetcher {
public:
AccessKeyFetcher(std::shared_ptr<cspot::Context> ctx);

View File

@@ -1,13 +1,9 @@
#pragma once
#include <memory>
#include <string>
#include "HTTPClient.h"
#include <string> // for string
#ifdef BELL_ONLY_CJSON
#include "cJSON.h"
#else
#include "nlohmann/json.hpp"
#endif
namespace cspot {

View File

@@ -1,20 +1,14 @@
#pragma once
#include <algorithm>
#include <climits>
#include <functional>
#include <memory>
#include <random>
#include <vector>
#include <cstdint> // for uint8_t
#include <memory> // for unique_ptr
#include <string> // for string
#include <vector> // for vector
#include "Crypto.h"
#include "Logger.h"
#include "NanoPBHelper.h"
#include "Utils.h"
#include "protobuf/authentication.pb.h"
#include "protobuf/keyexchange.pb.h"
#include "Crypto.h" // for Crypto
#include "protobuf/authentication.pb.h" // for ClientResponseEncrypted
#include "protobuf/keyexchange.pb.h" // for APResponseMessage, ClientHello
namespace cspot {
class AuthChallenges {

View File

@@ -1,16 +1,20 @@
#pragma once
#include <cstddef>
#include <memory>
#include "Crypto.h"
#include "WrappedSemaphore.h"
#include <cstddef> // for size_t
#include <cstdint> // for uint8_t
#include <memory> // for shared_ptr, unique_ptr
#include <string> // for string
#include <vector> // for vector
#include "Logger.h"
#include "Utils.h"
#include "CSpotContext.h"
#include "AccessKeyFetcher.h"
#include "Crypto.h" // for Crypto
#include "HTTPClient.h" // for HTTPClient
namespace bell {
class WrappedSemaphore;
} // namespace bell
namespace cspot {
class AccessKeyFetcher;
class CDNTrackStream {

View File

@@ -5,6 +5,7 @@
#include "MercurySession.h"
#include "TimeProvider.h"
#include "protobuf/metadata.pb.h"
#include "LoginBlob.h"
namespace cspot {
struct Context {

View File

@@ -1,17 +1,12 @@
#pragma once
#include <iostream>
#include <map>
#include <memory>
#ifndef BELL_ONLY_CJSON
#include <nlohmann/json.hpp>
#endif
#include <vector>
#include <cstdint> // for uint8_t, uint32_t
#include <map> // for map
#include <memory> // for unique_ptr
#include <string> // for string
#include <vector> // for vector
#include "ConstantParameters.h"
#include "Crypto.h"
#include "protobuf/authentication.pb.h"
#include "Crypto.h" // for CryptoMbedTLS, Crypto
namespace cspot {
class LoginBlob {

View File

@@ -1,22 +1,23 @@
#pragma once
#include <atomic>
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "BellTask.h"
#include "Logger.h"
#include "NanoPBHelper.h"
#include "Packet.h"
#include "Queue.h"
#include "Session.h"
#include "TimeProvider.h"
#include "Utils.h"
#include "protobuf/mercury.pb.h"
#include <atomic> // for atomic
#include <cstdint> // for uint8_t, uint64_t, uint32_t
#include <functional> // for function
#include <memory> // for shared_ptr
#include <mutex> // for mutex
#include <string> // for string
#include <unordered_map> // for unordered_map
#include <vector> // for vector
#include "BellTask.h" // for Task
#include "Packet.h" // for Packet
#include "Queue.h" // for Queue
#include "Session.h" // for Session
#include "protobuf/mercury.pb.h" // for Header
namespace cspot {
class TimeProvider;
class MercurySession : public bell::Task, public cspot::Session {
public:
MercurySession(std::shared_ptr<cspot::TimeProvider> timeProvider);

View File

@@ -3,19 +3,15 @@
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include "win32shim.h"
#else
#include <netdb.h>
#include <unistd.h>
#include "sys/socket.h"
#include <netinet/in.h>
#include <unistd.h> // for size_t
#endif
#include <cstdint>
#include <functional>
#include <string>
#include <vector>
#include "Packet.h"
#include "Utils.h"
#include <cstdint> // for uint8_t
#include <functional> // for function
#include <string> // for string
#include <vector> // for vector
typedef std::function<bool()> timeoutCallback;

View File

@@ -1,18 +1,14 @@
#pragma once
#include <NanoPBHelper.h>
#include <memory>
#include <string>
#include <vector>
#include "CSpotContext.h"
#include "ConstantParameters.h"
#include "CspotAssert.h"
#include "TimeProvider.h"
#include "Utils.h"
#include <stdint.h> // for uint8_t, uint32_t
#include <memory> // for shared_ptr
#include <string> // for string
#include <vector> // for vector
#include "protobuf/spirc.pb.h"
#include "protobuf/spirc.pb.h" // for Frame, TrackRef, CapabilityType, Mess...
namespace cspot {
struct Context;
class PlaybackState {
private:

View File

@@ -1,20 +1,16 @@
#pragma once
#include <algorithm>
#include <functional>
#include <memory>
#include <vector>
#include <stdint.h> // for uint8_t
#include <memory> // for shared_ptr, unique_ptr
#include <string> // for string
#include <vector> // for vector
#include "ApResolve.h"
#include "AuthChallenges.h"
#include "ConstantParameters.h"
#include "Logger.h"
#include "LoginBlob.h"
#include "Packet.h"
#include "PlainConnection.h"
#include "ShannonConnection.h"
#include "Utils.h"
#include "protobuf/mercury.pb.h"
namespace cspot {
class AuthChallenges;
class LoginBlob;
class PlainConnection;
class ShannonConnection;
} // namespace cspot
#define LOGIN_REQUEST_COMMAND 0xAB
#define AUTH_SUCCESSFUL_COMMAND 0xAC

View File

@@ -1,8 +1,9 @@
#ifndef SHANNON_H
#define SHANNON_H
#include <cstdint>
#include <vector>
#include <cstdint> // for uint32_t, uint8_t
#include <vector> // for vector
class Shannon
{
public:

View File

@@ -1,18 +1,18 @@
#ifndef SHANNONCONNECTION_H
#define SHANNONCONNECTION_H
#include <sys/types.h>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include <cstdint> // for uint8_t, uint32_t
#include <memory> // for shared_ptr, unique_ptr
#include <mutex> // for mutex
#include <vector> // for vector
#include "Packet.h"
#include "PlainConnection.h"
#include "Shannon.h"
#include <mutex>
#include "Utils.h"
#include "Logger.h"
#include "Packet.h" // for Packet
class Shannon;
namespace cspot {
class PlainConnection;
} // namespace cspot
#define MAC_SIZE 4
namespace cspot {

View File

@@ -1,16 +1,20 @@
#pragma once
#include <memory>
#include "BellTask.h"
#include <stdint.h> // for uint32_t, uint8_t
#include <functional> // for function
#include <memory> // for shared_ptr, unique_ptr
#include <string> // for string
#include <variant> // for variant
#include <vector> // for vector
#include "CDNTrackStream.h"
#include "CSpotContext.h"
#include "PlaybackState.h"
#include "TrackPlayer.h"
#include "TrackProvider.h"
#include "protobuf/spirc.pb.h"
#include "CDNTrackStream.h" // for CDNTrackStream, CDNTrackStream::Track...
#include "PlaybackState.h" // for PlaybackState
#include "protobuf/spirc.pb.h" // for MessageType
namespace cspot {
class TrackPlayer;
struct Context;
class SpircHandler {
public:
SpircHandler(std::shared_ptr<cspot::Context> ctx);

View File

@@ -1,9 +1,7 @@
#pragma once
#include <stdint.h>
#include <vector>
#include "Utils.h"
#include <stdint.h> // for uint8_t
#include <vector> // for vector
namespace cspot {
class TimeProvider {

View File

@@ -1,22 +1,31 @@
#pragma once
#include <functional>
#include <memory>
#include <mutex>
#include <atomic>
#include <BellUtils.h>
#include <WrappedSemaphore.h>
#include "CDNTrackStream.h"
#include "CSpotContext.h"
#include "TrackProvider.h"
#include "TrackReference.h"
#include <atomic> // for atomic
#include <cstdint> // for uint8_t, int64_t
#include <ctime> // for size_t, time
#include <functional> // for function
#include <memory> // for shared_ptr, unique_ptr
#include <mutex> // for mutex
#include <string_view> // for string_view
#include <vector> // for vector
#include "BellTask.h" // for Task
#include "CDNTrackStream.h" // for CDNTrackStream, CDNTrackStream::TrackInfo
namespace bell {
class WrappedSemaphore;
} // namespace bell
#ifdef BELL_VORBIS_FLOAT
#include "vorbis/vorbisfile.h"
#else
#include "ivorbisfile.h"
#include "ivorbisfile.h" // for OggVorbis_File, ov_callbacks
#endif
namespace cspot {
class TrackProvider;
struct Context;
struct TrackReference;
class TrackPlayer : bell::Task {
public:
typedef std::function<void()> TrackLoadedCallback;

View File

@@ -1,15 +1,18 @@
#pragma once
#include <memory>
#include <stdint.h> // for uint8_t
#include <memory> // for shared_ptr, unique_ptr, weak_ptr
#include <vector> // for vector
#include "AccessKeyFetcher.h"
#include "CDNTrackStream.h"
#include "CSpotContext.h"
#include "TrackReference.h"
#include "protobuf/metadata.pb.h"
#include "protobuf/spirc.pb.h"
#include "MercurySession.h" // for MercurySession
#include "TrackReference.h" // for TrackReference
#include "protobuf/metadata.pb.h" // for Episode, Restriction, Track
namespace cspot {
class AccessKeyFetcher;
class CDNTrackStream;
struct Context;
class TrackProvider {
public:
TrackProvider(std::shared_ptr<cspot::Context> ctx);
@@ -23,11 +26,13 @@ class TrackProvider {
std::unique_ptr<cspot::CDNTrackStream> cdnStream;
Track trackInfo;
Episode episodeInfo;
std::weak_ptr<CDNTrackStream> currentTrackReference;
TrackReference trackIdInfo;
void queryMetadata();
void onMetadataResponse(MercurySession::Response& res);
bool doRestrictionsApply(Restriction* restrictions, int count);
void fetchFile(const std::vector<uint8_t>& fileId,
const std::vector<uint8_t>& trackId);
bool canPlayTrack(int index);

View File

@@ -28,12 +28,11 @@ struct TrackReference {
// Episode GID is being fetched via base62 encoded URI
auto uri = std::string(ref->uri);
auto idString = uri.substr(uri.find_last_of(":") + 1, uri.size());
trackRef.gid = {0};
std::string_view alphabet(base62Alphabet);
for (int x = 0; x < uri.size(); x++) {
size_t d = alphabet.find(uri[x]);
for (int x = 0; x < idString.size(); x++) {
size_t d = alphabet.find(idString[x]);
trackRef.gid = bigNumMultiply(trackRef.gid, 62);
trackRef.gid = bigNumAdd(trackRef.gid, d);
}

View File

@@ -1,27 +1,20 @@
#ifndef UTILS_H
#define UTILS_H
#include <vector>
#include <cstdio> // for snprintf, size_t
#include <vector> // for vector
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include "win32shim.h"
#else
#include <unistd.h>
#include "sys/socket.h"
#include <netdb.h>
#include <netinet/in.h>
#endif
#include <cstdint>
#include <cstring>
#include <memory>
#include <chrono>
#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <memory>
#include <string>
#include <stdexcept>
#include <cstdint> // for uint8_t, uint64_t
#include <cstring> // for memcpy
#include <memory> // for unique_ptr
#include <stdexcept> // for runtime_error
#include <string> // for string
#define HMAC_SHA1_BLOCKSIZE 64

View File

@@ -12,7 +12,8 @@ Album.name type: FT_POINTER
Episode.gid type: FT_POINTER
Episode.name type: FT_POINTER
ImageGroup.image type: FT_POINTER
Episode.audio type: FT_POINTER
Episode.file type: FT_POINTER
Episode.restriction type: FT_POINTER
Episode.covers type: FT_POINTER
Restriction.countries_allowed type: FT_POINTER
Restriction.countries_forbidden type: FT_POINTER

View File

@@ -44,7 +44,8 @@ message Episode {
optional bytes gid = 1;
optional string name = 2;
optional sint32 duration = 7;
repeated AudioFile audio = 12;
repeated AudioFile file = 12;
repeated Restriction restriction = 0x4B;
optional ImageGroup covers = 0x44;
}

View File

@@ -1,7 +1,24 @@
#include "AccessKeyFetcher.h"
#include <cstring>
#include "Logger.h"
#include "Utils.h"
#include <cstring> // for strrchr
#include <initializer_list> // for initializer_list
#include <map> // for operator!=, operator==
#include <type_traits> // for remove_extent_t
#include <vector> // for vector
#include "BellLogger.h" // for AbstractLogger
#include "CSpotContext.h" // for Context
#include "Logger.h" // for CSPOT_LOG
#include "MercurySession.h" // for MercurySession, MercurySession::Res...
#include "Packet.h" // for cspot
#include "TimeProvider.h" // for TimeProvider
#include "Utils.h" // for string_format
#ifdef BELL_ONLY_CJSON
#include "cJSON.h"
#else
#include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json
#include "nlohmann/json_fwd.hpp" // for json
#endif
using namespace cspot;
@@ -38,18 +55,21 @@ void AccessKeyFetcher::getAccessKey(AccessKeyFetcher::Callback callback) {
ctx->session->execute(
MercurySession::RequestType::GET, url,
[this, timeProvider, callback](MercurySession::Response& res) {
if (res.fail) return;
if (res.fail)
return;
char* accessKeyJson = (char*)res.parts[0].data();
auto accessJSON = std::string(accessKeyJson, strrchr(accessKeyJson, '}') - accessKeyJson + 1);
auto accessJSON = std::string(
accessKeyJson, strrchr(accessKeyJson, '}') - accessKeyJson + 1);
#ifdef BELL_ONLY_CJSON
cJSON* jsonBody = cJSON_Parse(accessJSON.c_str());
this->accessKey = cJSON_GetObjectItem(jsonBody, "accessToken")->valuestring;
this->accessKey =
cJSON_GetObjectItem(jsonBody, "accessToken")->valuestring;
int expiresIn = cJSON_GetObjectItem(jsonBody, "expiresIn")->valueint;
#else
auto jsonBody = nlohmann::json::parse(accessJSON);
this->accessKey = jsonBody["accessToken"];
int expiresIn = jsonBody["expiresIn"];
#endif
#endif
expiresIn = expiresIn / 2; // Refresh token before it expires
this->expiresAt =
@@ -57,8 +77,8 @@ void AccessKeyFetcher::getAccessKey(AccessKeyFetcher::Callback callback) {
#ifdef BELL_ONLY_CJSON
callback(cJSON_GetObjectItem(jsonBody, "accessToken")->valuestring);
cJSON_Delete(jsonBody);
#else
#else
callback(jsonBody["accessToken"]);
#endif
#endif
});
}

View File

@@ -1,5 +1,19 @@
#include "ApResolve.h"
#include <initializer_list> // for initializer_list
#include <map> // for operator!=, operator==
#include <memory> // for allocator, unique_ptr
#include <string_view> // for string_view
#include <vector> // for vector
#include "HTTPClient.h" // for HTTPClient, HTTPClient::Response
#ifdef BELL_ONLY_CJSON
#include "cJSON.h"
#else
#include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json
#include "nlohmann/json_fwd.hpp" // for json
#endif
using namespace cspot;
ApResolve::ApResolve(std::string apOverride)
@@ -18,7 +32,7 @@ std::string ApResolve::fetchFirstApAddress()
std::string_view responseStr = request->body();
// parse json with nlohmann
#if BELL_ONLY_CJSON
#ifdef BELL_ONLY_CJSON
cJSON* json = cJSON_Parse(responseStr.data());
auto ap_string = std::string(cJSON_GetArrayItem(cJSON_GetObjectItem(json, "ap_list"), 0)->valuestring);
cJSON_Delete(json);

View File

@@ -1,5 +1,13 @@
#include "AuthChallenges.h"
#include <algorithm> // for copy
#include <climits> // for CHAR_BIT
#include <random> // for default_random_engine, independent_bits_en...
#include "NanoPBHelper.h" // for pbPutString, pbEncode, pbDecode
#include "pb.h" // for pb_byte_t
#include "pb_decode.h" // for pb_release
using namespace cspot;
using random_bytes_engine =
std::independent_bits_engine<std::default_random_engine, CHAR_BIT, uint8_t>;

View File

@@ -1,5 +1,26 @@
#include "CDNTrackStream.h"
#include <string.h> // for memcpy
#include <functional> // for __base
#include <initializer_list> // for initializer_list
#include <map> // for operator!=, operator==
#include <string_view> // for string_view
#include <type_traits> // for remove_extent_t
#include "AccessKeyFetcher.h" // for AccessKeyFetcher
#include "BellLogger.h" // for AbstractLogger
#include "Logger.h" // for CSPOT_LOG
#include "Packet.h" // for cspot
#include "SocketStream.h" // for SocketStream
#include "Utils.h" // for bigNumAdd, bytesToHexString, string...
#include "WrappedSemaphore.h" // for WrappedSemaphore
#ifdef BELL_ONLY_CJSON
#include "cJSON.h"
#else
#include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json
#include "nlohmann/json_fwd.hpp" // for json
#endif
using namespace cspot;
CDNTrackStream::CDNTrackStream(
@@ -10,8 +31,7 @@ CDNTrackStream::CDNTrackStream(
this->crypto = std::make_unique<Crypto>();
}
CDNTrackStream::~CDNTrackStream() {
}
CDNTrackStream::~CDNTrackStream() {}
void CDNTrackStream::fail() {
this->status = Status::FAILED;
@@ -40,7 +60,9 @@ void CDNTrackStream::fetchFile(const std::vector<uint8_t>& trackId,
#ifdef BELL_ONLY_CJSON
cJSON* jsonResult = cJSON_Parse(result.data());
std::string cdnUrl = cJSON_GetArrayItem(cJSON_GetObjectItem(jsonResult, "cdnurl"), 0)->valuestring;
std::string cdnUrl =
cJSON_GetArrayItem(cJSON_GetObjectItem(jsonResult, "cdnurl"), 0)
->valuestring;
cJSON_Delete(jsonResult);
#else
auto jsonResult = nlohmann::json::parse(result);

View File

@@ -1,8 +1,18 @@
#include "LoginBlob.h"
#include "ConstantParameters.h"
#include "Logger.h"
#include <stdio.h> // for sprintf
#include <initializer_list> // for initializer_list
#include "BellLogger.h" // for AbstractLogger
#include "ConstantParameters.h" // for brandName, cspot, protoc...
#include "Logger.h" // for CSPOT_LOG
#include "protobuf/authentication.pb.h" // for AuthenticationType_AUTHE...
#ifdef BELL_ONLY_CJSON
#include "cJSON.h"
#else
#include "nlohmann/detail/json_pointer.hpp" // for json_pointer<>::string_t
#include "nlohmann/json.hpp" // for basic_json<>::object_t
#include "nlohmann/json_fwd.hpp" // for json
#endif
using namespace cspot;

View File

@@ -1,11 +1,25 @@
#include "MercurySession.h"
#include <memory>
#include <mutex>
#include "BellLogger.h"
#include "BellTask.h"
#include "BellUtils.h"
#include "CSpotContext.h"
#include "Logger.h"
#include <string.h> // for memcpy
#include <memory> // for shared_ptr
#include <mutex> // for scoped_lock
#include <stdexcept> // for runtime_error
#include <type_traits> // for remove_extent_t, __underlying_type_impl<>:...
#include <utility> // for pair
#ifndef _WIN32
#include <arpa/inet.h>
#endif
#include "BellLogger.h" // for AbstractLogger
#include "BellTask.h" // for Task
#include "BellUtils.h" // for BELL_SLEEP_MS
#include "Logger.h" // for CSPOT_LOG
#include "NanoPBHelper.h" // for pbPutString, pbDecode, pbEncode
#include "PlainConnection.h" // for PlainConnection
#include "ShannonConnection.h" // for ShannonConnection
#include "TimeProvider.h" // for TimeProvider
#include "Utils.h" // for extract, pack, hton64
using namespace cspot;
@@ -120,9 +134,9 @@ void MercurySession::handlePacket() {
switch (static_cast<RequestType>(packet.command)) {
case RequestType::COUNTRY_CODE_RESPONSE: {
this->countryCode = std::string();
this->countryCode.reserve(2);
this->countryCode.resize(2);
memcpy(this->countryCode.data(), packet.data.data(), 2);
CSPOT_LOG(debug, "Received country code");
CSPOT_LOG(debug, "Received country code %s", this->countryCode.c_str());
break;
}
case RequestType::AUDIO_KEY_FAILURE_RESPONSE:
@@ -167,7 +181,7 @@ void MercurySession::handlePacket() {
}
void MercurySession::failAllPending() {
Response response = { };
Response response = {};
response.fail = true;
// Fail all callbacks

View File

@@ -1,12 +1,24 @@
#include "PlainConnection.h"
#include <cstring>
#ifndef _WIN32
#include <netdb.h> // for addrinfo, freeaddrinfo, getaddrinfo
#include <netinet/in.h> // for IPPROTO_IP, IPPROTO_TCP
#include <sys/errno.h> // for EAGAIN, EINTR, ETIMEDOUT, errno
#include <sys/socket.h> // for setsockopt, connect, recv, send, shutdown
#include <sys/time.h> // for timeval
#endif
#include <cstring> // for memset
#include <stdexcept> // for runtime_error
#ifdef _WIN32
#include <ws2tcpip.h>
#else
#include <netinet/tcp.h>
#include <netinet/tcp.h> // for TCP_NODELAY
#include <arpa/inet.h>
#endif
#include <errno.h>
#include "Logger.h"
#include "BellLogger.h" // for AbstractLogger
#include "Logger.h" // for CSPOT_LOG
#include "Packet.h" // for cspot
#include "Utils.h" // for extract, pack
using namespace cspot;

View File

@@ -1,7 +1,20 @@
#include "PlaybackState.h"
#include <memory>
#include "CSpotContext.h"
#include "Logger.h"
#include <string.h> // for strdup, memcpy, strcpy, strlen
#include <cstdint> // for uint8_t
#include <cstdlib> // for free, NULL, realloc, rand
#include <memory> // for shared_ptr
#include <type_traits> // for remove_extent_t
#include <utility> // for swap
#include "BellLogger.h" // for AbstractLogger
#include "CSpotContext.h" // for Context::ConfigState, Context (ptr o...
#include "ConstantParameters.h" // for protocolVersion, swVersion
#include "Logger.h" // for CSPOT_LOG
#include "NanoPBHelper.h" // for pbEncode, pbPutString
#include "Packet.h" // for cspot
#include "pb.h" // for pb_bytes_array_t, PB_BYTES_ARRAY_T_A...
#include "pb_decode.h" // for pb_release
using namespace cspot;

View File

@@ -1,6 +1,21 @@
#include "Session.h"
#include <memory>
#include "AuthChallenges.h"
#include <limits.h> // for CHAR_BIT
#include <cstdint> // for uint8_t
#include <functional> // for __base
#include <memory> // for shared_ptr, unique_ptr, make_unique
#include <random> // for default_random_engine, independent_bi...
#include <type_traits> // for remove_extent_t
#include <utility> // for move
#include "ApResolve.h" // for ApResolve, cspot
#include "AuthChallenges.h" // for AuthChallenges
#include "BellLogger.h" // for AbstractLogger
#include "Logger.h" // for CSPOT_LOG
#include "LoginBlob.h" // for LoginBlob
#include "Packet.h" // for Packet
#include "PlainConnection.h" // for PlainConnection, timeoutCallback
#include "ShannonConnection.h" // for ShannonConnection
using random_bytes_engine =
std::independent_bits_engine<std::default_random_engine, CHAR_BIT, uint8_t>;

View File

@@ -1,9 +1,7 @@
#include "Shannon.h"
// #include <bit>
#include <stdint.h> // for uint32_t
#include <limits.h> // for CHAR_BIT
// #define NDEBUG
#include <assert.h>
#include <limits.h> // for CHAR_BIT
#include <stddef.h> // for size_t
using std::size_t;

View File

@@ -1,5 +1,17 @@
#include "ShannonConnection.h"
#include "Packet.h"
#include <type_traits> // for remove_extent_t
#ifndef _WIN32
#include <arpa/inet.h>
#endif
#include "BellLogger.h" // for AbstractLogger
#include "Logger.h" // for CSPOT_LOG
#include "Packet.h" // for Packet, cspot
#include "PlainConnection.h" // for PlainConnection
#include "Shannon.h" // for Shannon
#include "Utils.h" // for pack, extract
using namespace cspot;

View File

@@ -1,14 +1,22 @@
#include "SpircHandler.h"
#include <memory>
#include "AccessKeyFetcher.h"
#include "BellUtils.h"
#include "CSpotContext.h"
#include "Logger.h"
#include "MercurySession.h"
#include "PlaybackState.h"
#include "TrackPlayer.h"
#include "TrackReference.h"
#include "protobuf/spirc.pb.h"
#include <cstdint> // for uint8_t
#include <memory> // for shared_ptr, make_unique, unique_ptr
#include <type_traits> // for remove_extent_t
#include <utility> // for move
#include "BellLogger.h" // for AbstractLogger
#include "CSpotContext.h" // for Context::ConfigState, Context (ptr only)
#include "Logger.h" // for CSPOT_LOG
#include "MercurySession.h" // for MercurySession, MercurySession::Response
#include "NanoPBHelper.h" // for pbDecode
#include "Packet.h" // for cspot
#include "PlaybackState.h" // for PlaybackState, PlaybackState::State
#include "TrackPlayer.h" // for TrackPlayer
#include "TrackReference.h" // for TrackReference
#include "Utils.h" // for stringHexToBytes
#include "pb_decode.h" // for pb_release
#include "protobuf/spirc.pb.h" // for Frame, State, Frame_fields, MessageTy...
using namespace cspot;
@@ -155,9 +163,9 @@ void SpircHandler::handleFrame(std::vector<uint8_t>& data) {
* when last track has been reached, we has to restart as we can't tell the difference */
if ((!isNextTrackPreloaded && this->playbackState.getNextTrackRef()) || isRequestedFromLoad) {
CSPOT_LOG(debug, "Seek command while streaming current");
sendEvent(EventType::SEEK, (int)playbackState.remoteFrame.position);
playbackState.updatePositionMs(playbackState.remoteFrame.position);
trackPlayer->seekMs(playbackState.remoteFrame.position);
sendEvent(EventType::SEEK, (int)playbackState.remoteFrame.position);
} else {
CSPOT_LOG(debug, "Seek command while streaming next or before started");
isRequestedFromLoad = true;

View File

@@ -1,5 +1,12 @@
#include "TimeProvider.h"
#include "Logger.h"
#ifndef _WIN32
#include <arpa/inet.h>
#endif
#include "BellLogger.h" // for AbstractLogger
#include "Logger.h" // for CSPOT_LOG
#include "Utils.h" // for extract, getCurrentTimestamp
using namespace cspot;

View File

@@ -1,12 +1,22 @@
#include "TrackPlayer.h"
#include <cstddef>
#include <fstream>
#include <memory>
#include <mutex>
#include <vector>
#include "CDNTrackStream.h"
#include "Logger.h"
#include "TrackReference.h"
#include <mutex> // for mutex, scoped_lock
#include <string> // for string
#include <type_traits> // for remove_extent_t
#include <vector> // for vector, vector<>::value_type
#include "BellLogger.h" // for AbstractLogger
#include "BellUtils.h" // for BELL_SLEEP_MS
#include "CDNTrackStream.h" // for CDNTrackStream, CDNTrackStream::TrackInfo
#include "Logger.h" // for CSPOT_LOG
#include "Packet.h" // for cspot
#include "TrackProvider.h" // for TrackProvider
#include "WrappedSemaphore.h" // for WrappedSemaphore
namespace cspot {
struct Context;
struct TrackReference;
} // namespace cspot
using namespace cspot;

View File

@@ -1,12 +1,26 @@
#include "TrackProvider.h"
#include <memory>
#include "AccessKeyFetcher.h"
#include "CDNTrackStream.h"
#include "Logger.h"
#include "MercurySession.h"
#include "TrackReference.h"
#include "Utils.h"
#include "protobuf/metadata.pb.h"
#include <assert.h> // for assert
#include <string.h> // for strlen
#include <cstdint> // for uint8_t
#include <functional> // for __base
#include <memory> // for shared_ptr, weak_ptr, make_shared
#include <string> // for string, operator+
#include <type_traits> // for remove_extent_t
#include "AccessKeyFetcher.h" // for AccessKeyFetcher
#include "BellLogger.h" // for AbstractLogger
#include "CDNTrackStream.h" // for CDNTrackStream, CDNTrackStream::Tr...
#include "CSpotContext.h" // for Context::ConfigState, Context (ptr...
#include "Logger.h" // for CSPOT_LOG
#include "MercurySession.h" // for MercurySession, MercurySession::Da...
#include "NanoPBHelper.h" // for pbArrayToVector, pbDecode
#include "Packet.h" // for cspot
#include "TrackReference.h" // for TrackReference, TrackReference::Type
#include "Utils.h" // for bytesToHexString, string_format
#include "WrappedSemaphore.h" // for WrappedSemaphore
#include "pb_decode.h" // for pb_release
#include "protobuf/metadata.pb.h" // for Track, _Track, AudioFile, Episode
using namespace cspot;
@@ -21,9 +35,11 @@ TrackProvider::TrackProvider(std::shared_ptr<cspot::Context> ctx) {
TrackProvider::~TrackProvider() {
pb_release(Track_fields, &trackInfo);
pb_release(Episode_fields, &trackInfo);
}
std::shared_ptr<cspot::CDNTrackStream> TrackProvider::loadFromTrackRef(TrackReference& trackRef) {
std::shared_ptr<cspot::CDNTrackStream> TrackProvider::loadFromTrackRef(
TrackReference& trackRef) {
auto track = std::make_shared<cspot::CDNTrackStream>(this->accessKeyFetcher);
this->currentTrackReference = track;
this->trackIdInfo = trackRef;
@@ -34,7 +50,8 @@ std::shared_ptr<cspot::CDNTrackStream> TrackProvider::loadFromTrackRef(TrackRefe
void TrackProvider::queryMetadata() {
std::string requestUrl = string_format(
"hm://metadata/3/%s/%s", trackIdInfo.type == TrackReference::Type::TRACK ? "track" : "episode",
"hm://metadata/3/%s/%s",
trackIdInfo.type == TrackReference::Type::TRACK ? "track" : "episode",
bytesToHexString(trackIdInfo.gid).c_str());
CSPOT_LOG(debug, "Requesting track metadata from %s", requestUrl.c_str());
@@ -50,55 +67,40 @@ void TrackProvider::queryMetadata() {
void TrackProvider::onMetadataResponse(MercurySession::Response& res) {
CSPOT_LOG(debug, "Got track metadata response");
pb_release(Track_fields, &trackInfo);
pbDecode(trackInfo, Track_fields, res.parts[0]);
int alternativeCount, filesCount = 0;
bool canPlay = false;
AudioFile* selectedFiles;
std::vector<uint8_t> trackId, fileId;
CSPOT_LOG(info, "Track name: %s", trackInfo.name);
CSPOT_LOG(info, "Track duration: %d", trackInfo.duration);
if (trackIdInfo.type == TrackReference::Type::TRACK) {
pb_release(Track_fields, &trackInfo);
assert(res.parts.size() > 0);
pbDecode(trackInfo, Track_fields, res.parts[0]);
CSPOT_LOG(info, "Track name: %s", trackInfo.name);
CSPOT_LOG(info, "Track duration: %d", trackInfo.duration);
CSPOT_LOG(debug, "trackInfo.restriction.size() = %d",
trackInfo.restriction_count);
CSPOT_LOG(debug, "trackInfo.restriction.size() = %d",
trackInfo.restriction_count);
int altIndex = -1;
while (!canPlayTrack(altIndex)) {
altIndex++;
CSPOT_LOG(info, "Trying alternative %d", altIndex);
if (altIndex >= trackInfo.alternative_count) {
// no alternatives for song
if (!this->currentTrackReference.expired()) {
auto trackRef = this->currentTrackReference.lock();
trackRef->status = CDNTrackStream::Status::FAILED;
trackRef->trackReady->give();
if (doRestrictionsApply(trackInfo.restriction,
trackInfo.restriction_count)) {
// Go through alternatives
for (int x = 0; x < trackInfo.alternative_count; x++) {
if (!doRestrictionsApply(trackInfo.alternative[x].restriction,
trackInfo.alternative[x].restriction_count)) {
selectedFiles = trackInfo.alternative[x].file;
filesCount = trackInfo.alternative[x].file_count;
trackId = pbArrayToVector(trackInfo.alternative[x].gid);
break;
}
}
return;
} else {
selectedFiles = trackInfo.file;
filesCount = trackInfo.file_count;
trackId = pbArrayToVector(trackInfo.gid);
}
}
std::vector<uint8_t> trackId;
std::vector<uint8_t> fileId;
AudioFormat format = AudioFormat_OGG_VORBIS_160;
if (altIndex < 0) {
trackId = pbArrayToVector(trackInfo.gid);
for (int x = 0; x < trackInfo.file_count; x++) {
if (trackInfo.file[x].format == format) {
fileId = pbArrayToVector(trackInfo.file[x].file_id);
break; // If file found stop searching
}
}
} else {
trackId = pbArrayToVector(trackInfo.alternative[altIndex].gid);
for (int x = 0; x < trackInfo.alternative[altIndex].file_count; x++) {
if (trackInfo.alternative[altIndex].file[x].format == format) {
fileId =
pbArrayToVector(trackInfo.alternative[altIndex].file[x].file_id);
break; // If file found stop searching
}
}
}
if (!this->currentTrackReference.expired()) {
// Set track's metadata
auto trackRef = this->currentTrackReference.lock();
auto imageId =
@@ -111,6 +113,60 @@ void TrackProvider::onMetadataResponse(MercurySession::Response& res) {
trackRef->trackInfo.imageUrl =
"https://i.scdn.co/image/" + bytesToHexString(imageId);
trackRef->trackInfo.duration = trackInfo.duration;
} else {
pb_release(Episode_fields, &episodeInfo);
assert(res.parts.size() > 0);
pbDecode(episodeInfo, Episode_fields, res.parts[0]);
CSPOT_LOG(info, "Episode name: %s", episodeInfo.name);
CSPOT_LOG(info, "Episode duration: %d", episodeInfo.duration);
CSPOT_LOG(debug, "episodeInfo.restriction.size() = %d",
episodeInfo.restriction_count);
if (!doRestrictionsApply(episodeInfo.restriction,
episodeInfo.restriction_count)) {
selectedFiles = episodeInfo.file;
filesCount = episodeInfo.file_count;
trackId = pbArrayToVector(episodeInfo.gid);
}
auto trackRef = this->currentTrackReference.lock();
auto imageId = pbArrayToVector(episodeInfo.covers->image[0].file_id);
trackRef->trackInfo.trackId = bytesToHexString(trackIdInfo.gid);
trackRef->trackInfo.name = std::string(episodeInfo.name);
trackRef->trackInfo.album = "";
trackRef->trackInfo.artist = "",
trackRef->trackInfo.imageUrl =
"https://i.scdn.co/image/" + bytesToHexString(imageId);
trackRef->trackInfo.duration = episodeInfo.duration;
}
for (int x = 0; x < filesCount; x++) {
CSPOT_LOG(debug, "File format: %d", selectedFiles[x].format);
if (selectedFiles[x].format == ctx->config.audioFormat) {
fileId = pbArrayToVector(selectedFiles[x].file_id);
break; // If file found stop searching
}
// Fallback to OGG Vorbis 96kbps
if (fileId.size() == 0 &&
selectedFiles[x].format == AudioFormat_OGG_VORBIS_96) {
fileId = pbArrayToVector(selectedFiles[x].file_id);
}
}
// No viable files found for playback
if (fileId.size() == 0) {
CSPOT_LOG(info, "File not available for playback");
// no alternatives for song
if (!this->currentTrackReference.expired()) {
auto trackRef = this->currentTrackReference.lock();
trackRef->status = CDNTrackStream::Status::FAILED;
trackRef->trackReady->give();
}
return;
}
this->fetchFile(fileId, trackId);
@@ -148,20 +204,25 @@ bool countryListContains(char* countryList, char* country) {
return false;
}
bool TrackProvider::doRestrictionsApply(Restriction* restrictions, int count) {
for (int x = 0; x < count; x++) {
if (restrictions[x].countries_allowed != nullptr) {
return !countryListContains(restrictions[x].countries_allowed,
(char*)ctx->config.countryCode.c_str());
}
if (restrictions[x].countries_forbidden != nullptr) {
return countryListContains(restrictions[x].countries_forbidden,
(char*)ctx->config.countryCode.c_str());
}
}
return false;
}
bool TrackProvider::canPlayTrack(int altIndex) {
if (altIndex < 0) {
for (int x = 0; x < trackInfo.restriction_count; x++) {
if (trackInfo.restriction[x].countries_allowed != nullptr) {
return countryListContains(trackInfo.restriction[x].countries_allowed,
(char*)ctx->config.countryCode.c_str());
}
if (trackInfo.restriction[x].countries_forbidden != nullptr) {
return !countryListContains(
trackInfo.restriction[x].countries_forbidden,
(char*)ctx->config.countryCode.c_str());
}
}
} else {
for (int x = 0; x < trackInfo.alternative[altIndex].restriction_count;
x++) {

View File

@@ -1,168 +1,154 @@
#include "Utils.h"
#include <cstring>
#include <memory>
#include <chrono>
#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>
unsigned long long getCurrentTimestamp()
{
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
#include <stdlib.h> // for strtol
#include <iomanip> // for operator<<, setfill, setw
#include <iostream> // for basic_ostream, hex
#include <sstream> // for stringstream
#include <string> // for string
#include <type_traits> // for enable_if<>::type
#include <chrono>
#ifndef _WIN32
#include <arpa/inet.h>
#endif
unsigned long long getCurrentTimestamp() {
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
}
uint64_t hton64(uint64_t value) {
int num = 42;
if (*(char *)&num == 42) {
uint32_t high_part = htonl((uint32_t)(value >> 32));
uint32_t low_part = htonl((uint32_t)(value & 0xFFFFFFFFLL));
return (((uint64_t)low_part) << 32) | high_part;
} else {
return value;
}
int num = 42;
if (*(char*)&num == 42) {
uint32_t high_part = htonl((uint32_t)(value >> 32));
uint32_t low_part = htonl((uint32_t)(value & 0xFFFFFFFFLL));
return (((uint64_t)low_part) << 32) | high_part;
} else {
return value;
}
}
std::vector<uint8_t> stringHexToBytes(const std::string & s) {
std::vector<uint8_t> v;
v.reserve(s.length() / 2);
std::vector<uint8_t> stringHexToBytes(const std::string& s) {
std::vector<uint8_t> v;
v.reserve(s.length() / 2);
for (std::string::size_type i = 0; i < s.length(); i += 2) {
std::string byteString = s.substr(i, 2);
uint8_t byte = (uint8_t) strtol(byteString.c_str(), NULL, 16);
v.push_back(byte);
}
for (std::string::size_type i = 0; i < s.length(); i += 2) {
std::string byteString = s.substr(i, 2);
uint8_t byte = (uint8_t)strtol(byteString.c_str(), NULL, 16);
v.push_back(byte);
}
return v;
return v;
}
std::string bytesToHexString(const std::vector<uint8_t>& v) {
std::stringstream ss;
ss << std::hex << std::setfill('0');
std::vector<uint8_t>::const_iterator it;
std::stringstream ss;
ss << std::hex << std::setfill('0');
std::vector<uint8_t>::const_iterator it;
for (it = v.begin(); it != v.end(); it++) {
ss << std::setw(2) << static_cast<unsigned>(*it);
}
for (it = v.begin(); it != v.end(); it++) {
ss << std::setw(2) << static_cast<unsigned>(*it);
}
return ss.str();
return ss.str();
}
std::vector<uint8_t> bigNumAdd(std::vector<uint8_t> num, int n)
{
auto carry = n;
for (int x = num.size() - 1; x >= 0; x--)
{
int res = num[x] + carry;
if (res < 256)
{
carry = 0;
num[x] = res;
}
else
{
// Carry the rest of the division
carry = res / 256;
num[x] = res % 256;
std::vector<uint8_t> bigNumAdd(std::vector<uint8_t> num, int n) {
auto carry = n;
for (int x = num.size() - 1; x >= 0; x--) {
int res = num[x] + carry;
if (res < 256) {
carry = 0;
num[x] = res;
} else {
// Carry the rest of the division
carry = res / 256;
num[x] = res % 256;
// extend the vector at the last index
if (x == 0)
{
num.insert(num.begin(), carry);
return num;
}
}
}
return num;
}
std::vector<uint8_t> bigNumDivide(std::vector<uint8_t> num, int n)
{
auto carry = 0;
for (int x = 0; x < num.size(); x++)
{
int res = num[x] + carry * 256;
if (res < n)
{
carry = res;
num[x] = 0;
}
else
{
// Carry the rest of the division
carry = res % n;
num[x] = res / n;
}
}
return num;
}
std::vector<uint8_t> bigNumMultiply(std::vector<uint8_t> num, int n)
{
auto carry = 0;
for (int x = num.size() - 1; x >= 0; x--)
{
int res = num[x] * n + carry;
if (res < 256)
{
carry = 0;
num[x] = res;
}
else
{
// Carry the rest of the division
carry = res / 256;
num[x] = res % 256;
// extend the vector at the last index
if (x == 0)
{
num.insert(num.begin(), carry);
return num;
}
}
}
return num;
}
unsigned char h2int(char c)
{
if (c >= '0' && c <='9'){
return((unsigned char)c - '0');
}
if (c >= 'a' && c <='f'){
return((unsigned char)c - 'a' + 10);
}
if (c >= 'A' && c <='F'){
return((unsigned char)c - 'A' + 10);
}
return(0);
}
std::string urlDecode(std::string str)
{
std::string encodedString="";
char c;
char code0;
char code1;
for (int i =0; i < str.length(); i++){
c=str[i];
if (c == '+'){
encodedString+=' ';
}else if (c == '%') {
i++;
code0=str[i];
i++;
code1=str[i];
c = (h2int(code0) << 4) | h2int(code1);
encodedString+=c;
} else{
encodedString+=c;
// extend the vector at the last index
if (x == 0) {
num.insert(num.begin(), carry);
return num;
}
}
return encodedString;
}
return num;
}
std::vector<uint8_t> bigNumDivide(std::vector<uint8_t> num, int n) {
auto carry = 0;
for (int x = 0; x < num.size(); x++) {
int res = num[x] + carry * 256;
if (res < n) {
carry = res;
num[x] = 0;
} else {
// Carry the rest of the division
carry = res % n;
num[x] = res / n;
}
}
return num;
}
std::vector<uint8_t> bigNumMultiply(std::vector<uint8_t> num, int n) {
auto carry = 0;
for (int x = num.size() - 1; x >= 0; x--) {
int res = num[x] * n + carry;
if (res < 256) {
carry = 0;
num[x] = res;
} else {
// Carry the rest of the division
carry = res / 256;
num[x] = res % 256;
// extend the vector at the last index
if (x == 0) {
num.insert(num.begin(), carry);
return num;
}
}
}
return num;
}
unsigned char h2int(char c) {
if (c >= '0' && c <= '9') {
return ((unsigned char)c - '0');
}
if (c >= 'a' && c <= 'f') {
return ((unsigned char)c - 'a' + 10);
}
if (c >= 'A' && c <= 'F') {
return ((unsigned char)c - 'A' + 10);
}
return (0);
}
std::string urlDecode(std::string str) {
std::string encodedString = "";
char c;
char code0;
char code1;
for (int i = 0; i < str.length(); i++) {
c = str[i];
if (c == '+') {
encodedString += ' ';
} else if (c == '%') {
i++;
code0 = str[i];
i++;
code1 = str[i];
c = (h2int(code0) << 4) | h2int(code1);
encodedString += c;
} else {
encodedString += c;
}
}
return encodedString;
}

View File

@@ -85,7 +85,7 @@ const static actrls_t controls = {
NULL, NULL, // rew, fwd
cspot_prev, cspot_next, // prev, next
NULL, NULL, NULL, NULL, // left, right, up, down
NULL, NULL, NULL, NULL, NULL, NULL, // pre1-6
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // pre1-10
cspot_volume_down, cspot_volume_up, cspot_toggle// knob left, knob_right, knob push
};

View File

@@ -13,6 +13,7 @@ idf_component_register( SRC_DIRS . external ac101 tas57xx wm8978
display
tools
audio
_override
EMBED_FILES vu_s.data arrow.data
)

View File

@@ -21,7 +21,8 @@ static log_level loglevel = lINFO;
enum { BUTN_POWER_FRONT = 0X0A, BUTN_ARROW_UP, BUTN_ARROW_DOWN, BUTN_ARROW_LEFT, BUTN_KNOB_PUSH, BUTN_SEARCH,
BUTN_REW, BUTN_FWD, BUTN_PLAY, BUTN_ADD, BUTN_BRIGHTNESS, BUTN_NOW_PLAYING,
BUTN_PAUSE = 0X17, BUTN_BROWSE, BUTN_VOLUP_FRONT, BUTN_VOLDOWN_FRONT, BUTN_SIZE, BUTN_VISUAL, BUTN_VOLUMEMODE,
BUTN_PRESET_1 = 0X23, BUTN_PRESET_2, BUTN_PRESET_3, BUTN_PRESET_4, BUTN_PRESET_5, BUTN_PRESET_6, BUTN_SNOOZE,
BUTN_PRESET_0 = 0x22, BUTN_PRESET_1, BUTN_PRESET_2, BUTN_PRESET_3, BUTN_PRESET_4, BUTN_PRESET_5, BUTN_PRESET_6, BUTN_PRESET_7, BUTN_PRESET_8, BUTN_PRESET_9,
BUTN_SNOOZE,
BUTN_KNOB_LEFT = 0X5A, BUTN_KNOB_RIGHT };
#define BUTN_ARROW_RIGHT BUTN_KNOB_PUSH
@@ -142,12 +143,16 @@ LMS_CALLBACK(down, ARROW_DOWN, arrow_down)
LMS_CALLBACK(left, ARROW_LEFT, arrow_left)
LMS_CALLBACK(right, ARROW_RIGHT, arrow_right)
LMS_CALLBACK(pre0, PRESET_0, preset_0.single)
LMS_CALLBACK(pre1, PRESET_1, preset_1.single)
LMS_CALLBACK(pre2, PRESET_2, preset_2.single)
LMS_CALLBACK(pre3, PRESET_3, preset_3.single)
LMS_CALLBACK(pre4, PRESET_4, preset_4.single)
LMS_CALLBACK(pre5, PRESET_5, preset_5.single)
LMS_CALLBACK(pre6, PRESET_6, preset_6.single)
LMS_CALLBACK(pre7, PRESET_7, preset_7.single)
LMS_CALLBACK(pre8, PRESET_8, preset_8.single)
LMS_CALLBACK(pre9, PRESET_9, preset_9.single)
LMS_CALLBACK(knob_left, KNOB_LEFT, knob_left)
LMS_CALLBACK(knob_right, KNOB_RIGHT, knob_right)
@@ -162,7 +167,7 @@ const actrls_t LMS_controls = {
lms_prev, lms_next, // prev, next
lms_up, lms_down,
lms_left, lms_right,
lms_pre1, lms_pre2, lms_pre3, lms_pre4, lms_pre5, lms_pre6,
lms_pre0, lms_pre1, lms_pre2, lms_pre3, lms_pre4, lms_pre5, lms_pre6, lms_pre7, lms_pre8, lms_pre9,
lms_knob_left, lms_knob_right, lms_knob_push,
};

View File

@@ -0,0 +1,561 @@
/*
* Squeezelite for esp32
*
* (c) Sebastien 2019
* Philippe G. 2019, philippe_44@outlook.com
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*
*/
#include <string.h>
//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s.h"
#include "driver/i2c.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "adac.h"
#include "stdio.h"
#include "math.h"
#define CS4265_PULL_UP (0x4F )
#define CS4265_PULL_DOWN (0x4E )
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif
static const char TAG[] = "CS4265";
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config);
static void speaker(bool active);
static void headset(bool active);
static bool volume(unsigned left, unsigned right);
static void power(adac_power_e mode);
static esp_err_t cs4265_update_bit(uint8_t reg_no,uint8_t mask,uint8_t val );
static esp_err_t set_clock();
const struct adac_s dac_cs4265 = { "CS4265", init, adac_deinit, power, speaker, headset, volume };
struct cs4265_cmd_s {
uint8_t reg;
uint8_t value;
};
struct cs4265_private {
uint8_t format;
uint32_t sysclk;
i2s_config_t *i2s_config;
int i2c_port;
};
struct cs4265_private cs4265;
#define CS4265_CHIP_ID 0x1
#define CS4265_CHIP_ID_VAL 0xD0
#define CS4265_CHIP_ID_MASK 0xF0
#define CS4265_REV_ID_MASK 0x0F
#define CS4265_PWRCTL 0x02
#define CS4265_PWRCTL_PDN (1 << 0)
#define CS4265_PWRCTL_PDN_DAC (1 << 1)
#define CS4265_PWRCTL_PDN_ADC (1 << 2)
#define CS4265_PWRCTL_PDN_MIC (1 << 3)
#define CS4265_PWRCTL_FREEZE (1 << 7)
#define CS4265_PWRCTL_PDN_ALL CS4265_PWRCTL_PDN | CS4265_PWRCTL_PDN_ADC | CS4265_PWRCTL_PDN_DAC | CS4265_PWRCTL_PDN_MIC
#define CS4265_DAC_CTL 0x3
// De-Emphasis Control (Bit 1)
// The standard 50/15 i2s digital de-emphasis filter response may be implemented for a sample
// rate of 44.1 kHz when the DeEmph bit is set. NOTE: De-emphasis is available only in Single-Speed Mode.
#define CS4265_DAC_CTL_DEEMPH (1 << 1)
// MUTE DAC
// The DAC outputs will mute and the MUTEC pin will become active when this bit is set. Though this bit is
// active high, it should be noted that the MUTEC pin is active low. The common mode voltage on the outputs
// will be retained when this bit is set. The muting function is effected, similar to attenuation changes, by the
// DACSoft and DACZero bits in the DAC Control 2 register.
#define CS4265_DAC_CTL_MUTE (1 << 2)
// The required relationship between LRCK, SCLK and SDIN for the DAC is defined by the DAC Digital Interface
// DAC_DIF1 DAC_DIF0 Description Format Figure
// 0 0 Left Justified, up to 24-bit data (default) 0 5
// 0 1 I²S, up to 24-bit data 1 6
// 1 0 Right-Justified, 16-bit Data 2 7
// 1 1 Right-Justified, 24-bit Data 3 7
#define CS4265_DAC_CTL_DIF0 (1 << 4)
// The required relationship between LRCK, SCLK and SDIN for the DAC is defined by the DAC Digital Interface
// DAC_DIF1 DAC_DIF0 Description Format Figure
// 0 0 Left Justified, up to 24-bit data (default) 0 5
// 0 1 I²S, up to 24-bit data 1 6
// 1 0 Right-Justified, 16-bit Data 2 7
// 1 1 Right-Justified, 24-bit Data 3 7
#define CS4265_DAC_CTL_DIF1 (1 << 5)
#define CS4265_ADC_CTL 0x4
#define CS4265_ADC_MASTER 1
#define CS4265_ADC_CTL_MUTE (1 << 2)
#define CS4265_ADC_DIF (1 << 4)
#define CS4265_ADC_FM (3 << 6)
//Master Clock Dividers (Bits 6:4)
//Sets the frequency of the supplied MCLK signal.
//
//MCLK Divider MCLK Freq2 MCLK Freq1 MCLK Freq0
// ÷ 1 0 0 0
// ÷ 1.5 0 0 1
// ÷ 2 0 1 0
// ÷ 3 0 1 1
// ÷ 4 1 0 0
// NA 1 0 1
// NA 1 1 x
#define CS4265_MCLK_FREQ 0x5
#define CS4265_MCLK_FREQ_1_0X (0b000<<4 )
#define CS4265_MCLK_FREQ_1_5X (0b001<<4 )
#define CS4265_MCLK_FREQ_2_0X (0b010<<4 )
#define CS4265_MCLK_FREQ_3_0X (0b011<<4 )
#define CS4265_MCLK_FREQ_4_0X (0b100<<4 )
#define CS4265_MCLK_FREQ_MASK (7 << 4)
#define CS4265_SIG_SEL 0x6
#define CS4265_SIG_SEL_LOOP (1 << 1)
#define CS4265_SIG_SEL_SDIN2 (1 << 7)
#define CS4265_SIG_SEL_SDIN1 (0 << 7)
// Sets the gain or attenuation for the ADC input PGA stage. The gain may be adjusted from -12 dB to
// +12 dB in 0.5 dB steps. The gain bits are in twos complement with the Gain0 bit set for a 0.5 dB step.
// Register settings outside of the ±12 dB range are reserved and must not be used. See Table 13 for example settings
#define CS4265_CHB_PGA_CTL 0x7
// Sets the gain or attenuation for the ADC input PGA stage. The gain may be adjusted from -12 dB to
// +12 dB in 0.5 dB steps. The gain bits are in twos complement with the Gain0 bit set for a 0.5 dB step.
// Register settings outside of the ±12 dB range are reserved and must not be used. See Table 13 for example settings
#define CS4265_CHA_PGA_CTL 0x8
// Gain[5:0] Setting
// 101000 -12 dB
// 000000 0 dB
// 011000 +12 dB
#define CS4265_ADC_CTL2 0x9
// The digital volume control allows the user to attenuate the signal in 0.5 dB increments from 0 to -127 dB.
// The Vol0 bit activates a 0.5 dB attenuation when set, and no attenuation when cleared. The Vol[7:1] bits
// activate attenuation equal to their decimal equivalent (in dB).
//Binary Code Volume Setting
//00000000 0 dB
//00000001 -0.5 dB
//00101000 -20 dB
//00101001 -20.5 dB
//11111110 -127 dB
//11111111 -127.5 dB
#define CS4265_DAC_CHA_VOL 0xA
// The digital volume control allows the user to attenuate the signal in 0.5 dB increments from 0 to -127 dB.
// The Vol0 bit activates a 0.5 dB attenuation when set, and no attenuation when cleared. The Vol[7:1] bits
// activate attenuation equal to their decimal equivalent (in dB).
//Binary Code Volume Setting
//00000000 0 dB
//00000001 -0.5 dB
//00101000 -20 dB
//00101001 -20.5 dB
//11111110 -127 dB
//11111111 -127.5 dB
#define CS4265_DAC_CHB_VOL 0xB
#define CS4265_DAC_VOL_ATT_000_0 0b00000000
#define CS4265_DAC_VOL_ATT_000_5 0b00000001
#define CS4265_DAC_VOL_ATT_020_0 0b00101000
#define CS4265_DAC_VOL_ATT_020_5 0b00101001
#define CS4265_DAC_VOL_ATT_127_0 0b11111110
#define CS4265_DAC_VOL_ATT_127_5 0b11111111
// DAC Soft Ramp or Zero Cross Enable (Bits 7:6)
//
// Soft Ramp Enable
// Soft Ramp allows level changes, both muting and attenuation, to be implemented by incrementally ramping, in 1/8 dB steps, from the current level to the new level at a rate of 1 dB per 8 left/right clock periods.
// See Table 17.
// Zero Cross Enable
// Zero Cross Enable dictates that signal-level changes, either by attenuation changes or muting, will occur
// on a signal zero crossing to minimize audible artifacts. The requested level change will occur after a timeout period between 512 and 1024 sample periods (10.7 ms to 21.3 ms at 48 kHz sample rate) if the signal
// does not encounter a zero crossing. The zero cross function is independently monitored and implemented
// for each channel. See Table 17.
// Soft Ramp and Zero Cross Enable
// Soft Ramp and Zero Cross Enable dictate that signal-level changes, either by attenuation changes or muting, will occur in 1/8 dB steps and be implemented on a signal zero crossing. The 1/8 dB level change will
// occur after a time-out period between 512 and 1024 sample periods (10.7 ms to 21.3 ms at 48 kHz sample rate) if the signal does not encounter a zero crossing. The zero cross function is independently monitored and implemented for each channel
// DACSoft DACZeroCross Mode
// 0 0 Changes to affect immediately
// 0 1 Zero Cross enabled
// 1 0 Soft Ramp enabled
// 1 1 Soft Ramp and Zero Cross enabled (default)
#define CS4265_DAC_CTL2 0xC
#define CS4265_DAC_CTL2_ZERO_CROSS_EN (uint8_t)(0b01 <<7)
#define CS4265_DAC_CTL2_SOFT_RAMP_EN (uint8_t)(0b10 <<7)
#define CS4265_DAC_CTL2_SOFT_RAMP_ZERO_CROSS_EN (uint8_t)(0b11 <<7)
#define CS4265_INT_STATUS 0xD
#define CS4265_INT_STATUS_ADC_UNDF (1<<0)
#define CS4265_INT_STATUS_ADC_OVF (1<<1)
#define CS4265_INT_STATUS_CLKERR (1<<3)
#define CS4265_INT_MASK 0xE
#define CS4265_STATUS_MODE_MSB 0xF
#define CS4265_STATUS_MODE_LSB 0x10
//Transmitter Control 1 - Address 11h
#define CS4265_SPDIF_CTL1 0x11
#define CS4265_SPDIF_CTL2 0x12
// Transmitter Digital Interface Format (Bits 7:6)
// Function:
// The required relationship between LRCK, SCLK and SDIN for the transmitter is defined
// Tx_DIF1 Tx_DIF0 Description Format Figure
// 0 0 Left Justified, up to 24-bit data (default) 0 5
// 0 1 I²S, up to 24-bit data 1 6
// 1 0 Right-Justified, 16-bit Data 2 7
// 1 1 Right-Justified, 24-bit Data 3 7
#define CS4265_SPDIF_CTL2_MMTLR (1<<0)
#define CS4265_SPDIF_CTL2_MMTCS (1<<1)
#define CS4265_SPDIF_CTL2_MMT (1<<2)
#define CS4265_SPDIF_CTL2_V (1<<3)
#define CS4265_SPDIF_CTL2_TXMUTE (1<<4)
#define CS4265_SPDIF_CTL2_TXOFF (1<<5)
#define CS4265_SPDIF_CTL2_MUTE (1 << 4)
#define CS4265_SPDIF_CTL2_DIF (3 << 6)
#define CS4265_SPDIF_CTL2_DIF0 (1 << 6)
#define CS4265_SPDIF_CTL2_DIF1 (1 << 7)
#define CS4265_C_DATA_BUFF 0x13
#define CS4265_MAX_REGISTER 0x2A
struct cs4265_clk_para {
uint32_t mclk;
uint32_t rate;
uint8_t fm_mode; /* values 1, 2, or 4 */
uint8_t mclkdiv;
};
static const struct cs4265_clk_para clk_map_table[] = {
/*32k*/
{8192000, 32000, 0, 0},
{12288000, 32000, 0, 1},
{16384000, 32000, 0, 2},
{24576000, 32000, 0, 3},
{32768000, 32000, 0, 4},
/*44.1k*/
{11289600, 44100, 0, 0},
{16934400, 44100, 0, 1},
{22579200, 44100, 0, 2},
{33868000, 44100, 0, 3},
{45158400, 44100, 0, 4},
/*48k*/
{12288000, 48000, 0, 0},
{18432000, 48000, 0, 1},
{24576000, 48000, 0, 2},
{36864000, 48000, 0, 3},
{49152000, 48000, 0, 4},
/*64k*/
{8192000, 64000, 1, 0},
{12288000, 64000, 1, 1},
{16934400, 64000, 1, 2},
{24576000, 64000, 1, 3},
{32768000, 64000, 1, 4},
/* 88.2k */
{11289600, 88200, 1, 0},
{16934400, 88200, 1, 1},
{22579200, 88200, 1, 2},
{33868000, 88200, 1, 3},
{45158400, 88200, 1, 4},
/* 96k */
{12288000, 96000, 1, 0},
{18432000, 96000, 1, 1},
{24576000, 96000, 1, 2},
{36864000, 96000, 1, 3},
{49152000, 96000, 1, 4},
/* 128k */
{8192000, 128000, 2, 0},
{12288000, 128000, 2, 1},
{16934400, 128000, 2, 2},
{24576000, 128000, 2, 3},
{32768000, 128000, 2, 4},
/* 176.4k */
{11289600, 176400, 2, 0},
{16934400, 176400, 2, 1},
{22579200, 176400, 2, 2},
{33868000, 176400, 2, 3},
{49152000, 176400, 2, 4},
/* 192k */
{12288000, 192000, 2, 0},
{18432000, 192000, 2, 1},
{24576000, 192000, 2, 2},
{36864000, 192000, 2, 3},
{49152000, 192000, 2, 4},
};
static const struct cs4265_cmd_s cs4265_init_sequence[] = {
{CS4265_PWRCTL, CS4265_PWRCTL_PDN_ADC | CS4265_PWRCTL_FREEZE | CS4265_PWRCTL_PDN_DAC | CS4265_PWRCTL_PDN_MIC},
{CS4265_DAC_CTL, CS4265_DAC_CTL_DIF0 | CS4265_DAC_CTL_MUTE},
{CS4265_SIG_SEL, CS4265_SIG_SEL_SDIN1},/// SDIN1
{CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_DIF0 },//
{CS4265_ADC_CTL, 0x00 },// // Set the serial audio port in slave mode
{CS4265_MCLK_FREQ, CS4265_MCLK_FREQ_1_0X },// // no divider
{CS4265_CHB_PGA_CTL, 0x00 },// // sets the gain to 0db on channel B
{CS4265_CHA_PGA_CTL, 0x00 },// // sets the gain to 0db on channel A
{CS4265_ADC_CTL2, 0x19 },//
{CS4265_DAC_CHA_VOL,CS4265_DAC_VOL_ATT_000_0 },// Full volume out
{CS4265_DAC_CHB_VOL, CS4265_DAC_VOL_ATT_000_0 },// // Full volume out
{CS4265_DAC_CTL2, CS4265_DAC_CTL2_SOFT_RAMP_ZERO_CROSS_EN },//
{CS4265_SPDIF_CTL1, 0x00 },//
{CS4265_INT_MASK, 0x00 },//
{CS4265_STATUS_MODE_MSB, 0x00 },//
{CS4265_STATUS_MODE_LSB, 0x00 },//
{0xff,0xff}
};
// matching orders
typedef enum { cs4265_ACTIVE = 0, cs4265_STANDBY, cs4265_DOWN, cs4265_ANALOGUE_OFF, cs4265_ANALOGUE_ON, cs4265_VOLUME } dac_cmd_e;
static int cs4265_addr;
static void dac_cmd(dac_cmd_e cmd, ...);
static int cs4265_detect(void);
static uint32_t calc_rnd_mclk_freq(){
float m_scale = (cs4265.i2s_config->sample_rate > 96000 && cs4265.i2s_config->bits_per_sample > 16) ? 4 : 8;
float num_channels = cs4265.i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? 2 : 1;
return (uint32_t) round(cs4265.i2s_config->bits_per_sample*i2s_get_clk(cs4265.i2c_port)* m_scale*num_channels/100)*100;
}
static int cs4265_get_clk_index(int mclk, int rate)
{
for (int i = 0; i < ARRAY_SIZE(clk_map_table); i++) {
if (clk_map_table[i].rate == rate &&
clk_map_table[i].mclk == mclk)
return i;
}
return -1;
}
static esp_err_t set_clock(){
esp_err_t err = ESP_OK;
uint32_t mclk = calc_rnd_mclk_freq();
int index = cs4265_get_clk_index(mclk,cs4265.i2s_config->sample_rate );
if (index >= 0) {
ESP_LOGD(TAG, "Setting clock for mclk %u, rate %u (fm mode:%u, clk div:%u))", mclk,cs4265.i2s_config->sample_rate,clk_map_table[index].fm_mode,clk_map_table[index].mclkdiv);
err=cs4265_update_bit(CS4265_ADC_CTL,CS4265_ADC_FM, clk_map_table[index].fm_mode << 6);
err|=cs4265_update_bit( CS4265_MCLK_FREQ,CS4265_MCLK_FREQ_MASK,clk_map_table[index].mclkdiv << 4);
} else {
ESP_LOGE(TAG,"can't get correct mclk for ");
return -1;
}
return err;
}
static void get_status(){
uint8_t sts1= adac_read_byte(cs4265_addr, CS4265_INT_STATUS);
ESP_LOGD(TAG,"Status: %s",sts1&CS4265_INT_STATUS_CLKERR?"CLK Error":"CLK OK");
}
/****************************************************************************************
* init
*/
static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) {
// find which TAS we are using (if any)
cs4265_addr = adac_init(config, i2c_port);
cs4265.i2s_config = i2s_config;
cs4265.i2c_port=i2c_port;
if (!cs4265_addr) cs4265_addr = cs4265_detect();
if (!cs4265_addr) {
ESP_LOGE(TAG, "No cs4265 detected");
adac_deinit();
return false;
}
#if BYTES_PER_FRAME == 8
ESP_LOGE(TAG,"The CS4265 does not support 32 bits mode. ");
adac_deinit();
return false;
#endif
// configure MLK
ESP_LOGD(TAG, "Configuring MCLK on GPIO0");
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
REG_WRITE(PIN_CTRL, 0xFFFFFFF0);
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
for (int i = 0; cs4265_init_sequence[i].reg != 0xff; i++) {
i2c_master_start(i2c_cmd);
i2c_master_write_byte(i2c_cmd, (cs4265_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
i2c_master_write_byte(i2c_cmd, cs4265_init_sequence[i].reg, I2C_MASTER_NACK);
i2c_master_write_byte(i2c_cmd, cs4265_init_sequence[i].value, I2C_MASTER_NACK);
ESP_LOGD(TAG, "i2c write %x at %u", cs4265_init_sequence[i].reg, cs4265_init_sequence[i].value);
}
i2c_master_stop(i2c_cmd);
esp_err_t res = i2c_master_cmd_begin(i2c_port, i2c_cmd, 500 / portTICK_RATE_MS);
i2c_cmd_link_delete(i2c_cmd);
if (res != ESP_OK) {
ESP_LOGE(TAG, "could not intialize cs4265 %d", res);
return false;
}
return true;
}
static esp_err_t cs4265_update_bit(uint8_t reg_no,uint8_t mask,uint8_t val ){
esp_err_t ret=ESP_OK;
uint8_t old= adac_read_byte(cs4265_addr, reg_no);
uint8_t newval = (old & ~mask) | (val & mask);
bool change = old != newval;
if (change){
ret = adac_write_byte(cs4265_addr, reg_no, newval);
if(ret != ESP_OK){
ESP_LOGE(TAG,"Unable to change dac register 0x%02x [0x%02x->0x%02x] from value 0x%02x, mask 0x%02x ",reg_no,old,newval,val,mask);
}
else {
ESP_LOGD(TAG,"Changed dac register 0x%02x [0x%02x->0x%02x] from value 0x%02x, mask 0x%02x ",reg_no,old,newval,val,mask);
}
}
return ret;
}
/****************************************************************************************
* change volume
*/
static bool volume(unsigned left, unsigned right) {
return false;
}
/****************************************************************************************
* power
*/
static void power(adac_power_e mode) {
switch(mode) {
case ADAC_STANDBY:
dac_cmd(cs4265_STANDBY);
break;
case ADAC_ON:
dac_cmd(cs4265_ACTIVE);
break;
case ADAC_OFF:
dac_cmd(cs4265_DOWN);
break;
default:
ESP_LOGW(TAG, "unknown DAC command");
break;
}
}
/****************************************************************************************
* speaker
*/
static void speaker(bool active) {
if (active) dac_cmd(cs4265_ANALOGUE_ON);
else dac_cmd(cs4265_ANALOGUE_OFF);
}
/****************************************************************************************
* headset
*/
static void headset(bool active) { }
/****************************************************************************************
* DAC specific commands
*/
void dac_cmd(dac_cmd_e cmd, ...) {
va_list args;
esp_err_t ret = ESP_OK;
va_start(args, cmd);
switch(cmd) {
case cs4265_VOLUME:
ESP_LOGE(TAG, "DAC volume not handled yet");
break;
case cs4265_ACTIVE:
ESP_LOGD(TAG, "Activating DAC");
adac_write_byte(cs4265_addr, CS4265_PWRCTL,0);
cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXOFF,0);
cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXMUTE,0);
cs4265_update_bit(CS4265_DAC_CTL,CS4265_DAC_CTL_MUTE,0);
break;
case cs4265_STANDBY:
ESP_LOGD(TAG, "DAC Stand-by");
cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXOFF,CS4265_SPDIF_CTL2_TXOFF);
cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXMUTE,CS4265_SPDIF_CTL2_TXMUTE);
cs4265_update_bit(CS4265_DAC_CTL,CS4265_DAC_CTL_MUTE,CS4265_DAC_CTL_MUTE);
break;
case cs4265_DOWN:
ESP_LOGD(TAG, "DAC Power Down");
adac_write_byte(cs4265_addr, CS4265_PWRCTL,CS4265_PWRCTL_PDN_ALL);
break;
case cs4265_ANALOGUE_OFF:
ESP_LOGD(TAG, "DAC Analog off");
cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXOFF,CS4265_SPDIF_CTL2_TXOFF);
cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXMUTE,CS4265_SPDIF_CTL2_TXMUTE);
cs4265_update_bit(CS4265_DAC_CTL,CS4265_DAC_CTL_MUTE,CS4265_DAC_CTL_MUTE);
break;
case cs4265_ANALOGUE_ON:
ESP_LOGD(TAG, "DAC Analog on");
adac_write_byte(cs4265_addr, CS4265_PWRCTL,0);
cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXOFF,0);
cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXMUTE,0);
cs4265_update_bit(CS4265_DAC_CTL,CS4265_DAC_CTL_MUTE,0);
break;
}
if (ret != ESP_OK) {
ESP_LOGE(TAG, "could not use cs4265 %d", ret);
}
get_status();
// now set the clock
ret=set_clock(cs4265.i2s_config,cs4265.i2c_port);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "could not set the cs4265's clock %d", ret);
}
va_end(args);
}
/****************************************************************************************
* TAS57 detection
*/
static int cs4265_detect(void) {
uint8_t addr[] = {CS4265_PULL_DOWN,CS4265_PULL_UP};
for (int i = 0; i < sizeof(addr); i++) {
ESP_LOGI(TAG,"Looking for CS4265 @0x%x",addr[i]);
uint8_t reg=adac_read_byte(addr[i], CS4265_CHIP_ID);
if(reg==255){
continue;
}
// found a device at that address
uint8_t devid = reg & CS4265_CHIP_ID_MASK;
if (devid != CS4265_CHIP_ID_VAL) {
ESP_LOGE(TAG,"CS4265 Device ID (%X). Expected %X",devid, CS4265_CHIP_ID);
return 0;
}
ESP_LOGI(TAG,"Found DAC @0x%x, Version %x",addr[i], reg & CS4265_REV_ID_MASK);
return addr[i];
}
return 0;
}

View File

@@ -247,13 +247,17 @@ void decode_close(void) {
#endif
}
void decode_flush(void) {
void decode_flush(bool close) {
LOG_INFO("decode flush");
LOCK_D;
decode.state = DECODE_STOPPED;
IF_PROCESS(
process_flush();
);
if (close && codec) {
codec->close();
codec = NULL;
}
UNLOCK_D;
}

View File

@@ -123,7 +123,7 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
{
// don't LOCK_O as there is always a chance that LMS takes control later anyway
if (output.external != DECODE_BT && output.state > OUTPUT_STOPPED) {
LOG_WARN("Cannot use BT sink while LMS/AirPlay/CSpot are controlling player");
LOG_WARN("Cannot use BT sink while LMS/AirPlay/CSpot are controlling player %d", output.external);
return false;
}
@@ -205,7 +205,7 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
{
// don't LOCK_O as there is always a chance that LMS takes control later anyway
if (output.external != DECODE_RAOP && output.state > OUTPUT_STOPPED) {
LOG_WARN("Cannot use Airplay sink while LMS/BT/CSpot are controlling player");
LOG_WARN("Cannot use Airplay sink while LMS/BT/CSpot are controlling player %d", output.external);
return false;
}
@@ -338,7 +338,7 @@ static bool cspot_cmd_handler(cspot_event_t cmd, va_list args)
{
// don't LOCK_O as there is always a chance that LMS takes control later anyway
if (output.external != DECODE_CSPOT && output.state > OUTPUT_STOPPED) {
LOG_WARN("Cannot use CSpot sink while LMS/BT/Airplay are controlling player");
LOG_WARN("Cannot use CSpot sink while LMS/BT/Airplay are controlling player %d", output.external);
return false;
}
@@ -406,9 +406,7 @@ static bool cspot_cmd_handler(cspot_event_t cmd, va_list args)
case CSPOT_VOLUME: {
u32_t volume = va_arg(args, u32_t);
LOG_INFO("CSpot volume %u", volume);
//volume = 65536 * powf(volume / 32768.0f, 3);
// TODO spotify seems to volume normalize crazy high
volume = 4096 * powf(volume / 32768.0f, 3);
volume = 65536 * powf(volume / 65536.0f, 2);
set_volume(volume, volume);
break;
default:

View File

@@ -17,6 +17,7 @@
#include "esp_wifi.h"
#include "monitor.h"
#include "platform_config.h"
#include "messaging.h"
mutex_type slimp_mutex;
static jmp_buf jumpbuf;
@@ -29,6 +30,15 @@ _sig_func_ptr signal(int sig, _sig_func_ptr func) {
return NULL;
}
void em_logprint(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
vmessaging_post_message(MESSAGING_ERROR, MESSAGING_CLASS_SYSTEM, fmt, args);
va_end(args);
fflush(stderr);
}
void *audio_calloc(size_t nmemb, size_t size) {
return calloc(nmemb, size);
}
@@ -55,6 +65,7 @@ int embedded_init(void) {
mutex_create(slimp_mutex);
sb_controls_init();
custom_player_id = sb_displayer_init() ? 100 : 101;
return setjmp(jumpbuf);
}

View File

@@ -51,6 +51,10 @@ extern u8_t custom_player_id;
// to force some special buffer attribute
#define EXT_BSS __attribute__((section(".ext_ram.bss")))
// otherwise just leave it empty
void em_logprint(const char *fmt, ...);
#define LOG_ERROR(fmt, ...) em_logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__);
// all exit() calls are made from main thread (or a function called in main thread)
void embedded_exit(int code);
#define exit(code) do { embedded_exit(code); } while (0)

View File

@@ -275,7 +275,11 @@ static void sighandler(int signum) {
signal(signum, SIG_DFL);
}
#ifndef EMBEDDED
int main(int argc, char **argv) {
#else
int squeezelite_main(int argc, char **argv) {
#endif
char *server = NULL;
char *output_device = "default";
char *include_codecs = NULL;
@@ -382,7 +386,7 @@ int main(int argc, char **argv) {
optarg = NULL;
optind += 1;
} else {
fprintf(stderr, "\nOption error: -%s\n\n", opt);
LOG_ERROR("=> Option error: -%s", opt);
usage(argv[0]);
exit(1);
}
@@ -433,7 +437,7 @@ int main(int argc, char **argv) {
if (!strcmp(l, "all") || !strcmp(l, "ir")) log_ir = new;
#endif
} else {
fprintf(stderr, "\nDebug settings error: -d %s\n\n", optarg);
LOG_ERROR("=> Debug settings error: -d %s", optarg);
usage(argv[0]);
exit(1);
}
@@ -447,7 +451,7 @@ int main(int argc, char **argv) {
int byte = 0;
char *tmp;
if (!strncmp(optarg, "00:04:20", 8)) {
LOG_ERROR("ignoring mac address from hardware player range 00:04:20:**:**:**");
LOG_ERROR("=> ignoring mac address from hardware player range 00:04:20:**:**:**");
} else {
char *t = strtok(optarg, ":");
while (t && byte < 6) {
@@ -685,14 +689,14 @@ int main(int argc, char **argv) {
exit(0);
break;
default:
fprintf(stderr, "Arg error: %s\n", argv[optind]);
LOG_ERROR("=> arg error: %s", argv[optind]);
break;
}
}
// warn if command line includes something which isn't parsed
if (optind < argc) {
fprintf(stderr, "\nError: command line argument error\n\n");
LOG_ERROR("=> command line argument error");
usage(argv[0]);
exit(1);
}
@@ -802,7 +806,7 @@ int main(int argc, char **argv) {
#endif
if (name && namefile) {
fprintf(stderr, "-n and -N option should not be used at same time\n");
LOG_ERROR("=> -n and -N option should not be used at same time");
exit(1);
}
@@ -845,5 +849,5 @@ int main(int argc, char **argv) {
free_ssl_symbols();
#endif
exit(0);
return(0);
}

View File

@@ -30,9 +30,6 @@
* thread has a higher priority. Using an interim buffer where opus decoder writes the output is not great from
* an efficiency (one extra memory copy) point of view, but it allows the lock to not be kept for too long
*/
#if EMBEDDED
#define FRAME_BUF 2048
#endif
#if BYTES_PER_FRAME == 4
#define ALIGN(n) (n)
@@ -40,23 +37,52 @@
#define ALIGN(n) (n << 16)
#endif
#include <opusfile.h>
#include <ogg/ogg.h>
#include <opus.h>
// opus maximum output frames is 120ms @ 48kHz
#define MAX_OPUS_FRAMES 5760
struct opus {
struct OggOpusFile *of;
bool end;
#if FRAME_BUF
u8_t *write_buf;
#endif
#if !LINKALL
// opus symbols to be dynamically loaded
void (*op_free)(OggOpusFile *_of);
int (*op_read)(OggOpusFile *_of, opus_int16 *_pcm, int _buf_size, int *_li);
const OpusHead* (*op_head)(OggOpusFile *_of, int _li);
OggOpusFile* (*op_open_callbacks) (void *_source, OpusFileCallbacks *_cb, unsigned char *_initial_data, size_t _initial_bytes, int *_error);
#endif
enum {OGG_SYNC, OGG_ID_HEADER, OGG_COMMENT_HEADER} status;
ogg_stream_state state;
ogg_packet packet;
ogg_sync_state sync;
ogg_page page;
OpusDecoder* decoder;
int rate, gain, pre_skip;
size_t overframes;
u8_t *overbuf;
int channels;
};
#if !LINKALL
static struct {
void *handle;
int (*ogg_stream_init)(ogg_stream_state* os, int serialno);
int (*ogg_stream_clear)(ogg_stream_state* os);
int (*ogg_stream_reset)(ogg_stream_state* os);
int (*ogg_stream_eos)(ogg_stream_state* os);
int (*ogg_stream_reset_serialno)(ogg_stream_state* os, int serialno);
int (*ogg_sync_clear)(ogg_sync_state* oy);
void (*ogg_packet_clear)(ogg_packet* op);
char* (*ogg_sync_buffer)(ogg_sync_state* oy, long size);
int (*ogg_sync_wrote)(ogg_sync_state* oy, long bytes);
long (*ogg_sync_pageseek)(ogg_sync_state* oy, ogg_page* og);
int (*ogg_sync_pageout)(ogg_sync_state* oy, ogg_page* og);
int (*ogg_stream_pagein)(ogg_stream_state* os, ogg_page* og);
int (*ogg_stream_packetout)(ogg_stream_state* os, ogg_packet* op);
int (*ogg_page_packets)(const ogg_page* og);
} go;
static struct {
void* handle;
OpusDecoder* (*opus_decoder_create)(opus_int32 Fs, int channels, int* error);
int (*opus_decode)(OpusDecoder* st, const unsigned char* data, opus_int32 len, opus_int16* pcm, int frame_size, int decode_fec);
void (*opus_decoder_destroy)(OpusDecoder* st);
} gu;
#endif
static struct opus *u;
extern log_level loglevel;
@@ -75,72 +101,139 @@ extern struct processstate process;
#if PROCESS
#define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex)
#define LOCK_O_not_direct if (!decode.direct) mutex_lock(outputbuf->mutex)
#define UNLOCK_O_not_direct if (!decode.direct) mutex_unlock(outputbuf->mutex)
#define IF_DIRECT(x) if (decode.direct) { x }
#define IF_PROCESS(x) if (!decode.direct) { x }
#else
#define LOCK_O_direct mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct mutex_unlock(outputbuf->mutex)
#define LOCK_O_not_direct
#define UNLOCK_O_not_direct
#define IF_DIRECT(x) { x }
#define IF_PROCESS(x)
#endif
#if LINKALL
#define OP(h, fn, ...) (op_ ## fn)(__VA_ARGS__)
#define OG(h, fn, ...) (ogg_ ## fn)(__VA_ARGS__)
#define OP(h, fn, ...) (opus_ ## fn)(__VA_ARGS__)
#else
#define OP(h, fn, ...) (h)->op_ ## fn(__VA_ARGS__)
#define OG(h, fn, ...) (h)->ogg_ ## fn(__VA_ARGS__)
#define OP(h, fn, ...) (h)->opus_ ## fn(__VA_ARGS__)
#endif
// called with mutex locked within vorbis_decode to avoid locking O before S
static int _read_cb(void *datasource, char *ptr, int size) {
size_t bytes;
static unsigned parse_uint16(const unsigned char* _data) {
return _data[0] | _data[1] << 8;
}
static int parse_int16(const unsigned char* _data) {
return ((_data[0] | _data[1] << 8) ^ 0x8000) - 0x8000;
}
static opus_uint32 parse_uint32(const unsigned char* _data) {
return _data[0] | (opus_uint32)_data[1] << 8 |
(opus_uint32)_data[2] << 16 | (opus_uint32)_data[3] << 24;
}
static int get_opus_packet(void) {
int status, packet = -1;
LOCK_S;
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
while (!(status = OG(&go, stream_packetout, &u->state, &u->packet)) && bytes) {
do {
size_t consumed = min(bytes, 4096);
char* buffer = OG(&gu, sync_buffer, &u->sync, consumed);
memcpy(buffer, streambuf->readp, consumed);
OG(&gu, sync_wrote, &u->sync, consumed);
bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
bytes = min(bytes, size);
_buf_inc_readp(streambuf, consumed);
bytes -= consumed;
} while (!(status = OG(&gu, sync_pageseek, &u->sync, &u->page)) && bytes);
memcpy(ptr, streambuf->readp, bytes);
_buf_inc_readp(streambuf, bytes);
// if we have a new page, put it in
if (status) OG(&go, stream_pagein, &u->state, &u->page);
}
// only return a negative value when end of streaming is reached
if (status > 0) packet = status;
else if (stream.state > DISCONNECT) packet = 0;
UNLOCK_S;
return packet;
}
return bytes;
static int read_opus_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);
char* buffer = OG(&gu, sync_buffer, &u->sync, consumed);
memcpy(buffer, streambuf->readp, consumed);
OG(&gu, sync_wrote, &u->sync, consumed);
_buf_inc_readp(streambuf, consumed);
bytes -= consumed;
if (!OG(&gu, sync_pageseek, &u->sync, &u->page)) continue;
}
switch (u->status) {
case OGG_SYNC:
u->status = OGG_ID_HEADER;
OG(&gu, stream_reset_serialno, &u->state, OG(&gu, page_serialno, &u->page));
fetch = false;
break;
case OGG_ID_HEADER:
status = OG(&gu, 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)) {
LOG_ERROR("wrong opus header packet (size:%u)", u->packet.bytes);
status = -100;
break;
}
u->status = OGG_COMMENT_HEADER;
u->channels = u->packet.packet[9];
u->pre_skip = parse_uint16(u->packet.packet + 10);
u->rate = parse_uint32(u->packet.packet + 12);
u->gain = parse_int16(u->packet.packet + 16);
u->decoder = OP(&gu, decoder_create, 48000, u->channels, &status);
if (!u->decoder || status != OPUS_OK) {
LOG_ERROR("can't create decoder %d (channels:%u)", status, u->channels);
}
}
fetch = true;
break;
case OGG_COMMENT_HEADER:
// skip pakets to consume VorbisComment. With opus, header packets align on pages
status = OG(&gu, page_packets, &u->page);
break;
default:
break;
}
}
UNLOCK_S;
return status;
}
static decode_state opus_decompress(void) {
frames_t frames;
int n;
static int channels;
u8_t *write_buf;
LOCK_S;
if (decode.new_stream) {
int status = read_opus_header();
if (stream.state <= DISCONNECT && u->end) {
UNLOCK_S;
return DECODE_COMPLETE;
}
UNLOCK_S;
if (decode.new_stream) {
struct OpusFileCallbacks cbs;
const struct OpusHead *info;
int err;
cbs.read = (op_read_func) _read_cb;
cbs.seek = NULL; cbs.tell = NULL; cbs.close = NULL;
if ((u->of = OP(u, open_callbacks, streambuf, &cbs, NULL, 0, &err)) == NULL) {
LOG_WARN("open_callbacks error: %d", err);
return DECODE_COMPLETE;
if (status == 0) {
return DECODE_RUNNING;
} else if (status < 0) {
LOG_WARN("can't create codec");
return DECODE_ERROR;
}
info = OP(u, head, u->of, -1);
LOCK_O;
output.next_sample_rate = decode_newstream(48000, output.supported_rates);
IF_DSD( output.next_fmt = PCM; )
@@ -148,46 +241,61 @@ static decode_state opus_decompress(void) {
if (output.fade_mode) _checkfade(true);
decode.new_stream = false;
UNLOCK_O;
channels = info->channel_count;
if (u->channels > 2) {
LOG_WARN("too many channels: %d", u->channels);
return DECODE_ERROR;
}
LOG_INFO("setting track_start");
}
#if FRAME_BUF
IF_DIRECT(
frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
frames = min(frames, FRAME_BUF);
write_buf = u->write_buf;
);
#else
LOCK_O_direct;
IF_DIRECT(
frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
write_buf = outputbuf->writep;
);
#endif
IF_PROCESS(
frames = process.max_in_frames;
write_buf = process.inbuf;
);
int packet, n = 0;
u->end = frames == 0;
// write the decoded frames into outputbuf then unpack them (they are 16 bits)
n = OP(u, read, u->of, (opus_int16*) write_buf, frames * channels, NULL);
// get some packets and decode them, or use the leftover from previous pass
if (u->overframes) {
/* use potential leftover from previous encoding. We know that it will fit this time
* as min_space is >=MAX_OPUS_FRAMES and we start from the beginning of the buffer */
memcpy(write_buf, u->overbuf, u->overframes * BYTES_PER_FRAME);
n = u->overframes;
u->overframes = 0;
} else if ((packet = get_opus_packet()) > 0) {
if (frames < MAX_OPUS_FRAMES) {
// 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);
if (n > 0) {
u->overframes = n - min(n, frames);
n = min(n, frames);
memcpy(write_buf, u->overbuf, n * BYTES_PER_FRAME);
memmove(u->overbuf, u->overbuf + n, u->overframes);
}
} else {
/* we just do one packet at a time, although we could loop on packets but that means locking the
* 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);
}
} else if (!packet && !OG(&go, page_eos, &u->page)) {
UNLOCK_O_direct;
return DECODE_RUNNING;
}
#if FRAME_BUF
LOCK_O_direct;
#endif
if (n > 0) {
frames_t count;
s16_t *iptr;
ISAMPLE_T *optr;
frames = n;
count = frames * channels;
count = frames * u->channels;
// work backward to unpack samples (if needed)
iptr = (s16_t *) write_buf + count;
@@ -198,20 +306,13 @@ static decode_state opus_decompress(void) {
optr = (ISAMPLE_T *) write_buf + frames * 2;
)
if (channels == 2) {
#if BYTES_PER_FRAME == 4
#if FRAME_BUF
// copy needed only when DIRECT and FRAME_BUF
IF_DIRECT(
memcpy(outputbuf->writep, write_buf, frames * BYTES_PER_FRAME);
)
#endif
#else
if (u->channels == 2) {
#if BYTES_PER_FRAME == 8
while (count--) {
*--optr = ALIGN(*--iptr);
}
#endif
} else if (channels == 1) {
} else if (u->channels == 1) {
while (count--) {
*--optr = ALIGN(*--iptr);
*--optr = ALIGN(*iptr);
@@ -229,69 +330,84 @@ static decode_state opus_decompress(void) {
} else if (n == 0) {
if (stream.state <= DISCONNECT) {
LOG_INFO("partial decode");
if (packet < 0) {
LOG_INFO("end of decode");
UNLOCK_O_direct;
return DECODE_COMPLETE;
} else {
LOG_INFO("no frame decoded");
}
} else if (n == OP_HOLE) {
// recoverable hole in stream, seen when skipping
LOG_DEBUG("hole in stream");
} else {
LOG_INFO("op_read error: %d", n);
LOG_INFO("opus decode error: %d", n);
UNLOCK_O_direct;
return DECODE_COMPLETE;
}
UNLOCK_O_direct;
return DECODE_RUNNING;
}
static void opus_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
if (!u->of) {
#if FRAME_BUF
if (!u->write_buf) u->write_buf = malloc(FRAME_BUF * BYTES_PER_FRAME);
#endif
} else {
OP(u, free, u->of);
u->of = NULL;
}
u->end = false;
static void opus_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
if (u->decoder) OP(&gu, decoder_destroy, u->decoder);
u->decoder = NULL;
if (!u->overbuf) u->overbuf = malloc(MAX_OPUS_FRAMES * BYTES_PER_FRAME);
u->status = OGG_SYNC;
u->overframes = 0;
OG(&gu, sync_clear, &u->sync);
OG(&gu, stream_clear, &u->state);
OG(&gu, stream_init, &u->state, -1);
}
static void opus_close(void) {
if (u->of) {
OP(u, free, u->of);
u->of = NULL;
}
#if FRAME_BUF
free(u->write_buf);
u->write_buf = NULL;
#endif
static void opus_close(void) {
if (u->decoder) OP(&gu, decoder_destroy, u->decoder);
u->decoder = NULL;
free(u->overbuf);
u->overbuf = NULL;
OG(&gu, stream_clear, &u->state);
OG(&gu, sync_clear, &u->sync);
}
static bool load_opus(void) {
#if !LINKALL
void *handle = dlopen(LIBOPUS, RTLD_NOW);
char *err;
if (!handle) {
LOG_INFO("dlerror: %s", dlerror());
void *u.handle = dlopen(LIBOPUS, RTLD_NOW);
if (!u_handle) {
LOG_INFO("opus dlerror: %s", dlerror());
return false;
}
u->op_free = dlsym(handle, "op_free");
u->op_read = dlsym(handle, "op_read");
u->op_head = dlsym(handle, "op_head");
u->op_open_callbacks = dlsym(handle, "op_open_callbacks");
void *g_handle = dlopen(LIBOGG, RTLD_NOW);
if (!g_handle) {
dlclose(u_handle);
LOG_INFO("ogg dlerror: %s", dlerror());
return false;
}
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_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_sync_clear = dlsym(g_handle->handle, "ogg_sync_clear");
g_handle->ogg_packet_clear = dlsym(g_handle->handle, "ogg_packet_clear");
g_handle->ogg_sync_buffer = dlsym(g_handle->handle, "ogg_sync_buffer");
g_handle->ogg_sync_wrote = dlsym(g_handle->handle, "ogg_sync_wrote");
g_handle->ogg_sync_pageseek = dlsym(g_handle->handle, "ogg_sync_pageseek");
g_handle->ogg_sync_pageout = dlsym(g_handle->handle, "ogg_sync_pageout");
g_handle->ogg_stream_pagein = dlsym(g_handle->handle, "ogg_stream_pagein");
g_handle->ogg_stream_packetout = dlsym(g_handle->handle, "ogg_stream_packetout");
g_handle->ogg_page_packets = dlsym(g_handle->handle, "ogg_page_packets");
u_handle->opus_decoder_create = dlsym(u_handle->handle, "opus_decoder_create");
u_handle->opus_decoder_destroy = dlsym(u_handle->handle, "opus_decoder_destroy");
u_handle->opus_decode = dlsym(u_handle->handle, "opus_decode");
if ((err = dlerror()) != NULL) {
LOG_INFO("dlerror: %s", err);
@@ -308,23 +424,17 @@ struct codec *register_opus(void) {
static struct codec ret = {
'u', // id
"ops", // types
4*1024, // min read
32*1024, // min space
8*1024, // min read
MAX_OPUS_FRAMES*BYTES_PER_FRAME*2, // min space
opus_open, // open
opus_close, // close
opus_decompress, // decode
};
u = malloc(sizeof(struct opus));
if (!u) {
if ((u = calloc(1, sizeof(struct opus))) == NULL) {
return NULL;
}
u->of = NULL;
#if FRAME_BUF
u->write_buf = NULL;
#endif
if (!load_opus()) {
return NULL;
}

View File

@@ -306,7 +306,7 @@ static void process_strm(u8_t *pkt, int len) {
break;
case 'f':
case 'q':
decode_flush();
decode_flush(strm->command == 'q');
if (!output.external) output_flush();
status.frames_played = 0;
if (stream_disconnect() && strm->command == 'f') sendSTAT("STMf", 0);

View File

@@ -393,7 +393,6 @@ typedef enum { lERROR = 0, lWARN, lINFO, lDEBUG, lSDEBUG } log_level;
const char *logtime(void);
void logprint(const char *fmt, ...);
#define LOG_ERROR(fmt, ...) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...) if (loglevel >= lWARN) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) if (loglevel >= lINFO) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_DEBUG(fmt, ...) if (loglevel >= lDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
@@ -406,6 +405,10 @@ typedef int sockfd;
#include "embedded.h"
#endif
#ifndef LOG_ERROR
#define LOG_ERROR(fmt, ...) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#endif
#if !defined(MSG_NOSIGNAL)
#define MSG_NOSIGNAL 0
#endif
@@ -619,7 +622,7 @@ struct codec {
void decode_init(log_level level, const char *include_codecs, const char *exclude_codecs);
void decode_close(void);
void decode_flush(void);
void decode_flush(bool close);
unsigned decode_newstream(unsigned sample_rate, unsigned supported_rates[]);
void codec_open(u8_t format, u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness);

View File

@@ -390,7 +390,7 @@ void stream_init(log_level level, unsigned stream_buf_size) {
buf_init(streambuf, stream_buf_size);
if (streambuf->buf == NULL) {
LOG_ERROR("unable to malloc buffer");
exit(0);
exit(2);
}
#if USE_SSL
@@ -401,7 +401,7 @@ void stream_init(log_level level, unsigned stream_buf_size) {
SSLctx = SSL_CTX_new(SSLv23_client_method());
if (SSLctx == NULL) {
LOG_ERROR("unable to allocate SSL context");
exit(0);
exit(3);
}
SSL_CTX_set_options(SSLctx, SSL_OP_NO_SSLv2);
#if !LINKALL && !NO_SSLSYM

View File

@@ -69,7 +69,11 @@ const char *logtime(void) {
struct timeval tv;
gettimeofday(&tv, NULL);
strftime(buf, sizeof(buf), "[%T.", localtime(&tv.tv_sec));
sprintf(buf+strlen(buf), "%06ld]", (long)tv.tv_usec);
#ifdef EMBEDDED
sprintf(buf+strlen(buf), "%03ld]", (long)tv.tv_usec/1000);
#else
sprintf(buf+strlen(buf), "%06ld]", (long)tv.tv_usec);
#endif
#endif
return buf;
}

View File

@@ -21,52 +21,80 @@
#include "squeezelite.h"
/*
* with some low-end CPU, the decode call takes a fair bit of time and if the outputbuf is locked during that
* period, the output_thread (or equivalent) will be locked although there is plenty of samples available.
* Normally, with PRIO_INHERIT, that thread should increase decoder priority and get the lock quickly but it
* seems that when the streambuf has plenty of data, the decode thread grabs the CPU to much, even it the output
* thread has a higher priority. Using an interim buffer where vorbis decoder writes the output is not great from
* an efficiency (one extra memory copy) point of view, but it allows the lock to not be kept for too long
*/
#if EMBEDDED
#define FRAME_BUF 2048
#endif
#if BYTES_PER_FRAME == 4
#define ALIGN(n) (n)
#else
#define ALIGN(n) (n << 16)
#endif
// automatically select between floating point (preferred) and fixed point libraries:
// NOTE: works with Tremor version here: http://svn.xiph.org/trunk/Tremor, not vorbisidec.1.0.2 currently in ubuntu
// we take common definations from <vorbis/vorbisfile.h> even though we can use tremor at run time
// tremor's OggVorbis_File struct is normally smaller so this is ok, but padding added to malloc in case it is bigger
#define OV_EXCLUDE_STATIC_CALLBACKS
#include <ogg/ogg.h>
#ifdef TREMOR_ONLY
#include <ivorbisfile.h>
#include <vorbis/ivorbiscodec.h>
#else
#include <vorbis/vorbisfile.h>
#include <vorbis/codec.h>
static bool tremor = false;
#endif
// this is tremor packing, not mine...
static inline int32_t clip15(int32_t x) {
int ret = x;
ret -= ((x<=32767)-1)&(x-32767);
ret -= ((x>=-32768)-1)&(x+32768);
return ret;
}
#if BYTES_PER_FRAME == 4
#define ALIGN(n) clip15((n) >> 9);
#define ALIGN_FLOAT(n) ((n)*32768.0f + 0.5f)
#else
#define ALIGN(n) (clip15((n) >> 9) << 16)
#define ALIGN_FLOAT ((n)*32768.0f*65536.0f + 0.5f)
#endif
struct vorbis {
OggVorbis_File *vf;
bool opened, end;
#if FRAME_BUF
u8_t *write_buf;
#endif
bool opened;
enum { OGG_SYNC, OGG_ID_HEADER, OGG_COMMENT_HEADER, OGG_SETUP_HEADER } status;
struct {
ogg_stream_state state;
ogg_packet packet;
ogg_sync_state sync;
ogg_page page;
};
struct {
vorbis_dsp_state decoder;
vorbis_info info;
vorbis_comment comment;
vorbis_block block;
};
int rate, channels;
uint32_t overflow;
};
#if !LINKALL
static struct vorbis {
// vorbis symbols to be dynamically loaded - from either vorbisfile or vorbisidec (tremor) version of library
vorbis_info *(* ov_info)(OggVorbis_File *vf, int link);
int (* ov_clear)(OggVorbis_File *vf);
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);
int (* ov_open_callbacks)(void *datasource, OggVorbis_File *vf, const char *initial, long ibytes, ov_callbacks callbacks);
} gv;
static struct {
void *handle;
int (*ogg_stream_init)(ogg_stream_state* os, int serialno);
int (*ogg_stream_clear)(ogg_stream_state* os);
int (*ogg_stream_reset)(ogg_stream_state* os);
int (*ogg_stream_eos)(ogg_stream_state* os);
int (*ogg_stream_reset_serialno)(ogg_stream_state* os, int serialno);
int (*ogg_sync_clear)(ogg_sync_state* oy);
void (*ogg_packet_clear)(ogg_packet* op);
char* (*ogg_sync_buffer)(ogg_sync_state* oy, long size);
int (*ogg_sync_wrote)(ogg_sync_state* oy, long bytes);
long (*ogg_sync_pageseek)(ogg_sync_state* oy, ogg_page* og);
int (*ogg_sync_pageout)(ogg_sync_state* oy, ogg_page* og);
int (*ogg_stream_pagein)(ogg_stream_state* os, ogg_page* og);
int (*ogg_stream_packetout)(ogg_stream_state* os, ogg_packet* op);
int (*ogg_page_packets)(const ogg_page* og);
} go;
#endif
};
static struct vorbis *v;
@@ -86,183 +114,254 @@ extern struct processstate process;
#if PROCESS
#define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex)
#define LOCK_O_not_direct if (!decode.direct) mutex_lock(outputbuf->mutex)
#define UNLOCK_O_not_direct if (!decode.direct) mutex_unlock(outputbuf->mutex)
#define IF_DIRECT(x) if (decode.direct) { x }
#define IF_PROCESS(x) if (!decode.direct) { x }
#else
#define LOCK_O_direct mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct mutex_unlock(outputbuf->mutex)
#define LOCK_O_not_direct
#define UNLOCK_O_not_direct
#define IF_DIRECT(x) { x }
#define IF_PROCESS(x)
#endif
#if LINKALL
#define OV(h, fn, ...) (ov_ ## fn)(__VA_ARGS__)
#define TREMOR(h) 0
#if !WIN
extern int ov_read_tremor(); // needed to enable compilation, not linked
#endif
#define OV(h, fn, ...) (vorbis_ ## fn)(__VA_ARGS__)
#define OG(h, fn, ...) (ogg_ ## fn)(__VA_ARGS__)
#else
#define OV(h, fn, ...) (h)->ov_##fn(__VA_ARGS__)
#define TREMOR(h) (h)->ov_read_tremor
#define OG(h, fn, ...) (h)->ogg_ ## fn(__VA_ARGS__)
#endif
// called with mutex locked within vorbis_decode to avoid locking O before S
static size_t _read_cb(void *ptr, size_t size, size_t nmemb, void *datasource) {
size_t bytes;
static int get_ogg_packet(void) {
int status, packet = -1;
LOCK_S;
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
bytes = min(bytes, size * nmemb);
while (!(status = OG(&go, stream_packetout, &v->state, &v->packet)) && bytes) {
do {
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);
memcpy(ptr, streambuf->readp, bytes);
_buf_inc_readp(streambuf, bytes);
_buf_inc_readp(streambuf, consumed);
bytes -= consumed;
} while (!(status = OG(&go, sync_pageseek, &v->sync, &v->page)) && bytes);
// 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 end of streaming is reached
if (status > 0) packet = status;
else if (stream.state > DISCONNECT) packet = 0;
UNLOCK_S;
return bytes / size;
return packet;
}
// these are needed for older versions of tremor, later versions and libvorbis allow NULL to be used
static int _seek_cb(void *datasource, ogg_int64_t offset, int whence) { return -1; }
static int _close_cb(void *datasource) { return 0; }
static long _tell_cb(void *datasource) { return 0; }
static decode_state vorbis_decode(void) {
static int channels;
frames_t frames;
int bytes, s, n;
u8_t *write_buf;
static int read_vorbis_header(void) {
int status = 0;
bool fetch = true;
LOCK_S;
if (stream.state <= DISCONNECT && v->end) {
UNLOCK_S;
return DECODE_COMPLETE;
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);
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;
if (!OG(&go, sync_pageseek, &v->sync, &v->page)) continue;
}
switch (v->status) {
case OGG_SYNC:
v->status = OGG_ID_HEADER;
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);
if (!OG(&go, stream_packetout, &v->state, &v->packet)) break;
OV(&gv, info_init, &v->info);
status = OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet);
if (status) {
LOG_ERROR("vorbis id header packet error %d", status);
status = -1;
} else {
v->channels = v->info.channels;
v->rate = v->info.rate;
v->status = OGG_COMMENT_HEADER;
// 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");
}
break;
case OGG_SETUP_HEADER:
// header packets don't align with pages on Vorbis (contrary to Opus)
if (fetch) OG(&go, stream_pagein, &v->state, &v->page);
// finally build a codec if we have the packet
status = OG(&go, stream_packetout, &v->state, &v->packet);
if (status && ((status = OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet)) ||
(status = OV(&gv, synthesis_init, &v->decoder, &v->info)))) {
LOG_ERROR("vorbis setup header packet error %d", status);
// no need to free comment, it's fake
OV(&gv, info_clear, &v->info);
status = -1;
} else {
OV(&gv, block_init, &v->decoder, &v->block);
v->opened = true;
LOG_INFO("codec up and running (rate: %d, channels:%d)", v->rate, v->channels);
status = 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;
}
}
UNLOCK_S;
return status;
}
inline int pcm_out(vorbis_dsp_state* decoder, void*** pcm) {
#ifndef TREMOR_ONLY
if (!tremor) return OV(&gv, synthesis_pcmout, decoder, (ogg_float_t***) pcm);
#endif
return OV(&gv, synthesis_pcmout, decoder, (ogg_int32_t***) pcm);
}
static decode_state vorbis_decode(void) {
frames_t frames;
u8_t *write_buf;
if (decode.new_stream) {
ov_callbacks cbs;
int err;
struct vorbis_info *info;
int status = read_vorbis_header();
cbs.read_func = _read_cb;
if (TREMOR(v)) {
cbs.seek_func = _seek_cb; cbs.close_func = _close_cb; cbs.tell_func = _tell_cb;
} else {
cbs.seek_func = NULL; cbs.close_func = NULL; cbs.tell_func = NULL;
if (status == 0) {
return DECODE_RUNNING;
} else if (status < 0) {
LOG_WARN("can't create codec");
return DECODE_ERROR;
}
if ((err = OV(v, open_callbacks, streambuf, v->vf, NULL, 0, cbs)) < 0) {
LOG_WARN("open_callbacks error: %d", err);
return DECODE_COMPLETE;
}
v->opened = true;
info = OV(v, info, v->vf, -1);
LOG_INFO("setting track_start");
LOCK_O;
output.next_sample_rate = decode_newstream(info->rate, output.supported_rates);
output.next_sample_rate = decode_newstream(v->rate, output.supported_rates);
IF_DSD( output.next_fmt = PCM; )
output.track_start = outputbuf->writep;
if (output.fade_mode) _checkfade(true);
decode.new_stream = false;
UNLOCK_O;
channels = info->channels;
if (channels > 2) {
LOG_WARN("too many channels: %d", channels);
if (v->channels > 2) {
LOG_WARN("too many channels: %d", v->channels);
return DECODE_ERROR;
}
LOG_INFO("setting track_start");
}
#if FRAME_BUF
IF_DIRECT(
frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
frames = min(frames, FRAME_BUF);
write_buf = v->write_buf;
);
#else
LOCK_O_direct;
IF_DIRECT(
frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
write_buf = outputbuf->writep;
);
#endif
IF_PROCESS(
frames = process.max_in_frames;
write_buf = process.inbuf;
);
void** pcm = NULL;
int packet, n = 0;
bytes = frames * 2 * channels; // samples returned are 16 bits
v->end = frames == 0;
// write the decoded frames into outputbuf even though they are 16 bits per sample, then unpack them
#ifdef TREMOR_ONLY
n = OV(v, read, v->vf, (char *)write_buf, bytes, &s);
#else
if (!TREMOR(v)) {
#if SL_LITTLE_ENDIAN
n = OV(v, read, v->vf, (char *)write_buf, bytes, 0, 2, 1, &s);
#else
n = OV(v, read, v->vf, (char *)write_buf, bytes, 1, 2, 1, &s);
#endif
#if !WIN
} else {
n = OV(v, read_tremor, v->vf, (char *)write_buf, bytes, &s);
#endif
if (v->overflow) {
n = pcm_out(&v->decoder, &pcm);
v->overflow = n - min(n, frames);
} else if ((packet = get_ogg_packet()) > 0) {
n = OV(&gv, synthesis, &v->block, &v->packet);
if (n == 0) n = OV(&gv, synthesis_blockin, &v->decoder, &v->block);
if (n == 0) n = pcm_out(&v->decoder, &pcm);
v->overflow = n - min(n, frames);
} else if (!packet && !OG(&go, page_eos, &v->page)) {
UNLOCK_O_direct;
return DECODE_RUNNING;
}
#endif
#if FRAME_BUF
LOCK_O_direct;
#endif
if (n > 0) {
frames_t count;
s16_t *iptr;
ISAMPLE_T *optr;
ISAMPLE_T *optr = (ISAMPLE_T*) write_buf;
frames = min(n, frames);
frames_t count = frames;
#ifndef TREMOR_ONLY
if (!tremor) {
if (v->channels == 2) {
float* iptr_l = (float*) pcm[0];
float* iptr_r = (float*) pcm[1];
frames = n / 2 / channels;
count = frames * channels;
// work backward to unpack samples (if needed)
iptr = (s16_t *) write_buf + count;
IF_DIRECT(
optr = (ISAMPLE_T *) outputbuf->writep + frames * 2;
)
IF_PROCESS(
optr = (ISAMPLE_T *) write_buf + frames * 2;
)
if (channels == 2) {
#if BYTES_PER_FRAME == 4
#if FRAME_BUF
// copy needed only when DIRECT and FRAME_BUF
IF_DIRECT(
memcpy(outputbuf->writep, write_buf, frames * BYTES_PER_FRAME);
)
#endif
while (count--) {
*optr++ = ALIGN_FLOAT(*iptr_l++);
*optr++ = ALIGN_FLOAT(*iptr_r++);;
}
} else if (v->channels == 1) {
float* iptr = pcm[0];
while (count--) {
*optr++ = ALIGN_FLOAT(*iptr);
*optr++ = ALIGN_FLOAT(*iptr++);
}
}
} else
#else
while (count--) {
*--optr = ALIGN(*--iptr);
}
{
if (v->channels == 2) {
s32_t* iptr_l = (s32_t*) pcm[0];
s32_t* iptr_r = (s32_t*) pcm[1];
while (count--) {
*optr++ = ALIGN(*iptr_l++);
*optr++ = ALIGN(*iptr_r++);
}
} else if (v->channels == 1) {
s32_t* iptr = (s32_t*) pcm[0];
while (count--) {
*optr++ = ALIGN(*iptr);
*optr++ = ALIGN(*iptr++);
}
}
}
#endif
} else if (channels == 1) {
while (count--) {
*--optr = ALIGN(*--iptr);
*--optr = ALIGN(*iptr);
}
}
// return samples to vorbis/tremor decoder
OV(&gv, synthesis_read, &v->decoder, frames);
IF_DIRECT(
_buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME);
@@ -275,19 +374,14 @@ static decode_state vorbis_decode(void) {
} else if (n == 0) {
if (stream.state <= DISCONNECT) {
LOG_INFO("partial decode");
if (packet < 0) {
LOG_INFO("end of decode");
UNLOCK_O_direct;
return DECODE_COMPLETE;
} else {
LOG_INFO("no frame decoded");
}
} else if (n == OV_HOLE) {
// recoverable hole in stream, seen when skipping
LOG_DEBUG("hole in stream");
} else {
LOG_INFO("ov_read error: %d", n);
@@ -300,55 +394,80 @@ static decode_state vorbis_decode(void) {
}
static void vorbis_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
if (!v->vf) {
v->vf = malloc(sizeof(OggVorbis_File) + 128); // add some padding as struct size may be larger
memset(v->vf, 0, sizeof(OggVorbis_File) + 128);
#if FRAME_BUF
v->write_buf = malloc(FRAME_BUF * BYTES_PER_FRAME);
#endif
} else {
if (v->opened) {
OV(v, clear, v->vf);
v->opened = false;
}
LOG_INFO("OPENING CODEC");
if (v->opened) {
OV(&go, block_clear, &v->block);
OV(&go, info_clear, &v->info);
OV(&go, dsp_clear, &v->decoder);
}
v->opened = false;
v->status = OGG_SYNC;
v->overflow = 0;
OG(&gu, sync_clear, &v->sync);
OG(&gu, stream_clear, &v->state);
OG(&gu, stream_init, &v->state, -1);
}
static void vorbis_close(void) {
static void vorbis_close() {
return;
LOG_INFO("CLOSING CODEC");
if (v->opened) {
OV(v, clear, v->vf);
v->opened = false;
OV(&go, block_clear, &v->block);
OV(&go, info_clear, &v->info);
OV(&go, dsp_clear, &v->decoder);
}
free(v->vf);
#if FRAME_BUF
free(v->write_buf);
v->write_buf = NULL;
#endif
v->vf = NULL;
v->end = false;
v->opened = false;
OG(&go, stream_clear, &v->state);
OG(&go, sync_clear, &v->sync);
}
static bool load_vorbis() {
#if !LINKALL
void *handle = dlopen(LIBVORBIS, RTLD_NOW);
char *err;
bool tremor = false;
if (!handle) {
handle = dlopen(LIBTREMOR, RTLD_NOW);
if (handle) {
char *err;
void *g_handle = dlopen(LIBOGG, RTLD_NOW);
if (!g_handle) {
LOG_INFO("ogg dlerror: %s", dlerror());
return false
}
void *v_handle = NULL;
#ifndef TREMOR_ONLY
v_handle = dlopen(LIBVORBIS, RTLD_NOW);
#endif
if (!v_handle) {
v_handle = dlopen(LIBTREMOR, RTLD_NOW);
if (v_handle) {
tremor = true;
} else {
LOG_INFO("dlerror: %s", dlerror());
dlclose(g_handle);
LOG_INFO("vorbis/tremor dlerror: %s", dlerror());
return false;
}
}
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_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_sync_clear = dlsym(g_handle->handle, "ogg_sync_clear");
g_handle->ogg_packet_clear = dlsym(g_handle->handle, "ogg_packet_clear");
g_handle->ogg_sync_buffer = dlsym(g_handle->handle, "ogg_sync_buffer");
g_handle->ogg_sync_wrote = dlsym(g_handle->handle, "ogg_sync_wrote");
g_handle->ogg_sync_pageseek = dlsym(g_handle->handle, "ogg_sync_pageseek");
g_handle->ogg_sync_pageout = dlsym(g_handle->handle, "ogg_sync_pageout");
g_handle->ogg_stream_pagein = dlsym(g_handle->handle, "ogg_stream_pagein");
g_handle->ogg_stream_packetout = dlsym(g_handle->handle, "ogg_stream_packetout");
g_handle->ogg_page_packets = dlsym(g_handle->handle, "ogg_page_packets");
v->ov_read = tremor ? NULL : dlsym(handle, "ov_read");
v->ov_read_tremor = tremor ? dlsym(handle, "ov_read") : NULL;
v->ov_info = dlsym(handle, "ov_info");
v->ov_clear = dlsym(handle, "ov_clear");
v->ov_open_callbacks = dlsym(handle, "ov_open_callbacks");
v_handle.ov_read = dlsym(handle, "ov_read");
v_handle.ov_info = dlsym(handle, "ov_info");
v_handle.ov_clear = dlsym(handle, "ov_clear");
v_handle.ov_open_callbacks = dlsym(handle, "ov_open_callbacks");
if ((err = dlerror()) != NULL) {
LOG_INFO("dlerror: %s", err);
@@ -372,12 +491,10 @@ struct codec *register_vorbis(void) {
vorbis_decode,// decode
};
v = malloc(sizeof(struct vorbis));
if (!v) {
if ((v = calloc(1, sizeof(struct vorbis))) == NULL) {
return NULL;
}
v->vf = NULL;
v->opened = false;
if (!load_vorbis()) {

View File

@@ -128,8 +128,8 @@ void init_telnet(){
if (bMirrorToUART) uart_fd = open("/dev/uart/0", O_RDWR);
ESP_ERROR_CHECK(esp_vfs_register("/dev/pkspstdout", &vfs, NULL));
freopen("/dev/pkspstdout", "wb", stdout);
freopen("/dev/pkspstdout", "wb", stderr);
freopen("/dev/pkspstdout", "w", stdout);
freopen("/dev/pkspstdout", "w", stderr);
bIsEnabled=true;
}

View File

@@ -266,6 +266,11 @@ cJSON* network_status_get_basic_info(cJSON** old) {
*old = network_status_update_float(old, "avg_conn_time", nm->num_disconnect > 0 ? (nm->total_connected_time / nm->num_disconnect) : 0);
*old = network_update_cjson_number(old, "bt_status", bt_app_source_get_a2d_state());
*old = network_update_cjson_number(old, "bt_sub_status", bt_app_source_get_media_state());
#if DEPTH == 16
*old = network_update_cjson_number(old, "depth", 16);
#elif DEPTH == 32
*old = network_update_cjson_number(old, "depth", 32);
#endif
#if CONFIG_I2C_LOCKED
*old = network_status_update_bool(old, "is_i2c_locked", true);
#else

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -24,6 +24,27 @@ declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
@@ -50,6 +71,48 @@ declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;

View File

@@ -1,3 +1,4 @@
declare const PORT: 9100;
import HtmlWebPackPlugin = require("html-webpack-plugin");
export namespace entry {
const test: string;
@@ -17,7 +18,7 @@ export namespace devServer {
}
export const open: boolean;
export const compress: boolean;
export const port: number;
export { PORT as port };
export const host: string;
export const allowedHosts: string;
export const headers: {
@@ -33,3 +34,4 @@ export namespace devServer {
export function onBeforeSetupMiddleware(devServer: any): void;
}
export const plugins: HtmlWebPackPlugin[];
export {};

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