Compare commits

...

67 Commits

Author SHA1 Message Date
philippe44
e6723dfa2f Update CHANGELOG 2024-09-12 11:27:55 +02:00
philippe44
ffaff5ac27 Merge pull request #430 from StefanKrupop/aw9523_expander
Add support for AW9523 GPIO expander
2024-09-12 11:25:31 +02:00
philippe44
33ef4b01e7 Update CHANGELOG 2024-09-12 11:24:23 +02:00
github-actions
302865b167 Update prebuilt objects [skip actions] 2024-09-11 18:05:43 +00:00
philippe44
7f0ae69e81 Merge pull request #439 from digidocs/eq_update_fix2
Fix for ESP32 equalizer settings don't update when expected
2024-09-11 20:02:25 +02:00
David Carr
e21e2cf7f9 Equalizer: change gain to int8 and memcmp-based update check 2024-09-04 15:07:42 -05:00
David Carr
57cd009e4c Revert "Equalizer: check if requested gain has changed before updating"
This reverts commit 78e8d60021.
2024-09-04 14:52:27 -05:00
David Carr
78e8d60021 Equalizer: check if requested gain has changed before updating 2024-09-04 13:49:51 -05:00
digidocs
4068e07a45 Fix for I2S noise burst when ESP32 panic occurs (#437) 2024-09-02 08:51:40 -04:00
David Carr
a633524936 Fix for ESP32 equalizer settings don't update when expected 2024-08-21 00:08:06 -05:00
Stefan Krupop
9d71b8ee26 Added "aw9523" to list of possible expanders 2024-07-26 21:01:10 +02:00
Stefan Krupop
672aca8258 Fixed resetting interrupt 2024-07-26 20:18:55 +02:00
Stefan Krupop
a2351ba0d5 Add support for AW9523 port expander 2024-07-25 01:13:14 +02:00
wizmo2
40a698e2f1 add checks for grfX handlers to prevent divide-by-zero with slimproto (#424)
Thank you!
2024-06-07 18:19:33 -04:00
github-actions
38d28ae8c4 Update prebuilt objects [skip actions] 2024-05-07 21:41:36 +00:00
Sebastien L
21407e8c1c Fix crash from led_vu when no display - release 2024-05-07 17:39:02 -04:00
github-actions
12aa555ff3 Update prebuilt objects [skip actions] 2024-03-22 03:03:10 +00:00
philippe44
cb47ec855b Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-03-20 22:36:28 -07:00
philippe44
787a5d9a6e spdif glitch at track transition 2024-03-20 22:36:22 -07:00
github-actions
7b9deb795c Update prebuilt objects [skip actions] 2024-01-28 06:33:54 +00:00
philippe44
4b1f8a8d4b see CHANGELOG - release 2024-01-27 22:32:01 -08:00
philippe44
4f8661100b Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-01-19 16:14:12 -08:00
philippe44
6b2eb1b3c0 see CHANGELOG 2024-01-19 16:14:08 -08:00
github-actions
f3593fa2f4 Update prebuilt objects [skip actions] 2024-01-17 02:53:45 +00:00
philippe44
ccc7b86369 See CHANGELOG - release 2024-01-16 18:51:33 -08:00
philippe44
9e3c6dcf30 Update README.md 2024-01-10 21:53:03 -08:00
github-actions
7be81887a6 Update prebuilt objects [skip actions] 2024-01-11 05:41:18 +00:00
philippe44
29f71fc677 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-01-10 21:36:53 -08:00
philippe44
a14a6edc1b reset flac packet flag - release 2024-01-10 21:36:50 -08:00
github-actions
35bf0a3c10 Update prebuilt objects [skip actions] 2024-01-11 03:00:35 +00:00
philippe44
55a8658f3a OggFlac & displayer fixes (see CHANGELOG) - release 2024-01-10 18:58:47 -08:00
github-actions
b0d9e1668c Update prebuilt objects [skip actions] 2024-01-04 08:48:28 +00:00
philippe44
922367baf5 no false alarm on OggS miss + homogeneous use of u32/size_t/int - release 2024-01-04 00:46:36 -08:00
philippe44
01320db007 always copy granule unless it's -1 (not valid) 2024-01-03 23:47:00 -08:00
philippe44
f677695fc7 handle pages with no terminated packet 2024-01-03 23:25:08 -08:00
github-actions
7902af2bf0 Update prebuilt objects [skip actions] 2024-01-03 04:08:04 +00:00
philippe44
5faecb08f6 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-01-02 18:15:43 -08:00
philippe44
8e3d91020f bring ogg parsing context purely inside stream.c - release 2024-01-02 18:15:39 -08:00
github-actions
4f54a47c83 Update prebuilt objects [skip actions] 2024-01-02 08:50:27 +00:00
philippe44
58840f894f Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-01-02 00:44:20 -08:00
philippe44
94109ebf38 fix copy typo - release 2024-01-02 00:43:32 -08:00
github-actions
fc20618fa2 Update prebuilt objects [skip actions] 2024-01-02 08:37:41 +00:00
philippe44
70720d3445 don't allocate segments - release 2024-01-02 00:35:47 -08:00
github-actions
4abe1304e8 Update prebuilt objects [skip actions] 2024-01-01 02:54:46 +00:00
philippe44
be7fb4b669 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-12-31 18:50:29 -08:00
philippe44
3554af1460 add vorbis/ogg live metadata (and fix some ogg issues) - release 2023-12-31 18:50:25 -08:00
Sébastien
02d422c1f4 update workflow dependency 2023-12-28 09:11:46 -05:00
github-actions
343b728323 Update prebuilt objects [skip actions] 2023-12-28 13:56:07 +00:00
philippe44
9679c5c104 fix flush mode 2023-12-27 19:46:16 -08:00
philippe44
5c90086bbd sync with upstream/cspot 2023-11-21 12:52:55 -08:00
philippe44
807a0e547a airplay improvements 2023-11-19 22:57:21 -08:00
philippe44
1d54331224 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-11-16 22:52:23 -08:00
philippe44
e85a3cddf1 GDS optimization 2023-11-16 22:52:18 -08:00
github-actions
d4d97d5a60 Update prebuilt objects [skip actions] 2023-11-17 05:45:59 +00:00
philippe44
2afbee7cb1 add SH1122 - release 2023-11-16 21:43:56 -08:00
philippe44
bbf9a3af70 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-11-13 22:30:51 -08:00
philippe44
a9ca4c4450 attempt to fix cspot track issues - release 2023-11-13 22:30:45 -08:00
github-actions
55bce084eb Update prebuilt objects [skip actions] 2023-11-09 21:49:08 +00:00
philippe44
d31697e7ef force pad_select for gpio use in dac_controlset - release 2023-11-09 13:47:08 -08:00
philippe44
e830b4db73 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-11-09 13:42:11 -08:00
philippe44
8fe21327b8 Update README.md 2023-11-08 16:54:54 -08:00
philippe44
3f487366ee Update README.md 2023-11-08 16:51:47 -08:00
philippe44
b875585aec Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-11-08 16:37:53 -08:00
philippe44
b46fccfc83 execute dac_controlset even w/o i2c (for gpio) - release 2023-11-08 16:37:49 -08:00
philippe44
655c17fb29 Update README.md 2023-11-08 11:36:29 -08:00
philippe44
b8bda0435a Update README.md 2023-11-08 11:34:17 -08:00
github-actions
dc5cb31efb Update prebuilt objects [skip actions] 2023-11-08 06:18:54 +00:00
127 changed files with 1498 additions and 771 deletions

View File

@@ -88,7 +88,7 @@ jobs:
git commit -m "Update prebuilt objects [skip actions]"
git push https://${{secrets.github_token}}@github.com/sle118/squeezelite-esp32.git
- name: Locally store commonly built objects
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: prebuilt_objects
path: |
@@ -130,7 +130,7 @@ jobs:
git status
build_tools.py environment --build ${{ needs.bootstrap.outputs.build_number }} --env_file "$GITHUB_ENV" --node "${{matrix.node}}" --depth ${{matrix.depth}} --major 2 --docker sle118/squeezelite-esp32-idfv435
- uses: actions/download-artifact@master
- uses: actions/download-artifact@v4
name: Restore common objects
with:
name: prebuilt_objects

View File

@@ -1,3 +1,51 @@
2024-09-12
- add AW9523 GPIO expander credits @Stefan Krupop (https://github.com/sle118/squeezelite-esp32/pull/430
2024-09-10
- Merge pull request # 439 from digidocs/eq_update_fix2 (# 309)
- Fix for I2S noise burst when ESP32 panic occurs (# 437)
2024-05-05
- Fix crash when led_vu is configured without display
2024-01-27
- complete libflac fix and add chaining enablement
- fixed stream Ogg demux issue with unknown granule
2024-01-19
- fixed libflac with OggFlac
- AirPlay missed frame logging
2024-01-16
- catch-up with cspot latest
- refactor airplay flush/first packet
- new libFLAC that supports multi-stream OggFlac
- fix output threshold
- log missed frames
2024-01-10
- add OggFlac to stream metadata
- fix OggFlac deadlock in flac callback when not enough data in streambuf
- fix no displayer due to threadshold too high (use 500ms instead)
- reset outputbuf when cspot starts
2024-01-01
- ogg stream are parsed to foward metadata to LMS
- fix some ogg parsing on multi-stream containers
2023-11-19
- more robust (?) airplay RTP frame recovery
- initialize of scratch string in monitor (trying to figure out random reboot)
2023-11-16
- add SH1122 support
- optimize GDS DrawPixel function
2023-11-09
- force gpio_pad_select_gpio in dac_controlset in case somebody uses UART gpio's (or other pre-programmed)
2023-11-08
- execute dac_controlset even when there is no i2s (for gpio)
2023-11-07
- led-vu gain + misc fixes
- bump plugin version to 0.600

View File

@@ -73,7 +73,7 @@ set_target_properties(recovery.elf PROPERTIES LINK_LIBRARIES "${BCA};idf::app_re
# build squeezelite, add app_squeezelite to the link
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)

View File

@@ -189,13 +189,19 @@ if "model" is not set or is not recognized, then default "I2S" is used. The opti
So far, TAS57xx, TAS5713, AC101, WM8978 and ES8388 are recognized models where the proper init sequence/volume/power controls are sent. For other codecs that might require an I2C commands, please use the parameter "dac_controlset" that allows definition of simple commands to be sent over i2c for init, power, speaker and headset on and off using a JSON syntax:
```json
{ <command>: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
<command>: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
{ <command>: [ <item1>, <item2>, ... <item3> ],
<command>: [ <item1>, <item2>, ... <item3> ],
... }
```
Where `<command>` is one of init, poweron, poweroff, speakeron, speakeroff, headseton, headsetoff
Where `<command>` is one of init, poweron, poweroff, speakeron, speakeroff, headseton, headsetoff (it **must** be an array even for a single item). Item is any of the following elements
```
{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}
{"gpio":<gpio>,"level":0|1}
{"delay":<ms>}
```
This is standard JSON notation, so if you are not familiar with it, Google is your best friend. Be aware that the '...' means you can have as many entries as you want, it's not part of the syntax. Every section is optional, but it does not make sense to set i2c in the 'dac_config' parameter and not setting anything here.
This is standard JSON notation, so if you are not familiar with it, Google is your best friend. Be aware that the '...' means you can have as many entries as you want, it's not part of the syntax. Every section is optional, but it does not make sense to set i2c in the 'dac_config' parameter and not setting anything here. The parameter 'mode' allows to *or* the register with the value or to *and* it. Don't set 'mode' if you simply want to write. The 'val parameter can be an array [v1, v2,...] to write a serie of bytes in a single i2c burst (in that case 'mode' is ignored). **Note that all values must be decimal**. You can use a validator like [this](https://jsonlint.com) to verify your syntax
The `reg` key allow to write registers on i2c bus. The parameter `mode` allows to *or* the register with the value or to *and* it. Don't set `mode` if you simply want to write. The `val` parameter can be an array [v1, v2,...] to write a serie of bytes in a single i2c burst (in that case 'mode' is ignored). **Note that all values must be decimal**. You can use a validator like [this](https://jsonlint.com) to verify your syntax. The `gpio` key is simply to set a gpio as part of DAC action and `delay` allows a pause between elements.
The 'power' command is used when powering on/off the DAC after the idle period (see -C option of squeezelite) and the 'speaker/headset' commands are sent when switching between speakers and headsets (see headset jack detection).
@@ -302,7 +308,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
@@ -631,7 +637,11 @@ You can install IDF manually on Linux or Windows (using the Subsystem for Linux)
**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
## 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)**

View File

@@ -705,6 +705,26 @@ esp_err_t i2s_stop(i2s_port_t i2s_num)
return ESP_OK;
}
/*
* 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_hal_stop_tx(&(p_i2s_obj[CONFIG_I2S_NUM]->hal));
/* Call the original panic handler function to finish processing this error */
__real_esp_panic_handler(info);
}
#if SOC_I2S_SUPPORTS_ADC_DAC
esp_err_t i2s_set_dac_mode(i2s_dac_mode_t dac_mode)
{

View File

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

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

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

View File

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

View File

@@ -54,6 +54,7 @@ struct GDS_Device* GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[], s
}
void GDS_ClearExt(struct GDS_Device* Device, bool full, ...) {
GDS_CHECK_FOR_DEVICE(Device,return);
bool commit = true;
if (full) {
@@ -74,6 +75,7 @@ void GDS_ClearExt(struct GDS_Device* Device, bool full, ...) {
}
void GDS_Clear( struct GDS_Device* Device, int Color ) {
GDS_CHECK_FOR_DEVICE(Device,return);
if (Color == GDS_COLOR_BLACK) memset( Device->Framebuffer, 0, Device->FramebufferSize );
else if (Device->Depth == 1) memset( Device->Framebuffer, 0xff, Device->FramebufferSize );
else if (Device->Depth == 4) memset( Device->Framebuffer, Color | (Color << 4), Device->FramebufferSize );
@@ -89,6 +91,7 @@ void GDS_Clear( struct GDS_Device* Device, int Color ) {
}
void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) {
GDS_CHECK_FOR_DEVICE(Device,return);
// -1 means up to width/height
if (x2 < 0) x2 = Device->Width - 1;
if (y2 < 0) y2 = Device->Height - 1;
@@ -109,7 +112,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
int c = x1;
// for a row that is not on a boundary, no optimization possible
while (r & 0x07 && r <= y2) {
for (c = x1; c <= x2; c++) DrawPixelFast( Device, c, r, Color );
for (c = x1; c <= x2; c++) Device->DrawPixelFast( Device, c, r, Color );
r++;
}
// go fast if we have more than 8 lines to write
@@ -117,7 +120,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
memset(optr + Width * r + x1, _Color, x2 - x1 + 1);
r += 8;
} else while (r <= y2) {
for (c = x1; c <= x2; c++) DrawPixelFast( Device, c, r, Color );
for (c = x1; c <= x2; c++) Device->DrawPixelFast( Device, c, r, Color );
r++;
}
}
@@ -133,10 +136,10 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
// try to do byte processing as much as possible
for (int r = y1; r <= y2; r++) {
int c = x1;
if (c & 0x01) DrawPixelFast( Device, c++, r, Color);
if (c & 0x01) Device->DrawPixelFast( Device, c++, r, Color);
int chunk = (x2 - c + 1) >> 1;
memset(optr + ((r * Width + c) >> 1), _Color, chunk);
if (c + chunk <= x2) DrawPixelFast( Device, x2, r, Color);
if (c + chunk <= x2) Device->DrawPixelFast( Device, x2, r, Color);
}
}
} else if (Device->Depth == 8) {
@@ -148,7 +151,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
} else {
for (int y = y1; y <= y2; y++) {
for (int x = x1; x <= x2; x++) {
DrawPixelFast( Device, x, y, Color);
Device->DrawPixelFast( Device, x, y, Color);
}
}
}
@@ -158,11 +161,13 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
}
void GDS_Update( struct GDS_Device* Device ) {
GDS_CHECK_FOR_DEVICE(Device,return);
if (Device->Dirty) Device->Update( Device );
Device->Dirty = false;
}
bool GDS_Reset( struct GDS_Device* Device ) {
GDS_CHECK_FOR_DEVICE(Device,return false);
if ( Device->RSTPin >= 0 ) {
gpio_set_level( Device->RSTPin, 0 );
vTaskDelay( pdMS_TO_TICKS( 100 ) );
@@ -171,10 +176,76 @@ bool GDS_Reset( struct GDS_Device* Device ) {
return true;
}
static void IRAM_ATTR DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint32_t YBit = ( Y & 0x07 );
uint8_t* FBOffset;
/*
* We only need to modify the Y coordinate since the pitch
* of the screen is the same as the width.
* Dividing Y by 8 gives us which row the pixel is in but not
* the bit position.
*/
Y>>= 3;
FBOffset = Device->Framebuffer + ( ( Y * Device->Width ) + X );
if ( Color == GDS_COLOR_XOR ) {
*FBOffset ^= BIT( YBit );
} else {
*FBOffset = ( Color == GDS_COLOR_BLACK ) ? *FBOffset & ~BIT( YBit ) : *FBOffset | BIT( YBit );
}
}
static void IRAM_ATTR DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f));
}
static void IRAM_ATTR DrawPixel4FastHigh( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
*FBOffset = X & 0x01 ? ((*FBOffset & 0xf0) | (Color & 0x0f)) : (*FBOffset & 0x0f) | ((Color & 0x0f) << 4);
}
static void IRAM_ATTR DrawPixel8Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
Device->Framebuffer[Y * Device->Width + X] = Color;
}
// assumes that Color is 16 bits R..RG..GB..B from MSB to LSB and FB wants 1st serialized byte to start with R
static void IRAM_ATTR DrawPixel16Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint16_t* FBOffset = (uint16_t*) Device->Framebuffer + Y * Device->Width + X;
*FBOffset = __builtin_bswap16(Color);
}
// assumes that Color is 18 bits RGB from MSB to LSB RRRRRRGGGGGGBBBBBB, so byte[0] is B
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = xxRRRRRR xxGGGGGG xxBBBBBB
static void IRAM_ATTR DrawPixel18Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
*FBOffset++ = Color >> 12; *FBOffset++ = (Color >> 6) & 0x3f; *FBOffset = Color & 0x3f;
}
// assumes that Color is 24 bits RGB from MSB to LSB RRRRRRRRGGGGGGGGBBBBBBBB, so byte[0] is B
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = RRRRRRRR GGGGGGGG BBBBBBBB
static void IRAM_ATTR DrawPixel24Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
*FBOffset++ = Color >> 16; *FBOffset++ = Color >> 8; *FBOffset = Color;
}
bool GDS_Init( struct GDS_Device* Device ) {
GDS_CHECK_FOR_DEVICE(Device,return false);
if (Device->Depth > 8) Device->FramebufferSize = Device->Width * Device->Height * ((8 + Device->Depth - 1) / 8);
else Device->FramebufferSize = (Device->Width * Device->Height) / (8 / Device->Depth);
// set the proper DrawPixel function if not already set by driver
if (!Device->DrawPixelFast) {
if (Device->Depth == 1) Device->DrawPixelFast = DrawPixel1Fast;
else if (Device->Depth == 4 && Device->HighNibble) Device->DrawPixelFast = DrawPixel4FastHigh;
else if (Device->Depth == 4) Device->DrawPixelFast = DrawPixel4Fast;
else if (Device->Depth == 8) Device->DrawPixelFast = DrawPixel8Fast;
else if (Device->Depth == 16) Device->DrawPixelFast = DrawPixel16Fast;
else if (Device->Depth == 24 && Device->Mode == GDS_RGB666) Device->DrawPixelFast = DrawPixel18Fast;
else if (Device->Depth == 24 && Device->Mode == GDS_RGB888) Device->DrawPixelFast = DrawPixel24Fast;
}
// allocate FB unless explicitely asked not to
if (!(Device->Alloc & GDS_ALLOC_NONE)) {
@@ -208,6 +279,7 @@ bool GDS_Init( struct GDS_Device* Device ) {
}
int GDS_GrayMap( struct GDS_Device* Device, uint8_t Level) {
GDS_CHECK_FOR_DEVICE(Device,return -1);
switch(Device->Mode) {
case GDS_MONO: return Level;
case GDS_GRAYSCALE: return Level >> (8 - Device->Depth);
@@ -234,6 +306,7 @@ int GDS_GrayMap( struct GDS_Device* Device, uint8_t Level) {
}
void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
GDS_CHECK_FOR_DEVICE(Device,return);
if (Device->SetContrast) Device->SetContrast( Device, Contrast );
else if (Device->Backlight.Pin >= 0) {
Device->Backlight.PWM = PWMConfig.Max * powf(Contrast / 255.0, 3);
@@ -242,12 +315,12 @@ void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
}
}
void GDS_SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) { if (Device->SetLayout) Device->SetLayout( Device, Layout ); }
void GDS_SetDirty( struct GDS_Device* Device ) { Device->Dirty = true; }
void GDS_SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) { if (Device && Device->SetLayout) Device->SetLayout( Device, Layout ); }
void GDS_SetDirty( struct GDS_Device* Device ) { GDS_CHECK_FOR_DEVICE(Device,return); Device->Dirty = true; }
int GDS_GetWidth( struct GDS_Device* Device ) { return Device ? Device->Width : 0; }
void GDS_SetTextWidth( struct GDS_Device* Device, int TextWidth ) { Device->TextWidth = Device && TextWidth && TextWidth < Device->Width ? TextWidth : Device->Width; }
void GDS_SetTextWidth( struct GDS_Device* Device, int TextWidth ) { GDS_CHECK_FOR_DEVICE(Device,return); Device->TextWidth = Device && TextWidth && TextWidth < Device->Width ? TextWidth : Device->Width; }
int GDS_GetHeight( struct GDS_Device* Device ) { return Device ? Device->Height : 0; }
int GDS_GetDepth( struct GDS_Device* Device ) { return Device ? Device->Depth : 0; }
int GDS_GetMode( struct GDS_Device* Device ) { return Device ? Device->Mode : 0; }
void GDS_DisplayOn( struct GDS_Device* Device ) { if (Device->DisplayOn) Device->DisplayOn( Device ); }
void GDS_DisplayOff( struct GDS_Device* Device ) { if (Device->DisplayOff) Device->DisplayOff( Device ); }
void GDS_DisplayOn( struct GDS_Device* Device ) { if (Device && Device->DisplayOn) Device->DisplayOn( Device ); }
void GDS_DisplayOff( struct GDS_Device* Device ) { if (Device && Device->DisplayOff) Device->DisplayOff( Device ); }

View File

@@ -52,5 +52,6 @@ int GDS_GrayMap( struct GDS_Device* Device, uint8_t Level );
void GDS_ClearExt( struct GDS_Device* Device, bool full, ...);
void GDS_Clear( struct GDS_Device* Device, int Color );
void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color );
#define GDS_CHECK_FOR_DEVICE(dev,ret_act) if(!dev) ret_act
#endif

