forked from gronod/squeezelite-esp32
Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| db7d90e736 | |||
| c5233cf15c | |||
| 0203682200 | |||
| 0fee152cce | |||
| 7ca2e931eb | |||
| b15263b961 | |||
| 967f448994 | |||
| 689a9fb057 | |||
| a71aff6882 | |||
| e46df63090 | |||
| d27825d62c | |||
| a33f699dc7 | |||
| 70a79b8731 | |||
| b585f24f37 | |||
| 9d8be934ac | |||
| d55768ffe1 | |||
| dd62f1aae6 | |||
| adfb8b0767 | |||
| 83d6322bcf | |||
| cd56b20a06 | |||
| ec60400673 | |||
| 25f4855375 | |||
| 1d21662f89 | |||
| 1835423924 | |||
| 2abdd91aa6 | |||
| d7093b0bab | |||
| 4d36958008 | |||
| 2d90c823af | |||
| 289026527b | |||
| bca8c21322 | |||
| f6763ebead | |||
| f64c09cf30 | |||
| c0d2add55b | |||
| 1e3de24bdf | |||
| f87d38b5e9 | |||
| 23234f7189 | |||
| 9f783b6b5d | |||
| 752cfbf3b2 | |||
| cecb7fd876 | |||
| e92e431b45 | |||
| db792e47bd | |||
| a22f75a13a | |||
| 1f220895e6 | |||
| 769ff99f7d | |||
| 424fb93ec4 | |||
| e270963dbd | |||
| 2cae41d29c | |||
| 84b95cd79c | |||
| 6369f4bd69 | |||
| 4c1bca3166 | |||
| 3a5163e6f6 | |||
| cbe42b56bc | |||
| ab9812cb75 | |||
| 084caedd7e | |||
| f254bf49af | |||
| 66bd26f007 | |||
| dd6c932c39 | |||
| 50070378ad | |||
| b50bc8f376 | |||
| e6723dfa2f | |||
| ffaff5ac27 | |||
| 33ef4b01e7 | |||
| 302865b167 | |||
| 7f0ae69e81 | |||
| e21e2cf7f9 | |||
| 57cd009e4c | |||
| fdd8b0a4c9 | |||
| 78e8d60021 | |||
| 4068e07a45 | |||
| f8d7ac23e1 | |||
| befc81f573 | |||
| a633524936 | |||
| 9d71b8ee26 | |||
| 672aca8258 | |||
| a2351ba0d5 | |||
| 40a698e2f1 | |||
| 38d28ae8c4 | |||
| 21407e8c1c | |||
| 12aa555ff3 | |||
| cb47ec855b | |||
| 787a5d9a6e | |||
| 7b9deb795c | |||
| 4b1f8a8d4b | |||
| 4f8661100b | |||
| 6b2eb1b3c0 | |||
| f3593fa2f4 | |||
| ccc7b86369 | |||
| 9e3c6dcf30 | |||
| 7be81887a6 | |||
| 29f71fc677 | |||
| a14a6edc1b | |||
| 35bf0a3c10 | |||
| 55a8658f3a | |||
| b0d9e1668c |
+1
-1
@@ -1,5 +1,5 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
# * text=auto
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.cs diff=csharp
|
||||
|
||||
@@ -136,6 +136,8 @@ jobs:
|
||||
name: prebuilt_objects
|
||||
- name: Build the firmware
|
||||
if: ${{ needs.bootstrap.outputs.mock == 0 }}
|
||||
env:
|
||||
SPOTIFY_SECRET: -DCLIENT_ID="${{ secrets.SPOTIFY_CLIENT_ID }}" -DCLIENT_SECRET="${{ secrets.SPOTIFY_CLIENT_SECRET }}"
|
||||
run: |
|
||||
. ${IDF_PYTHON_ENV_PATH}/bin/activate
|
||||
echo "Copying target sdkconfig"
|
||||
@@ -171,7 +173,7 @@ jobs:
|
||||
zip build/${artifact_file_name} partitions*.csv components/ build/*.bin build/bootloader/bootloader.bin build/partition_table/partition-table.bin build/flash_project_args build/size_*.txt
|
||||
fi
|
||||
- name: Upload Build Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ needs.bootstrap.outputs.mock == 0 }}
|
||||
with:
|
||||
name: ${{ env.artifact_prefix }}
|
||||
|
||||
@@ -18,3 +18,4 @@ components/wifi-manager/UML-State-Machine-in-C
|
||||
envfile.txt
|
||||
artifacts
|
||||
web-installer
|
||||
client_info.h
|
||||
|
||||
@@ -1,3 +1,47 @@
|
||||
2025-12-25
|
||||
- Use dedicated client_id and client_secret for spotify
|
||||
|
||||
2025-02-17
|
||||
- reverse some checks on display not NULL in gds.c. As it is about being fast, I'd prefer the caller to know that there is no display and don't call. I'm sure I have missed something when there is only led_vu and no display, but people will remind me soon enough :-)
|
||||
|
||||
2024-09-28
|
||||
- add dedicated volume encoder
|
||||
- fix memory leak in rotary config creation
|
||||
|
||||
2024-09-28
|
||||
- create autoexec NVS entry at the right place (not only whne BT is enabled!
|
||||
- try to make i2s panic mode work for all esp versions
|
||||
|
||||
2024-09-12
|
||||
- add AW9523 GPIO expander credits @Stefan Krupop (https://github.com/sle118/squeezelite-esp32/pull/430
|
||||
|
||||
2024-09-10
|
||||
- Merge pull request # 439 from digidocs/eq_update_fix2 (# 309)
|
||||
- Fix for I2S noise burst when ESP32 panic occurs (# 437)
|
||||
|
||||
2024-05-05
|
||||
- Fix crash when led_vu is configured without display
|
||||
2024-01-27
|
||||
- complete libflac fix and add chaining enablement
|
||||
- fixed stream Ogg demux issue with unknown granule
|
||||
|
||||
2024-01-19
|
||||
- fixed libflac with OggFlac
|
||||
- AirPlay missed frame logging
|
||||
|
||||
2024-01-16
|
||||
- catch-up with cspot latest
|
||||
- refactor airplay flush/first packet
|
||||
- new libFLAC that supports multi-stream OggFlac
|
||||
- fix output threshold
|
||||
- log missed frames
|
||||
|
||||
2024-01-10
|
||||
- add OggFlac to stream metadata
|
||||
- fix OggFlac deadlock in flac callback when not enough data in streambuf
|
||||
- fix no displayer due to threadshold too high (use 500ms instead)
|
||||
- reset outputbuf when cspot starts
|
||||
|
||||
2024-01-01
|
||||
- ogg stream are parsed to foward metadata to LMS
|
||||
- fix some ogg parsing on multi-stream containers
|
||||
@@ -14,7 +58,7 @@
|
||||
- force gpio_pad_select_gpio in dac_controlset in case somebody uses UART gpio's (or other pre-programmed)
|
||||
|
||||
2023-11-08
|
||||
- execute dac_controlset even whne there is no i2s (for gpio)
|
||||
- execute dac_controlset even when there is no i2s (for gpio)
|
||||
|
||||
2023-11-07
|
||||
- led-vu gain + misc fixes
|
||||
|
||||
+2
-2
@@ -73,7 +73,7 @@ set_target_properties(recovery.elf PROPERTIES LINK_LIBRARIES "${BCA};idf::app_re
|
||||
# build squeezelite, add app_squeezelite to the link
|
||||
add_executable(squeezelite.elf "CMakeLists.txt")
|
||||
add_dependencies(squeezelite.elf recovery.elf)
|
||||
set_target_properties(squeezelite.elf PROPERTIES LINK_LIBRARIES "${BCA};idf::app_squeezelite;-Wl,--Map=${BUILD_DIR}/squeezelite.map")
|
||||
set_target_properties(squeezelite.elf PROPERTIES LINK_LIBRARIES "${BCA};idf::app_squeezelite;-Wl,--Map=${BUILD_DIR}/squeezelite.map,--wrap=esp_panic_handler")
|
||||
add_custom_command(
|
||||
TARGET squeezelite.elf
|
||||
POST_BUILD
|
||||
@@ -228,4 +228,4 @@ endif()
|
||||
# target_compile_definitions(__idf_wear_levelling PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
|
||||
# target_compile_definitions(__idf_wifi_provisioning PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
|
||||
# target_compile_definitions(__idf_wpa_supplicant PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
|
||||
# target_compile_definitions(__idf_xtensa PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
|
||||
# target_compile_definitions(__idf_xtensa PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
# Guition JC4827W543C Board Setup Guide
|
||||
|
||||
This guide explains how to configure and build squeezelite-esp32 for the Guition JC4827W543C development board.
|
||||
|
||||
## Board Features
|
||||
|
||||
- **Processor**: ESP32-S3-WROOM-1 (dual-core, 240MHz)
|
||||
- **Display**: 4.3" ILI9488 LCD (480x272 pixels)
|
||||
- **Touch**: GT911 capacitive touch controller
|
||||
- **Interface**: QSPI display interface
|
||||
- **Memory**: 4MB Flash, 4MB PSRAM
|
||||
- **Connectivity**: WiFi, Bluetooth
|
||||
|
||||
## Hardware Configuration
|
||||
|
||||
### Display Connections (QSPI)
|
||||
- **CLK**: GPIO47
|
||||
- **DATA0**: GPIO21
|
||||
- **DATA1**: GPIO48
|
||||
- **DATA2**: GPIO40
|
||||
- **DATA3**: GPIO39
|
||||
- **CS**: GPIO45
|
||||
- **DC**: Not used with QSPI
|
||||
- **RST**: GPIO38 (if available)
|
||||
|
||||
### I2C Connections (Touch)
|
||||
- **SDA**: GPIO8
|
||||
- **SCL**: GPIO4
|
||||
- **Touch INT**: GPIO3
|
||||
- **Touch RST**: GPIO38
|
||||
|
||||
### Audio I2S (External DAC Required)
|
||||
- **BCK**: GPIO15
|
||||
- **WS**: GPIO16
|
||||
- **DO**: GPIO17
|
||||
- **DI**: (not used for output only)
|
||||
|
||||
### Other GPIO
|
||||
- **Backlight**: GPIO1 (PWM controlled)
|
||||
|
||||
## Software Configuration
|
||||
|
||||
### 1. Build Configuration
|
||||
|
||||
Use the provided build script:
|
||||
|
||||
```bash
|
||||
# Make the script executable
|
||||
chmod +x build-guition.sh
|
||||
|
||||
# Configure and build
|
||||
./build-guition.sh
|
||||
```
|
||||
|
||||
Or manually:
|
||||
|
||||
```bash
|
||||
# Set ESP32-S3 target
|
||||
export IDF_TARGET=esp32s3
|
||||
|
||||
# Copy Guition configuration
|
||||
cp squeezelite-esp32-Guition-sdkconfig.defaults sdkconfig.defaults
|
||||
|
||||
# Configure project
|
||||
idf.py menuconfig
|
||||
|
||||
# Build
|
||||
idf.py build
|
||||
```
|
||||
|
||||
### 2. Menuconfig Settings
|
||||
|
||||
In `idf.py menuconfig`, select:
|
||||
|
||||
**Target Configuration**:
|
||||
- `Squeezelite-ESP32` → `Guition JC4827W543C`
|
||||
|
||||
**Audio Settings**:
|
||||
- Configure I2S GPIO pins for your external DAC
|
||||
- Default: BCK=15, WS=16, DO=17
|
||||
|
||||
**Display Settings**:
|
||||
- Should be automatically configured as:
|
||||
- Type: QSPI
|
||||
- Driver: ILI9488
|
||||
- Width: 480
|
||||
- Height: 272
|
||||
- CS: 45
|
||||
- Speed: 20MHz
|
||||
|
||||
**I2C Settings**:
|
||||
- SDA: 8
|
||||
- SCL: 4
|
||||
- Speed: 400kHz
|
||||
- Port: 0
|
||||
|
||||
### 3. External Audio DAC
|
||||
|
||||
The Guition board does not have a built-in audio DAC. You must connect an external I2S DAC such as:
|
||||
|
||||
- PCM5102
|
||||
- TAS575x series
|
||||
- ES8388
|
||||
- AC101
|
||||
|
||||
Connect the DAC to the I2S pins and configure it in the NVS settings or through the web UI.
|
||||
|
||||
## Flashing the Firmware
|
||||
|
||||
```bash
|
||||
# Flash to the board
|
||||
idf.py -p /dev/ttyUSB0 flash
|
||||
|
||||
# Monitor output (optional)
|
||||
idf.py -p /dev/ttyUSB0 monitor
|
||||
```
|
||||
|
||||
## First Boot Configuration
|
||||
|
||||
1. Connect to the WiFi AP created by the device (SSID: SqueezeESP32-XXXXXX)
|
||||
2. Open the web configuration interface
|
||||
3. Configure your WiFi network
|
||||
4. Set up your audio DAC if needed
|
||||
5. Configure LMS server connection
|
||||
|
||||
## Display Features
|
||||
|
||||
The ILI9488 driver supports:
|
||||
- 16-bit RGB565 color
|
||||
- Hardware acceleration
|
||||
- Shadow buffering for performance
|
||||
- Automatic dirty region tracking
|
||||
- Rotation and flip options
|
||||
|
||||
## Touch Support
|
||||
|
||||
Touch functionality is planned but not yet implemented. The GT911 touch controller is connected via I2C.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Display Not Working
|
||||
- Check QSPI GPIO connections
|
||||
- Verify CS pin is correctly set to GPIO45
|
||||
- Ensure proper power supply (3.3V for display logic)
|
||||
|
||||
### Audio Not Working
|
||||
- Verify external DAC is properly connected
|
||||
- Check I2S GPIO assignments
|
||||
- Configure DAC model in web UI if using a supported DAC
|
||||
|
||||
### Build Issues
|
||||
- Ensure ESP-IDF supports ESP32-S3
|
||||
- Check that all required components are included
|
||||
- Verify GPIO pin assignments don't conflict
|
||||
|
||||
## Performance Notes
|
||||
|
||||
- The 480x272 display requires significant memory bandwidth
|
||||
- PSRAM is used for display framebuffer (≈260KB)
|
||||
- Audio performance may be affected at high display refresh rates
|
||||
- Consider reducing display update rate if audio issues occur
|
||||
|
||||
## GPIO Map Summary
|
||||
|
||||
| GPIO | Function | Direction |
|
||||
|------|----------|-----------|
|
||||
| 1 | Backlight PWM | Out |
|
||||
| 3 | Touch INT | In |
|
||||
| 4 | I2C SCL | Out |
|
||||
| 8 | I2C SDA | I/O |
|
||||
| 15 | I2S BCK | Out |
|
||||
| 16 | I2S WS | Out |
|
||||
| 17 | I2S DO | Out |
|
||||
| 21 | QSPI DATA0 | Out |
|
||||
| 39 | QSPI DATA3 | Out |
|
||||
| 40 | QSPI DATA2 | Out |
|
||||
| 45 | QSPI CS | Out |
|
||||
| 47 | QSPI CLK | Out |
|
||||
| 48 | QSPI DATA1 | Out |
|
||||
| 38 | Touch RST | Out |
|
||||
|
||||
## Support
|
||||
|
||||
For issues specific to the Guition board implementation:
|
||||
1. Check this README first
|
||||
2. Review the squeezelite-esp32 documentation
|
||||
3. Open an issue on the GitHub repository
|
||||
|
||||
For general squeezelite-esp32 questions, refer to the main project documentation and forums.
|
||||
@@ -0,0 +1,124 @@
|
||||
# SqueezeLite ESP32 - PlatformIO Configuration
|
||||
|
||||
This project has been configured to work with PlatformIO while maintaining compatibility with the original ESP-IDF structure.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
squeezelite-esp32-gronod/
|
||||
├── platformio.ini # PlatformIO configuration file
|
||||
├── src/ # Main source files
|
||||
│ ├── esp_app_main.c # Application entry point
|
||||
│ ├── platform_esp32.h # ESP32 platform definitions
|
||||
│ └── Params.txt # Configuration parameters
|
||||
├── lib/ # Component libraries (moved from components/)
|
||||
│ ├── wifi-manager/ # WiFi management component
|
||||
│ ├── squeezelite/ # Squeezelite audio player
|
||||
│ ├── platform_console/ # Platform console
|
||||
│ ├── display/ # Display drivers
|
||||
│ ├── telnet/ # Telnet interface
|
||||
│ ├── audio/ # Audio processing
|
||||
│ ├── codecs/ # Audio codecs
|
||||
│ ├── spotify/ # Spotify integration (CSpot)
|
||||
│ └── ... # Other components
|
||||
├── include/ # Global headers
|
||||
├── partitions.csv # Partition table
|
||||
└── build_scripts/ # Custom build scripts
|
||||
```
|
||||
|
||||
## Key Differences from ESP-IDF
|
||||
|
||||
### 1. Directory Structure
|
||||
- `main/` → `src/` (PlatformIO standard)
|
||||
- `components/` → `lib/` (PlatformIO libraries)
|
||||
- `include/` created for global headers
|
||||
|
||||
### 2. Build System
|
||||
- Uses PlatformIO's build system instead of CMake
|
||||
- Components are configured as libraries with `library.json` files
|
||||
- Build flags and options configured in `platformio.ini`
|
||||
|
||||
### 3. Multiple Targets
|
||||
The original ESP-IDF project builds both `recovery` and `squeezelite` binaries. PlatformIO builds the main squeezelite application. Recovery functionality can be added as a separate build environment if needed.
|
||||
|
||||
## Building with PlatformIO
|
||||
|
||||
### Prerequisites
|
||||
- PlatformIO IDE or command-line tools
|
||||
- ESP32 development board
|
||||
|
||||
### Build Commands
|
||||
```bash
|
||||
# Build the project
|
||||
pio run
|
||||
|
||||
# Upload to device
|
||||
pio run --target upload
|
||||
|
||||
# Monitor serial output
|
||||
pio device monitor
|
||||
|
||||
# Clean build
|
||||
pio run --target clean
|
||||
```
|
||||
|
||||
### Configuration
|
||||
- Edit `platformio.ini` for board-specific settings
|
||||
- Adjust `src/Params.txt` for application configuration
|
||||
- Modify build flags in `platformio.ini` as needed
|
||||
|
||||
## Component Libraries
|
||||
|
||||
Each major component has been converted to a PlatformIO library with a `library.json` file:
|
||||
|
||||
- **wifi-manager**: WiFi network management
|
||||
- **squeezelite**: Core audio player functionality
|
||||
- **platform_console**: System console and commands
|
||||
- **display**: Display drivers and UI
|
||||
- **telnet**: Telnet interface for remote control
|
||||
- **audio**: Audio processing and output
|
||||
- **codecs**: Audio codec support
|
||||
- **spotify**: Spotify integration via CSpot
|
||||
|
||||
## ESP-IDF Compatibility
|
||||
|
||||
The project maintains ESP-IDF compatibility:
|
||||
- All ESP-IDF components are still available
|
||||
- ESP-IDF configuration files (sdkconfig.*) are preserved
|
||||
- Original ESP-IDF build system can still be used
|
||||
|
||||
## Migration Notes
|
||||
|
||||
### What Changed
|
||||
1. **platformio.ini**: Main configuration file
|
||||
2. **Directory structure**: Reorganized for PlatformIO
|
||||
3. **Library metadata**: Added library.json files
|
||||
4. **Build flags**: Migrated to platformio.ini format
|
||||
|
||||
### What Remained
|
||||
- All source code files remain unchanged
|
||||
- ESP-IDF component structure preserved in lib/
|
||||
- Configuration files (sdkconfig, partitions.csv)
|
||||
- Original functionality maintained
|
||||
|
||||
### Known Limitations
|
||||
1. **Multiple ELF files**: PlatformIO doesn't natively support building both recovery and squeezelite binaries in one build
|
||||
2. **Complex build scripts**: Some advanced ESP-IDF features may need custom PlatformIO scripts
|
||||
3. **Kconfig**: ESP-IDF's Kconfig system is not directly supported in PlatformIO
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build Issues
|
||||
- Ensure all required libraries are in lib/ directory
|
||||
- Check build flags in platformio.ini
|
||||
- Verify ESP-IDF environment variables if using local ESP-IDF
|
||||
|
||||
### Component Issues
|
||||
- Each component needs a library.json file
|
||||
- Check component dependencies in platformio.ini
|
||||
- Verify include paths and source file locations
|
||||
|
||||
### Flash Issues
|
||||
- Check partition table configuration
|
||||
- Verify flash size settings
|
||||
- Ensure correct board configuration in platformio.ini
|
||||
@@ -15,6 +15,7 @@ Depending on the hardware connected to the esp32, you can send audio to a local
|
||||
But squeezelite-esp32 is highly extensible and you can add
|
||||
|
||||
- [Buttons](#buttons) and [Rotary Encoder](#rotary-encoder) and map/combine them to various functions (play, pause, volume, next ...)
|
||||
- [Volume Encoder](#volume-rotary-encoder) for a dedicated volume rotary encoder
|
||||
- [GPIO expander](#gpio-expanders) (buttons, led and rotary)
|
||||
- [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).
|
||||
@@ -147,15 +148,30 @@ VCC - 3.3V
|
||||
GND - GND
|
||||
FLT - GND
|
||||
DMP - GND
|
||||
SCL - GND
|
||||
SCK - GND
|
||||
BCK - (BCK - see below)
|
||||
DIN - (DO - see below)
|
||||
LCK - (WS - see below)
|
||||
LCK - (WS - see below)
|
||||
FMT - GND
|
||||
XMT - 3.3V
|
||||
XMT - 3.3V
|
||||
|
||||
Use the `squeezelite-esp32-I2S-4MFlash-sdkconfig.defaults` configuration file.
|
||||
|
||||
### ESP32 LyraT Mini v1.2
|
||||
|
||||
This board is one of the [audio developpement board](https://docs.espressif.com/projects/esp-adf/en/latest/design-guide/dev-boards/get-started-esp32-lyrat-mini.html) designed by espressif for their ESP-ADF (Espressif Audio Development Framework). It uses ESP32-WROVER-E as it core and ES8311 as DAC.
|
||||
A jack connector can be used or an HP output.
|
||||
It also contains 2 LEDS (one green, one blue) and 8 Buttons. Those buttons are not supported for now.
|
||||
An ADC is also present on the board, but not used in squeezelite use case.
|
||||
|
||||
As for now, audio playback and LEDs are working.
|
||||
|
||||
Use the `squeezelite-esp32-I2S-4MFlash-sdkconfig.defaults` configuration file.
|
||||
|
||||
- i2c_config: `scl=25,sda=18,speed=400000,port=1`
|
||||
- dac_config: `model=es8311,bck=5,ws=25,do=26,sda=18,scl=23,i2c=24`
|
||||
- set_GPIO: `22=green,27=red,19=jack`
|
||||
|
||||
### SqueezeAmpToo !
|
||||
|
||||
And the super cool project https://github.com/rochuck/squeeze-amp-too
|
||||
@@ -183,11 +199,11 @@ Default and only "host" is 1 as others are used already by flash and spiram. The
|
||||
### 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 named configurations
|
||||
```
|
||||
bck=<gpio>,ws=<gpio>,do=<gpio>[,mck=0|1|2][,mute=<gpio>[:0|1][,model=TAS57xx|TAS5713|AC101|WM8978|ES8388|I2S][,sda=<gpio>,scl=<gpio>[,i2c=<addr>]]
|
||||
bck=<gpio>,ws=<gpio>,do=<gpio>[,mck=0|1|2][,mute=<gpio>[:0|1][,model=TAS57xx|TAS5713|AC101|WM8978|ES8388|ES8311|I2S][,sda=<gpio>,scl=<gpio>[,i2c=<addr>]]
|
||||
```
|
||||
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). By default GPIO0 is used as MCLK and only recent builds (post mid-2023) can use 1 or 2. Also 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, speaker and headset on and off using a JSON syntax:
|
||||
So far, TAS57xx, TAS5713, AC101, WM8978, ES8388 and ES8311 are recognized models where the proper init sequence/volume/power controls are sent. For other codecs that might require an I2C commands, please use the parameter "dac_controlset" that allows definition of simple commands to be sent over i2c for init, power, speaker and headset on and off using a JSON syntax:
|
||||
```json
|
||||
{ <command>: [ <item1>, <item2>, ... <item3> ],
|
||||
<command>: [ <item1>, <item2>, ... <item3> ],
|
||||
@@ -237,13 +253,14 @@ Ground -------------------------- coax signal ground
|
||||
### Display
|
||||
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[:x=<offset>][:y=<offset>]|ILI9341[:16|18][,rotate]]
|
||||
I2C,width=<pixels>,height=<pixels>[address=<i2c_address>][,reset=<gpio>][,HFlip][,VFlip][,invert][driver=SSD1306|SSD1326[:1|4]|SSD1327|SH1106]
|
||||
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,reset=<gpio>][,speed=<speed>][,HFlip][,VFlip][,invert][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735|ST7789[:x=<offset>][:y=<offset>]|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. 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
|
||||
- invert: invert each pixel colorspace
|
||||
- Default speed is 8000000 (8MHz) but SPI can work up to 26MHz or even 40MHz
|
||||
- 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)
|
||||
@@ -308,7 +325,7 @@ The parameter "gpio_exp_config" is a semicolon (;) separated list with following
|
||||
```
|
||||
model=<model>,addr=<addr>,[,port=system|dac][,base=<n>][,count=<n>][,intr=<gpio>][,cs=<gpio>][,speed=<Hz>]
|
||||
```
|
||||
- model: pca9535, pca85xx, mcp23017 and mcp23s17 (SPI version)
|
||||
- model: pca9535, pca85xx, mcp23017, aw9523 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
|
||||
@@ -341,9 +358,9 @@ The latest LMS plugin update is required to set the visualizer mode and brightne
|
||||
| \<playerid\> dmx \<R,G,B,R,G,B, ... R,G,B\> \[\<offset\>\] | Sets the LED color starting at position "offset"<br /> with "R"(red),"G"(green),and "B"(blue) color sequences.<br />Add additional RGB values to the delimited string to set multiple LEDs.<br /> |
|
||||
|
||||
### Rotary Encoder
|
||||
One rotary encoder is supported, quadrature shift with press. Such encoders usually have 2 pins for encoders (A and B), and common C that must be set to ground and an optional SW pin for press. A, B and SW must be pulled up, so automatic pull-up is provided by ESP32, but you can add your own resistors. A bit of filtering on A and B (~470nF) helps for debouncing which is not made by software.
|
||||
One general rotary encoder is supported, quadrature shift with press. Such encoders usually have 2 pins for encoders (A and B), and common C that must be set to ground and an optional SW pin for press. A, B and SW must be pulled up, so automatic pull-up is provided by ESP32, but you can add your own resistors. A bit of filtering on A and B (~470nF) helps for debouncing which is not made by software.
|
||||
|
||||
Encoder is normally hard-coded to respectively knob left, right and push on LMS and to volume down/up/play toggle on BT and AirPlay. Using the option 'volume' makes it hard-coded to volume down/up/play toggle all the time (even in LMS). The option 'longpress' allows an alternate mode when SW is long-pressed. In that mode, left is previous, right is next and press is toggle. Every long press on SW alternates between modes (the main mode actual behavior depends on 'volume').
|
||||
Encoder is normally hard-coded to respectively knob left, right and push on LMS and to volume down/up/play toggle on BT, AirPlay and Spotify. Using the option 'volume' makes it hard-coded to volume down/up/play toggle all the time (even in LMS). The option 'longpress' allows an alternate mode when SW is long-pressed. In that mode, left is previous, right is next and press is toggle. Every long press on SW alternates between modes (the main mode actual behavior depends on 'volume').
|
||||
|
||||
There is also the possibility to use 'knobonly' option (exclusive with 'volume' and 'longpress'). This mode attempts to offer a single knob full navigation which is a bit contorded due to LMS UI's principles. Left, Right and Press obey to LMS's navigation rules and especially Press always goes to lower submenu item, even when navigating in the Music Library. That causes a challenge as there is no 'Play', 'Back' or 'Pause' button. Workaround are as of below:
|
||||
- longpress is 'Play'
|
||||
@@ -364,7 +381,16 @@ The SW gpio is optional, you can re-affect it to a pure button if you prefer but
|
||||
|
||||
See also the "IMPORTANT NOTE" on the "Buttons" section and remember that when 'lms_ctrls_raw' (see below) is activated, none of these knobonly,volume,longpress options apply, raw button codes (not actions) are simply sent to LMS
|
||||
|
||||
**Note that gpio 36 and 39 are input only and cannot use interrupt, so they cannot be set to A or B. When using them for SW, a 100ms polling is used which is expensive**
|
||||
**Note that on esp32, gpio 36 and 39 are input only and cannot use interrupt, so they cannot be set to A or B. When using them for SW, a 100ms polling is used which is expensive**
|
||||
|
||||
### Volume Rotary Encoder
|
||||
One dedicated volume rotary encoder is supported, quadrature shift with press. Encoder is hard-coded to volume-up, down and play toggle for LMS, BT, AirPlay and Spotify (see note above for filtering and HW note as well GPIO 36 and 39 on esp32)
|
||||
|
||||
Use parameter volume_rotary with the following syntax:
|
||||
|
||||
```
|
||||
A=<gpio>,B=<gpio>[,SW=gpio>]
|
||||
```
|
||||
|
||||
### Buttons
|
||||
Buttons are described using a JSON string with the following syntax
|
||||
@@ -467,11 +493,12 @@ The benefit of the "raw" mode is that you can build a player which is as close a
|
||||
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**
|
||||
|
||||
**Note:** Touch buttons that can be found on some board like the LyraT V4.3 are not supported currently.
|
||||
|
||||
### Ethernet
|
||||
Wired ethernet is supported by esp32 with various options but squeezeESP32 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
|
||||
|
||||
@@ -632,12 +659,20 @@ docker run -it -v `pwd`:/workspace/squeezelite-esp32 sle118/squeezelite-esp32-id
|
||||
The above command will mount this repo into the docker container and start a bash terminal. From there, simply run idf.py build to build, etc. Note that at the time of writing these lines, flashing is not possible for docker running under windows https://github.com/docker/for-win/issues/1018.
|
||||
|
||||
### Manual Install of ESP-IDF
|
||||
You can install IDF manually on Linux or Windows (using the Subsystem for Linux) following the instructions at: https://www.instructables.com/id/ESP32-Development-on-Windows-Subsystem-for-Linux/ or see here https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/windows-setup.html for a direct install. You also need a few extra Python libraries for cspot by addingsudo `pip3 install protobuf grpcio-tools`
|
||||
|
||||
**Use the esp-idf 4.3.5 https://github.com/espressif/esp-idf/tree/release/v4.3.5 ** or the 4.4.5 (and above version) if you want to build for esp32-s3
|
||||
First you need git and python (e.g 3.10.x), install these and let it add to system path.
|
||||
|
||||
**Use the esp-idf 4.3.5 https://github.com/espressif/esp-idf/tree/release/v4.3.5 ** or the 4.4.5 (and above version) if you want to build for esp32-s3. You should clone recursively the whole branch (at the version you need) `git clone -b v4.3.5 https://github.com/espressif/esp-idf --recursive`and run the installer (`install.bat [esp32[,esp32s3]]` from there. Some Windows version (at least) have now a SSL certificate issue. You can workaround this by editing idf-tools.py and adding the following under ìmport ssl`
|
||||
```
|
||||
import ssl
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
```
|
||||
And because the fun never ends, some Windows installations might fail to build a few files and spit a tons of errors on the output. It seems that the cache of the compile is a problem, so try to disable it by running `idf.py --no-ccache build` (I know...)
|
||||
## Building SqueezeESP32
|
||||
When initially cloning the repo, make sure you do it recursively. For example: `git clone --recursive https://github.com/sle118/squeezelite-esp32.git`
|
||||
When initially cloning the repo, make sure you do it recursively. For example: `git clone --recursive https://github.com/sle118/squeezelite-esp32.git`. You also should install cspot additional components for protobuf use.
|
||||
```
|
||||
$ sudo pip3 install protobuf grpcio-tools
|
||||
```
|
||||
NB: I need to check on a fresh installation, but you might also require "protoc". You should do that within the esp32 local Python environment.
|
||||
|
||||
Don't forget to choose one of the config files in build_scripts/ and rename it sdkconfig.defaults or sdkconfig as many important WiFi/BT options are set there. **The codecs libraries will not be rebuilt by these scripts (it's a tedious process - see below)**
|
||||
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
# Windows Build Guide for Guition JC4827W543C
|
||||
|
||||
This guide explains how to build squeezelite-esp32 for the Guition board on Windows.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **ESP-IDF for Windows**
|
||||
- Download and install ESP-IDF from: https://dl.espressif.com/dl/esp-idf/
|
||||
- Run the installer and follow the setup instructions
|
||||
- Make sure to install the required tools (Git, Python, CMake)
|
||||
|
||||
2. **Visual Studio Build Tools** (if not already installed by ESP-IDF)
|
||||
- Install from Visual Studio Installer
|
||||
- Select "C++ build tools"
|
||||
|
||||
3. **Hardware**
|
||||
- Guition JC4827W543C board
|
||||
- USB cable for power and programming
|
||||
- External I2S audio DAC (required)
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
### 1. Install ESP-IDF
|
||||
```cmd
|
||||
# Run the ESP-IDF installer
|
||||
# Launch ESP-IDF Command Prompt from Start Menu
|
||||
```
|
||||
|
||||
### 2. Initialize ESP-IDF Environment
|
||||
```cmd
|
||||
# In ESP-IDF Command Prompt
|
||||
cd C:\Espressif\esp-idf
|
||||
export.bat
|
||||
```
|
||||
|
||||
### 3. Clone and Setup Project
|
||||
```cmd
|
||||
# Navigate to your workspace
|
||||
cd C:\Users\YourName\Projects
|
||||
|
||||
# Clone the repository (if not already done)
|
||||
git clone <repository-url> squeezelite-esp32-gronod
|
||||
cd squeezelite-esp32-gronod
|
||||
```
|
||||
|
||||
### 4. Set Environment for Project
|
||||
```cmd
|
||||
# Set ESP-IDF environment for this project
|
||||
C:\Espressif\esp-idf\export.bat
|
||||
```
|
||||
|
||||
## Build Methods
|
||||
|
||||
### Method 1: PowerShell Script (Recommended)
|
||||
```powershell
|
||||
# Open PowerShell as Administrator
|
||||
# Navigate to project directory
|
||||
cd C:\Users\YourName\Projects\squeezelite-esp32-gronod
|
||||
|
||||
# Run the build script
|
||||
.\build-guition.ps1
|
||||
|
||||
# For clean build
|
||||
.\build-guition.ps1 -Clean
|
||||
```
|
||||
|
||||
**Note:** If you get execution policy errors, run:
|
||||
```powershell
|
||||
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
```
|
||||
|
||||
### Method 2: Batch File (Simple)
|
||||
```cmd
|
||||
# Open Command Prompt
|
||||
# Navigate to project directory
|
||||
cd C:\Users\YourName\Projects\squeezelite-esp32-gronod
|
||||
|
||||
# Run the build script
|
||||
build-guition.bat
|
||||
|
||||
# For clean build
|
||||
build-guition.bat clean
|
||||
```
|
||||
|
||||
### Method 3: Manual Commands
|
||||
```cmd
|
||||
# Set ESP-IDF environment
|
||||
C:\Espressif\esp-idf\export.bat
|
||||
|
||||
# Set target
|
||||
set IDF_TARGET=esp32s3
|
||||
|
||||
# Copy Guition config
|
||||
copy /Y squeezelite-esp32-Guition-sdkconfig.defaults sdkconfig.defaults
|
||||
|
||||
# Configure (opens menu interface)
|
||||
idf.py menuconfig
|
||||
|
||||
# Build
|
||||
idf.py build
|
||||
```
|
||||
|
||||
## Menuconfig Configuration
|
||||
|
||||
When `idf.py menuconfig` runs:
|
||||
|
||||
1. **Select Target Hardware**:
|
||||
- Navigate to: `Squeezelite-ESP32` → `Guition JC4827W543C`
|
||||
|
||||
2. **Configure Audio** (if needed):
|
||||
- Navigate to: `Audio settings` → `DAC settings`
|
||||
- Set I2S GPIO pins for your external DAC
|
||||
- Default: BCK=15, WS=16, DO=17
|
||||
|
||||
3. **Save and Exit**:
|
||||
- Press `S` to save
|
||||
- Press `Q` to exit
|
||||
|
||||
## Flashing the Firmware
|
||||
|
||||
### Find Your COM Port
|
||||
```cmd
|
||||
# List all COM ports
|
||||
mode
|
||||
|
||||
# Or check Device Manager under "Ports (COM & LPT)"
|
||||
```
|
||||
|
||||
### Flash and Monitor
|
||||
```cmd
|
||||
# Flash firmware
|
||||
idf.py -p COM3 flash
|
||||
|
||||
# Flash and monitor (recommended)
|
||||
idf.py -p COM3 flash monitor
|
||||
|
||||
# Just monitor (if already flashed)
|
||||
idf.py -p COM3 monitor
|
||||
```
|
||||
|
||||
### Using the Scripts
|
||||
Both PowerShell and batch scripts will ask if you want to flash after building.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **"ESP-IDF environment not found"**
|
||||
- Make sure you're running from ESP-IDF Command Prompt
|
||||
- Or run `C:\Espressif\esp-idf\export.bat` first
|
||||
|
||||
2. **"Python not found"**
|
||||
- ESP-IDF installer should include Python
|
||||
- Check that Python is in your PATH
|
||||
|
||||
3. **"Build tools not found"**
|
||||
- Install Visual Studio Build Tools
|
||||
- Make sure C++ tools are selected
|
||||
|
||||
4. **"Permission denied" (PowerShell)**
|
||||
- Run PowerShell as Administrator
|
||||
- Or set execution policy: `Set-ExecutionPolicy RemoteSigned`
|
||||
|
||||
5. **"COM port not found"**
|
||||
- Check device connections
|
||||
- Install USB drivers if needed (CH340/CP210x)
|
||||
- Check Device Manager
|
||||
|
||||
### Clean Build
|
||||
If you encounter build issues:
|
||||
```cmd
|
||||
# Using batch file
|
||||
build-guition.bat clean
|
||||
|
||||
# Or manually
|
||||
idf.py fullclean
|
||||
idf.py build
|
||||
```
|
||||
|
||||
### Verifying Installation
|
||||
```cmd
|
||||
# Check ESP-IDF version
|
||||
idf.py --version
|
||||
|
||||
# Check target
|
||||
echo %IDF_TARGET%
|
||||
|
||||
# List available targets
|
||||
idf.py --help | findstr target
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Initial Setup**: Run the build script once to configure
|
||||
2. **Code Changes**: Make your modifications
|
||||
3. **Build**: Run `.\build-guition.ps1` or `build-guition.bat`
|
||||
4. **Flash**: Let the script flash automatically, or use `idf.py -p COMX flash`
|
||||
5. **Monitor**: Use `idf.py -p COMX monitor` to view logs
|
||||
|
||||
## Performance Tips
|
||||
|
||||
- **SSD Storage**: Build on SSD for much faster compilation
|
||||
- **RAM**: 8GB+ recommended for large projects
|
||||
- **CPU**: Multi-core CPU helps with parallel compilation
|
||||
- **Antivirus**: Exclude project directory from real-time scanning
|
||||
|
||||
## Next Steps
|
||||
|
||||
After successful build and flash:
|
||||
|
||||
1. Connect to WiFi AP created by device
|
||||
2. Configure your network settings
|
||||
3. Set up audio DAC configuration
|
||||
4. Connect to your Logitech Media Server
|
||||
|
||||
For more detailed configuration, see `GUITION_SETUP.md`.
|
||||
@@ -0,0 +1,80 @@
|
||||
@echo off
|
||||
REM Build script for Guition JC4827W543C board - Batch file version
|
||||
REM Usage: build-guition.bat [clean]
|
||||
|
||||
echo Building Squeezelite-ESP32 for Guition JC4827W543C
|
||||
|
||||
REM Check if ESP-IDF environment is set up
|
||||
if "%IDF_PATH%"=="" (
|
||||
echo Error: ESP-IDF environment not found!
|
||||
echo Please run esp-idf\export.bat first
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Set target to ESP32-S3
|
||||
set IDF_TARGET=esp32s3
|
||||
echo Target set to: %IDF_TARGET%
|
||||
|
||||
REM Copy Guition-specific configuration
|
||||
if exist "squeezelite-esp32-Guition-sdkconfig.defaults" (
|
||||
echo Using Guition configuration...
|
||||
copy /Y "squeezelite-esp32-Guition-sdkconfig.defaults" "sdkconfig.defaults"
|
||||
) else (
|
||||
echo Warning: Guition configuration file not found
|
||||
)
|
||||
|
||||
REM Clean if requested
|
||||
if /i "%1"=="clean" (
|
||||
echo Cleaning build...
|
||||
idf.py fullclean
|
||||
if errorlevel 1 (
|
||||
echo Clean failed!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
REM Configure the project
|
||||
echo Configuring project...
|
||||
echo Running: idf.py menuconfig
|
||||
echo Select: Squeezelite-ESP32 -^> Guition JC4827W543C
|
||||
echo.
|
||||
echo Press any key to continue to menuconfig...
|
||||
pause > nul
|
||||
|
||||
idf.py menuconfig
|
||||
if errorlevel 1 (
|
||||
echo Configuration failed!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Build the project
|
||||
echo Building firmware...
|
||||
idf.py build
|
||||
if errorlevel 1 (
|
||||
echo Build failed!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Build complete!
|
||||
echo Firmware location: build\squeezelite.bin
|
||||
echo.
|
||||
echo To flash the firmware:
|
||||
echo idf.py -p ^<PORT^> flash
|
||||
echo.
|
||||
echo To monitor output:
|
||||
echo idf.py -p ^<PORT^> monitor
|
||||
|
||||
REM Ask if user wants to flash
|
||||
echo.
|
||||
set /p flash="Do you want to flash the firmware now? (y/n) "
|
||||
if /i "%flash%"=="y" (
|
||||
set /p port="Enter COM port (e.g., COM3): "
|
||||
echo Flashing to %port%...
|
||||
idf.py -p %port% flash monitor
|
||||
)
|
||||
|
||||
pause
|
||||
@@ -0,0 +1,76 @@
|
||||
# Build script for Guition JC4827W543C board - PowerShell version
|
||||
# Usage: .\build-guition.ps1 [-Clean]
|
||||
|
||||
param(
|
||||
[switch]$Clean
|
||||
)
|
||||
|
||||
Write-Host "Building Squeezelite-ESP32 for Guition JC4827W543C" -ForegroundColor Green
|
||||
|
||||
# Check if ESP-IDF environment is set up
|
||||
if (-not $env:IDF_PATH) {
|
||||
Write-Host "Error: ESP-IDF environment not found!" -ForegroundColor Red
|
||||
Write-Host "Please run esp-idf/export.bat first" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Set target to ESP32-S3
|
||||
$env:IDF_TARGET = "esp32s3"
|
||||
Write-Host "Target set to: $env:IDF_TARGET" -ForegroundColor Cyan
|
||||
|
||||
# Copy Guition-specific configuration
|
||||
if (Test-Path "squeezelite-esp32-Guition-sdkconfig.defaults") {
|
||||
Write-Host "Using Guition configuration..." -ForegroundColor Cyan
|
||||
Copy-Item "squeezelite-esp32-Guition-sdkconfig.defaults" "sdkconfig.defaults" -Force
|
||||
} else {
|
||||
Write-Host "Warning: Guition configuration file not found" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Clean if requested
|
||||
if ($Clean) {
|
||||
Write-Host "Cleaning build..." -ForegroundColor Cyan
|
||||
idf.py fullclean
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Clean failed!" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Configure the project
|
||||
Write-Host "Configuring project..." -ForegroundColor Cyan
|
||||
Write-Host "Running: idf.py menuconfig" -ForegroundColor White
|
||||
Write-Host "Select: Squeezelite-ESP32 -> Guition JC4827W543C" -ForegroundColor Yellow
|
||||
Write-Host "Press Enter to continue to menuconfig..." -ForegroundColor White
|
||||
Read-Host
|
||||
|
||||
idf.py menuconfig
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Configuration failed!" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Build the project
|
||||
Write-Host "Building firmware..." -ForegroundColor Cyan
|
||||
idf.py build
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Build failed!" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Build complete!" -ForegroundColor Green
|
||||
Write-Host "Firmware location: build\squeezelite.bin" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "To flash the firmware:" -ForegroundColor White
|
||||
Write-Host "idf.py -p <PORT> flash" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "To monitor output:" -ForegroundColor White
|
||||
Write-Host "idf.py -p <PORT> monitor" -ForegroundColor Yellow
|
||||
|
||||
# Ask if user wants to flash
|
||||
Write-Host ""
|
||||
$flash = Read-Host "Do you want to flash the firmware now? (y/n)"
|
||||
if ($flash -eq 'y' -or $flash -eq 'Y') {
|
||||
$port = Read-Host "Enter COM port (e.g., COM3)"
|
||||
Write-Host "Flashing to $port..." -ForegroundColor Cyan
|
||||
idf.py -p $port flash monitor
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Build script for Guition JC4827W543C board
|
||||
# Usage: ./build-guition.sh [clean]
|
||||
|
||||
set -e
|
||||
|
||||
echo "Building Squeezelite-ESP32 for Guition JC4827W543C"
|
||||
|
||||
# Set target to ESP32-S3
|
||||
export IDF_TARGET=esp32s3
|
||||
|
||||
# Copy Guition-specific configuration
|
||||
if [ -f "squeezelite-esp32-Guition-sdkconfig.defaults" ]; then
|
||||
echo "Using Guition configuration..."
|
||||
cp squeezelite-esp32-Guition-sdkconfig.defaults sdkconfig.defaults
|
||||
fi
|
||||
|
||||
# Clean if requested
|
||||
if [ "$1" == "clean" ]; then
|
||||
echo "Cleaning build..."
|
||||
idf.py fullclean
|
||||
fi
|
||||
|
||||
# Configure with Guition target
|
||||
echo "Configuring project..."
|
||||
idf.py menuconfig
|
||||
|
||||
# Build the project
|
||||
echo "Building firmware..."
|
||||
idf.py build
|
||||
|
||||
echo "Build complete!"
|
||||
echo "Firmware location: build/squeezelite.bin"
|
||||
echo ""
|
||||
echo "To flash the firmware:"
|
||||
echo "idf.py -p <PORT> flash"
|
||||
echo ""
|
||||
echo "To monitor output:"
|
||||
echo "idf.py -p <PORT> monitor"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
/* libFLAC - Free Lossless Audio Codec library
|
||||
* Copyright (C) 2000-2009 Josh Coalson
|
||||
* Copyright (C) 2011-2022 Xiph.Org Foundation
|
||||
* Copyright (C) 2011-2023 Xiph.Org Foundation
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -782,6 +782,25 @@ FLAC_API void FLAC__stream_decoder_delete(FLAC__StreamDecoder *decoder);
|
||||
*/
|
||||
FLAC_API FLAC__bool FLAC__stream_decoder_set_ogg_serial_number(FLAC__StreamDecoder *decoder, long serial_number);
|
||||
|
||||
/** Set the "allow Ogg chaining" flag. If set, the Ogg decoder will
|
||||
* prepare to receive a new stream once the last Ogg page arrives for
|
||||
* the stream encapsulating the FLAC audio data. This can be used to
|
||||
* support chained Ogg FLAC streams; a new \c STREAMINFO signals the
|
||||
* beginning of a new stream.
|
||||
*
|
||||
* \note
|
||||
* This function has no effect with native FLAC decoding.
|
||||
*
|
||||
* \default \c false
|
||||
* \param decoder A decoder instance to set.
|
||||
* \param allow Whether to allow chained streams.
|
||||
* \assert
|
||||
* \code decoder != NULL \endcode
|
||||
* \retval FLAC__bool
|
||||
* \c false if the decoder is already initialized, else \c true.
|
||||
*/
|
||||
FLAC_API FLAC__bool FLAC__stream_decoder_set_ogg_chaining(FLAC__StreamDecoder* decoder, FLAC__bool value);
|
||||
|
||||
/** Set the "MD5 signature checking" flag. If \c true, the decoder will
|
||||
* compute the MD5 signature of the unencoded audio data while decoding
|
||||
* and compare it to the signature from the STREAMINFO block, if it
|
||||
@@ -906,6 +925,17 @@ FLAC_API FLAC__StreamDecoderState FLAC__stream_decoder_get_state(const FLAC__Str
|
||||
*/
|
||||
FLAC_API const char *FLAC__stream_decoder_get_resolved_state_string(const FLAC__StreamDecoder *decoder);
|
||||
|
||||
/** Get the "allow Ogg chaining" flag as described in
|
||||
* \code FLAC__stream_decoder_set_ogg_chaining \endcode.
|
||||
*
|
||||
* \param decoder A decoder instance to query.
|
||||
* \assert
|
||||
* \code decoder != NULL \endcode
|
||||
* \retval FLAC__bool
|
||||
* See above.
|
||||
*/
|
||||
FLAC_API FLAC__bool FLAC__stream_decoder_get_ogg_chaining(const FLAC__StreamDecoder* decoder);
|
||||
|
||||
/** Get the "MD5 signature checking" flag.
|
||||
* This is the value of the setting, not whether or not the decoder is
|
||||
* currently checking the MD5 (remember, it can be turned off automatically
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,297 @@
|
||||
/**
|
||||
* ILI9488 Display Driver for Guition JC4827W543C
|
||||
* Supports QSPI interface for 480x272 resolution
|
||||
* Based on ILI9341 driver adapted for ILI9488
|
||||
*
|
||||
* (c) Guition Support 2026
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <esp_heap_caps.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
#include "gds.h"
|
||||
#include "gds_private.h"
|
||||
|
||||
#define SHADOW_BUFFER
|
||||
#define USE_IRAM
|
||||
#define PAGE_BLOCK 4096
|
||||
#define ENABLE_WRITE 0x2c
|
||||
|
||||
#define min(a,b) (((a) < (b)) ? (a) : (b))
|
||||
|
||||
static char TAG[] = "ILI9488";
|
||||
|
||||
struct PrivateSpace {
|
||||
uint8_t *iRAM, *Shadowbuffer;
|
||||
struct {
|
||||
uint16_t Height, Width;
|
||||
} Offset;
|
||||
uint8_t MADCtl, PageSize;
|
||||
uint8_t Model;
|
||||
};
|
||||
|
||||
// ILI9488 Commands
|
||||
static const uint8_t ILI9488_INIT_SEQUENCE[] = {
|
||||
// Software reset
|
||||
0x01, 0x80, 150, // SWRESET, delay 150ms
|
||||
// Power control
|
||||
0xD0, 3, 0x07, 0x42, 0x18, // Power Control
|
||||
0xD1, 3, 0x00, 0x07, 0x10, // VCOM Control
|
||||
0xD2, 1, 0x01, // Power Control for Normal Mode
|
||||
0xC0, 2, 0x10, 0x3B, // Panel Driving Setting
|
||||
0xC1, 1, 0x10, // Frame Rate Control
|
||||
0xC5, 5, 0x0A, 0x3A, 0x28, 0x28, 0x02, // MCU Control
|
||||
0xC6, 1, 0x00, // Frame Rate Control
|
||||
0xB1, 2, 0x00, 0x1B, // Display Function Control
|
||||
0xB4, 1, 0x02, // Inversion Control
|
||||
0xB6, 3, 0x02, 0x02, 0x3B, // Display Function Control
|
||||
0xB7, 1, 0xC6, // Entry Mode Set
|
||||
0xE0, 16, 0x00, 0x07, 0x10, 0x0E, 0x09, 0x16, 0x06, 0x0A,
|
||||
0x0E, 0x09, 0x15, 0x0D, 0x0E, 0x11, 0x0F, 0x12, // Positive Gamma Control
|
||||
0xE1, 16, 0x00, 0x17, 0x1A, 0x04, 0x0E, 0x06, 0x2F, 0x24,
|
||||
0x1B, 0x1B, 0x22, 0x1F, 0x1E, 0x37, 0x3F, 0x00, // Negative Gamma Control
|
||||
0x36, 1, 0xE8, // Memory Access Control (MX, MY, RGB mode)
|
||||
0x3A, 1, 0x55, // Interface Pixel Format (16bpp)
|
||||
0x11, 0x80, 150, // Sleep Out, delay 150ms
|
||||
0x29, 0x80, 50, // Display On, delay 50ms
|
||||
0xFF, 0x00 // End of sequence
|
||||
};
|
||||
|
||||
static void WriteByte( struct GDS_Device* Device, uint8_t Data ) {
|
||||
Device->WriteData( Device, &Data, 1 );
|
||||
}
|
||||
|
||||
static void SetColumnAddress( struct GDS_Device* Device, uint16_t Start, uint16_t End ) {
|
||||
uint32_t Addr = __builtin_bswap16(Start) | (__builtin_bswap16(End) << 16);
|
||||
Device->WriteCommand( Device, 0x2A );
|
||||
Device->WriteData( Device, (uint8_t*) &Addr, 4 );
|
||||
}
|
||||
|
||||
static void SetRowAddress( struct GDS_Device* Device, uint16_t Start, uint16_t End ) {
|
||||
uint32_t Addr = __builtin_bswap16(Start) | (__builtin_bswap16(End) << 16);
|
||||
Device->WriteCommand( Device, 0x2B );
|
||||
Device->WriteData( Device, (uint8_t*) &Addr, 4 );
|
||||
}
|
||||
|
||||
static void Update16( struct GDS_Device* Device ) {
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
|
||||
#ifdef SHADOW_BUFFER
|
||||
uint32_t *optr = (uint32_t*) Private->Shadowbuffer, *iptr = (uint32_t*) Device->Framebuffer;
|
||||
int FirstCol = Device->Width / 2, LastCol = 0, FirstRow = -1, LastRow = 0;
|
||||
|
||||
for (int r = 0; r < Device->Height; r++) {
|
||||
// look for change and update shadow (cheap optimization = width is always a multiple of 2)
|
||||
for (int c = 0; c < Device->Width / 2; c++, iptr++, optr++) {
|
||||
if (*optr != *iptr) {
|
||||
*optr = *iptr;
|
||||
if (c < FirstCol) FirstCol = c;
|
||||
if (c > LastCol) LastCol = c;
|
||||
if (FirstRow < 0) FirstRow = r;
|
||||
LastRow = r;
|
||||
}
|
||||
}
|
||||
|
||||
// wait for a large enough window - careful that window size might increase by more than a line at once !
|
||||
if (FirstRow < 0 || ((LastCol - FirstCol + 1) * (r - FirstRow + 1) * 4 < PAGE_BLOCK && r != Device->Height - 1)) continue;
|
||||
|
||||
FirstCol *= 2;
|
||||
LastCol = LastCol * 2 + 1;
|
||||
SetRowAddress( Device, FirstRow + Private->Offset.Height, LastRow + Private->Offset.Height);
|
||||
SetColumnAddress( Device, FirstCol + Private->Offset.Width, LastCol + Private->Offset.Width );
|
||||
Device->WriteCommand( Device, ENABLE_WRITE );
|
||||
|
||||
int ChunkSize = (LastCol - FirstCol + 1) * 2;
|
||||
|
||||
// own use of IRAM has not proven to be much better than letting SPI do its copy
|
||||
if (Private->iRAM) {
|
||||
uint8_t *optr = Private->iRAM;
|
||||
for (int i = FirstRow; i <= LastRow; i++) {
|
||||
memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 2, ChunkSize);
|
||||
optr += ChunkSize;
|
||||
if (optr - Private->iRAM <= (PAGE_BLOCK - ChunkSize) && i < LastRow) continue;
|
||||
Device->WriteData(Device, Private->iRAM, optr - Private->iRAM);
|
||||
optr = Private->iRAM;
|
||||
}
|
||||
} else for (int i = FirstRow; i <= LastRow; i++) {
|
||||
Device->WriteData( Device, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 2, ChunkSize );
|
||||
}
|
||||
|
||||
FirstCol = Device->Width / 2;
|
||||
LastCol = 0;
|
||||
FirstRow = -1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void Clear( struct GDS_Device* Device ) {
|
||||
memset( Device->Framebuffer, 0, Device->Width * Device->Height * 2 );
|
||||
Device->Update( Device);
|
||||
}
|
||||
|
||||
static void SetPixel( struct GDS_Device* Device, int X, int Y, uint32_t Color ) {
|
||||
if( X < 0 || X >= Device->Width || Y < 0 || Y >= Device->Height) return;
|
||||
|
||||
*((uint16_t*) Device->Framebuffer + (Y * Device->Width) + X) = (uint16_t) Color;
|
||||
}
|
||||
|
||||
static uint32_t GetPixel( struct GDS_Device* Device, int X, int Y ) {
|
||||
if( X < 0 || X >= Device->Width || Y < 0 || Y >= Device->Height) return 0;
|
||||
|
||||
return *((uint16_t*) Device->Framebuffer + (Y * Device->Width) + X);
|
||||
}
|
||||
|
||||
static void DrawPixel( struct GDS_Device* Device, int X, int Y, uint32_t Color ) {
|
||||
SetPixel( Device, X, Y, Color );
|
||||
}
|
||||
|
||||
static void DrawPixelFast( struct GDS_Device* Device, int X, int Y, uint32_t Color ) {
|
||||
*((uint16_t*) Device->Framebuffer + (Y * Device->Width) + X) = (uint16_t) Color;
|
||||
}
|
||||
|
||||
static void DrawCBR( struct GDS_Device* Device, int X, int Y, int Width, int Height, uint32_t Color ) {
|
||||
uint16_t *fb = (uint16_t*) Device->Framebuffer + Y * Device->Width + X;
|
||||
|
||||
for( int y = 0; y < Height; y++) {
|
||||
for( int x = 0; x < Width; x++) {
|
||||
fb[x] = (uint16_t) Color;
|
||||
}
|
||||
fb += Device->Width;
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawHLine( struct GDS_Device* Device, int X0, int X1, int Y, uint32_t Color ) {
|
||||
if( Y < 0 || Y >= Device->Height) return;
|
||||
|
||||
if( X0 > X1) {
|
||||
int Temp = X0;
|
||||
X0 = X1;
|
||||
X1 = Temp;
|
||||
}
|
||||
|
||||
if( X0 < 0) X0 = 0;
|
||||
if( X1 >= Device->Width) X1 = Device->Width - 1;
|
||||
|
||||
uint16_t *fb = (uint16_t*) Device->Framebuffer + Y * Device->Width + X0;
|
||||
|
||||
for( int x = X0; x <= X1; x++) {
|
||||
*fb++ = (uint16_t) Color;
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawVLine( struct GDS_Device* Device, int X, int Y0, int Y1, uint32_t Color ) {
|
||||
if( X < 0 || X >= Device->Width) return;
|
||||
|
||||
if( Y0 > Y1) {
|
||||
int Temp = Y0;
|
||||
Y0 = Y1;
|
||||
Y1 = Temp;
|
||||
}
|
||||
|
||||
if( Y0 < 0) Y0 = 0;
|
||||
if( Y1 >= Device->Height) Y1 = Device->Height - 1;
|
||||
|
||||
uint16_t *fb = (uint16_t*) Device->Framebuffer + Y0 * Device->Width + X;
|
||||
|
||||
for( int y = Y0; y <= Y1; y++) {
|
||||
*fb = (uint16_t) Color;
|
||||
fb += Device->Width;
|
||||
}
|
||||
}
|
||||
|
||||
static bool Init( struct GDS_Device* Device ) {
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
const uint8_t *p = ILI9488_INIT_SEQUENCE;
|
||||
|
||||
ESP_LOGI(TAG, "Initializing ILI9488 display %dx%d", Device->Width, Device->Height);
|
||||
|
||||
// Allocate IRAM buffer if available
|
||||
Private->iRAM = (uint8_t*) heap_caps_malloc(PAGE_BLOCK, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
|
||||
if (!Private->iRAM) {
|
||||
ESP_LOGW(TAG, "Could not allocate IRAM buffer, using direct write");
|
||||
}
|
||||
|
||||
#ifdef SHADOW_BUFFER
|
||||
Private->Shadowbuffer = (uint8_t*) heap_caps_malloc(Device->Width * Device->Height * 2, MALLOC_CAP_DMA);
|
||||
if (!Private->Shadowbuffer) {
|
||||
ESP_LOGE(TAG, "Could not allocate shadow buffer");
|
||||
if (Private->iRAM) free(Private->iRAM);
|
||||
return false;
|
||||
}
|
||||
memset(Private->Shadowbuffer, 0, Device->Width * Device->Height * 2);
|
||||
#endif
|
||||
|
||||
// Send initialization sequence
|
||||
while(*p != 0xFF) {
|
||||
uint8_t cmd = *p++;
|
||||
uint8_t len = (*p & 0x7F);
|
||||
bool delay = (*p++ & 0x80);
|
||||
|
||||
Device->WriteCommand(Device, cmd);
|
||||
if(len) Device->WriteData(Device, (uint8_t*)p, len);
|
||||
p += len;
|
||||
|
||||
if(delay) {
|
||||
uint8_t ms = *p++;
|
||||
vTaskDelay(ms / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
// Set orientation and layout
|
||||
Private->MADCtl = 0xE8; // MX=1, MY=1, RGB=1, BGR=0
|
||||
Device->WriteCommand(Device, 0x36);
|
||||
Device->WriteData(Device, &Private->MADCtl, 1);
|
||||
|
||||
// Set pixel format to 16-bit
|
||||
uint8_t fmt = 0x55;
|
||||
Device->WriteCommand(Device, 0x3A);
|
||||
Device->WriteData(Device, &fmt, 1);
|
||||
|
||||
// Turn on display
|
||||
Device->WriteCommand(Device, 0x29);
|
||||
|
||||
ESP_LOGI(TAG, "ILI9488 initialization complete");
|
||||
return true;
|
||||
}
|
||||
|
||||
static void Deinit( struct GDS_Device* Device ) {
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
|
||||
if (Private->iRAM) free(Private->iRAM);
|
||||
#ifdef SHADOW_BUFFER
|
||||
if (Private->Shadowbuffer) free(Private->Shadowbuffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
struct GDS_Device* ILI9488_Detect( char *Driver, struct GDS_Device *Device ) {
|
||||
if(strcasecmp(Driver, "ILI9488") != 0) return NULL;
|
||||
|
||||
Device->Private = calloc(1, sizeof(struct PrivateSpace));
|
||||
if(!Device->Private) {
|
||||
ESP_LOGE(TAG, "Cannot allocate private data");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Device->Mode = GDS_RGB565;
|
||||
Device->Depth = 16;
|
||||
Device->Update = Update16;
|
||||
Device->Clear = Clear;
|
||||
Device->SetPixel = SetPixel;
|
||||
Device->GetPixel = GetPixel;
|
||||
Device->DrawPixel = DrawPixel;
|
||||
Device->DrawPixelFast = DrawPixelFast;
|
||||
Device->DrawCBR = DrawCBR;
|
||||
Device->DrawHLine = DrawHLine;
|
||||
Device->DrawVLine = DrawVLine;
|
||||
Device->Init = Init;
|
||||
Device->Deinit = Deinit;
|
||||
|
||||
ESP_LOGI(TAG, "ILI9488 driver loaded");
|
||||
return Device;
|
||||
}
|
||||
@@ -13,6 +13,10 @@ bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int
|
||||
bool GDS_SPIInit( int SPI, int DC );
|
||||
bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int Speed, int BacklightPin, int Mode );
|
||||
|
||||
bool GDS_QSPIInit( int QSPI, int DC );
|
||||
bool GDS_QSPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int Speed, int BacklightPin, int Mode );
|
||||
bool GDS_QSPIBusInit( int MOSIPin, int MISOPin, int CLKPin, int Host );
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* QSPI Interface for Guition JC4827W543C ILI9488 Display
|
||||
* Supports ESP32-S3 QSPI peripheral
|
||||
*
|
||||
* (c) Guition Support 2026
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <driver/spi_master.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <freertos/task.h>
|
||||
#include "gds.h"
|
||||
#include "gds_err.h"
|
||||
#include "gds_private.h"
|
||||
#include "gds_default_if.h"
|
||||
|
||||
static const int GDS_QSPI_Command_Mode = 0;
|
||||
static const int GDS_QSPI_Data_Mode = 1;
|
||||
|
||||
static spi_host_device_t QSPIHost;
|
||||
static int DCPin;
|
||||
|
||||
static bool QSPIDefaultWriteBytes( spi_device_handle_t QSPIHandle, int WriteMode, const uint8_t* Data, size_t DataLength );
|
||||
static bool QSPIDefaultWriteCommand( struct GDS_Device* Device, uint8_t Command );
|
||||
static bool QSPIDefaultWriteData( struct GDS_Device* Device, const uint8_t* Data, size_t DataLength );
|
||||
|
||||
bool GDS_QSPIInit( int QSPI, int DC ) {
|
||||
QSPIHost = QSPI;
|
||||
DCPin = DC;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GDS_QSPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int BackLightPin, int Speed, int Mode ) {
|
||||
spi_device_interface_config_t QSPIDeviceConfig = { };
|
||||
spi_device_handle_t QSPIDevice;
|
||||
|
||||
NullCheck( Device, return false );
|
||||
|
||||
if (CSPin >= 0) {
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_direction( CSPin, GPIO_MODE_OUTPUT ), return false );
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_level( CSPin, 0 ), return false );
|
||||
}
|
||||
|
||||
QSPIDeviceConfig.clock_speed_hz = Speed > 0 ? Speed : SPI_MASTER_FREQ_20M;
|
||||
QSPIDeviceConfig.spics_io_num = CSPin;
|
||||
QSPIDeviceConfig.queue_size = 4;
|
||||
QSPIDeviceConfig.mode = Mode;
|
||||
QSPIDeviceConfig.flags = SPI_DEVICE_HALFDUPLEX;
|
||||
QSPIDeviceConfig.duty_cycle_pos = 128; // 50% duty cycle
|
||||
|
||||
// QSPI specific configuration for ESP32-S3
|
||||
QSPIDeviceConfig.command_bits = 8;
|
||||
QSPIDeviceConfig.address_bits = 24;
|
||||
QSPIDeviceConfig.address_len = 3;
|
||||
|
||||
if( spi_bus_add_device( QSPIHost, &QSPIDeviceConfig, &QSPIDevice ) != ESP_OK ) {
|
||||
GDS_LOG_ERROR( "Failed to add QSPI device to host" );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( DCPin >= 0 ) {
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_direction( DCPin, GPIO_MODE_OUTPUT ), return false );
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_level( DCPin, 1 ), return false );
|
||||
}
|
||||
|
||||
if( RSTPin >= 0 ) {
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_direction( RSTPin, GPIO_MODE_OUTPUT ), return false );
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_level( RSTPin, 1 ), return false );
|
||||
}
|
||||
|
||||
if( BackLightPin >= 0 ) {
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_direction( BackLightPin, GPIO_MODE_OUTPUT ), return false );
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_level( BackLightPin, 1 ), return false );
|
||||
}
|
||||
|
||||
Device->WriteCommand = QSPIDefaultWriteCommand;
|
||||
Device->WriteData = QSPIDefaultWriteData;
|
||||
Device->DeviceHandle = QSPIDevice;
|
||||
|
||||
// Hardware reset if pin is available
|
||||
if( RSTPin >= 0 ) {
|
||||
gpio_set_level( RSTPin, 0 );
|
||||
vTaskDelay( pdMS_TO_TICKS( 10 ) );
|
||||
gpio_set_level( RSTPin, 1 );
|
||||
vTaskDelay( pdMS_TO_TICKS( 120 ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool QSPIDefaultWriteCommand( struct GDS_Device* Device, uint8_t Command ) {
|
||||
return QSPIDefaultWriteBytes( Device->DeviceHandle, GDS_QSPI_Command_Mode, &Command, 1 );
|
||||
}
|
||||
|
||||
static bool QSPIDefaultWriteData( struct GDS_Device* Device, const uint8_t* Data, size_t DataLength ) {
|
||||
if( DCPin >= 0 ) {
|
||||
gpio_set_level( DCPin, 1 );
|
||||
}
|
||||
|
||||
bool Result = QSPIDefaultWriteBytes( Device->DeviceHandle, GDS_QSPI_Data_Mode, Data, DataLength );
|
||||
|
||||
if( DCPin >= 0 ) {
|
||||
gpio_set_level( DCPin, 0 );
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
static bool QSPIDefaultWriteBytes( spi_device_handle_t QSPIHandle, int WriteMode, const uint8_t* Data, size_t DataLength ) {
|
||||
spi_transaction_ext_t SPITransaction = { };
|
||||
SPITransaction.base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_DUMMY;
|
||||
SPITransaction.base.cmd = 0;
|
||||
SPITransaction.base.addr = 0;
|
||||
SPITransaction.base.length = DataLength * 8;
|
||||
SPITransaction.base.tx_buffer = Data;
|
||||
SPITransaction.base.rx_buffer = NULL;
|
||||
SPITransaction.command_bits = 8;
|
||||
SPITransaction.address_bits = 0;
|
||||
SPITransaction.dummy_bits = 0;
|
||||
|
||||
if( WriteMode == GDS_QSPI_Command_Mode ) {
|
||||
SPITransaction.command_bits = 8;
|
||||
SPITransaction.base.cmd = Data[0];
|
||||
SPITransaction.base.length = 0;
|
||||
SPITransaction.base.tx_buffer = NULL;
|
||||
}
|
||||
|
||||
if( spi_device_transmit( QSPIHandle, (spi_transaction_t*) &SPITransaction ) != ESP_OK ) {
|
||||
GDS_LOG_ERROR( "QSPI transaction failed" );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GDS_QSPIBusInit( int MOSIPin, int MISOPin, int CLKPin, int Host ) {
|
||||
spi_bus_config_t BusConfig = { };
|
||||
|
||||
NullCheck( Host >= SPI2_HOST && Host <= SPI3_HOST, return false );
|
||||
|
||||
// QSPI pin configuration for ESP32-S3
|
||||
BusConfig.mosi_io_num = MOSIPin;
|
||||
BusConfig.miso_io_num = MISOPin;
|
||||
BusConfig.sclk_io_num = CLKPin;
|
||||
BusConfig.quadwp_io_num = -1; // Not used for ILI9488
|
||||
BusConfig.quadhd_io_num = -1; // Not used for ILI9488
|
||||
BusConfig.max_transfer_sz = 65536;
|
||||
BusConfig.flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_NATIVE_PINS;
|
||||
|
||||
if( spi_bus_initialize( Host, &BusConfig, DMA_CH_AUTO ) != ESP_OK ) {
|
||||
GDS_LOG_ERROR( "Failed to initialize QSPI bus" );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -73,6 +73,7 @@ static const char *known_drivers[] = {"SH1106",
|
||||
"ST7735",
|
||||
"ST7789",
|
||||
"ILI9341",
|
||||
"ILI9488",
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -80,8 +81,8 @@ static void displayer_task(void *args);
|
||||
static void display_sleep(void);
|
||||
|
||||
struct GDS_Device *display;
|
||||
extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SH1122_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect;
|
||||
GDS_DetectFunc *drivers[] = { SH1106_Detect, SH1122_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect, NULL };
|
||||
extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SH1122_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect, ILI9488_Detect;
|
||||
GDS_DetectFunc *drivers[] = { SH1106_Detect, SH1122_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect, ILI9488_Detect, NULL };
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
@@ -134,6 +135,31 @@ void display_init(char *welcome) {
|
||||
GDS_SPIAttachDevice( display, width, height, CS_pin, RST_pin, backlight_pin, speed, mode );
|
||||
|
||||
ESP_LOGI(TAG, "Display is SPI host %u with cs:%d", spi_system_host, CS_pin);
|
||||
} else if (strcasestr(config, "QSPI") && spi_system_host != -1) {
|
||||
int CS_pin = -1, speed = 0, mode = 0;
|
||||
|
||||
PARSE_PARAM(config, "cs", '=', CS_pin);
|
||||
PARSE_PARAM(config, "speed", '=', speed);
|
||||
PARSE_PARAM(config, "mode", '=', mode);
|
||||
|
||||
// Parse QSPI data pins
|
||||
int data_pins[4] = {-1, -1, -1, -1};
|
||||
char *data_str = strstr(config, "data=");
|
||||
if (data_str) {
|
||||
sscanf(data_str + 5, "%d,%d,%d,%d", &data_pins[0], &data_pins[1], &data_pins[2], &data_pins[3]);
|
||||
}
|
||||
|
||||
init = true;
|
||||
|
||||
// Initialize QSPI bus with data pins
|
||||
if (data_pins[0] != -1 && data_pins[1] != -1 && data_pins[2] != -1 && data_pins[3] != -1) {
|
||||
GDS_QSPIBusInit( data_pins[0], data_pins[1], spi_system_clk_gpio, spi_system_host );
|
||||
}
|
||||
|
||||
GDS_QSPIInit( spi_system_host, spi_system_dc_gpio );
|
||||
GDS_QSPIAttachDevice( display, width, height, CS_pin, RST_pin, backlight_pin, speed, mode );
|
||||
|
||||
ESP_LOGI(TAG, "Display is QSPI host %u with cs:%d", spi_system_host, CS_pin);
|
||||
} else {
|
||||
display = NULL;
|
||||
ESP_LOGI(TAG, "Unsupported display interface or serial link not configured");
|
||||
|
||||
@@ -62,6 +62,7 @@ static void register_free();
|
||||
static void register_setdevicename();
|
||||
static void register_heap();
|
||||
static void register_dump_heap();
|
||||
static void register_abort();
|
||||
static void register_version();
|
||||
static void register_restart();
|
||||
#if CONFIG_WITH_CONFIG_UI
|
||||
@@ -90,6 +91,7 @@ void register_system()
|
||||
register_free();
|
||||
register_heap();
|
||||
register_dump_heap();
|
||||
register_abort();
|
||||
register_version();
|
||||
register_restart();
|
||||
register_factory_boot();
|
||||
@@ -144,6 +146,27 @@ static void register_version()
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
/* 'abort' command */
|
||||
static int cmd_abort(int argc, char **argv)
|
||||
{
|
||||
cmd_send_messaging(argv[0],MESSAGING_INFO,"ABORT!\r\n");
|
||||
|
||||
abort();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_abort()
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "abort",
|
||||
.help = "Crash now!",
|
||||
.hint = NULL,
|
||||
.func = &cmd_abort,
|
||||
};
|
||||
cmd_to_json(&cmd);
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
|
||||
{
|
||||
if(is_recovery_running){
|
||||
|
||||
+72
-53
@@ -96,6 +96,7 @@ typedef struct __attribute__((__packed__)) audio_buffer_entry { // decoded aud
|
||||
u16_t len;
|
||||
u8_t ready;
|
||||
u8_t allocated;
|
||||
u8_t missed;
|
||||
} abuf_t;
|
||||
|
||||
typedef struct rtp_s {
|
||||
@@ -126,11 +127,6 @@ typedef struct rtp_s {
|
||||
u32_t rtp, time;
|
||||
u8_t status;
|
||||
} synchro;
|
||||
struct {
|
||||
u32_t time;
|
||||
seq_t seqno;
|
||||
u32_t rtptime;
|
||||
} record;
|
||||
int latency; // rtp hold depth in samples
|
||||
u32_t resent_req, resent_rec; // total resent + recovered frames
|
||||
u32_t silent_frames; // total silence frames
|
||||
@@ -147,8 +143,8 @@ typedef struct rtp_s {
|
||||
#endif
|
||||
|
||||
struct alac_codec_s *alac_codec;
|
||||
int flush_seqno;
|
||||
bool playing;
|
||||
int first_seqno;
|
||||
enum { RTP_WAIT, RTP_STREAM, RTP_PLAY } state;
|
||||
int stalled;
|
||||
raop_data_cb_t data_cb;
|
||||
raop_cmd_cb_t cmd_cb;
|
||||
@@ -231,7 +227,7 @@ rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv,
|
||||
ctx->rtp_host.sin_family = AF_INET;
|
||||
ctx->rtp_host.sin_addr.s_addr = INADDR_ANY;
|
||||
pthread_mutex_init(&ctx->ab_mutex, 0);
|
||||
ctx->flush_seqno = -1;
|
||||
ctx->first_seqno = -1;
|
||||
ctx->latency = latency;
|
||||
ctx->ab_read = ctx->ab_write;
|
||||
|
||||
@@ -339,24 +335,23 @@ void rtp_end(rtp_t *ctx)
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime, bool exit_locked)
|
||||
{
|
||||
bool rc = true;
|
||||
u32_t now = gettime_ms();
|
||||
{
|
||||
pthread_mutex_lock(&ctx->ab_mutex);
|
||||
|
||||
// always store flush seqno as we only want stricly above it, even when equal to RECORD
|
||||
ctx->first_seqno = seqno;
|
||||
bool flushed = false;
|
||||
|
||||
if (now < ctx->record.time + 250 || (ctx->record.seqno == seqno && ctx->record.rtptime == rtptime)) {
|
||||
rc = false;
|
||||
LOG_ERROR("[%p]: FLUSH ignored as same as RECORD (%hu - %u)", ctx, seqno, rtptime);
|
||||
} else {
|
||||
pthread_mutex_lock(&ctx->ab_mutex);
|
||||
buffer_reset(ctx->audio_buffer);
|
||||
ctx->playing = false;
|
||||
ctx->flush_seqno = seqno;
|
||||
if (!exit_locked) pthread_mutex_unlock(&ctx->ab_mutex);
|
||||
// no need to stop playing if recent or equal to record - but first_seqno is needed
|
||||
if (ctx->state == RTP_PLAY) {
|
||||
buffer_reset(ctx->audio_buffer);
|
||||
ctx->state = RTP_WAIT;
|
||||
flushed = true;
|
||||
LOG_INFO("[%p]: FLUSH packets below %hu - %u", ctx, seqno, rtptime);
|
||||
}
|
||||
|
||||
LOG_INFO("[%p]: flush %hu %u", ctx, seqno, rtptime);
|
||||
|
||||
return rc;
|
||||
|
||||
if (!exit_locked || !flushed) pthread_mutex_unlock(&ctx->ab_mutex);
|
||||
return flushed;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
@@ -367,11 +362,9 @@ void rtp_flush_release(rtp_t *ctx) {
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void rtp_record(rtp_t *ctx, unsigned short seqno, unsigned rtptime) {
|
||||
ctx->record.seqno = seqno;
|
||||
ctx->record.rtptime = rtptime;
|
||||
ctx->record.time = gettime_ms();
|
||||
|
||||
LOG_INFO("[%p]: record %hu %u", ctx, seqno, rtptime);
|
||||
ctx->first_seqno = (seqno || rtptime) ? seqno : -1;
|
||||
ctx->state = RTP_WAIT;
|
||||
LOG_INFO("[%p]: record %hu - %u", ctx, seqno, rtptime);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
@@ -442,29 +435,54 @@ static void alac_decode(rtp_t *ctx, s16_t *dest, char *buf, int len, u16_t *outs
|
||||
/*---------------------------------------------------------------------------*/
|
||||
static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool first, char *data, int len) {
|
||||
abuf_t *abuf = NULL;
|
||||
u32_t playtime;
|
||||
|
||||
pthread_mutex_lock(&ctx->ab_mutex);
|
||||
|
||||
if (!ctx->playing) {
|
||||
if ((ctx->flush_seqno == -1 || seq_order(ctx->flush_seqno, seqno)) &&
|
||||
(ctx->synchro.status & RTP_SYNC) && (ctx->synchro.status & NTP_SYNC)) {
|
||||
ctx->ab_write = seqno-1;
|
||||
ctx->ab_read = seqno;
|
||||
ctx->flush_seqno = -1;
|
||||
ctx->playing = true;
|
||||
ctx->resent_req = ctx->resent_rec = ctx->silent_frames = ctx->discarded = 0;
|
||||
playtime = ctx->synchro.time + ((rtptime - ctx->synchro.rtp) * 10) / (RAOP_SAMPLE_RATE / 100);
|
||||
ctx->cmd_cb(RAOP_PLAY, playtime);
|
||||
} else {
|
||||
pthread_mutex_unlock(&ctx->ab_mutex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* if we have received a RECORD with a seqno, then this is the first allowed rtp sequence number
|
||||
* and we are in RTP_WAIT state. If seqno was 0, then we are waiting for a flush that will tell
|
||||
* us what should be our first allowed packet but we must accept everything, wait and clean when
|
||||
* we the it arrives. This means that first packet moves us to RTP_STREAM state where we accept
|
||||
* frames but wait for the FLUSH. If this was a FLUSH while playing, then we are also in RTP_WAIT
|
||||
* state but we do have an allowed seqno and we should not accept any frame before we have it */
|
||||
|
||||
// if we have a pending first seqno and we are below, always ignore it
|
||||
if (ctx->first_seqno != -1 && seq_order(seqno, ctx->first_seqno)) {
|
||||
pthread_mutex_unlock(&ctx->ab_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->state == RTP_WAIT) {
|
||||
ctx->ab_write = seqno - 1;
|
||||
ctx->ab_read = ctx->ab_write + 1;
|
||||
ctx->resent_req = ctx->resent_rec = ctx->silent_frames = ctx->discarded = 0;
|
||||
if (ctx->first_seqno != -1) {
|
||||
LOG_INFO("[%p]: 1st accepted packet:%d, now playing", ctx, seqno);
|
||||
ctx->state = RTP_PLAY;
|
||||
ctx->first_seqno = -1;
|
||||
u32_t playtime = ctx->synchro.time + ((rtptime - ctx->synchro.rtp) * 10) / (RAOP_SAMPLE_RATE / 100);
|
||||
ctx->cmd_cb(RAOP_PLAY, playtime);
|
||||
} else {
|
||||
ctx->state = RTP_STREAM;
|
||||
LOG_INFO("[%p]: 1st accepted packet:%hu, waiting for FLUSH", ctx, seqno);
|
||||
}
|
||||
} else if (ctx->state == RTP_STREAM && ctx->first_seqno != -1 && seq_order(ctx->first_seqno, seqno + 1)) {
|
||||
// now we're talking, but first discard all packets with a seqno below first_seqno AND not ready
|
||||
while (seq_order(ctx->ab_read, ctx->first_seqno) ||
|
||||
!ctx->audio_buffer[BUFIDX(ctx->ab_read)].ready) {
|
||||
ctx->audio_buffer[BUFIDX(ctx->ab_read)].ready = false;
|
||||
ctx->ab_read++;
|
||||
}
|
||||
LOG_INFO("[%p]: done waiting for FLUSH with packet:%d, now playing starting:%hu", ctx, seqno, ctx->ab_read);
|
||||
ctx->state = RTP_PLAY;
|
||||
ctx->first_seqno = -1;
|
||||
u32_t playtime = ctx->synchro.time + ((rtptime - ctx->synchro.rtp) * 10) / (RAOP_SAMPLE_RATE / 100);
|
||||
ctx->cmd_cb(RAOP_PLAY, playtime);
|
||||
}
|
||||
|
||||
abuf = ctx->audio_buffer + BUFIDX(seqno);
|
||||
|
||||
if (seqno == (u16_t) (ctx->ab_write+1)) {
|
||||
// expected packet
|
||||
abuf = ctx->audio_buffer + BUFIDX(seqno);
|
||||
ctx->ab_write = seqno;
|
||||
LOG_SDEBUG("packet expected seqno:%hu rtptime:%u (W:%hu R:%hu)", seqno, rtptime, ctx->ab_write, ctx->ab_read);
|
||||
} else if (seq_order(ctx->ab_write, seqno)) {
|
||||
@@ -475,7 +493,7 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
|
||||
ctx->ab_read = seqno;
|
||||
} else {
|
||||
// request re-send missed frames and evaluate resent date as a whole *after*
|
||||
rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1);
|
||||
if (ctx->state == RTP_PLAY) rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1);
|
||||
|
||||
// resend date is after all requests have been sent
|
||||
u32_t now = gettime_ms();
|
||||
@@ -488,16 +506,15 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
|
||||
LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
|
||||
}
|
||||
|
||||
abuf = ctx->audio_buffer + BUFIDX(seqno);
|
||||
ctx->ab_write = seqno;
|
||||
} else if (seq_order(ctx->ab_read, seqno + 1)) {
|
||||
// recovered packet, not yet sent
|
||||
abuf = ctx->audio_buffer + BUFIDX(seqno);
|
||||
ctx->resent_rec++;
|
||||
LOG_DEBUG("[%p]: packet recovered seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
|
||||
} else {
|
||||
// too late
|
||||
LOG_DEBUG("[%p]: packet too late seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
|
||||
// too late
|
||||
if (abuf->missed) LOG_INFO("[%p]: packet too late seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
|
||||
abuf = NULL;
|
||||
}
|
||||
|
||||
if (ctx->in_frames++ > 1000) {
|
||||
@@ -508,6 +525,7 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
|
||||
if (abuf) {
|
||||
alac_decode(ctx, abuf->data, data, len, &abuf->len);
|
||||
abuf->ready = 1;
|
||||
abuf->missed = 0;
|
||||
// this is the local rtptime when this frame is expected to play
|
||||
abuf->rtptime = rtptime;
|
||||
buffer_push_packet(ctx);
|
||||
@@ -528,7 +546,7 @@ static void buffer_push_packet(rtp_t *ctx) {
|
||||
u32_t now, playtime, hold = max((ctx->latency * 1000) / (8 * RAOP_SAMPLE_RATE), 100);
|
||||
|
||||
// not ready to play yet
|
||||
if (!ctx->playing || ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
|
||||
if (ctx->state != RTP_PLAY || ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
|
||||
|
||||
// there is always at least one frame in the buffer
|
||||
do {
|
||||
@@ -551,6 +569,7 @@ static void buffer_push_packet(rtp_t *ctx) {
|
||||
LOG_DEBUG("[%p]: created zero frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read);
|
||||
ctx->data_cb(silence_frame, ctx->frame_size * 4, playtime);
|
||||
ctx->silent_frames++;
|
||||
curframe->missed = 1;
|
||||
}
|
||||
} else if (curframe->ready) {
|
||||
ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime);
|
||||
|
||||
@@ -70,7 +70,7 @@ static char * get_dac_config_string(){
|
||||
return config_alloc_get_str("dac_config", CONFIG_DAC_CONFIG, "model=i2s,bck=" STR(CONFIG_I2S_BCK_IO)
|
||||
",ws=" STR(CONFIG_I2S_WS_IO) ",do=" STR(CONFIG_I2S_DO_IO)
|
||||
",sda=" STR(CONFIG_I2C_SDA) ",scl=" STR(CONFIG_I2C_SCL)
|
||||
",mute=" STR(CONFIG_MUTE_GPIO));
|
||||
",mute=" STR(CONFIG_MUTE_GPIO) ",mck=" STR(CONFIG_I2S_MCK_IO));
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
|
||||
@@ -38,6 +38,7 @@ static esp_err_t actrls_process_action (const cJSON * member, actrls_config_t *c
|
||||
|
||||
static esp_err_t actrls_init_json(const char *profile_name, bool create);
|
||||
static void control_rotary_handler(void *client, rotary_event_e event, bool long_press);
|
||||
static void volume_rotary_handler(void *client, rotary_event_e event, bool long_press);
|
||||
static void rotary_timer( TimerHandle_t xTimer );
|
||||
|
||||
static const actrls_config_map_t actrls_config_map[] =
|
||||
@@ -157,6 +158,24 @@ esp_err_t actrls_init(const char *profile_name) {
|
||||
err = create_rotary(NULL, A, B, SW, longpress, control_rotary_handler) ? ESP_OK : ESP_FAIL;
|
||||
}
|
||||
|
||||
free(config);
|
||||
config = config_alloc_get_default(NVS_TYPE_STR, "volume_rotary", NULL, 0);
|
||||
|
||||
// now see if we have a dedicated volume rotary
|
||||
if (config && *config) {
|
||||
int A = -1, B = -1, SW = -1;
|
||||
|
||||
// parse config
|
||||
PARSE_PARAM(config, "A", '=', A);
|
||||
PARSE_PARAM(config, "B", '=', B);
|
||||
PARSE_PARAM(config, "SW", '=', SW);
|
||||
|
||||
// create rotary (no handling of long press)
|
||||
err |= create_volume_rotary(NULL, A, B, SW, volume_rotary_handler) ? ESP_OK : ESP_FAIL;
|
||||
}
|
||||
|
||||
free(config);
|
||||
|
||||
// set infrared GPIO if any
|
||||
parse_set_GPIO(set_ir_gpio);
|
||||
|
||||
@@ -290,6 +309,29 @@ static void control_rotary_handler(void *client, rotary_event_e event, bool long
|
||||
if (action != ACTRLS_NONE) (*current_controls[action])(pressed);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static void volume_rotary_handler(void *client, rotary_event_e event, bool long_press) {
|
||||
actrls_action_e action = ACTRLS_NONE;
|
||||
bool pressed = true;
|
||||
|
||||
switch(event) {
|
||||
case ROTARY_LEFT:
|
||||
action = ACTRLS_VOLDOWN;
|
||||
break;
|
||||
case ROTARY_RIGHT:
|
||||
action = ACTRLS_VOLUP;
|
||||
break;
|
||||
case ROTARY_PRESSED:
|
||||
action = ACTRLS_TOGGLE;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (action != ACTRLS_NONE) (*current_controls[action])(pressed);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
@@ -568,6 +610,13 @@ exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
actrls_handler get_ctrl_handler(actrls_action_e action) {
|
||||
return current_controls[action];
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -53,3 +53,9 @@ void actrls_set_default(const actrls_t controls, bool raw_controls, actrls_hook_
|
||||
void actrls_set(const actrls_t controls, bool raw_controls, actrls_hook_t *hook, actrls_ir_handler_t *ir_handler);
|
||||
void actrls_unset(void);
|
||||
bool actrls_ir_action(uint16_t addr, uint16_t code);
|
||||
|
||||
/* Call this to get the handler for any of the audio actions. It will map to the control specific
|
||||
to the current mode (LMS, AirPlay, Spotify). This is useful if you have a custom way to create
|
||||
buttons (like analogue buttons)
|
||||
*/
|
||||
actrls_handler get_ctrl_handler(actrls_action_e);
|
||||
|
||||
@@ -58,13 +58,13 @@ static struct {
|
||||
|
||||
static TimerHandle_t polled_timer;
|
||||
|
||||
static EXT_RAM_ATTR struct {
|
||||
static EXT_RAM_ATTR struct encoder {
|
||||
QueueHandle_t queue;
|
||||
void *client;
|
||||
rotary_encoder_info_t info;
|
||||
int A, B, SW;
|
||||
rotary_handler handler;
|
||||
} rotary;
|
||||
} rotary, volume;
|
||||
|
||||
static EXT_RAM_ATTR struct {
|
||||
RingbufHandle_t rb;
|
||||
@@ -227,11 +227,22 @@ static void buttons_task(void* arg) {
|
||||
// received a rotary event
|
||||
xQueueReceive(rotary.queue, &event, 0);
|
||||
|
||||
ESP_LOGD(TAG, "Event: position %d, direction %s", event.state.position,
|
||||
ESP_LOGD(TAG, "Rotary event: position %d, direction %s", event.state.position,
|
||||
event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET");
|
||||
|
||||
rotary.handler(rotary.client, event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ?
|
||||
ROTARY_RIGHT : ROTARY_LEFT, false);
|
||||
} else if (xActivatedMember == volume.queue) {
|
||||
rotary_encoder_event_t event = { 0 };
|
||||
|
||||
// received a volume rotary event
|
||||
xQueueReceive(volume.queue, &event, 0);
|
||||
|
||||
ESP_LOGD(TAG, "Volume event: position %d, direction %s", event.state.position,
|
||||
event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET");
|
||||
|
||||
volume.handler(volume.client, event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ?
|
||||
ROTARY_RIGHT : ROTARY_LEFT, false);
|
||||
} else {
|
||||
// this is IR
|
||||
active = infrared_receive(infrared.rb, infrared.handler);
|
||||
@@ -395,7 +406,55 @@ void *button_remap(void *client, int gpio, button_handler handler, int long_pres
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Rotary encoder handler
|
||||
* Create rotary encoder
|
||||
*/
|
||||
static bool create_rotary_encoder(struct encoder *encoder, void *id, int A, int B, int SW, int long_press, rotary_handler handler, button_handler button) {
|
||||
// nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
|
||||
if (A == -1 || B == -1 || A == 36 || A == 39 || B == 36 || B == 39) {
|
||||
ESP_LOGI(TAG, "Cannot create rotary %d %d", A, B);
|
||||
return false;
|
||||
}
|
||||
|
||||
encoder->A = A;
|
||||
encoder->B = B;
|
||||
encoder->SW = SW;
|
||||
encoder->client = id;
|
||||
encoder->handler = handler;
|
||||
|
||||
// Initialise the rotary encoder device with the GPIOs for A and B signals
|
||||
rotary_encoder_init(&encoder->info, A, B);
|
||||
|
||||
// Create a queue for events from the rotary encoder driver.
|
||||
encoder->queue = rotary_encoder_create_queue();
|
||||
rotary_encoder_set_queue(&encoder->info, encoder->queue);
|
||||
|
||||
common_task_init();
|
||||
xQueueAddToSet( encoder->queue, common_queue_set );
|
||||
|
||||
// create companion button if rotary has a switch
|
||||
if (SW != -1) button_create(id, SW, BUTTON_LOW, true, 0, button, long_press, -1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Volume button encoder handler
|
||||
*/
|
||||
static void volume_button_handler(void *id, button_event_e event, button_press_e mode, bool long_press) {
|
||||
ESP_LOGI(TAG, "Volume encoder push-button %d", event);
|
||||
volume.handler(id, event == BUTTON_PRESSED ? ROTARY_PRESSED : ROTARY_RELEASED, long_press);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Create volume encoder
|
||||
*/
|
||||
bool create_volume_rotary(void *id, int A, int B, int SW, rotary_handler handler) {
|
||||
ESP_LOGI(TAG, "Created volume encoder A:%d B:%d, SW:%d", A, B, SW);
|
||||
return create_rotary_encoder(&volume, id, A, B, SW, false, handler, volume_button_handler);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Rotary button encoder handler
|
||||
*/
|
||||
static void rotary_button_handler(void *id, button_event_e event, button_press_e mode, bool long_press) {
|
||||
ESP_LOGI(TAG, "Rotary push-button %d", event);
|
||||
@@ -406,34 +465,8 @@ static void rotary_button_handler(void *id, button_event_e event, button_press_e
|
||||
* Create rotary encoder
|
||||
*/
|
||||
bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler) {
|
||||
// nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
|
||||
if (A == -1 || B == -1 || A == 36 || A == 39 || B == 36 || B == 39) {
|
||||
ESP_LOGI(TAG, "Cannot create rotary %d %d", A, B);
|
||||
return false;
|
||||
}
|
||||
|
||||
rotary.A = A;
|
||||
rotary.B = B;
|
||||
rotary.SW = SW;
|
||||
rotary.client = id;
|
||||
rotary.handler = handler;
|
||||
|
||||
// Initialise the rotary encoder device with the GPIOs for A and B signals
|
||||
rotary_encoder_init(&rotary.info, A, B);
|
||||
|
||||
// Create a queue for events from the rotary encoder driver.
|
||||
rotary.queue = rotary_encoder_create_queue();
|
||||
rotary_encoder_set_queue(&rotary.info, rotary.queue);
|
||||
|
||||
common_task_init();
|
||||
xQueueAddToSet( rotary.queue, common_queue_set );
|
||||
|
||||
// create companion button if rotary has a switch
|
||||
if (SW != -1) button_create(id, SW, BUTTON_LOW, true, 0, rotary_button_handler, long_press, -1);
|
||||
|
||||
ESP_LOGI(TAG, "Created rotary encoder A:%d B:%d, SW:%d", A, B, SW);
|
||||
|
||||
return true;
|
||||
return create_rotary_encoder(&rotary, id, A, B, SW, long_press, handler, rotary_button_handler);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
|
||||
@@ -34,5 +34,5 @@ typedef enum { ROTARY_LEFT, ROTARY_RIGHT, ROTARY_PRESSED, ROTARY_RELEASED } rota
|
||||
typedef void (*rotary_handler)(void *id, rotary_event_e event, bool long_press);
|
||||
|
||||
bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler);
|
||||
|
||||
bool create_volume_rotary(void *id, int A, int B, int SW, rotary_handler handler);
|
||||
bool create_infrared(int gpio, infrared_handler handler, infrared_mode_t mode);
|
||||
|
||||
@@ -83,6 +83,10 @@ static void mcp23s17_set_direction(gpio_exp_t* self);
|
||||
static uint32_t mcp23s17_read(gpio_exp_t* self);
|
||||
static void mcp23s17_write(gpio_exp_t* self);
|
||||
|
||||
static void aw9523_set_direction(gpio_exp_t* self);
|
||||
static uint32_t aw9523_read(gpio_exp_t* self);
|
||||
static void aw9523_write(gpio_exp_t* self);
|
||||
|
||||
static void service_handler(void *arg);
|
||||
static void debounce_handler( TimerHandle_t xTimer );
|
||||
|
||||
@@ -130,6 +134,11 @@ static const struct gpio_exp_model_s {
|
||||
.set_pull_mode = mcp23s17_set_pull_mode,
|
||||
.read = mcp23s17_read,
|
||||
.write = mcp23s17_write, },
|
||||
{ .model = "aw9523",
|
||||
.trigger = GPIO_INTR_LOW_LEVEL,
|
||||
.set_direction = aw9523_set_direction,
|
||||
.read = aw9523_read,
|
||||
.write = aw9523_write, },
|
||||
};
|
||||
|
||||
static EXT_RAM_ATTR uint8_t n_expanders;
|
||||
@@ -671,6 +680,24 @@ static void mcp23s17_write(gpio_exp_t* self) {
|
||||
spi_write(self->spi_handle, self->phy.addr, 0x12, self->shadow, 2);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* AW9523 family : direction, read and write
|
||||
*/
|
||||
static void aw9523_set_direction(gpio_exp_t* self) {
|
||||
i2c_write(self->phy.port, self->phy.addr, 0x04, self->r_mask, 2);
|
||||
i2c_write(self->phy.port, self->phy.addr, 0x06, ~self->r_mask, 2);
|
||||
}
|
||||
|
||||
static uint32_t aw9523_read(gpio_exp_t* self) {
|
||||
// Reading both registers in one go does not seem to reset IRQ correctly
|
||||
uint8_t port1 = i2c_read(self->phy.port, self->phy.addr, 0x00, 1);
|
||||
return (i2c_read(self->phy.port, self->phy.addr, 0x01, 1) << 8) | port1;
|
||||
}
|
||||
|
||||
static void aw9523_write(gpio_exp_t* self) {
|
||||
i2c_write(self->phy.port, self->phy.addr, 0x02, self->shadow, 2);
|
||||
}
|
||||
|
||||
/***************************************************************************************
|
||||
I2C low level
|
||||
***************************************************************************************/
|
||||
@@ -793,4 +820,4 @@ static uint32_t spi_read(spi_device_handle_t handle, uint8_t addr, uint8_t reg,
|
||||
free(transaction);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +248,8 @@ void monitor_svc_init(void) {
|
||||
// re-use button management for jack handler, it's a GPIO after all
|
||||
if (jack.gpio != -1) {
|
||||
ESP_LOGI(TAG,"Adding jack (%s) detection GPIO %d", jack.active ? "high" : "low", jack.gpio);
|
||||
button_create(NULL, jack.gpio, jack.active ? BUTTON_HIGH : BUTTON_LOW, false, 250, jack_handler_default, 0, -1);
|
||||
// Use GPIO pull so the line does not float (Muse jack detect is active-low and needs pull-up)
|
||||
button_create(NULL, jack.gpio, jack.active ? BUTTON_HIGH : BUTTON_LOW, true, 250, jack_handler_default, 0, -1);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SPKFAULT_GPIO_LEVEL
|
||||
@@ -297,4 +298,4 @@ void monitor_svc_init(void) {
|
||||
*/
|
||||
monitor_gpio_t * get_jack_insertion_gpio(){
|
||||
return &jack;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,12 @@ set(BELL_DISABLE_MQTT ON)
|
||||
set(BELL_DISABLE_WEBSERVER ON)
|
||||
set(CSPOT_TARGET_ESP32 ON)
|
||||
|
||||
set(_secret $ENV{SPOTIFY_SECRET})
|
||||
if(_secret)
|
||||
separate_arguments(_secret)
|
||||
set_source_files_properties(Shim.cpp PROPERTIES COMPILE_OPTIONS "${_secret}")
|
||||
endif()
|
||||
|
||||
# because CMake is so broken, the cache set below overrides a normal "set" for the first build
|
||||
set(BELL_EXTERNAL_VORBIS "idf::codecs" CACHE STRING "provide own codecs")
|
||||
set(BELL_EXTERNAL_CJSON "idf::json" CACHE STRING "provide own CJSON")
|
||||
|
||||
@@ -32,6 +32,16 @@
|
||||
#include "platform_config.h"
|
||||
#include "nvs_utilities.h"
|
||||
#include "tools.h"
|
||||
|
||||
#if !defined(CLIENT_ID) || !defined(CLIENT_SECRET)
|
||||
#if __has_include("client_info.h")
|
||||
#include "client_info.h"
|
||||
#else
|
||||
#warning "missing Spotify's CLIENT_ID and/or CLIENT_SECRET (set SPOTIFY_SECRET env variable or set it in client_info.h)"
|
||||
#define CLIENT_ID "<your client id>"
|
||||
#define CLIENT_SECRET "<your client secret>"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static class cspotPlayer *player;
|
||||
|
||||
@@ -123,7 +133,7 @@ size_t cspotPlayer::pcmWrite(uint8_t *pcm, size_t bytes, std::string_view trackI
|
||||
}
|
||||
|
||||
return dataHandler(pcm, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
static esp_err_t handleGET(httpd_req_t *request) {
|
||||
@@ -365,6 +375,8 @@ void cspotPlayer::runTask() {
|
||||
|
||||
ctx->session->connectWithRandomAp();
|
||||
ctx->config.authData = ctx->session->authenticate(blob);
|
||||
ctx->config.clientId = CLIENT_ID;
|
||||
ctx->config.clientSecret = CLIENT_SECRET;
|
||||
|
||||
// Auth successful
|
||||
if (ctx->config.authData.size() > 0) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
#define CLIENT_ID "<your spotify client's id>"
|
||||
#define CLIENT_SECRET "<your spotify client's secret>"
|
||||
@@ -22482,9 +22482,13 @@ mg_init_library(unsigned features)
|
||||
file_mutex_init =
|
||||
pthread_mutex_init(&global_log_file_lock, &pthread_mutex_attr);
|
||||
if (file_mutex_init == 0) {
|
||||
#ifdef WINSOCK_START
|
||||
/* Start WinSock */
|
||||
WSADATA data;
|
||||
failed = wsa = WSAStartup(MAKEWORD(2, 2), &data);
|
||||
#else
|
||||
failed = wsa = 0;
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
mutexattr_init = pthread_mutexattr_init(&pthread_mutex_attr);
|
||||
@@ -22498,7 +22502,9 @@ mg_init_library(unsigned features)
|
||||
if (failed) {
|
||||
#if defined(_WIN32)
|
||||
if (wsa == 0) {
|
||||
#ifdef WINSOCK_START
|
||||
(void)WSACleanup();
|
||||
#endif
|
||||
}
|
||||
if (file_mutex_init == 0) {
|
||||
(void)pthread_mutex_destroy(&global_log_file_lock);
|
||||
@@ -22598,7 +22604,9 @@ mg_exit_library(void)
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifdef WINSOCK_START
|
||||
(void)WSACleanup();
|
||||
#endif
|
||||
(void)pthread_mutex_destroy(&global_log_file_lock);
|
||||
#else
|
||||
(void)pthread_mutexattr_destroy(&pthread_mutex_attr);
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
using namespace bell;
|
||||
|
||||
std::mutex BellHTTPServer::initMutex;
|
||||
|
||||
class WebSocketHandler : public CivetWebSocketHandler {
|
||||
public:
|
||||
BellHTTPServer::WSDataHandler dataHandler;
|
||||
@@ -187,6 +189,7 @@ bool BellHTTPServer::handlePost(CivetServer* server,
|
||||
}
|
||||
|
||||
BellHTTPServer::BellHTTPServer(int serverPort) {
|
||||
std::lock_guard lock(initMutex);
|
||||
mg_init_library(0);
|
||||
BELL_LOG(info, "HttpServer", "Server listening on port %d", serverPort);
|
||||
this->serverPort = serverPort;
|
||||
@@ -197,6 +200,11 @@ BellHTTPServer::BellHTTPServer(int serverPort) {
|
||||
server = std::make_unique<CivetServer>(civetWebOptions);
|
||||
}
|
||||
|
||||
BellHTTPServer::~BellHTTPServer() {
|
||||
std::lock_guard lock(initMutex);
|
||||
mg_exit_library();
|
||||
}
|
||||
|
||||
std::unique_ptr<BellHTTPServer::HTTPResponse> BellHTTPServer::makeJsonResponse(
|
||||
const std::string& json, int status) {
|
||||
auto response = std::make_unique<BellHTTPServer::HTTPResponse>();
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace bell {
|
||||
class BellHTTPServer : public CivetHandler {
|
||||
public:
|
||||
BellHTTPServer(int serverPort);
|
||||
~BellHTTPServer();
|
||||
|
||||
enum class WSState { CONNECTED, READY, CLOSED };
|
||||
|
||||
@@ -100,6 +101,8 @@ class BellHTTPServer : public CivetHandler {
|
||||
std::mutex responseMutex;
|
||||
HTTPHandler notFoundHandler;
|
||||
|
||||
static std::mutex initMutex;
|
||||
|
||||
bool handleGet(CivetServer* server, struct mg_connection* conn);
|
||||
bool handlePost(CivetServer* server, struct mg_connection* conn);
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
#if __has_include("avahi-client/client.h")
|
||||
#include <avahi-client/client.h>
|
||||
@@ -41,8 +42,9 @@ class implMDNSService : public MDNSService {
|
||||
#endif
|
||||
static struct mdnsd* mdnsServer;
|
||||
static in_addr_t host;
|
||||
static std::atomic<size_t> instances;
|
||||
|
||||
implMDNSService(struct mdns_service* service) : service(service){};
|
||||
implMDNSService(struct mdns_service* service) : service(service){ instances++; };
|
||||
#ifndef BELL_DISABLE_AVAHI
|
||||
implMDNSService(AvahiEntryGroup* avahiGroup) : avahiGroup(avahiGroup){};
|
||||
#endif
|
||||
@@ -51,6 +53,7 @@ class implMDNSService : public MDNSService {
|
||||
|
||||
struct mdnsd* implMDNSService::mdnsServer = NULL;
|
||||
in_addr_t implMDNSService::host = INADDR_ANY;
|
||||
std::atomic<size_t> implMDNSService::instances = 0;
|
||||
static std::mutex registerMutex;
|
||||
#ifndef BELL_DISABLE_AVAHI
|
||||
AvahiClient* implMDNSService::avahiClient = NULL;
|
||||
@@ -66,11 +69,21 @@ void implMDNSService::unregisterService() {
|
||||
#ifndef BELL_DISABLE_AVAHI
|
||||
if (avahiGroup) {
|
||||
avahi_entry_group_free(avahiGroup);
|
||||
if (!--instances && implMDNSService::avahiClient) {
|
||||
avahi_client_free(implMDNSService::avahiClient);
|
||||
avahi_simple_poll_free(implMDNSService::avahiPoll);
|
||||
implMDNSService::avahiClient = nullptr;
|
||||
implMDNSService::avahiPoll = nullptr;
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
mdns_service_remove(implMDNSService::mdnsServer, service);
|
||||
}
|
||||
if (!--instances && implMDNSService::mdnsServer) {
|
||||
mdnsd_stop(implMDNSService::mdnsServer);
|
||||
implMDNSService::mdnsServer = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<MDNSService> MDNSService::registerService(
|
||||
@@ -180,19 +193,14 @@ std::unique_ptr<MDNSService> MDNSService::registerService(
|
||||
std::string type(serviceType + "." + serviceProto + ".local");
|
||||
|
||||
BELL_LOG(info, "MDNS", "using built-in mDNS for %s", serviceName.c_str());
|
||||
struct mdns_service* mdnsService =
|
||||
auto service =
|
||||
mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
|
||||
type.c_str(), servicePort, NULL, txt.data());
|
||||
if (mdnsService) {
|
||||
auto service =
|
||||
mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
|
||||
type.c_str(), servicePort, NULL, txt.data());
|
||||
|
||||
return std::make_unique<implMDNSService>(service);
|
||||
}
|
||||
if (service) return std::make_unique<implMDNSService>(service);
|
||||
}
|
||||
|
||||
BELL_LOG(error, "MDNS", "cannot start any mDNS listener for %s",
|
||||
serviceName.c_str());
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
#include "BellLogger.h"
|
||||
#include "MDNSService.h"
|
||||
@@ -19,13 +20,12 @@ using namespace bell;
|
||||
class implMDNSService : public MDNSService {
|
||||
private:
|
||||
struct mdns_service* service;
|
||||
void unregisterService(void) {
|
||||
mdns_service_remove(implMDNSService::mdnsServer, service);
|
||||
};
|
||||
void unregisterService(void);
|
||||
|
||||
public:
|
||||
static struct mdnsd* mdnsServer;
|
||||
implMDNSService(struct mdns_service* service) : service(service){};
|
||||
static std::atomic<size_t> instances;
|
||||
implMDNSService(struct mdns_service* service) : service(service) { instances++; };
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -33,8 +33,18 @@ class implMDNSService : public MDNSService {
|
||||
**/
|
||||
|
||||
struct mdnsd* implMDNSService::mdnsServer = NULL;
|
||||
std::atomic<size_t> implMDNSService::instances = 0;
|
||||
|
||||
static std::mutex registerMutex;
|
||||
|
||||
void implMDNSService::unregisterService() {
|
||||
mdns_service_remove(implMDNSService::mdnsServer, service);
|
||||
if (!--instances && implMDNSService::mdnsServer) {
|
||||
mdnsd_stop(implMDNSService::mdnsServer);
|
||||
implMDNSService::mdnsServer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<MDNSService> MDNSService::registerService(
|
||||
const std::string& serviceName, const std::string& serviceType,
|
||||
const std::string& serviceProto, const std::string& serviceHost,
|
||||
@@ -94,5 +104,5 @@ std::unique_ptr<MDNSService> MDNSService::registerService(
|
||||
mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
|
||||
type.c_str(), servicePort, NULL, txt.data());
|
||||
|
||||
return std::make_unique<implMDNSService>(service);
|
||||
return service ? std::make_unique<implMDNSService>(service) : nullptr;
|
||||
}
|
||||
|
||||
@@ -72,7 +72,11 @@ class Task {
|
||||
(LPTHREAD_START_ROUTINE)taskEntryFunc, this, 0, NULL);
|
||||
return thread != NULL;
|
||||
#else
|
||||
return (pthread_create(&thread, NULL, taskEntryFunc, this) == 0);
|
||||
if (!pthread_create(&thread, NULL, taskEntryFunc, this)) {
|
||||
pthread_detach(thread);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -108,13 +112,7 @@ class Task {
|
||||
#endif
|
||||
|
||||
static void* taskEntryFunc(void* This) {
|
||||
Task* self = (Task*)This;
|
||||
self->runTask();
|
||||
#if _WIN32
|
||||
WaitForSingleObject(self->thread, INFINITE);
|
||||
#else
|
||||
pthread_join(self->thread, NULL);
|
||||
#endif
|
||||
((Task*)This)->runTask();
|
||||
return NULL;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,6 +24,8 @@ struct Context {
|
||||
AudioFormat audioFormat = AudioFormat::AudioFormat_OGG_VORBIS_160;
|
||||
std::string deviceId;
|
||||
std::string deviceName;
|
||||
std::string clientId;
|
||||
std::string clientSecret;
|
||||
std::vector<uint8_t> authData;
|
||||
int volume;
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class CDNAudioFile;
|
||||
// Used in got track info event
|
||||
struct TrackInfo {
|
||||
std::string name, album, artist, imageUrl, trackId;
|
||||
uint32_t duration;
|
||||
uint32_t duration, number, discNumber;
|
||||
|
||||
void loadPbTrack(Track* pbTrack, const std::vector<uint8_t>& gid);
|
||||
void loadPbEpisode(Episode* pbEpisode, const std::vector<uint8_t>& gid);
|
||||
|
||||
@@ -32,6 +32,8 @@ message Artist {
|
||||
message Track {
|
||||
optional bytes gid = 1;
|
||||
optional string name = 2;
|
||||
optional sint32 number = 5;
|
||||
optional sint32 disc_number = 6;
|
||||
optional sint32 duration = 0x7;
|
||||
optional Album album = 0x3;
|
||||
repeated Artist artist = 0x4;
|
||||
@@ -44,6 +46,7 @@ message Episode {
|
||||
optional bytes gid = 1;
|
||||
optional string name = 2;
|
||||
optional sint32 duration = 7;
|
||||
optional sint32 number = 65;
|
||||
repeated AudioFile file = 12;
|
||||
repeated Restriction restriction = 0x4B;
|
||||
optional ImageGroup covers = 0x44;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "BellLogger.h" // for AbstractLogger
|
||||
#include "BellUtils.h" // for BELL_SLEEP_MS
|
||||
#include "CSpotContext.h" // for Context
|
||||
#include "HTTPClient.h"
|
||||
#include "Logger.h" // for CSPOT_LOG
|
||||
@@ -24,13 +25,8 @@
|
||||
#include "nlohmann/json_fwd.hpp" // for json
|
||||
#endif
|
||||
|
||||
#include "protobuf/login5.pb.h" // for LoginRequest
|
||||
|
||||
using namespace cspot;
|
||||
|
||||
static std::string CLIENT_ID =
|
||||
"65b708073fc0480ea92a077233ca87bd"; // Spotify web client's client id
|
||||
|
||||
static std::string SCOPES =
|
||||
"streaming,user-library-read,user-library-modify,user-top-read,user-read-"
|
||||
"recently-played"; // Required access scopes
|
||||
@@ -68,69 +64,43 @@ void AccessKeyFetcher::updateAccessKey() {
|
||||
|
||||
keyPending = true;
|
||||
|
||||
// Prepare a protobuf login request
|
||||
static LoginRequest loginRequest = LoginRequest_init_zero;
|
||||
static LoginResponse loginResponse = LoginResponse_init_zero;
|
||||
|
||||
// Assign necessary request fields
|
||||
loginRequest.client_info.client_id.funcs.encode = &bell::nanopb::encodeString;
|
||||
loginRequest.client_info.client_id.arg = &CLIENT_ID;
|
||||
|
||||
loginRequest.client_info.device_id.funcs.encode = &bell::nanopb::encodeString;
|
||||
loginRequest.client_info.device_id.arg = &ctx->config.deviceId;
|
||||
|
||||
loginRequest.login_method.stored_credential.username.funcs.encode =
|
||||
&bell::nanopb::encodeString;
|
||||
loginRequest.login_method.stored_credential.username.arg =
|
||||
&ctx->config.username;
|
||||
|
||||
// Set login method to stored credential
|
||||
loginRequest.which_login_method = LoginRequest_stored_credential_tag;
|
||||
loginRequest.login_method.stored_credential.data.funcs.encode =
|
||||
&bell::nanopb::encodeVector;
|
||||
loginRequest.login_method.stored_credential.data.arg = &ctx->config.authData;
|
||||
|
||||
// Max retry of 3, can receive different hash cat types
|
||||
int retryCount = 3;
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
auto encodedRequest = pbEncode(LoginRequest_fields, &loginRequest);
|
||||
CSPOT_LOG(info, "Access token expired, fetching new one... %d",
|
||||
encodedRequest.size());
|
||||
CSPOT_LOG(info, "Access token expired, fetching new one...");
|
||||
|
||||
// Perform a login5 request, containing the encoded protobuf data
|
||||
auto credentials = "grant_type=client_credentials&client_id=" + ctx->config.clientId + "&client_secret=" + ctx->config.clientSecret;
|
||||
std::vector<uint8_t> body(credentials.begin(), credentials.end());
|
||||
|
||||
auto response = bell::HTTPClient::post(
|
||||
"https://login5.spotify.com/v3/login",
|
||||
{{"Content-Type", "application/x-protobuf"}}, encodedRequest);
|
||||
|
||||
auto responseBytes = response->bytes();
|
||||
|
||||
// Deserialize the response
|
||||
pbDecode(loginResponse, LoginResponse_fields, responseBytes);
|
||||
|
||||
if (loginResponse.which_response == LoginResponse_ok_tag) {
|
||||
// Successfully received an auth token
|
||||
"https://accounts.spotify.com/api/token",
|
||||
{ {"Content-Type", "application/x-www-form-urlencoded"} }, body);
|
||||
|
||||
#ifdef BELL_ONLY_CJSON
|
||||
cJSON* root = cJSON_Parse(response->body().data());
|
||||
if (!cJSON_GetObjectItem(root, "error")) {
|
||||
accessKey = std::string(cJSON_GetObjectItem(root, "access_token")->valuestring);
|
||||
int expiresIn = cJSON_GetObjectItem(root, "expires_in")->valueint;
|
||||
cJSON_Delete(root);
|
||||
#else
|
||||
auto root = nlohmann::json::parse(response->bytes());
|
||||
if (!root.contains("error")) {
|
||||
accessKey = std::string(root["access_token"]);
|
||||
int expiresIn = root["expires_in"];
|
||||
#endif
|
||||
// Successfully received an auth token
|
||||
CSPOT_LOG(info, "Access token sucessfully fetched");
|
||||
success = true;
|
||||
|
||||
accessKey = std::string(loginResponse.response.ok.access_token);
|
||||
|
||||
// Expire in ~30 minutes
|
||||
int expiresIn = 3600 / 2;
|
||||
|
||||
if (loginResponse.response.ok.has_access_token_expires_in) {
|
||||
int expiresIn = loginResponse.response.ok.access_token_expires_in / 2;
|
||||
}
|
||||
|
||||
this->expiresAt =
|
||||
ctx->timeProvider->getSyncedTimestamp() + (expiresIn * 1000);
|
||||
} else {
|
||||
CSPOT_LOG(error, "Failed to fetch access token");
|
||||
ctx->timeProvider->getSyncedTimestamp() + (expiresIn * 1000);
|
||||
}
|
||||
else {
|
||||
CSPOT_LOG(error, "Failed to fetch access token");
|
||||
BELL_SLEEP_MS(3000);
|
||||
}
|
||||
|
||||
// Free up allocated memory for response
|
||||
pb_release(LoginResponse_fields, &loginResponse);
|
||||
|
||||
retryCount--;
|
||||
} while (retryCount >= 0 && !success);
|
||||
|
||||
@@ -97,6 +97,8 @@ void TrackInfo::loadPbTrack(Track* pbTrack, const std::vector<uint8_t>& gid) {
|
||||
}
|
||||
}
|
||||
|
||||
number = pbTrack->has_number ? pbTrack->number : 0;
|
||||
discNumber = pbTrack->has_disc_number ? pbTrack->disc_number : 0;
|
||||
duration = pbTrack->duration;
|
||||
}
|
||||
|
||||
@@ -113,6 +115,8 @@ void TrackInfo::loadPbEpisode(Episode* pbEpisode,
|
||||
imageUrl = "https://i.scdn.co/image/" + bytesToHexString(imageId);
|
||||
}
|
||||
|
||||
number = pbEpisode->has_number ? pbEpisode->number : 0;
|
||||
discNumber = 0;
|
||||
duration = pbEpisode->duration;
|
||||
}
|
||||
|
||||
|
||||
@@ -288,6 +288,7 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
|
||||
output.frames_played = 0;
|
||||
output.external = DECODE_RAOP;
|
||||
output.state = OUTPUT_STOPPED;
|
||||
|
||||
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
|
||||
LOG_INFO("resizing buffer %u", outputbuf->size);
|
||||
break;
|
||||
@@ -377,6 +378,7 @@ static bool cspot_cmd_handler(cspot_event_t cmd, va_list args)
|
||||
output.state = OUTPUT_STOPPED;
|
||||
sink_state = SINK_ABORT;
|
||||
_buf_flush(outputbuf);
|
||||
_buf_limit(outputbuf, 0);
|
||||
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
|
||||
LOG_INFO("CSpot start track");
|
||||
break;
|
||||
|
||||
@@ -687,6 +687,8 @@ void draw_VU(struct GDS_Device * display, int level, int x, int y, int width, bo
|
||||
static void grfe_handler( u8_t *data, int len) {
|
||||
struct grfe_packet *pkt = (struct grfe_packet*) data;
|
||||
|
||||
if (!display) return;
|
||||
|
||||
// we don't support transition, simply claim we're done
|
||||
if (pkt->transition != 'c') {
|
||||
LOG_INFO("Transition %c requested with offset %hu, param %d", pkt->transition, pkt->offset, pkt->param);
|
||||
@@ -763,6 +765,8 @@ static void grfs_handler(u8_t *data, int len) {
|
||||
int size = len - sizeof(struct grfs_packet);
|
||||
int offset = htons(pkt->offset);
|
||||
|
||||
if (!display) return;
|
||||
|
||||
LOG_DEBUG("grfs s:%u d:%u p:%u sp:%u by:%hu m:%hu w:%hu o:%hu",
|
||||
(int) pkt->screen,
|
||||
(int) pkt->direction, // 1=left, 2=right
|
||||
@@ -773,7 +777,7 @@ static void grfs_handler(u8_t *data, int len) {
|
||||
htons(pkt->width), // last column of animation that contains a "full" screen
|
||||
htons(pkt->offset) // offset if multiple packets are sent
|
||||
);
|
||||
|
||||
|
||||
// new grfs frame, build scroller info
|
||||
if (!offset) {
|
||||
// use the display as a general lock
|
||||
@@ -818,6 +822,8 @@ static void grfs_handler(u8_t *data, int len) {
|
||||
static void grfg_handler(u8_t *data, int len) {
|
||||
struct grfg_packet *pkt = (struct grfg_packet*) data;
|
||||
|
||||
if (!display) return;
|
||||
|
||||
LOG_DEBUG("gfrg s:%hu w:%hu (len:%u)", htons(pkt->screen), htons(pkt->width), len);
|
||||
|
||||
// full screen artwork or for small screen, visu has priority when full screen
|
||||
@@ -864,6 +870,8 @@ static void grfa_handler(u8_t *data, int len) {
|
||||
int offset = htonl(pkt->offset);
|
||||
int length = htonl(pkt->length);
|
||||
|
||||
if (!display) return;
|
||||
|
||||
// when using full screen visualizer on small screen there is a brief overlay
|
||||
artwork.enable = (length != 0);
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ static EXT_RAM_ATTR struct {
|
||||
void *handle;
|
||||
float loudness, volume;
|
||||
uint32_t samplerate;
|
||||
float gain[EQ_BANDS], loudness_gain[EQ_BANDS];
|
||||
int8_t gain[EQ_BANDS];
|
||||
float loudness_gain[EQ_BANDS];
|
||||
bool update;
|
||||
} equalizer;
|
||||
|
||||
@@ -151,6 +152,8 @@ void equalizer_set_gain(int8_t *gain) {
|
||||
char config[EQ_BANDS * 4 + 1] = { };
|
||||
int n = 0;
|
||||
|
||||
if (memcmp(equalizer.gain, gain, EQ_BANDS) != 0) equalizer.update = true;
|
||||
|
||||
for (int i = 0; i < EQ_BANDS; i++) {
|
||||
equalizer.gain[i] = gain[i];
|
||||
n += sprintf(config + n, "%d,", gain[i]);
|
||||
@@ -159,9 +162,6 @@ void equalizer_set_gain(int8_t *gain) {
|
||||
config[n-1] = '\0';
|
||||
config_set_value(NVS_TYPE_STR, "equalizer", config);
|
||||
|
||||
// update only if something changed
|
||||
if (!memcmp(equalizer.gain, gain, EQ_BANDS)) equalizer.update = true;
|
||||
|
||||
LOG_INFO("equalizer gain %s", config);
|
||||
#else
|
||||
LOG_INFO("no equalizer with 32 bits samples");
|
||||
|
||||
+13
@@ -51,6 +51,19 @@ static const struct {
|
||||
{\"reg\":26,\"val\":0}, {\"reg\":27,\"val\":0}, {\"reg\":25,\"val\":50}, {\"reg\":38,\"val\":0}, \
|
||||
{\"reg\":39,\"val\":184}, {\"reg\":42,\"val\":184}, {\"reg\":46,\"val\":30}, {\"reg\":47,\"val\":30}, \
|
||||
{\"reg\":48,\"val\":30}, {\"reg\":49,\"val\":30}, {\"reg\":2,\"val\":170}]}" },
|
||||
{ "es8311", true,
|
||||
"{\"init\":[ \
|
||||
{\"reg\":1,\"val\":48}, {\"reg\":2,\"val\":0}, {\"reg\":3,\"val\":16}, {\"reg\":22,\"val\":36}, \
|
||||
{\"reg\":4,\"val\":16}, {\"reg\":5,\"val\":0}, {\"reg\":11,\"val\":0}, {\"reg\":12,\"val\":0}, \
|
||||
{\"reg\":16,\"val\":31}, {\"reg\":17,\"val\":127}, {\"reg\":0,\"val\":128}, {\"reg\":0,\"val\":128}, \
|
||||
{\"reg\":1,\"val\":63}, {\"reg\":1,\"val\":63}, {\"reg\":2,\"val\":0}, {\"reg\":5,\"val\":0}, \
|
||||
{\"reg\":3,\"val\":16}, {\"reg\":4,\"val\":16}, {\"reg\":7,\"val\":0}, {\"reg\":8,\"val\":255}, \
|
||||
{\"reg\":6,\"val\":3}, {\"reg\":1,\"val\":63}, {\"reg\":6,\"val\":3}, {\"reg\":19,\"val\":16}, \
|
||||
{\"reg\":27,\"val\":10}, {\"reg\":28,\"val\":106}, {\"reg\":9,\"val\":12}, {\"reg\":10,\"val\":12}, \
|
||||
{\"reg\":9,\"val\":12}, {\"reg\":10,\"val\":12}, {\"reg\":50,\"val\":178}, {\"reg\":9,\"val\":12}, \
|
||||
{\"reg\":10,\"val\":12}, {\"reg\":23,\"val\":191}, {\"reg\":14,\"val\":2} ,{\"reg\":18,\"val\":0}, \
|
||||
{\"reg\":20,\"val\":26}, {\"reg\":20,\"val\":26}, {\"reg\":13,\"val\":1}, {\"reg\":21,\"val\":64}, \
|
||||
{\"reg\":55,\"val\":72}, {\"reg\":69,\"val\":0}, {\"reg\":50,\"val\":200}]}" },
|
||||
{ NULL, false, NULL }
|
||||
};
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ struct flac {
|
||||
);
|
||||
FLAC__bool (* FLAC__stream_decoder_process_single)(FLAC__StreamDecoder *decoder);
|
||||
FLAC__StreamDecoderState (* FLAC__stream_decoder_get_state)(const FLAC__StreamDecoder *decoder);
|
||||
FLAC__bool (*FLAC__stream_decoder_set_ogg_chaining)(FLAC__StreamDecoder* decoder, FLAC__bool allow);
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -121,6 +122,9 @@ static FLAC__StreamDecoderReadStatus read_cb(const FLAC__StreamDecoder *decoder,
|
||||
_buf_inc_readp(streambuf, bytes);
|
||||
UNLOCK_S;
|
||||
|
||||
// give some time for stream to acquire data otherwise flac will hammer us
|
||||
if (!end && !bytes) usleep(100 * 1000);
|
||||
|
||||
*want = bytes;
|
||||
|
||||
return end ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
|
||||
@@ -137,8 +141,8 @@ static FLAC__StreamDecoderWriteStatus write_cb(const FLAC__StreamDecoder *decode
|
||||
FLAC__int32 *rptr = (FLAC__int32 *)buffer[channels > 1 ? 1 : 0];
|
||||
|
||||
if (decode.new_stream) {
|
||||
LOG_INFO("setting track_start");
|
||||
LOCK_O;
|
||||
LOG_INFO("setting track_start");
|
||||
output.track_start = outputbuf->writep;
|
||||
decode.new_stream = false;
|
||||
|
||||
@@ -253,6 +257,7 @@ static void flac_open(u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t en
|
||||
|
||||
if ( f->container == 'o' ) {
|
||||
LOG_INFO("ogg/flac container - using init_ogg_stream");
|
||||
FLAC(f, stream_decoder_set_ogg_chaining, f->decoder, true);
|
||||
FLAC(f, stream_decoder_init_ogg_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL);
|
||||
} else {
|
||||
FLAC(f, stream_decoder_init_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL);
|
||||
@@ -295,6 +300,7 @@ static bool load_flac() {
|
||||
f->FLAC__stream_decoder_init_ogg_stream = dlsym(handle, "FLAC__stream_decoder_init_ogg_stream");
|
||||
f->FLAC__stream_decoder_process_single = dlsym(handle, "FLAC__stream_decoder_process_single");
|
||||
f->FLAC__stream_decoder_get_state = dlsym(handle, "FLAC__stream_decoder_get_state");
|
||||
f->FLAC__stream_decoder_set_ogg_chaining = dlsym(handle, "FLAC__stream_decoder_set_ogg_chaining");
|
||||
|
||||
if ((err = dlerror()) != NULL) {
|
||||
LOG_INFO("dlerror: %s", err);
|
||||
|
||||
@@ -194,7 +194,7 @@ static int read_opus_header(void) {
|
||||
// nothing has been found and we have no more bytes, come back later
|
||||
if (status <= 0) break;
|
||||
|
||||
// always set stream serialno if we have a new one
|
||||
// always set stream serialno if we have a new one (no multiplexed streams)
|
||||
if (OG(&go, page_bos, &u->page)) OG(&go, stream_reset_serialno, &u->state, OG(&go, page_serialno, &u->page));
|
||||
|
||||
// bring new page in if we want it (otherwise we're just skipping)
|
||||
|
||||
@@ -60,7 +60,7 @@ frames_t _output_frames(frames_t avail) {
|
||||
silence = false;
|
||||
|
||||
// start when threshold met
|
||||
if (output.state == OUTPUT_BUFFER && (frames * BYTES_PER_FRAME) > output.threshold * output.next_sample_rate / 10 && frames > output.start_frames) {
|
||||
if (output.state == OUTPUT_BUFFER && frames > output.threshold * output.next_sample_rate / 10 && frames > output.start_frames) {
|
||||
output.state = OUTPUT_RUNNING;
|
||||
LOG_INFO("start buffer frames: %u", frames);
|
||||
wake_controller();
|
||||
|
||||
@@ -139,8 +139,11 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g
|
||||
u8_t *buf = silencebuf;
|
||||
memcpy(btout + oframes * BYTES_PER_FRAME, buf, out_frames * BYTES_PER_FRAME);
|
||||
}
|
||||
|
||||
output_visu_export(btout + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2);
|
||||
|
||||
// don't update visu if we don't have enough data in buffer (500 ms)
|
||||
if (silence || _buf_used(outputbuf) > BYTES_PER_FRAME * output.current_sample_rate / 2) {
|
||||
output_visu_export(btout + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2);
|
||||
}
|
||||
|
||||
oframes += out_frames;
|
||||
|
||||
|
||||
@@ -222,6 +222,25 @@ static void set_i2s_pin(char *config, i2s_pin_config_t *pin_config) {
|
||||
#endif
|
||||
}
|
||||
|
||||
/* When a panic occurs during playback, the I2S interface can produce a loud noise burst.
|
||||
* This code runs just before the system panic handler to "emergency stop" the I2S iterface
|
||||
* to prevent the noise burst from happening. Note that when this code is called the system
|
||||
* has already crashed, so no need to disable interrupts, acquire locks, or otherwise be nice.
|
||||
*
|
||||
* This code makes use of the linker --wrap feature to intercept the call to esp_panic_handler.
|
||||
*/
|
||||
|
||||
void __real_esp_panic_handler(void*);
|
||||
|
||||
void __wrap_esp_panic_handler (void* info) {
|
||||
esp_rom_printf("I2S abort!\r\n");
|
||||
|
||||
i2s_stop(CONFIG_I2S_NUM);
|
||||
|
||||
/* Call the original panic handler function to finish processing this error */
|
||||
__real_esp_panic_handler(info);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Initialize the DAC output
|
||||
*/
|
||||
@@ -439,7 +458,7 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
|
||||
static EXT_RAM_ATTR StackType_t xStack[OUTPUT_THREAD_STACK_SIZE] __attribute__ ((aligned (4)));
|
||||
output_i2s_task = xTaskCreateStaticPinnedToCore( (TaskFunction_t) output_thread_i2s, "output_i2s", OUTPUT_THREAD_STACK_SIZE,
|
||||
NULL, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, xStack, &xTaskBuffer, 0 );
|
||||
NULL, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 10, xStack, &xTaskBuffer, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,8 +504,8 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32
|
||||
memcpy(obuf + oframes * BYTES_PER_FRAME, silencebuf, out_frames * BYTES_PER_FRAME);
|
||||
}
|
||||
|
||||
// don't update visu if we don't have enough data in buffer
|
||||
if (silence || output.external || _buf_used(outputbuf) > outputbuf->size >> 2 ) {
|
||||
// don't update visu if we don't have enough data in buffer (500 ms)
|
||||
if (silence || _buf_used(outputbuf) > BYTES_PER_FRAME * output.current_sample_rate / 2) {
|
||||
output_visu_export(obuf + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2);
|
||||
}
|
||||
|
||||
|
||||
@@ -393,7 +393,9 @@ static void process_strm(u8_t *pkt, int len) {
|
||||
stream_file(header, header_len, strm->threshold * 1024);
|
||||
autostart -= 2;
|
||||
} else {
|
||||
stream_sock(ip, port, strm->format, header, header_len, strm->threshold * 1024, autostart >= 2);
|
||||
stream_sock(ip, port, strm->flags & 0x20,
|
||||
strm->format == 'o' || strm->format == 'u' || (strm->format == 'f' && strm->pcm_sample_size == 'o'),
|
||||
header, header_len, strm->threshold * 1024, autostart >= 2);
|
||||
}
|
||||
sendSTAT("STMc", 0);
|
||||
sentSTMu = sentSTMo = sentSTMl = false;
|
||||
@@ -412,8 +414,8 @@ static void process_strm(u8_t *pkt, int len) {
|
||||
output.fade_secs = strm->transition_period;
|
||||
output.invert = (strm->flags & 0x03) == 0x03;
|
||||
output.channels = (strm->flags & 0x0c) >> 2;
|
||||
UNLOCK_O;
|
||||
LOG_DEBUG("set fade: %u, channels: %u, invert: %u", output.fade_mode, output.channels, output.invert);
|
||||
UNLOCK_O;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -585,7 +585,7 @@ struct streamstate {
|
||||
void stream_init(log_level level, unsigned stream_buf_size);
|
||||
void stream_close(void);
|
||||
void stream_file(const char *header, size_t header_len, unsigned threshold);
|
||||
void stream_sock(u32_t ip, u16_t port, char codec, const char *header, size_t header_len, unsigned threshold, bool cont_wait);
|
||||
void stream_sock(u32_t ip, u16_t port, bool use_ssl, bool use_ogg, const char *header, size_t header_len, unsigned threshold, bool cont_wait);
|
||||
bool stream_disconnect(void);
|
||||
|
||||
// decode.c
|
||||
|
||||
@@ -62,9 +62,10 @@ static sockfd fd;
|
||||
struct EXT_RAM_ATTR streamstate stream;
|
||||
|
||||
static EXT_RAM_ATTR struct {
|
||||
bool flac;
|
||||
u64_t serial;
|
||||
enum { OGG_OFF, OGG_SYNC, OGG_HEADER, OGG_SEGMENTS, OGG_PAGE } state;
|
||||
size_t want, miss, match;
|
||||
u64_t granule;
|
||||
u8_t* data, segments[255];
|
||||
#pragma pack(push, 1)
|
||||
struct {
|
||||
@@ -183,6 +184,10 @@ static size_t memfind(const u8_t* haystack, size_t n, const char* needle, size_t
|
||||
return i;
|
||||
}
|
||||
|
||||
/* https://xiph.org/ogg/doc/framing.html
|
||||
* https://xiph.org/flac/ogg_mapping.html
|
||||
* https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-610004.2 */
|
||||
|
||||
static void stream_ogg(size_t n) {
|
||||
if (ogg.state == OGG_OFF) return;
|
||||
u8_t* p = streambuf->writep;
|
||||
@@ -212,9 +217,8 @@ static void stream_ogg(size_t n) {
|
||||
ogg.data = (u8_t*) &ogg.header;
|
||||
ogg.match = 0;
|
||||
} else {
|
||||
if (!ogg.match) {
|
||||
LOG_INFO("OggS not at expected position %zu/%zu", pos, n);
|
||||
}
|
||||
if (!ogg.match) LOG_INFO("OggS not at expected position %zu/%zu", pos, n);
|
||||
LOG_INFO("OggS not at expected position %zu/%zu", pos, n);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -233,30 +237,37 @@ static void stream_ogg(size_t n) {
|
||||
// calculate size of page using lacing values
|
||||
for (size_t i = 0; i < ogg.want; i++) ogg.miss += ogg.data[i];
|
||||
ogg.want = ogg.miss;
|
||||
|
||||
// acquire serial number when we are looking for headers and hit a bos
|
||||
if (ogg.serial == ULLONG_MAX && (ogg.header.type & 0x02)) ogg.serial = ogg.header.serial;
|
||||
|
||||
if (ogg.header.granule == 0 || (ogg.header.granule == -1 && ogg.granule == 0)) {
|
||||
// granule 0 means a new stream, so let's look into it
|
||||
ogg.state = OGG_PAGE;
|
||||
ogg.data = malloc(ogg.want);
|
||||
} else {
|
||||
// we have overshot and missed header, reset serial number to restart search (O and -1 are le/be)
|
||||
if (ogg.header.serial == ogg.serial && ogg.header.granule && ogg.header.granule != -1) ogg.serial = ULLONG_MAX;
|
||||
|
||||
// not our serial (the above protected us from granule > 0)
|
||||
if (ogg.header.serial != ogg.serial) {
|
||||
// otherwise, jump over data
|
||||
ogg.state = OGG_SYNC;
|
||||
ogg.data = NULL;
|
||||
} else {
|
||||
ogg.state = OGG_PAGE;
|
||||
ogg.data = malloc(ogg.want);
|
||||
}
|
||||
|
||||
// memorize granule for next page
|
||||
if (ogg.header.granule != -1) ogg.granule = ogg.header.granule;
|
||||
break;
|
||||
case OGG_PAGE: {
|
||||
size_t offset = 0;
|
||||
char** tag = (char* []){ "\x3vorbis", "OpusTags", NULL };
|
||||
size_t ofs = 0;
|
||||
|
||||
// try to find one of valid Ogg pattern (vorbis, opus)
|
||||
for (char** tag = (char*[]) { "\x3vorbis", "OpusTags", NULL }; *tag; tag++, offset = 0) {
|
||||
size_t pos = memfind(ogg.data, ogg.want, *tag, strlen(*tag), &offset);
|
||||
if (offset != strlen(*tag)) continue;
|
||||
|
||||
/* with OggFlac, we need the next page (packet) - VorbisComment is wrapped into a FLAC_METADATA
|
||||
* and except with vorbis, comment packet starts a new page but even in vorbis, it won't span
|
||||
* accross multiple pages */
|
||||
if (ogg.flac) ofs = 4;
|
||||
else if (!memcmp(ogg.data, "\x7f""FLAC", 5)) ogg.flac = true;
|
||||
else for (size_t n = 0; *tag; tag++, ofs = 0) if ((ofs = memfind(ogg.data, ogg.want, *tag, strlen(*tag), &n)) && n == strlen(*tag)) break;
|
||||
|
||||
if (ofs) {
|
||||
// u32:len,char[]:vendorId, u32:N, N x (u32:len,char[]:comment)
|
||||
char* p = (char*) ogg.data + pos;
|
||||
char* p = (char*) ogg.data + ofs;
|
||||
p += *p + 4;
|
||||
u32_t count = *p;
|
||||
p += 4;
|
||||
@@ -280,10 +291,11 @@ static void stream_ogg(size_t n) {
|
||||
}
|
||||
}
|
||||
|
||||
ogg.flac = false;
|
||||
ogg.serial = ULLONG_MAX;
|
||||
stream.meta_send = true;
|
||||
wake_controller();
|
||||
LOG_INFO("Ogg metadata length: %u", stream.header_len - 3);
|
||||
break;
|
||||
}
|
||||
free(ogg.data);
|
||||
ogg.data = NULL;
|
||||
@@ -625,7 +637,7 @@ void stream_file(const char *header, size_t header_len, unsigned threshold) {
|
||||
UNLOCK;
|
||||
}
|
||||
|
||||
void stream_sock(u32_t ip, u16_t port, char codec, const char *header, size_t header_len, unsigned threshold, bool cont_wait) {
|
||||
void stream_sock(u32_t ip, u16_t port, bool use_ssl, bool use_ogg, const char *header, size_t header_len, unsigned threshold, bool cont_wait) {
|
||||
struct sockaddr_in addr;
|
||||
|
||||
#if EMBEDDED
|
||||
@@ -726,7 +738,9 @@ void stream_sock(u32_t ip, u16_t port, char codec, const char *header, size_t he
|
||||
stream.threshold = threshold;
|
||||
|
||||
ogg.miss = ogg.match = 0;
|
||||
ogg.state = (codec == 'o' || codec == 'p') ? OGG_SYNC : OGG_OFF;
|
||||
ogg.state = use_ogg ? OGG_SYNC : OGG_OFF;
|
||||
ogg.flac = false;
|
||||
ogg.serial = ULLONG_MAX;
|
||||
|
||||
UNLOCK;
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ static int read_vorbis_header(void) {
|
||||
// nothing has been found and we have no more bytes, come back later
|
||||
if (status <= 0) break;
|
||||
|
||||
// always set stream serialno if we have a new one
|
||||
// always set stream serialno if we have a new one (no multiplexed streams)
|
||||
if (OG(&go, page_bos, &v->page)) OG(&go, stream_reset_serialno, &v->state, OG(&go, page_serialno, &v->page));
|
||||
|
||||
// bring new page in if we want it (otherwise we're just skipping)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
idf_component_register( SRC_DIRS . muse
|
||||
idf_component_register( SRC_DIRS . muse guition
|
||||
INCLUDE_DIRS .
|
||||
PRIV_REQUIRES services
|
||||
)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "guition.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES driver esp_common
|
||||
)
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Guition JC4827W543C board support for squeezelite-esp32
|
||||
*
|
||||
* (c) Guition Board Support 2026
|
||||
* Based on squeezelite-esp32 architecture
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_types.h>
|
||||
#include <esp_system.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include "globdefs.h"
|
||||
#include "monitor.h"
|
||||
#include "targets.h"
|
||||
|
||||
static const char TAG[] = "guition";
|
||||
|
||||
static bool init(void);
|
||||
|
||||
const struct target_s target_guition = {
|
||||
.model = "guition",
|
||||
.init = init
|
||||
};
|
||||
|
||||
static bool init(void) {
|
||||
ESP_LOGI(TAG, "Initializing Guition JC4827W543C board");
|
||||
ESP_LOGI(TAG, "Board features:");
|
||||
ESP_LOGI(TAG, " - ESP32-S3-WROOM-1 processor");
|
||||
ESP_LOGI(TAG, " - 4.3\" ILI9488 display (480x272)");
|
||||
ESP_LOGI(TAG, " - GT911 capacitive touch");
|
||||
ESP_LOGI(TAG, " - QSPI display interface");
|
||||
ESP_LOGI(TAG, " - I2C touch interface (GPIO8/4)");
|
||||
|
||||
// No board-specific hardware initialization required
|
||||
// All configuration is handled through NVS parameters
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "string.h"
|
||||
#include "targets.h"
|
||||
|
||||
const struct target_s *target_set[] = { &target_muse, NULL };
|
||||
const struct target_s *target_set[] = { &target_muse, &target_guition, NULL };
|
||||
|
||||
void target_init(char *target) {
|
||||
for (int i = 0; *target && target_set[i]; i++) if (strcasestr(target_set[i]->model, target)) {
|
||||
|
||||
@@ -20,3 +20,4 @@ struct target_s {
|
||||
};
|
||||
|
||||
extern const struct target_s target_muse;
|
||||
extern const struct target_s target_guition;
|
||||
|
||||
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.
+1
-1
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
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
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
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -75,6 +75,18 @@ 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;
|
||||
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;
|
||||
@@ -229,6 +241,12 @@ 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 let sd: {};
|
||||
declare let rf: boolean;
|
||||
declare function refreshStatus(): void;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/css/index.4bbe29a78a667faa2b6f.css.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/css/index.3b0bbfde52d921a84f9b.css.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/favicon-32x32.png BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/index.html.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/js/index.baf383.bundle.js.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/js/node_vendors.baf383.bundle.js.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/js/index.d35fda.bundle.js.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/js/node_vendors.d35fda.bundle.js.gz BINARY)
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
// Automatically generated. Do not edit manually!.
|
||||
#include <inttypes.h>
|
||||
extern const uint8_t _index_4bbe29a78a667faa2b6f_css_gz_start[] asm("_binary_index_4bbe29a78a667faa2b6f_css_gz_start");
|
||||
extern const uint8_t _index_4bbe29a78a667faa2b6f_css_gz_end[] asm("_binary_index_4bbe29a78a667faa2b6f_css_gz_end");
|
||||
extern const uint8_t _index_3b0bbfde52d921a84f9b_css_gz_start[] asm("_binary_index_3b0bbfde52d921a84f9b_css_gz_start");
|
||||
extern const uint8_t _index_3b0bbfde52d921a84f9b_css_gz_end[] asm("_binary_index_3b0bbfde52d921a84f9b_css_gz_end");
|
||||
extern const uint8_t _favicon_32x32_png_start[] asm("_binary_favicon_32x32_png_start");
|
||||
extern const uint8_t _favicon_32x32_png_end[] asm("_binary_favicon_32x32_png_end");
|
||||
extern const uint8_t _index_html_gz_start[] asm("_binary_index_html_gz_start");
|
||||
extern const uint8_t _index_html_gz_end[] asm("_binary_index_html_gz_end");
|
||||
extern const uint8_t _index_baf383_bundle_js_gz_start[] asm("_binary_index_baf383_bundle_js_gz_start");
|
||||
extern const uint8_t _index_baf383_bundle_js_gz_end[] asm("_binary_index_baf383_bundle_js_gz_end");
|
||||
extern const uint8_t _node_vendors_baf383_bundle_js_gz_start[] asm("_binary_node_vendors_baf383_bundle_js_gz_start");
|
||||
extern const uint8_t _node_vendors_baf383_bundle_js_gz_end[] asm("_binary_node_vendors_baf383_bundle_js_gz_end");
|
||||
extern const uint8_t _index_d35fda_bundle_js_gz_start[] asm("_binary_index_d35fda_bundle_js_gz_start");
|
||||
extern const uint8_t _index_d35fda_bundle_js_gz_end[] asm("_binary_index_d35fda_bundle_js_gz_end");
|
||||
extern const uint8_t _node_vendors_d35fda_bundle_js_gz_start[] asm("_binary_node_vendors_d35fda_bundle_js_gz_start");
|
||||
extern const uint8_t _node_vendors_d35fda_bundle_js_gz_end[] asm("_binary_node_vendors_d35fda_bundle_js_gz_end");
|
||||
const char * resource_lookups[] = {
|
||||
"/css/index.4bbe29a78a667faa2b6f.css.gz",
|
||||
"/css/index.3b0bbfde52d921a84f9b.css.gz",
|
||||
"/favicon-32x32.png",
|
||||
"/index.html.gz",
|
||||
"/js/index.baf383.bundle.js.gz",
|
||||
"/js/node_vendors.baf383.bundle.js.gz",
|
||||
"/js/index.d35fda.bundle.js.gz",
|
||||
"/js/node_vendors.d35fda.bundle.js.gz",
|
||||
""
|
||||
};
|
||||
const uint8_t * resource_map_start[] = {
|
||||
_index_4bbe29a78a667faa2b6f_css_gz_start,
|
||||
_index_3b0bbfde52d921a84f9b_css_gz_start,
|
||||
_favicon_32x32_png_start,
|
||||
_index_html_gz_start,
|
||||
_index_baf383_bundle_js_gz_start,
|
||||
_node_vendors_baf383_bundle_js_gz_start
|
||||
_index_d35fda_bundle_js_gz_start,
|
||||
_node_vendors_d35fda_bundle_js_gz_start
|
||||
};
|
||||
const uint8_t * resource_map_end[] = {
|
||||
_index_4bbe29a78a667faa2b6f_css_gz_end,
|
||||
_index_3b0bbfde52d921a84f9b_css_gz_end,
|
||||
_favicon_32x32_png_end,
|
||||
_index_html_gz_end,
|
||||
_index_baf383_bundle_js_gz_end,
|
||||
_node_vendors_baf383_bundle_js_gz_end
|
||||
_index_d35fda_bundle_js_gz_end,
|
||||
_node_vendors_d35fda_bundle_js_gz_end
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/***********************************
|
||||
webpack_headers
|
||||
dist/css/index.4bbe29a78a667faa2b6f.css.gz,dist/favicon-32x32.png,dist/index.html.gz,dist/js/index.baf383.bundle.js.gz,dist/js/node_vendors.baf383.bundle.js.gz
|
||||
dist/css/index.3b0bbfde52d921a84f9b.css.gz,dist/favicon-32x32.png,dist/index.html.gz,dist/js/index.d35fda.bundle.js.gz,dist/js/node_vendors.d35fda.bundle.js.gz
|
||||
***********************************/
|
||||
#pragma once
|
||||
#include <inttypes.h>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_pthread.h"
|
||||
#ifndef CONFIG_SQUEEZELITE_ESP32_RELEASE_URL
|
||||
#define CONFIG_SQUEEZELITE_ESP32_RELEASE_URL "https://github.com/sle118/squeezelite-esp32/releases"
|
||||
#endif
|
||||
|
||||
extern bool wait_for_wifi();
|
||||
extern void console_start();
|
||||
extern pthread_cond_t wifi_connect_suspend_cond;
|
||||
extern pthread_t wifi_connect_suspend_mutex;
|
||||
typedef enum {
|
||||
INFO,
|
||||
WARNING,
|
||||
ERROR
|
||||
} message_severity_t;
|
||||
extern void set_status_message(message_severity_t severity, const char * message);
|
||||
@@ -0,0 +1,24 @@
|
||||
if(IDF_TARGET STREQUAL esp32 AND IDF_VERSION_MAJOR EQUAL 4 AND IDF_VERSION_MINOR LESS 4)
|
||||
set(lib_dir ${build_dir}/esp-idf)
|
||||
set(driver esp32/i2s.c)
|
||||
string(REPLACE ".c" ".c.obj" driver_obj "${driver}")
|
||||
|
||||
idf_component_register( SRCS ${driver}
|
||||
REQUIRES driver
|
||||
INCLUDE_DIRS ${IDF_PATH}/components/driver
|
||||
PRIV_INCLUDE_DIRS ${IDF_PATH}/components/driver/include/driver
|
||||
)
|
||||
|
||||
# CMake is just a pile of crap
|
||||
message(STATUS "!! overriding ${driver} !!")
|
||||
message(STATUS "CAREFUL, LIBRARIES STRIPPING FROM DUPLICATED COMPONENTS DEPENDS ON THIS BEING REBUILD")
|
||||
|
||||
add_custom_command(
|
||||
TARGET ${COMPONENT_LIB}
|
||||
PRE_LINK
|
||||
COMMAND xtensa-esp32-elf-ar -d ${lib_dir}/driver/libdriver.a ${driver_obj}
|
||||
VERBATIM
|
||||
)
|
||||
else()
|
||||
message(STATUS "==> NO OVERRIDE <==")
|
||||
endif()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
idf_component_register( SRC_DIRS .
|
||||
INCLUDE_DIRS . inc
|
||||
)
|
||||
add_prebuilt_library(esp_processing lib/libesp_processing.a)
|
||||
target_link_libraries(${COMPONENT_LIB} PRIVATE esp_processing)
|
||||
#target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--undefined=")
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u pow")
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u cos")
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u sin")
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u sqrt")
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
COMPONENT_ADD_LDFLAGS=-l$(COMPONENT_NAME) \
|
||||
$(COMPONENT_PATH)/lib/libesp_processing.a
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
|
||||
// All rights reserved.
|
||||
|
||||
#ifndef _ESP_EQUALIZER_H
|
||||
#define _ESP_EQUALIZER_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize the equalizer handle
|
||||
*
|
||||
* @param nch The audio channel number
|
||||
* @param g_rate The audio sample rate. Four sample rates are supported: 11025Hz, 22050Hz, 44100Hz and 48000Hz.
|
||||
* @param n_band The number of audio sub-bands. Fixed number of 10 sub-bands is supported and this value should be set to 10.
|
||||
* @param use_xmms_original_freqs Currently should be set 0
|
||||
*
|
||||
* @return The equalizer handle.
|
||||
*/
|
||||
void *esp_equalizer_init(int nch, int g_rate, int n_band, int use_xmms_original_freqs);
|
||||
|
||||
/**
|
||||
* @brief Uninitialize the equalizer handle.
|
||||
*
|
||||
* @param handle The the equalizer handle
|
||||
*/
|
||||
void esp_equalizer_uninit(void *handle);
|
||||
|
||||
/**
|
||||
* @brief Process the data through the equalizer
|
||||
*
|
||||
* @param handle The the equalizer handle
|
||||
* @param pcm_buf The audio pcm input & output buffer
|
||||
* @param length The length of current bytes in pcm_buf
|
||||
* @param g_rate The audio sample rate. Four sample rates are supported: 11025Hz, 22050Hz, 44100Hz and 48000Hz.
|
||||
* @param nch The audio channel number
|
||||
*
|
||||
* @return Length of pcm_buf after processing
|
||||
*/
|
||||
int esp_equalizer_process(void *handle, unsigned char *pcm_buf, int length, int g_rate, int nch);
|
||||
|
||||
/**
|
||||
* @brief Set the number of sub-bands for the equalizer
|
||||
*
|
||||
* @param handle The the equalizer handle
|
||||
* @param value The audio sub-bands gain. unit:db. 0 means no gain.
|
||||
* @param index The index of audio sub-bands. e.g. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.
|
||||
* @param nch The audio channel number
|
||||
*/
|
||||
void esp_equalizer_set_band_value(void *handle, float value, int index, int nch);
|
||||
|
||||
/**
|
||||
* @brief Get the number of the equalizer sub-bands
|
||||
*
|
||||
* @param handle The the equalizer handle
|
||||
*
|
||||
* @return The number of the equalizer sub-bands
|
||||
*/
|
||||
int esp_equalizer_get_band_count(void *handle);
|
||||
|
||||
/**
|
||||
* @brief Get the value of the equalizer sub-bands
|
||||
*
|
||||
* @param handle The the equalizer handle
|
||||
* @param index The index of audio sub-bands. Currently only support 10 sub-bands, so it should be 0-9.
|
||||
* @param nch The audio channel number
|
||||
*
|
||||
* @return The number of the equalizer sub-bands
|
||||
*/
|
||||
float esp_equalizer_get_band_value(void *handle, int index, int nch);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
void dummy_obj() {
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
idf_component_register(
|
||||
INCLUDE_DIRS . ./inc inc/alac inc/helix-aac inc/mad inc/resample16 inc/soxr inc/vorbis inc/opus
|
||||
)
|
||||
|
||||
if (DEFINED AAC_DISABLE_SBR)
|
||||
add_prebuilt_library(libhelix-aac lib/libhelix-aac.a )
|
||||
else ()
|
||||
add_prebuilt_library(libhelix-aac lib/libhelix-aac-sbr.a )
|
||||
endif()
|
||||
|
||||
add_prebuilt_library(libmad lib/libmad.a)
|
||||
add_prebuilt_library(libFLAC lib/libFLAC.a )
|
||||
add_prebuilt_library(libvorbisidec lib/libvorbisidec.a )
|
||||
add_prebuilt_library(libogg lib/libogg.a )
|
||||
add_prebuilt_library(libalac lib/libalac.a )
|
||||
add_prebuilt_library(libresample16 lib/libresample16.a )
|
||||
add_prebuilt_library(libopus lib/libopus.a )
|
||||
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE libmad)
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE libFLAC)
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE libhelix-aac)
|
||||
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 libopus)
|
||||
@@ -0,0 +1,24 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
COMPONENT_ADD_LDFLAGS=-l$(COMPONENT_NAME) \
|
||||
$(COMPONENT_PATH)/lib/libmad.a \
|
||||
$(COMPONENT_PATH)/lib/libFLAC.a \
|
||||
$(COMPONENT_PATH)/lib/libhelix-aac.a \
|
||||
$(COMPONENT_PATH)/lib/libvorbisidec.a \
|
||||
$(COMPONENT_PATH)/lib/libogg.a \
|
||||
$(COMPONENT_PATH)/lib/libalac.a \
|
||||
$(COMPONENT_PATH)/lib/libresample16.a \
|
||||
$(COMPONENT_PATH)/lib/libopusfile.a \
|
||||
$(COMPONENT_PATH)/lib/libopus.a
|
||||
|
||||
#$(COMPONENT_PATH)/lib/libFLAC.a
|
||||
#$(COMPONENT_PATH)/lib/libesp-flac.a
|
||||
#$(COMPONENT_PATH)/lib/libsoxr.a
|
||||
#$(COMPONENT_PATH)/lib/libfaad.a
|
||||
#$(COMPONENT_PATH)/lib/libvorbisidec.a
|
||||
#$(COMPONENT_PATH)/lib/libesp-opus.a
|
||||
#$(COMPONENT_PATH)/lib/libogg.a
|
||||
#$(COMPONENT_PATH)/lib/libesp-tremor.a
|
||||
#$(COMPONENT_PATH)/lib/libesp-ogg-container.a
|
||||
@@ -0,0 +1,450 @@
|
||||
/* libFLAC - Free Lossless Audio Codec library
|
||||
* Copyright (C) 2000-2009 Josh Coalson
|
||||
* Copyright (C) 2011-2022 Xiph.Org Foundation
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* - Neither the name of the Xiph.org Foundation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef FLAC__ALL_H
|
||||
#define FLAC__ALL_H
|
||||
|
||||
#include "export.h"
|
||||
|
||||
#include "assert.h"
|
||||
#include "callback.h"
|
||||
#include "format.h"
|
||||
#include "metadata.h"
|
||||
#include "ordinals.h"
|
||||
#include "stream_decoder.h"
|
||||
#include "stream_encoder.h"
|
||||
|
||||
/** \mainpage
|
||||
*
|
||||
* \section intro Introduction
|
||||
*
|
||||
* This is the documentation for the FLAC C and C++ APIs. It is
|
||||
* highly interconnected; this introduction should give you a top
|
||||
* level idea of the structure and how to find the information you
|
||||
* need. As a prerequisite you should have at least a basic
|
||||
* knowledge of the FLAC format, documented
|
||||
* <A HREF="https://xiph.org/flac/format.html">here</A>.
|
||||
*
|
||||
* \section c_api FLAC C API
|
||||
*
|
||||
* The FLAC C API is the interface to libFLAC, a set of structures
|
||||
* describing the components of FLAC streams, and functions for
|
||||
* encoding and decoding streams, as well as manipulating FLAC
|
||||
* metadata in files. The public include files will be installed
|
||||
* in your include area (for example /usr/include/FLAC/...).
|
||||
*
|
||||
* By writing a little code and linking against libFLAC, it is
|
||||
* relatively easy to add FLAC support to another program. The
|
||||
* library is licensed under <A HREF="https://xiph.org/flac/license.html">Xiph's BSD license</A>.
|
||||
* Complete source code of libFLAC as well as the command-line
|
||||
* encoder and plugins is available and is a useful source of
|
||||
* examples.
|
||||
*
|
||||
* Aside from encoders and decoders, libFLAC provides a powerful
|
||||
* metadata interface for manipulating metadata in FLAC files. It
|
||||
* allows the user to add, delete, and modify FLAC metadata blocks
|
||||
* and it can automatically take advantage of PADDING blocks to avoid
|
||||
* rewriting the entire FLAC file when changing the size of the
|
||||
* metadata.
|
||||
*
|
||||
* libFLAC usually only requires the standard C library and C math
|
||||
* library. In particular, threading is not used so there is no
|
||||
* dependency on a thread library. However, libFLAC does not use
|
||||
* global variables and should be thread-safe.
|
||||
*
|
||||
* libFLAC also supports encoding to and decoding from Ogg FLAC.
|
||||
* However the metadata editing interfaces currently have limited
|
||||
* read-only support for Ogg FLAC files.
|
||||
*
|
||||
* \section cpp_api FLAC C++ API
|
||||
*
|
||||
* The FLAC C++ API is a set of classes that encapsulate the
|
||||
* structures and functions in libFLAC. They provide slightly more
|
||||
* functionality with respect to metadata but are otherwise
|
||||
* equivalent. For the most part, they share the same usage as
|
||||
* their counterparts in libFLAC, and the FLAC C API documentation
|
||||
* can be used as a supplement. The public include files
|
||||
* for the C++ API will be installed in your include area (for
|
||||
* example /usr/include/FLAC++/...).
|
||||
*
|
||||
* libFLAC++ is also licensed under
|
||||
* <A HREF="https://xiph.org/flac/license.html">Xiph's BSD license</A>.
|
||||
*
|
||||
* \section getting_started Getting Started
|
||||
*
|
||||
* A good starting point for learning the API is to browse through
|
||||
* the <A HREF="modules.html">modules</A>. Modules are logical
|
||||
* groupings of related functions or classes, which correspond roughly
|
||||
* to header files or sections of header files. Each module includes a
|
||||
* detailed description of the general usage of its functions or
|
||||
* classes.
|
||||
*
|
||||
* From there you can go on to look at the documentation of
|
||||
* individual functions. You can see different views of the individual
|
||||
* functions through the links in top bar across this page.
|
||||
*
|
||||
* If you prefer a more hands-on approach, you can jump right to some
|
||||
* <A HREF="https://xiph.org/flac/documentation_example_code.html">example code</A>.
|
||||
*
|
||||
* \section porting_guide Porting Guide
|
||||
*
|
||||
* Starting with FLAC 1.1.3 a \link porting Porting Guide \endlink
|
||||
* has been introduced which gives detailed instructions on how to
|
||||
* port your code to newer versions of FLAC.
|
||||
*
|
||||
* \section embedded_developers Embedded Developers
|
||||
*
|
||||
* libFLAC has grown larger over time as more functionality has been
|
||||
* included, but much of it may be unnecessary for a particular embedded
|
||||
* implementation. Unused parts may be pruned by some simple editing of
|
||||
* src/libFLAC/Makefile.am. In general, the decoders, encoders, and
|
||||
* metadata interface are all independent from each other.
|
||||
*
|
||||
* It is easiest to just describe the dependencies:
|
||||
*
|
||||
* - All modules depend on the \link flac_format Format \endlink module.
|
||||
* - The decoders and encoders depend on the bitbuffer.
|
||||
* - The decoder is independent of the encoder. The encoder uses the
|
||||
* decoder because of the verify feature, but this can be removed if
|
||||
* not needed.
|
||||
* - Parts of the metadata interface require the stream decoder (but not
|
||||
* the encoder).
|
||||
* - Ogg support is selectable through the compile time macro
|
||||
* \c FLAC__HAS_OGG.
|
||||
*
|
||||
* For example, if your application only requires the stream decoder, no
|
||||
* encoder, and no metadata interface, you can remove the stream encoder
|
||||
* and the metadata interface, which will greatly reduce the size of the
|
||||
* library.
|
||||
*
|
||||
* Also, there are several places in the libFLAC code with comments marked
|
||||
* with "OPT:" where a \#define can be changed to enable code that might be
|
||||
* faster on a specific platform. Experimenting with these can yield faster
|
||||
* binaries.
|
||||
*/
|
||||
|
||||
/** \defgroup porting Porting Guide for New Versions
|
||||
*
|
||||
* This module describes differences in the library interfaces from
|
||||
* version to version. It assists in the porting of code that uses
|
||||
* the libraries to newer versions of FLAC.
|
||||
*
|
||||
* One simple facility for making porting easier that has been added
|
||||
* in FLAC 1.1.3 is a set of \#defines in \c export.h of each
|
||||
* library's includes (e.g. \c include/FLAC/export.h). The
|
||||
* \#defines mirror the libraries'
|
||||
* <A HREF="http://www.gnu.org/software/libtool/manual/libtool.html#Libtool-versioning">libtool version numbers</A>,
|
||||
* e.g. in libFLAC there are \c FLAC_API_VERSION_CURRENT,
|
||||
* \c FLAC_API_VERSION_REVISION, and \c FLAC_API_VERSION_AGE.
|
||||
* These can be used to support multiple versions of an API during the
|
||||
* transition phase, e.g.
|
||||
*
|
||||
* \code
|
||||
* #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
|
||||
* legacy code
|
||||
* #else
|
||||
* new code
|
||||
* #endif
|
||||
* \endcode
|
||||
*
|
||||
* The source will work for multiple versions and the legacy code can
|
||||
* easily be removed when the transition is complete.
|
||||
*
|
||||
* Another available symbol is FLAC_API_SUPPORTS_OGG_FLAC (defined in
|
||||
* include/FLAC/export.h), which can be used to determine whether or not
|
||||
* the library has been compiled with support for Ogg FLAC. This is
|
||||
* simpler than trying to call an Ogg init function and catching the
|
||||
* error.
|
||||
*/
|
||||
|
||||
/** \defgroup porting_1_1_2_to_1_1_3 Porting from FLAC 1.1.2 to 1.1.3
|
||||
* \ingroup porting
|
||||
*
|
||||
* \brief
|
||||
* This module describes porting from FLAC 1.1.2 to FLAC 1.1.3.
|
||||
*
|
||||
* The main change between the APIs in 1.1.2 and 1.1.3 is that they have
|
||||
* been simplified. First, libOggFLAC has been merged into libFLAC and
|
||||
* libOggFLAC++ has been merged into libFLAC++. Second, both the three
|
||||
* decoding layers and three encoding layers have been merged into a
|
||||
* single stream decoder and stream encoder. That is, the functionality
|
||||
* of FLAC__SeekableStreamDecoder and FLAC__FileDecoder has been merged
|
||||
* into FLAC__StreamDecoder, and FLAC__SeekableStreamEncoder and
|
||||
* FLAC__FileEncoder into FLAC__StreamEncoder. Only the
|
||||
* FLAC__StreamDecoder and FLAC__StreamEncoder remain. What this means
|
||||
* is there is now a single API that can be used to encode or decode
|
||||
* streams to/from native FLAC or Ogg FLAC and the single API can work
|
||||
* on both seekable and non-seekable streams.
|
||||
*
|
||||
* Instead of creating an encoder or decoder of a certain layer, now the
|
||||
* client will always create a FLAC__StreamEncoder or
|
||||
* FLAC__StreamDecoder. The old layers are now differentiated by the
|
||||
* initialization function. For example, for the decoder,
|
||||
* FLAC__stream_decoder_init() has been replaced by
|
||||
* FLAC__stream_decoder_init_stream(). This init function takes
|
||||
* callbacks for the I/O, and the seeking callbacks are optional. This
|
||||
* allows the client to use the same object for seekable and
|
||||
* non-seekable streams. For decoding a FLAC file directly, the client
|
||||
* can use FLAC__stream_decoder_init_file() and pass just a filename
|
||||
* and fewer callbacks; most of the other callbacks are supplied
|
||||
* internally. For situations where fopen()ing by filename is not
|
||||
* possible (e.g. Unicode filenames on Windows) the client can instead
|
||||
* open the file itself and supply the FILE* to
|
||||
* FLAC__stream_decoder_init_FILE(). The init functions now returns a
|
||||
* FLAC__StreamDecoderInitStatus instead of FLAC__StreamDecoderState.
|
||||
* Since the callbacks and client data are now passed to the init
|
||||
* function, the FLAC__stream_decoder_set_*_callback() functions and
|
||||
* FLAC__stream_decoder_set_client_data() are no longer needed. The
|
||||
* rest of the calls to the decoder are the same as before.
|
||||
*
|
||||
* There are counterpart init functions for Ogg FLAC, e.g.
|
||||
* FLAC__stream_decoder_init_ogg_stream(). All the rest of the calls
|
||||
* and callbacks are the same as for native FLAC.
|
||||
*
|
||||
* As an example, in FLAC 1.1.2 a seekable stream decoder would have
|
||||
* been set up like so:
|
||||
*
|
||||
* \code
|
||||
* FLAC__SeekableStreamDecoder *decoder = FLAC__seekable_stream_decoder_new();
|
||||
* if(decoder == NULL) do_something;
|
||||
* FLAC__seekable_stream_decoder_set_md5_checking(decoder, true);
|
||||
* [... other settings ...]
|
||||
* FLAC__seekable_stream_decoder_set_read_callback(decoder, my_read_callback);
|
||||
* FLAC__seekable_stream_decoder_set_seek_callback(decoder, my_seek_callback);
|
||||
* FLAC__seekable_stream_decoder_set_tell_callback(decoder, my_tell_callback);
|
||||
* FLAC__seekable_stream_decoder_set_length_callback(decoder, my_length_callback);
|
||||
* FLAC__seekable_stream_decoder_set_eof_callback(decoder, my_eof_callback);
|
||||
* FLAC__seekable_stream_decoder_set_write_callback(decoder, my_write_callback);
|
||||
* FLAC__seekable_stream_decoder_set_metadata_callback(decoder, my_metadata_callback);
|
||||
* FLAC__seekable_stream_decoder_set_error_callback(decoder, my_error_callback);
|
||||
* FLAC__seekable_stream_decoder_set_client_data(decoder, my_client_data);
|
||||
* if(FLAC__seekable_stream_decoder_init(decoder) != FLAC__SEEKABLE_STREAM_DECODER_OK) do_something;
|
||||
* \endcode
|
||||
*
|
||||
* In FLAC 1.1.3 it is like this:
|
||||
*
|
||||
* \code
|
||||
* FLAC__StreamDecoder *decoder = FLAC__stream_decoder_new();
|
||||
* if(decoder == NULL) do_something;
|
||||
* FLAC__stream_decoder_set_md5_checking(decoder, true);
|
||||
* [... other settings ...]
|
||||
* if(FLAC__stream_decoder_init_stream(
|
||||
* decoder,
|
||||
* my_read_callback,
|
||||
* my_seek_callback, // or NULL
|
||||
* my_tell_callback, // or NULL
|
||||
* my_length_callback, // or NULL
|
||||
* my_eof_callback, // or NULL
|
||||
* my_write_callback,
|
||||
* my_metadata_callback, // or NULL
|
||||
* my_error_callback,
|
||||
* my_client_data
|
||||
* ) != FLAC__STREAM_DECODER_INIT_STATUS_OK) do_something;
|
||||
* \endcode
|
||||
*
|
||||
* or you could do;
|
||||
*
|
||||
* \code
|
||||
* [...]
|
||||
* FILE *file = fopen("somefile.flac","rb");
|
||||
* if(file == NULL) do_somthing;
|
||||
* if(FLAC__stream_decoder_init_FILE(
|
||||
* decoder,
|
||||
* file,
|
||||
* my_write_callback,
|
||||
* my_metadata_callback, // or NULL
|
||||
* my_error_callback,
|
||||
* my_client_data
|
||||
* ) != FLAC__STREAM_DECODER_INIT_STATUS_OK) do_something;
|
||||
* \endcode
|
||||
*
|
||||
* or just:
|
||||
*
|
||||
* \code
|
||||
* [...]
|
||||
* if(FLAC__stream_decoder_init_file(
|
||||
* decoder,
|
||||
* "somefile.flac",
|
||||
* my_write_callback,
|
||||
* my_metadata_callback, // or NULL
|
||||
* my_error_callback,
|
||||
* my_client_data
|
||||
* ) != FLAC__STREAM_DECODER_INIT_STATUS_OK) do_something;
|
||||
* \endcode
|
||||
*
|
||||
* Another small change to the decoder is in how it handles unparseable
|
||||
* streams. Before, when the decoder found an unparseable stream
|
||||
* (reserved for when the decoder encounters a stream from a future
|
||||
* encoder that it can't parse), it changed the state to
|
||||
* \c FLAC__STREAM_DECODER_UNPARSEABLE_STREAM. Now the decoder instead
|
||||
* drops sync and calls the error callback with a new error code
|
||||
* \c FLAC__STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM. This is
|
||||
* more robust. If your error callback does not discriminate on the the
|
||||
* error state, your code does not need to be changed.
|
||||
*
|
||||
* The encoder now has a new setting:
|
||||
* FLAC__stream_encoder_set_apodization(). This is for setting the
|
||||
* method used to window the data before LPC analysis. You only need to
|
||||
* add a call to this function if the default is not suitable. There
|
||||
* are also two new convenience functions that may be useful:
|
||||
* FLAC__metadata_object_cuesheet_calculate_cddb_id() and
|
||||
* FLAC__metadata_get_cuesheet().
|
||||
*
|
||||
* The \a bytes parameter to FLAC__StreamDecoderReadCallback,
|
||||
* FLAC__StreamEncoderReadCallback, and FLAC__StreamEncoderWriteCallback
|
||||
* is now \c size_t instead of \c uint32_t.
|
||||
*/
|
||||
|
||||
/** \defgroup porting_1_1_3_to_1_1_4 Porting from FLAC 1.1.3 to 1.1.4
|
||||
* \ingroup porting
|
||||
*
|
||||
* \brief
|
||||
* This module describes porting from FLAC 1.1.3 to FLAC 1.1.4.
|
||||
*
|
||||
* There were no changes to any of the interfaces from 1.1.3 to 1.1.4.
|
||||
* There was a slight change in the implementation of
|
||||
* FLAC__stream_encoder_set_metadata(); the function now makes a copy
|
||||
* of the \a metadata array of pointers so the client no longer needs
|
||||
* to maintain it after the call. The objects themselves that are
|
||||
* pointed to by the array are still not copied though and must be
|
||||
* maintained until the call to FLAC__stream_encoder_finish().
|
||||
*/
|
||||
|
||||
/** \defgroup porting_1_1_4_to_1_2_0 Porting from FLAC 1.1.4 to 1.2.0
|
||||
* \ingroup porting
|
||||
*
|
||||
* \brief
|
||||
* This module describes porting from FLAC 1.1.4 to FLAC 1.2.0.
|
||||
*
|
||||
* There were only very minor changes to the interfaces from 1.1.4 to 1.2.0.
|
||||
* In libFLAC, \c FLAC__format_sample_rate_is_subset() was added.
|
||||
* In libFLAC++, \c FLAC::Decoder::Stream::get_decode_position() was added.
|
||||
*
|
||||
* Finally, value of the constant \c FLAC__FRAME_HEADER_RESERVED_LEN
|
||||
* has changed to reflect the conversion of one of the reserved bits
|
||||
* into active use. It used to be \c 2 and now is \c 1. However the
|
||||
* FLAC frame header length has not changed, so to skip the proper
|
||||
* number of bits, use \c FLAC__FRAME_HEADER_RESERVED_LEN +
|
||||
* \c FLAC__FRAME_HEADER_BLOCKING_STRATEGY_LEN
|
||||
*/
|
||||
|
||||
/** \defgroup porting_1_3_4_to_1_4_0 Porting from FLAC 1.3.4 to 1.4.0
|
||||
* \ingroup porting
|
||||
*
|
||||
* \brief
|
||||
* This module describes porting from FLAC 1.3.4 to FLAC 1.4.0.
|
||||
*
|
||||
* \section porting_1_3_4_to_1_4_0_summary Summary
|
||||
*
|
||||
* Between FLAC 1.3.4 and FLAC 1.4.0, there have four breaking changes
|
||||
* - the function get_client_data_from_decoder has been renamed to
|
||||
* FLAC__get_decoder_client_data
|
||||
* - some data types in the FLAC__Frame struct have changed
|
||||
* - all functions resizing metadata blocks now return the object
|
||||
* untouched if memory allocation fails, whereas previously the
|
||||
* handling varied and was more or less undefined
|
||||
* - all functions accepting a filename now take UTF-8 encoded filenames
|
||||
* on Windows instead of filenames in the current codepage
|
||||
*
|
||||
* Furthermore, there have been the following additions
|
||||
* - the functions FLAC__stream_encoder_set_limit_min_bitrate,
|
||||
* FLAC__stream_encoder_get_limit_min_bitrate,
|
||||
* FLAC::encoder::file::set_limit_min_bitrate() and
|
||||
* FLAC::encoder::file::get_limit_min_bitrate() have been added
|
||||
* - Added FLAC__STREAM_DECODER_ERROR_STATUS_BAD_METADATA to the
|
||||
* FLAC__StreamDecoderErrorStatus enum
|
||||
*
|
||||
* \section porting_1_3_4_to_1_4_0_breaking Breaking changes
|
||||
*
|
||||
* The function \b get_client_data_from_decoder was added in FLAC 1.3.3
|
||||
* but did not follow the API naming convention and was not properly
|
||||
* exported. The function is now renamed and properly integrated as
|
||||
* FLAC__stream_decoder_get_client_data
|
||||
*
|
||||
* To accomodate encoding and decoding 32-bit int PCM, some data types
|
||||
* in the \b FLAC__frame struct were changed. Specifically, warmup
|
||||
* in both the FLAC__Subframe_Fixed struc and the FLAC__Subframe_LPC
|
||||
* struct is changed from FLAC__int32 to FLAC__int64. Also, value
|
||||
* in the FLAC__Subframe_Constant is changed from FLAC__int32 to
|
||||
* FLAC__int64. Finally, in FLAC__Subframe_Verbatim struct data is
|
||||
* changes from a FLAC__int32 array to a union containing a FLAC__int32
|
||||
* array and a FLAC__int64 array. Also, a new member is added,
|
||||
* data_type, which clarifies whether the FLAC__int32 or FLAC__int64
|
||||
* array is in use.
|
||||
*
|
||||
* Furthermore, the following functions now return the object untouched
|
||||
* if memory allocation fails, whereas previously the handling varied
|
||||
* and was more or less undefined
|
||||
*
|
||||
* - FLAC__metadata_object_seektable_resize_points
|
||||
* - FLAC__metadata_object_vorbiscomment_resize_comments
|
||||
* - FLAC__metadata_object_cuesheet_track_resize_indices
|
||||
* - FLAC__metadata_object_cuesheet_resize_tracks
|
||||
*
|
||||
* The last breaking change is that all API functions taking a filename
|
||||
* as an argument now, on Windows, must be supplied with that filename
|
||||
* in the UTF-8 character encoding instead of using the current code
|
||||
* page. libFLAC internally translates these UTF-8 encoded filenames to
|
||||
* an appropriate representation to use with _wfopen. On all other
|
||||
* systems, filename is passed to fopen without any translation, as it
|
||||
* in libFLAC 1.3.4 and earlier.
|
||||
*
|
||||
* \section porting_1_3_4_to_1_4_0_additions Additions
|
||||
*
|
||||
* To aid in creating properly streamable FLAC files, a set of functions
|
||||
* was added to make it possible to enfore a minimum bitrate to files
|
||||
* created through libFLAC's stream_encoder.h interface. With this
|
||||
* function enabled the resulting FLAC files have a minimum bitrate of
|
||||
* 1bit/sample independent of the number of channels, i.e. 48kbit/s for
|
||||
* 48kHz. This can be beneficial for streaming, as very low bitrates for
|
||||
* silent sections compressed with 'constant' subframes can result in a
|
||||
* bitrate of 1kbit/s, creating problems with clients that aren't aware
|
||||
* of this possibility and buffer too much data.
|
||||
*
|
||||
* Finally, FLAC__STREAM_DECODER_ERROR_STATUS_BAD_METADATA was added to
|
||||
* the FLAC__StreamDecoderErrorStatus enum to signal that the decoder
|
||||
* encountered unreadable metadata.
|
||||
*
|
||||
*/
|
||||
|
||||
/** \defgroup flac FLAC C API
|
||||
*
|
||||
* The FLAC C API is the interface to libFLAC, a set of structures
|
||||
* describing the components of FLAC streams, and functions for
|
||||
* encoding and decoding streams, as well as manipulating FLAC
|
||||
* metadata in files.
|
||||
*
|
||||
* You should start with the format components as all other modules
|
||||
* are dependent on it.
|
||||
*/
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
/* libFLAC - Free Lossless Audio Codec library
|
||||
* Copyright (C) 2001-2009 Josh Coalson
|
||||
* Copyright (C) 2011-2022 Xiph.Org Foundation
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* - Neither the name of the Xiph.org Foundation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef FLAC__ASSERT_H
|
||||
#define FLAC__ASSERT_H
|
||||
|
||||
/* we need this since some compilers (like MSVC) leave assert()s on release code (and we don't want to use their ASSERT) */
|
||||
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||
#define FLAC__ASSERT(x) if(!(x)) __builtin_abort();
|
||||
#define FLAC__ASSERT_DECLARATION(x) x
|
||||
#else
|
||||
#ifndef NDEBUG
|
||||
#include <assert.h>
|
||||
#define FLAC__ASSERT(x) assert(x)
|
||||
#define FLAC__ASSERT_DECLARATION(x) x
|
||||
#else
|
||||
#define FLAC__ASSERT(x)
|
||||
#define FLAC__ASSERT_DECLARATION(x)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,185 @@
|
||||
/* libFLAC - Free Lossless Audio Codec library
|
||||
* Copyright (C) 2004-2009 Josh Coalson
|
||||
* Copyright (C) 2011-2022 Xiph.Org Foundation
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* - Neither the name of the Xiph.org Foundation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef FLAC__CALLBACK_H
|
||||
#define FLAC__CALLBACK_H
|
||||
|
||||
#include "ordinals.h"
|
||||
#include <stdlib.h> /* for size_t */
|
||||
|
||||
/** \file include/FLAC/callback.h
|
||||
*
|
||||
* \brief
|
||||
* This module defines the structures for describing I/O callbacks
|
||||
* to the other FLAC interfaces.
|
||||
*
|
||||
* See the detailed documentation for callbacks in the
|
||||
* \link flac_callbacks callbacks \endlink module.
|
||||
*/
|
||||
|
||||
/** \defgroup flac_callbacks FLAC/callback.h: I/O callback structures
|
||||
* \ingroup flac
|
||||
*
|
||||
* \brief
|
||||
* This module defines the structures for describing I/O callbacks
|
||||
* to the other FLAC interfaces.
|
||||
*
|
||||
* The purpose of the I/O callback functions is to create a common way
|
||||
* for the metadata interfaces to handle I/O.
|
||||
*
|
||||
* Originally the metadata interfaces required filenames as the way of
|
||||
* specifying FLAC files to operate on. This is problematic in some
|
||||
* environments so there is an additional option to specify a set of
|
||||
* callbacks for doing I/O on the FLAC file, instead of the filename.
|
||||
*
|
||||
* In addition to the callbacks, a FLAC__IOHandle type is defined as an
|
||||
* opaque structure for a data source.
|
||||
*
|
||||
* The callback function prototypes are similar (but not identical) to the
|
||||
* stdio functions fread, fwrite, fseek, ftell, feof, and fclose. If you use
|
||||
* stdio streams to implement the callbacks, you can pass fread, fwrite, and
|
||||
* fclose anywhere a FLAC__IOCallback_Read, FLAC__IOCallback_Write, or
|
||||
* FLAC__IOCallback_Close is required, and a FILE* anywhere a FLAC__IOHandle
|
||||
* is required. \warning You generally CANNOT directly use fseek or ftell
|
||||
* for FLAC__IOCallback_Seek or FLAC__IOCallback_Tell since on most systems
|
||||
* these use 32-bit offsets and FLAC requires 64-bit offsets to deal with
|
||||
* large files. You will have to find an equivalent function (e.g. ftello),
|
||||
* or write a wrapper. The same is true for feof() since this is usually
|
||||
* implemented as a macro, not as a function whose address can be taken.
|
||||
*
|
||||
* \{
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** This is the opaque handle type used by the callbacks. Typically
|
||||
* this is a \c FILE* or address of a file descriptor.
|
||||
*/
|
||||
typedef void* FLAC__IOHandle;
|
||||
|
||||
/** Signature for the read callback.
|
||||
* The signature and semantics match POSIX fread() implementations
|
||||
* and can generally be used interchangeably.
|
||||
*
|
||||
* \param ptr The address of the read buffer.
|
||||
* \param size The size of the records to be read.
|
||||
* \param nmemb The number of records to be read.
|
||||
* \param handle The handle to the data source.
|
||||
* \retval size_t
|
||||
* The number of records read.
|
||||
*/
|
||||
typedef size_t (*FLAC__IOCallback_Read) (void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle);
|
||||
|
||||
/** Signature for the write callback.
|
||||
* The signature and semantics match POSIX fwrite() implementations
|
||||
* and can generally be used interchangeably.
|
||||
*
|
||||
* \param ptr The address of the write buffer.
|
||||
* \param size The size of the records to be written.
|
||||
* \param nmemb The number of records to be written.
|
||||
* \param handle The handle to the data source.
|
||||
* \retval size_t
|
||||
* The number of records written.
|
||||
*/
|
||||
typedef size_t (*FLAC__IOCallback_Write) (const void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle);
|
||||
|
||||
/** Signature for the seek callback.
|
||||
* The signature and semantics mostly match POSIX fseek() WITH ONE IMPORTANT
|
||||
* EXCEPTION: the offset is a 64-bit type whereas fseek() is generally 'long'
|
||||
* and 32-bits wide.
|
||||
*
|
||||
* \param handle The handle to the data source.
|
||||
* \param offset The new position, relative to \a whence
|
||||
* \param whence \c SEEK_SET, \c SEEK_CUR, or \c SEEK_END
|
||||
* \retval int
|
||||
* \c 0 on success, \c -1 on error.
|
||||
*/
|
||||
typedef int (*FLAC__IOCallback_Seek) (FLAC__IOHandle handle, FLAC__int64 offset, int whence);
|
||||
|
||||
/** Signature for the tell callback.
|
||||
* The signature and semantics mostly match POSIX ftell() WITH ONE IMPORTANT
|
||||
* EXCEPTION: the offset is a 64-bit type whereas ftell() is generally 'long'
|
||||
* and 32-bits wide.
|
||||
*
|
||||
* \param handle The handle to the data source.
|
||||
* \retval FLAC__int64
|
||||
* The current position on success, \c -1 on error.
|
||||
*/
|
||||
typedef FLAC__int64 (*FLAC__IOCallback_Tell) (FLAC__IOHandle handle);
|
||||
|
||||
/** Signature for the EOF callback.
|
||||
* The signature and semantics mostly match POSIX feof() but WATCHOUT:
|
||||
* on many systems, feof() is a macro, so in this case a wrapper function
|
||||
* must be provided instead.
|
||||
*
|
||||
* \param handle The handle to the data source.
|
||||
* \retval int
|
||||
* \c 0 if not at end of file, nonzero if at end of file.
|
||||
*/
|
||||
typedef int (*FLAC__IOCallback_Eof) (FLAC__IOHandle handle);
|
||||
|
||||
/** Signature for the close callback.
|
||||
* The signature and semantics match POSIX fclose() implementations
|
||||
* and can generally be used interchangeably.
|
||||
*
|
||||
* \param handle The handle to the data source.
|
||||
* \retval int
|
||||
* \c 0 on success, \c EOF on error.
|
||||
*/
|
||||
typedef int (*FLAC__IOCallback_Close) (FLAC__IOHandle handle);
|
||||
|
||||
/** A structure for holding a set of callbacks.
|
||||
* Each FLAC interface that requires a FLAC__IOCallbacks structure will
|
||||
* describe which of the callbacks are required. The ones that are not
|
||||
* required may be set to NULL.
|
||||
*
|
||||
* If the seek requirement for an interface is optional, you can signify that
|
||||
* a data source is not seekable by setting the \a seek field to \c NULL.
|
||||
*/
|
||||
typedef struct {
|
||||
FLAC__IOCallback_Read read; /**< See FLAC__IOCallbacks */
|
||||
FLAC__IOCallback_Write write; /**< See FLAC__IOCallbacks */
|
||||
FLAC__IOCallback_Seek seek; /**< See FLAC__IOCallbacks */
|
||||
FLAC__IOCallback_Tell tell; /**< See FLAC__IOCallbacks */
|
||||
FLAC__IOCallback_Eof eof; /**< See FLAC__IOCallbacks */
|
||||
FLAC__IOCallback_Close close; /**< See FLAC__IOCallbacks */
|
||||
} FLAC__IOCallbacks;
|
||||
|
||||
/* \} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,115 @@
|
||||
/* libFLAC - Free Lossless Audio Codec library
|
||||
* Copyright (C) 2000-2009 Josh Coalson
|
||||
* Copyright (C) 2011-2022 Xiph.Org Foundation
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* - Neither the name of the Xiph.org Foundation nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef FLAC__EXPORT_H
|
||||
#define FLAC__EXPORT_H
|
||||
|
||||
/** \file include/FLAC/export.h
|
||||
*
|
||||
* \brief
|
||||
* This module contains \#defines and symbols for exporting function
|
||||
* calls, and providing version information and compiled-in features.
|
||||
*
|
||||
* See the \link flac_export export \endlink module.
|
||||
*/
|
||||
|
||||
/** \defgroup flac_export FLAC/export.h: export symbols
|
||||
* \ingroup flac
|
||||
*
|
||||
* \brief
|
||||
* This module contains \#defines and symbols for exporting function
|
||||
* calls, and providing version information and compiled-in features.
|
||||
*
|
||||
* If you are compiling for Windows (with Visual Studio or MinGW for
|
||||
* example) and will link to the static library (libFLAC++.lib) you
|
||||
* should define FLAC__NO_DLL in your project to make sure the symbols
|
||||
* are exported properly.
|
||||
*
|
||||
* \{
|
||||
*/
|
||||
|
||||
/** This \#define is used internally in libFLAC and its headers to make
|
||||
* sure the correct symbols are exported when working with shared
|
||||
* libraries. On Windows, this \#define is set to __declspec(dllexport)
|
||||
* when compiling libFLAC into a library and to __declspec(dllimport)
|
||||
* when the headers are used to link to that DLL. On non-Windows systems
|
||||
* it is used to set symbol visibility.
|
||||
*
|
||||
* Because of this, the define FLAC__NO_DLL must be defined when linking
|
||||
* to libFLAC statically or linking will fail.
|
||||
*/
|
||||
/* This has grown quite complicated. FLAC__NO_DLL is used by MSVC sln
|
||||
* files and CMake, which build either static or shared. autotools can
|
||||
* build static, shared or **both**. Therefore, DLL_EXPORT, which is set
|
||||
* by libtool, must override FLAC__NO_DLL on building shared components
|
||||
*/
|
||||
#if defined(_WIN32)
|
||||
|
||||
#if defined(FLAC__NO_DLL) && !(defined(DLL_EXPORT))
|
||||
#define FLAC_API
|
||||
#else
|
||||
#ifdef FLAC_API_EXPORTS
|
||||
#define FLAC_API __declspec(dllexport)
|
||||
#else
|
||||
#define FLAC_API __declspec(dllimport)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#elif defined(FLAC__USE_VISIBILITY_ATTR)
|
||||
#define FLAC_API __attribute__ ((visibility ("default")))
|
||||
|
||||
#else
|
||||
#define FLAC_API
|
||||
|
||||
#endif
|
||||
|
||||
/** These \#defines will mirror the libtool-based library version number, see
|
||||
* http://www.gnu.org/software/libtool/manual/libtool.html#Libtool-versioning
|
||||
*/
|
||||
#define FLAC_API_VERSION_CURRENT 12
|
||||
#define FLAC_API_VERSION_REVISION 0 /**< see above */
|
||||
#define FLAC_API_VERSION_AGE 0 /**< see above */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \c 1 if the library has been compiled with support for Ogg FLAC, else \c 0. */
|
||||
extern FLAC_API int FLAC_API_SUPPORTS_OGG_FLAC;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/* \} */
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user