View File

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

View File

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

View File

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

View File

@@ -63,6 +63,7 @@ static EXT_RAM_ATTR struct {
} displayer;
static const char *known_drivers[] = {"SH1106",
"SH1122",
"SSD1306",
"SSD1322",
"SSD1326",
@@ -79,8 +80,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, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect;
GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect, NULL };
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 };
/****************************************************************************************
*

View File

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

View File

@@ -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 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 (!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 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);
@@ -572,17 +591,25 @@ static void buffer_push_packet(rtp_t *ctx) {
}
LOG_SDEBUG("playtime %u %d [W:%hu R:%hu] %d", playtime, playtime - now, ctx->ab_write, ctx->ab_read, curframe->ready);
// try to request resend missing packet in order, explore up to 32 frames
for (int step = max((ctx->ab_write - ctx->ab_read + 1) / 32, 1),
i = 0, first = 0;
seq_order(ctx->ab_read + i, ctx->ab_write); i += step) {
abuf_t* frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i);
// each missing packet will be requested up to (latency_frames / 16) times
for (int i = 0; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) {
abuf_t *frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i);
if (!frame->ready && now - frame->last_resend > RESEND_TO) {
// stop if one fails
if (!rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i)) break;
frame->last_resend = now;
}
}
// stop when we reach a ready frame or a recent pending resend
if (first && (frame->ready || now - frame->last_resend <= RESEND_TO)) {
if (!rtp_request_resend(ctx, first, ctx->ab_read + i - 1)) break;
first = 0;
i += step - 1;
} else if (!frame->ready && now - frame->last_resend > RESEND_TO) {
if (!first) first = ctx->ab_read + i;
frame->last_resend = now;
}
}
}
/*---------------------------------------------------------------------------*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,8 @@
#include <unistd.h>
#include <cstring>
#include <vector>
#include <mutex>
#include <atomic>
#if __has_include("avahi-client/client.h")
#include <avahi-client/client.h>
@@ -40,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
@@ -50,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;
@@ -65,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(
@@ -179,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;
}

View File

@@ -19,13 +19,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 +32,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 +103,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;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -97,6 +97,8 @@ void TrackInfo::loadPbTrack(Track* pbTrack, const std::vector<uint8_t>& gid) {
}
}
number = pbTrack->has_number ? pbTrack->number : 0;
discNumber = pbTrack->has_disc_number ? pbTrack->disc_number : 0;
duration = pbTrack->duration;
}
@@ -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;
}
@@ -591,15 +595,14 @@ bool TrackQueue::updateTracks(uint32_t requestedPosition, bool initial) {
std::scoped_lock lock(tracksMutex);
bool cleared = true;
// Copy requested track list
currentTracks = playbackState->remoteTracks;
currentTracksIndex = playbackState->innerFrame.state.playing_track_index;
if (initial) {
// Clear preloaded tracks
preloadedTracks.clear();
// Copy requested track list
currentTracks = playbackState->remoteTracks;
currentTracksIndex = playbackState->innerFrame.state.playing_track_index;
if (currentTracksIndex < currentTracks.size()) {
// Push a song on the preloaded queue
queueNextTrack(0, requestedPosition);
@@ -609,28 +612,24 @@ bool TrackQueue::updateTracks(uint32_t requestedPosition, bool initial) {
notifyPending = true;
playableSemaphore->give();
} else {
// Copy requested track list
currentTracks = playbackState->remoteTracks;
} else if (preloadedTracks[0]->loading) {
// try to not re-load track if we are still loading it
if (preloadedTracks[0]->loading) {
// remove everything except first track
preloadedTracks.erase(preloadedTracks.begin() + 1, preloadedTracks.end());
// Push a song on the preloaded queue
CSPOT_LOG(info, "Keeping current track");
queueNextTrack(1);
// remove everything except first track
preloadedTracks.erase(preloadedTracks.begin() + 1, preloadedTracks.end());
cleared = false;
} else {
// Clear preloaded tracks
preloadedTracks.clear();
// Push a song on the preloaded queue
CSPOT_LOG(info, "Keeping current track %d", currentTracksIndex);
queueNextTrack(1);
// Push a song on the preloaded queue
CSPOT_LOG(info, "Re-loading current track");
queueNextTrack(0, requestedPosition);
}
cleared = false;
} else {
// Clear preloaded tracks
preloadedTracks.clear();
// Push a song on the preloaded queue
CSPOT_LOG(info, "Re-loading current track");
queueNextTrack(0, requestedPosition);
}
return cleared;

View File

@@ -288,6 +288,7 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
output.frames_played = 0;
output.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;

View File

@@ -687,6 +687,7 @@ 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;
GDS_CHECK_FOR_DEVICE(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);
@@ -773,7 +774,8 @@ 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
);
GDS_CHECK_FOR_DEVICE(display,return);
// new grfs frame, build scroller info
if (!offset) {
// use the display as a general lock
@@ -820,6 +822,7 @@ static void grfg_handler(u8_t *data, int len) {
LOG_DEBUG("gfrg s:%hu w:%hu (len:%u)", htons(pkt->screen), htons(pkt->width), len);
GDS_CHECK_FOR_DEVICE(display,return);
// full screen artwork or for small screen, visu has priority when full screen
if (((visu.mode & VISU_ESP32) && !visu.col && visu.row < displayer.height) || artwork.full) {
return;
@@ -864,6 +867,7 @@ static void grfa_handler(u8_t *data, int len) {
int offset = htonl(pkt->offset);
int length = htonl(pkt->length);
GDS_CHECK_FOR_DEVICE(display,return);
// when using full screen visualizer on small screen there is a brief overlay
artwork.enable = (length != 0);

View File

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

View File

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

View File

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

View File

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

View File

@@ -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();
@@ -443,7 +443,7 @@ void output_close_common(void) {
}
void output_flush(void) {
LOG_INFO("flush output buffer");
LOG_INFO("flush output buffer (full)");
buf_flush(outputbuf);
LOCK;
output.fade = FADE_INACTIVE;
@@ -457,3 +457,15 @@ void output_flush(void) {
output.frames_played = 0;
UNLOCK;
}
bool output_flush_streaming(void) {
LOG_INFO("flush output buffer (streaming)");
LOCK;
bool flushed = output.track_start != NULL;
if (output.track_start) {
outputbuf->writep = output.track_start;
output.track_start = NULL;
}
UNLOCK;
return flushed;
}

View File

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

View File

@@ -439,7 +439,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 +485,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);
}

View File

@@ -305,8 +305,18 @@ static void process_strm(u8_t *pkt, int len) {
sendSTAT("STMt", strm->replay_gain); // STMt replay_gain is no longer used to track latency, but support it
break;
case 'f':
{
decode_flush(false);
bool flushed = false;
if (!output.external) flushed |= output_flush_streaming();
// we can have fully finished the current streaming, that's still a flush
if (stream_disconnect() || flushed) sendSTAT("STMf", 0);
buf_flush(streambuf);
output.stop_time = gettime_ms();
break;
}
case 'q':
decode_flush(strm->command == 'q');
decode_flush(true);
if (!output.external) output_flush();
status.frames_played = 0;
if (stream_disconnect() && strm->command == 'f') sendSTAT("STMf", 0);
@@ -383,7 +393,9 @@ static void process_strm(u8_t *pkt, int len) {
stream_file(header, header_len, strm->threshold * 1024);
autostart -= 2;
} else {
stream_sock(ip, port, header, header_len, strm->threshold * 1024, autostart >= 2);
stream_sock(ip, port, strm->flags & 0x20,
strm->format == 'o' || strm->format == 'u' || (strm->format == 'f' && strm->pcm_sample_size == 'o'),
header, header_len, strm->threshold * 1024, autostart >= 2);
}
sendSTAT("STMc", 0);
sentSTMu = sentSTMo = sentSTMl = false;
@@ -402,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:

View File

@@ -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, 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
@@ -727,6 +727,7 @@ struct outputstate {
void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[], unsigned idle);
void output_close_common(void);
void output_flush(void);
bool output_flush_streaming(void);
// _* called with mutex locked
frames_t _output_frames(frames_t avail);
void _checkfade(bool);

View File

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

View File

@@ -50,7 +50,7 @@ static inline int32_t clip15(int32_t x) {
struct vorbis {
bool opened;
enum { OGG_SYNC, OGG_ID_HEADER, OGG_COMMENT_HEADER, OGG_SETUP_HEADER } status;
enum { OGG_ID_HEADER, OGG_COMMENT_HEADER, OGG_SETUP_HEADER } status;
struct {
ogg_stream_state state;
ogg_packet packet;
@@ -138,48 +138,16 @@ extern struct processstate process;
#define OG(h, fn, ...) (h)->ogg_ ## fn(__VA_ARGS__)
#endif
static int get_ogg_packet(void) {
static int get_audio_packet(void) {
int status, packet = -1;
LOCK_S;
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
while (!(status = OG(&go, stream_packetout, &v->state, &v->packet)) && bytes) {
// if sync_pageout (or sync_pageseek) is not called first, sync buffers build ups
while (!(status = OG(&go, sync_pageout, &v->sync, &v->page)) && bytes) {
size_t consumed = min(bytes, 4096);
char* buffer = OG(&gv, sync_buffer, &v->sync, consumed);
memcpy(buffer, streambuf->readp, consumed);
OG(&gv, sync_wrote, &v->sync, consumed);
_buf_inc_readp(streambuf, consumed);
bytes -= consumed;
}
// if we have a new page, put it in
if (status) OG(&go, stream_pagein, &v->state, &v->page);
}
// only return a negative value when true end of streaming is reached
if (status > 0) packet = status;
else if (stream.state > DISCONNECT || _buf_used(streambuf)) packet = 0;
UNLOCK_S;
return packet;
}
static int read_vorbis_header(void) {
int status = 0;
bool fetch = true;
LOCK_S;
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
while (bytes && !status) {
// first fetch a page if we need one
if (fetch) {
// if sync_pageout (or sync_pageseek) is not called here, sync buffers build up
while (!(status = OG(&go, sync_pageout, &v->sync, &v->page)) && bytes) {
size_t consumed = min(bytes, 4096);
char* buffer = OG(&go, sync_buffer, &v->sync, consumed);
memcpy(buffer, streambuf->readp, consumed);
@@ -187,81 +155,122 @@ static int read_vorbis_header(void) {
_buf_inc_readp(streambuf, consumed);
bytes -= consumed;
if (!OG(&go, sync_pageseek, &v->sync, &v->page)) continue;
}
switch (v->status) {
case OGG_SYNC:
v->status = OGG_ID_HEADER;
OG(&go, stream_reset_serialno, &v->state, OG(&go, page_serialno, &v->page));
fetch = false;
break;
case OGG_ID_HEADER:
status = OG(&go, stream_pagein, &v->state, &v->page);
if (!OG(&go, stream_packetout, &v->state, &v->packet)) break;
// if we have a new page, put it in and reset serialno at BoS
if (status) {
OG(&go, stream_pagein, &v->state, &v->page);
if (OG(&go, page_bos, &v->page)) OG(&go, stream_reset_serialno, &v->state, OG(&go, page_serialno, &v->page));
}
}
/* odd packets are not audio and should be discarded. With no packet, we
* return a negative value when there is really nothing more to proceed */
if (status > 0 && (v->packet.packet[0] & 0x01) == 0) packet = status;
else if (stream.state > DISCONNECT || _buf_used(streambuf)) packet = 0;
UNLOCK_S;
return packet;
}
static int read_vorbis_header(void) {
int done = 0;
bool fetch = true;
LOCK_S;
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
while (bytes && !done) {
int status;
// get aligned to a page and ready to bring it in
do {
size_t consumed = min(bytes, 4096);
char* buffer = OG(&go, sync_buffer, &v->sync, consumed);
memcpy(buffer, streambuf->readp, consumed);
OG(&go, sync_wrote, &v->sync, consumed);
_buf_inc_readp(streambuf, consumed);
bytes -= consumed;
status = fetch ? OG(&go, sync_pageout, &v->sync, &v->page) :
OG(&go, sync_pageseek, &v->sync, &v->page);
} while (bytes && status <= 0);
// nothing has been found and we have no more bytes, come back later
if (status <= 0) break;
// always set stream serialno if we have a new one (no multiplexed streams)
if (OG(&go, page_bos, &v->page)) OG(&go, stream_reset_serialno, &v->state, OG(&go, page_serialno, &v->page));
// bring new page in if we want it (otherwise we're just skipping)
if (fetch) OG(&go, stream_pagein, &v->state, &v->page);
// not a switch...case b/c we might have multiple packets in a page in vorbis
if (v->status == OGG_ID_HEADER) {
// we need the id packet, get more pages if we don't
if (!OG(&go, stream_packetout, &v->state, &v->packet)) continue;
OV(&gv, info_init, &v->info);
status = OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet);
if (status) {
LOG_ERROR("vorbis id header packet error %d", status);
status = -1;
LOG_ERROR("id header packet error %d", status);
done = -1;
} else {
v->channels = v->info.channels;
v->rate = v->info.rate;
v->status = OGG_COMMENT_HEADER;
// only fetch if no other packet already in (they should not)
fetch = OG(&go, page_packets, &v->page) <= 1;
if (!fetch) LOG_INFO("id packet should terminate page");
fetch = false;
LOG_INFO("id acquired");
// we should only have one packet, so get next pages
if (OG(&go, page_packets, &v->page) == 1) continue;
}
break;
case OGG_SETUP_HEADER:
// header packets don't align with pages on Vorbis (contrary to Opus)
if (fetch) OG(&go, stream_pagein, &v->state, &v->page);
}
if (v->status == OGG_COMMENT_HEADER) {
// don't consume VorbisComment which could be a huge packet, just skip it
int packets = OG(&go, page_packets, &v->page);
if (!packets) continue;
// we have a "fake" comment packet that is just has the last page...
v->status = OGG_SETUP_HEADER;
OG(&go, stream_pagein, &v->state, &v->page);
OG(&go, stream_packetout, &v->state, &v->packet);
OV(&gv, comment_init, &v->comment);
v->comment.vendor = "N/A";
fetch = true;
LOG_INFO("comment skipped successfully");
// because of lack of page alignment, we might have the setup page already fully in
if (packets == 1) continue;
}
if (v->status == OGG_SETUP_HEADER) {
// we need the setup packet, get more pages if we don't
if (OG(&go, stream_packetout, &v->state, &v->packet) <= 0) continue;
// finally build a codec if we have the packet
status = OG(&go, stream_packetout, &v->state, &v->packet);
if (status && ((status = OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet)) ||
(status = OV(&gv, synthesis_init, &v->decoder, &v->info)))) {
LOG_ERROR("vorbis setup header packet error %d", status);
if (OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet) ||
OV(&gv, synthesis_init, &v->decoder, &v->info)) {
LOG_ERROR("setup header packet error %d", status);
// no need to free comment, it's fake
OV(&gv, info_clear, &v->info);
status = -1;
done = -1;
} else {
OV(&gv, block_init, &v->decoder, &v->block);
v->opened = true;
LOG_INFO("codec up and running (rate: %d, channels:%d)", v->rate, v->channels);
status = 1;
LOG_INFO("codec up and running");
done = 1;
}
//@FIXME: can we have audio on that page as well?
break;
case OGG_COMMENT_HEADER: {
// don't consume VorbisComment, just skip it
int packets = OG(&go, page_packets, &v->page);
if (packets) {
v->status = OGG_SETUP_HEADER;
OG(&go, stream_pagein, &v->state, &v->page);
OG(&go, stream_packetout, &v->state, &v->packet);
OV(&gv, comment_init, &v->comment);
v->comment.vendor = "N/A";
// because of lack of page alignment, we might have the setup page already fully in
if (packets > 1) fetch = false;
LOG_INFO("comment skipped succesfully");
}
break;
}
default:
break;
}
}
UNLOCK_S;
return status;
return done;
}
inline int pcm_out(vorbis_dsp_state* decoder, void*** pcm) {
@@ -317,12 +326,12 @@ static decode_state vorbis_decode(void) {
if (v->overflow) {
n = pcm_out(&v->decoder, &pcm);
v->overflow = n - min(n, frames);
} else if ((packet = get_ogg_packet()) > 0) {
} else if ((packet = get_audio_packet()) > 0) {
n = OV(&gv, synthesis, &v->block, &v->packet);
if (n == 0) n = OV(&gv, synthesis_blockin, &v->decoder, &v->block);
if (n == 0) n = pcm_out(&v->decoder, &pcm);
v->overflow = n - min(n, frames);
} else if (!packet && !OG(&go, page_eos, &v->page)) {
} else if (!packet) {
UNLOCK_O_direct;
return DECODE_RUNNING;
}
@@ -410,7 +419,7 @@ static void vorbis_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
}
v->opened = false;
v->status = OGG_SYNC;
v->status = OGG_ID_HEADER;
v->overflow = 0;
OG(&go, stream_clear, &v->state);

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -70,6 +70,22 @@ declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
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;
@@ -214,6 +230,14 @@ declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare let sd: {};
declare let rf: boolean;
declare function refreshStatus(): void;

View File

@@ -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.6d425ac534311a0131b2.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.5fd84f.bundle.js.gz BINARY)
target_add_binary_data( __idf_wifi-manager webapp/dist/js/node_vendors.5fd84f.bundle.js.gz BINARY)
target_add_binary_data( __idf_wifi-manager webapp/dist/js/index.ca2484.bundle.js.gz BINARY)
target_add_binary_data( __idf_wifi-manager webapp/dist/js/node_vendors.ca2484.bundle.js.gz BINARY)

View File

@@ -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_6d425ac534311a0131b2_css_gz_start[] asm("_binary_index_6d425ac534311a0131b2_css_gz_start");
extern const uint8_t _index_6d425ac534311a0131b2_css_gz_end[] asm("_binary_index_6d425ac534311a0131b2_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_5fd84f_bundle_js_gz_start[] asm("_binary_index_5fd84f_bundle_js_gz_start");
extern const uint8_t _index_5fd84f_bundle_js_gz_end[] asm("_binary_index_5fd84f_bundle_js_gz_end");
extern const uint8_t _node_vendors_5fd84f_bundle_js_gz_start[] asm("_binary_node_vendors_5fd84f_bundle_js_gz_start");
extern const uint8_t _node_vendors_5fd84f_bundle_js_gz_end[] asm("_binary_node_vendors_5fd84f_bundle_js_gz_end");
extern const uint8_t _index_ca2484_bundle_js_gz_start[] asm("_binary_index_ca2484_bundle_js_gz_start");
extern const uint8_t _index_ca2484_bundle_js_gz_end[] asm("_binary_index_ca2484_bundle_js_gz_end");
extern const uint8_t _node_vendors_ca2484_bundle_js_gz_start[] asm("_binary_node_vendors_ca2484_bundle_js_gz_start");
extern const uint8_t _node_vendors_ca2484_bundle_js_gz_end[] asm("_binary_node_vendors_ca2484_bundle_js_gz_end");
const char * resource_lookups[] = {
"/css/index.4bbe29a78a667faa2b6f.css.gz",
"/css/index.6d425ac534311a0131b2.css.gz",
"/favicon-32x32.png",
"/index.html.gz",
"/js/index.5fd84f.bundle.js.gz",
"/js/node_vendors.5fd84f.bundle.js.gz",
"/js/index.ca2484.bundle.js.gz",
"/js/node_vendors.ca2484.bundle.js.gz",
""
};
const uint8_t * resource_map_start[] = {
_index_4bbe29a78a667faa2b6f_css_gz_start,
_index_6d425ac534311a0131b2_css_gz_start,
_favicon_32x32_png_start,
_index_html_gz_start,
_index_5fd84f_bundle_js_gz_start,
_node_vendors_5fd84f_bundle_js_gz_start
_index_ca2484_bundle_js_gz_start,
_node_vendors_ca2484_bundle_js_gz_start
};
const uint8_t * resource_map_end[] = {
_index_4bbe29a78a667faa2b6f_css_gz_end,
_index_6d425ac534311a0131b2_css_gz_end,
_favicon_32x32_png_end,
_index_html_gz_end,
_index_5fd84f_bundle_js_gz_end,
_node_vendors_5fd84f_bundle_js_gz_end
_index_ca2484_bundle_js_gz_end,
_node_vendors_ca2484_bundle_js_gz_end
};

View File

@@ -1,6 +1,6 @@
/***********************************
webpack_headers
dist/css/index.4bbe29a78a667faa2b6f.css.gz,dist/favicon-32x32.png,dist/index.html.gz,dist/js/index.5fd84f.bundle.js.gz,dist/js/node_vendors.5fd84f.bundle.js.gz
dist/css/index.6d425ac534311a0131b2.css.gz,dist/favicon-32x32.png,dist/index.html.gz,dist/js/index.ca2484.bundle.js.gz,dist/js/node_vendors.ca2484.bundle.js.gz
***********************************/
#pragma once
#include <inttypes.h>

Binary file not shown.

View File

@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEyDCCA7CgAwIBAgIQDPW9BitWAvR6uFAsI8zwZjANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0yMTAzMzAwMDAwMDBaFw0zMTAzMjkyMzU5NTlaMFkxCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEdsb2Jh
bCBHMiBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAMz3EGJPprtjb+2QUlbFbSd7ehJWivH0+dbn4Y+9lavyYEEV
cNsSAPonCrVXOFt9slGTcZUOakGUWzUb+nv6u8W+JDD+Vu/E832X4xT1FE3LpxDy
FuqrIvAxIhFhaZAmunjZlx/jfWardUSVc8is/+9dCopZQ+GssjoP80j812s3wWPc
3kbW20X+fSP9kOhRBx5Ro1/tSUZUfyyIxfQTnJcVPAPooTncaQwywa8WV0yUR0J8
osicfebUTVSvQpmowQTCd5zWSOTOEeAqgJnwQ3DPP3Zr0UxJqyRewg2C/Uaoq2yT
zGJSQnWS+Jr6Xl6ysGHlHx+5fwmY6D36g39HaaECAwEAAaOCAYIwggF+MBIGA1Ud
EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFHSFgMBmx9833s+9KTeqAx2+7c0XMB8G
A1UdIwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485MA4GA1UdDwEB/wQEAwIBhjAd
BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdgYIKwYBBQUHAQEEajBoMCQG
CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQAYIKwYBBQUHMAKG
NGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RH
Mi5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29t
L0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDA9BgNVHSAENjA0MAsGCWCGSAGG/WwC
ATAHBgVngQwBATAIBgZngQwBAgEwCAYGZ4EMAQICMAgGBmeBDAECAzANBgkqhkiG
9w0BAQsFAAOCAQEAkPFwyyiXaZd8dP3A+iZ7U6utzWX9upwGnIrXWkOH7U1MVl+t
wcW1BSAuWdH/SvWgKtiwla3JLko716f2b4gp/DA/JIS7w7d7kwcsr4drdjPtAFVS
slme5LnQ89/nD/7d+MS5EHKBCQRfz5eeLjJ1js+aWNJXMX43AYGyZm0pGrFmCW3R
bpD0ufovARTFXFZkAdl9h6g4U5+LXUZtXMYnhIHUfoyMo5tS58aI7Dd8KvvwVVo4
chDYABPPTHPbqjc1qCmBaZx2vN4Ye5DUys/vZwP9BFohFrH/6j/f3IL16/RZkiMN
JCqVJUzKoZHm1Lesh3Sz8W2jmdv51b2EQJ8HmA==
-----END CERTIFICATE-----

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,22 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----

Binary file not shown.

View File

@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----

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