Compare commits

...

21 Commits

Author SHA1 Message Date
philippe44
982ac3dc8a Update README.md 2022-12-07 23:51:51 -08:00
Sébastien
ae020daa09 Add status badge for web deploy [skip actions] 2022-11-30 20:28:44 -05:00
Sébastien
1aa05a3e32 update workflow [skip actions] 2022-11-29 10:18:48 -05:00
Sébastien
91a1d6e39e Update readme [skip actions] 2022-10-09 16:23:54 -04:00
Sébastien
4956d182ed Add link to web installer 2022-10-09 12:41:13 -04:00
Sébastien
850f155680 Create web_deploy.yml [skip actions] 2022-10-07 14:56:13 -04:00
Sébastien
2af9386786 Update BuildTest.yml [skip actions] 2022-10-06 11:36:08 -04:00
Sébastien
58e74fc43c Update Built_test.yml [skip actions] 2022-10-06 11:17:42 -04:00
Sébastien
192821cbc3 Update BuildTest.yml 2022-10-05 12:33:59 -04:00
Sébastien
aa67ce76ac Create BuildTest.yml 2022-10-05 12:30:37 -04:00
philippe44
aa4a859fac CRLF hell 2022-09-26 20:00:09 -07:00
philippe44
8f2a1aec53 update plugin (credits @mherger) 2022-09-25 11:35:37 -07:00
philippe44
e4d6ea9457 no workaround to use ROM version of TJPGD 2022-09-15 14:04:07 -07:00
philippe44
eb86ffd6a0 Use TJPGD in flash (stack in PSRAM?) - release 2022-09-15 13:36:52 -07:00
philippe44
1804b8b769 Move to 2.x in name as well 2022-09-12 14:33:25 -07:00
philippe44
6e8b124317 2.x version are for 4.3 IDF 2022-09-12 14:15:11 -07:00
philippe44
813bafb036 with traces 2022-09-11 10:14:33 -07:00
philippe44
97f8051e93 try to detect when JPEG decoding fails - release 2022-09-11 10:01:52 -07:00
philippe44
a2c94cf0b3 workaround for JPEG issue - release 2022-09-08 13:35:49 -07:00
philippe44
68a9970bed Update README.md 2022-09-01 22:38:18 -07:00
philippe44
4a666af681 fix 32 bits for AC101 & ES8388 - release 2022-08-29 17:38:12 -07:00
21 changed files with 1491 additions and 96 deletions

232
.github/workflows/Platform_build.yml vendored Normal file
View File

@@ -0,0 +1,232 @@
name: Platform Build
on:
push:
branches:
- '**4.3'
workflow_dispatch:
inputs:
ui_build:
description: 'Force Rebuilding the UI. When not forced, the system will check for [ui-build] in the last commit message to trigger a ui rebuild'
required: true
type: boolean
release_build:
description: 'Force a Release build. When not forced, the system will check for release word in the last commit message to trigger a release'
required: true
type: boolean
jobs:
bootstrap:
name: Global setup
runs-on: ubuntu-latest
container:
image: sle118/squeezelite-esp32-idfv43
outputs:
build_number: ${{ steps.buildnumber.outputs.build_number }}
ui_build: ${{ steps.build_flags.outputs.ui_build }}
release_flag: ${{ steps.build_flags.outputs.release_flag }}
mock: ${{ steps.build_flags.outputs.mock }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: true
- name: Generate common build number
id: buildnumber
uses: einaregilsson/build-number@v3
with:
token: ${{secrets.github_token}}
- name: Set build flags
id: build_flags
run: |
git config --global --add safe.directory /__w/squeezelite-esp32/squeezelite-esp32
[ ${{github.event.inputs.ui_build}} ] && ui_build_option="--ui_build" || ui_build_option=""
[ ${{github.event.inputs.release_build}} ] && release_build_option="--force" || release_build_option=""
echo "ui_build_option=$ui_build_option" >> $GITHUB_ENV
echo "release_build_option=$release_build_option" >> $GITHUB_ENV
echo "Dumping environment"
env
. /opt/esp/python_env/idf4.3_py3.8_env/bin/activate
# build_flags support the following options
# --mock - to mock the compilation part - this is to be used for testing only
# --force - to force a release build even if the last commit message doesn't contain the word "release"
# --ui_build - to force a ui_build even if the last commit message doesn't contain "[ui-build]"
build_tools.py build_flags $ui_build_option $release_build_option
- name: Show Build Flags
run: |
echo "Running with the following options"
echo "Web Build Flag=${{steps.build_flags.outputs.ui_build}}"
echo "Mock flag=${{steps.build_flags.outputs.mock}}"
echo "Release Flag=${{steps.build_flags.outputs.release_flag}}"
- name: Refresh certificates
if: ${{ steps.build_flags.outputs.release_flag }}
run: |
git update-index --chmod=+x ./server_certs/getcert.sh
cd server_certs;./getcert.sh;cat github.pem;cd ..
- name: Setup Node.js dependencies
if: ${{ !env.ACT }}
uses: actions/setup-node@v3
with:
node-version: 16
cache: 'npm'
cache-dependency-path: components/wifi-manager/webapp/package.json
- name: Build Web Application
if: ${{ steps.build_flags.outputs.ui_build == 1 }}
run: |
cd components/wifi-manager/webapp/
npm install
npm run-script build
- name: Update repository with prebuilt items
if: ${{ steps.build_flags.outputs.ui_build == 1 || steps.build_flags.outputs.release_flag == 1 }}
run: |
git config user.name github-actions
git config user.email github-actions@github.com
git add server_certs
git add components/wifi-manager/webapp/*.h
git add components/wifi-manager/webapp/*.c
git add components/wifi-manager/webapp/*.cmake
git add components/wifi-manager/webapp/dist/*
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
with:
name: prebuilt_objects
path: |
server_certs
components/wifi-manager/webapp/*.h
components/wifi-manager/webapp/*.c
components/wifi-manager/webapp/dist/*
components/wifi-manager/webapp/*.cmake
build:
runs-on: ubuntu-latest
container:
image: sle118/squeezelite-esp32-idfv43
needs: [bootstrap]
strategy:
matrix:
node: [I2S-4MFlash, SqueezeAmp, Muse]
depth: [16, 32]
exclude:
- node: Muse
depth: 32
- node: bootstrap
depth: 32
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: true
- name: Show Build Flags
run: |
echo "Running with the following options"
echo "Web Build Flag=${{needs.bootstrap.outputs.ui_build}}"
echo "Mock flag=${{needs.bootstrap.outputs.mock}}"
echo "Release Flag=${{needs.bootstrap.outputs.release_flag}}"
echo Environment File name: $GITHUB_ENV
- name: Set build parameters
run: |
. /opt/esp/python_env/idf4.3_py3.8_env/bin/activate
git config --global --add safe.directory /__w/squeezelite-esp32/squeezelite-esp32
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-idfv43
- uses: actions/download-artifact@master
name: Restore common objects
with:
name: prebuilt_objects
- name: Build the firmware
if: ${{ needs.bootstrap.outputs.mock == 0 }}
run: |
. ${IDF_PYTHON_ENV_PATH}/bin/activate
chmod +x ./components/spotify/cspot/bell/nanopb/generator/protoc
chmod +x ./components/spotify/cspot/bell/nanopb/generator/protoc-gen-nanopb
chmod +x ./components/spotify/cspot/bell/nanopb/generator/*.py
chmod +x ./components/spotify/cspot/bell/nanopb/generator/*.py2
chmod +x ./components/spotify/cspot/bell/nanopb/generator/proto/*.py
echo "Copying target sdkconfig"
cp build-scripts/${TARGET_BUILD_NAME}-sdkconfig.defaults sdkconfig
echo "Building project"
idf.py build -DDEPTH=${DEPTH} -DBUILD_NUMBER=${BUILD_NUMBER}-${DEPTH}
- name: Build Mock firmware
if: ${{ needs.bootstrap.outputs.mock == 1 }}
run: |
mkdir -p build
cd build
mkdir -p partition_table
mkdir -p bootloader
echo \\"mock content\\"> ./squeezelite.bin
echo \"mock content\"> ./recovery.bin
echo \"mock content\"> ./bootloader/bootloader.bin
echo \"mock content\"> ./partition_table/partition-table.bin
echo \"mock content\"> ./ota_data_initial.bin
echo \"mock content\"> ./flash_project_args
echo \"mock content\"> ./size_comp1.txt
echo \"mock content\"> ./size_comp2.txt
echo \"mock content\"> ./partitions.csv
echo { \"write_flash_args\" : [ \"--flash_mode\", \"dio\", \"--flash_size\", \"detect\", \"--flash_freq\", \"80m\" ], \"flash_settings\" : { \"flash_mode\": \"dio\", \"flash_size\": \"detect\", \"flash_freq\": \"80m\" }, \"flash_files\" : { \"0x8000\" : \"partition_table/partition-table.bin\", \"0xd000\" : \"ota_data_initial.bin\", \"0x1000\" : \"bootloader/bootloader.bin\", \"0x10000\" : \"recovery.bin\", \"0x150000\" : \"squeezelite.bin\" }, \"partition_table\" : { \"offset\" : \"0x8000\", \"file\" : \"partition_table/partition-table.bin\" }, \"otadata\" : { \"offset\" : \"0xd000\", \"file\" : \"ota_data_initial.bin\" }, \"bootloader\" : { \"offset\" : \"0x1000\", \"file\" : \"bootloader/bootloader.bin\" }, \"app\" : { \"offset\" : \"0x10000\", \"file\" : \"recovery.bin\" }, \"squeezelite\" : { \"offset\" : \"0x150000\", \"file\" : \"squeezelite.bin\" }, \"extra_esptool_args\" : { \"after\" : \"hard_reset\", \"before\" : \"default_reset\" } } > ./flasher_args.json
- name: Create Release Artifact Zip
if: ${{ needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }}
run: |
if [ -z "${artifact_file_name}" ]
then
echo "No artifact file name set. Will not generate zip file."
else
echo "Generating build artifact zip file"
zip -r build_output.zip build
zip build/${artifact_file_name} partitions*.csv components/ build/*.bin build/bootloader/bootloader.bin build/partition_table/partition-table.bin build/flash_project_args build/size_*.txt
fi
- name: Upload Build Artifacts
uses: actions/upload-artifact@v3
if: ${{ needs.bootstrap.outputs.mock == 0 }}
with:
name: ${{ env.artifact_prefix }}
path: |
build/flash_project_args
build/size_comp1.txt
build/size_comp2.txt
partitions.csv
sdkconfig
server_certs/github.pem
build/*.bin
build/bootloader/bootloader.bin
build/partition_table/partition-table.bin
build_output.zip
- name: Create Release
if: ${{ needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }}
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
with:
tag_name: ${{ env.tag }}
release_name: ${{ env.name }}
body: ${{ env.description }}
draft: false
prerelease: true
- name: Upload Release Asset - Squeezelite binary file
if: ${{ needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }}
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: build/squeezelite.bin
asset_name: ${{ env.artifact_bin_file_name }}
asset_content_type: application/octet-stream
- name: Upload Release Asset - Zip file
if: ${{ needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }}
id: upload-release-asset-zip
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: build/${{ env.artifact_file_name }}
asset_name: ${{ env.artifact_file_name }}
asset_content_type: application/octet-stream
update_web_installer:
name: Update Web Installer After Release
needs: [ bootstrap, build ]
if: ${{( always() && !cancelled() ) && needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }}
uses: ./.github/workflows/web_deploy.yml

View File

@@ -31,7 +31,7 @@ jobs:
- name: Set target name
run: |
echo "TARGET_BUILD_NAME=${{ matrix.node }}" >> $GITHUB_ENV
echo "build_version_prefix=1." >> $GITHUB_ENV
echo "build_version_prefix=2." >> $GITHUB_ENV
- uses: actions/checkout@v2
with:
fetch-depth: 15
@@ -57,7 +57,7 @@ jobs:
echo "tag=${tag}" >> $GITHUB_ENV
last_commit="$(git log --pretty=format:'%s' --max-count=1)"
if [[ "$last_commit" =~ .*"Release".* ]]; then echo "release_flag=1" >> $GITHUB_ENV; else echo "release_flag=0" >> $GITHUB_ENV; fi
name="1.${BUILD_NUMBER}-${{matrix.depth}}#v4.0#${TARGET_BUILD_NAME}#${branch_name}"
name="${build_version_prefix}${BUILD_NUMBER}-${{matrix.depth}}#v4.0#${TARGET_BUILD_NAME}#${branch_name}"
artifact_prefix="squeezelite-esp32-${branch_name}-${TARGET_BUILD_NAME}-${{matrix.depth}}-${build_version_prefix}${BUILD_NUMBER}"
artifact_file_name="${artifact_prefix}.zip"
artifact_bin_file_name="${artifact_prefix}.bin"

23
.github/workflows/web_deploy.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Update Web Installer
on:
workflow_call:
workflow_dispatch:
jobs:
update_web_installer:
name: Update Web Installer After Release
runs-on: ubuntu-latest
container:
image: sle118/squeezelite-esp32-idfv43
env:
WEB_INSTALLER: ${{ secrets.WEB_INSTALLER }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: true
- name: Update Web Installer Project
run: |
. /opt/esp/python_env/idf4.3_py3.8_env/bin/activate
git config --global --add safe.directory /__w/squeezelite-esp32/squeezelite-esp32
build_tools.py manifest --flash_file "/build/flash_project_args" --outdir "./bin_files" --manif_name "manifest" --max_count 3
build_tools.py pushinstaller --source "./bin_files" --manif_name "manifest" --target "web-installer" --url "https://github.com/sle118/squeezelite-esp32-installer.git" --artifacts "artifacts" --web_installer_branch "main" --token "${{env.WEB_INSTALLER}}"

1
.gitignore vendored
View File

@@ -98,5 +98,6 @@ test/.vscode/
node_modules/*
esp-dsp/
*.bak

View File

@@ -1,4 +1,4 @@
![Cross-Build](https://github.com/sle118/squeezelite-esp32/workflows/Cross-Build/badge.svg?branch=master-cmake)
![Cross-Build](https://github.com/sle118/squeezelite-esp32/workflows/Cross-Build/badge.svg?branch=master-cmake) [![Update Web Installer](https://github.com/sle118/squeezelite-esp32/actions/workflows/web_deploy.yml/badge.svg?branch=master-v4.3)](https://github.com/sle118/squeezelite-esp32/actions/workflows/web_deploy.yml)
# Squeezelite-esp32
@@ -8,10 +8,12 @@ Squeezelite-esp32 is an audio software suite made to run on espressif's ESP32 wi
- Stream your local music and connect to all major on-line music providers (Spotify, Deezer, Tidal, Qobuz) using [Logitech Media Server - a.k.a LMS](https://forums.slimdevices.com/) and enjoy multi-room audio synchronization. LMS can be extended by numerous plugins and can be controlled using a Web browser or dedicated applications (iPhone, Android). It can also send audio to UPnP, Sonos, ChromeCast and AirPlay speakers/devices.
- Stream from a **Bluetooth** device (iPhone, Android)
- Stream from an **AirPlay** controller (iPhone, iTunes ...) and enjoy synchronization multiroom as well (although it's AirPlay 1 only)
- Stream from a Spotify controller
- Stream from a **Spotify** controller
Depending on the hardware connected to the ESP32, you can send audio to a local DAC, to SPDIF or to a Bluetooth speaker. The bare minimum required hardware is a WROVER module with 4MB of Flash and 4MB of PSRAM (https://www.espressif.com/en/products/modules/esp32). With that module standalone, just apply power and you can stream to a Bluetooth speaker. You can also send audio to most I2S DAC as well as to SPDIF receivers using just a cable or an optical transducer.
Note that streaming **to** a Bluetooth speaker is not the main purpose and remains experimental, so your mileage will vary. We will not work on improving or fixing that feature, please don't open issues about that.
But squeezelite-esp32 is highly extensible and you can add
- [Buttons](#buttons) and [Rotary Encoder](#rotary-encoder) and map/combine them to various functions (play, pause, volume, next ...)
@@ -53,6 +55,8 @@ The esp32 must run at 240 MHz, with Quad-SPI I/O at 80 MHz and a clock of 40 Mhz
In 16 bits mode, although 192 kHz is reported as max rate, it's highly recommended to limit reported sampling rate to 96k (-Z 96000). Note that some high-speed 24/96k on-line streams might stutter because of TCP/IP stack performances. It is usually due to the fact that the server sends small packets of data and the esp32 cannot receive encoded audio fast enough, regardless of task priority settings (I've tried to tweak that a fair bit). The best option in that case is to let LMS proxy the stream as it will provide larger chunks and a "smoother" stream that can then be handled.
Note as well that some codecs consume more CPU than others or have not been optimized as much. I've done my best to tweak these, but that level of optimization includes writing some assembly which is painful. One very demanding codec is AAC when files are encoded with SBR. It allows reconstruction of upper part of spectrum and thus higher sampling rate, but the codec spec is such that this is optional, you can decode simply lower band and accept lower sampling rate - See the AAC_DISABLE_SBR option below.
## Installation
To get started, you'll need to perform an initial flash on one of the supported devices (see below) using a usb cable or a serial adapter, depending if your device came with one or if it didn't. The easiest way is to use the [esp-web-tool based installer](https://sle118.github.io/squeezelite-esp32-installer/), which is kept up to date with the latest builds we do. After you've completed this step, all subsequent updates will be done from our built-in web interface.
## Supported Hardware
Any esp32-based hardware with at least 4MB of flash and 4MB of PSRAM will be capable of running squeezelite-esp32 and there are various boards that include such chip. A few are mentionned below, but any should work. You can find various help & instructions [here](https://forums.slimdevices.com/showthread.php?112697-ANNOUNCE-Squeezelite-ESP32-(dedicated-thread))

View File

@@ -1,11 +1,16 @@
# the JPEG library is in ROM but seems to fail randomly (PSRAM issue?)
set(TJPGD tjpgd)
idf_component_register(SRC_DIRS . core core/ifaces fonts
INCLUDE_DIRS . fonts core
REQUIRES platform_config tools esp_common
PRIV_REQUIRES services freertos driver
EMBED_FILES note.jpg
)
PRIV_REQUIRES services freertos driver ${TJPGD}
EMBED_FILES note.jpg )
if (NOT TJPGD)
add_compile_definitions(TJPGD_ROM)
endif()
set_source_files_properties(display.c
PROPERTIES COMPILE_FLAGS
-Wno-format-overflow
)
-Wno-format-overflow )

View File

@@ -87,7 +87,7 @@ static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
Device->WriteCommand( Device, Contrast );
}
static void SPIParams(int Speed, uint8_t *mode, uint16_t *CS_pre, uint8_t *CS_post) {
static void SPIParams(int Speed, uint8_t *mode, uint8_t *CS_pre, uint8_t *CS_post) {
*CS_post = Speed / (8*1000*1000);
}

View File

@@ -8,7 +8,11 @@
#include <string.h>
#include "math.h"
#ifdef TJPGD_ROM
#include "esp32/rom/tjpgd.h"
#else
#include "tjpgd.h"
#endif
#include "esp_log.h"
#include "gds.h"
@@ -142,7 +146,7 @@ static unsigned OutHandlerDirect(JDEC *Decoder, void *Bitmap, JRECT *Frame) {
JpegCtx *Context = (JpegCtx*) Decoder->device;
uint8_t *Pixels = (uint8_t*) Bitmap;
int Shift = 8 - Context->Depth;
// decoded image is RGB888, shift only make sense for grayscale
if (Context->Mode == GDS_RGB888) {
OUTHANDLERDIRECT(Scaler888, 0);

View File

@@ -78,9 +78,8 @@ static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) {
vTaskDelay(100 / portTICK_PERIOD_MS);
// enable the PLL from BCLK source
adac_write_word(AC101_ADDR, PLL_CTRL1, BIN(0000,0001,0100,1111)); // F=1,M=1,PLL,INT=31 (medium)
adac_write_word(AC101_ADDR, PLL_CTRL2, BIN(1000,0110,0000,0000)); // PLL, F=96,N_i=1024-96,F=0,N_f=0*0.2;
// adac_write_word(AC101_ADDR, PLL_CTRL2, BIN(1000,0011,1100,0000));
adac_write_word(AC101_ADDR, PLL_CTRL1, BIN(0000,0001,0100,1111)); // F=1,M=1,PLL,INT=31 (medium)
adac_write_word(AC101_ADDR, PLL_CTRL2, BIN(1000,0010,0000,0000)); // PLL,N_i=64,N_f=0*0.2
// clocking system
adac_write_word(AC101_ADDR, SYSCLK_CTRL, BIN(1010,1010,0000,1000)); // PLLCLK, BCLK1, IS1CLK, PLL, SYSCLK
@@ -90,10 +89,10 @@ static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) {
// analogue config
#if BYTES_PER_FRAME == 8
adac_write_word(AC101_ADDR, I2S1LCK_CTRL, BIN(1000,1000,0111,0000)); // Slave, BCLK=I2S/8,LRCK=32,24bits,I2Smode, Stereo
i2s_config->bits_per_sample = 24;
// although it's 24 bits only, leave i2c_config.bits_per_sample at 32, DAC will only use what's needed
adac_write_word(AC101_ADDR, I2S1LCK_CTRL, BIN(1000,1000,1011,0000)); // Slave, BCLK=I2S/8,LRCK=64,24bits,I2Smode,Stereo
#else
adac_write_word(AC101_ADDR, I2S1LCK_CTRL, BIN(1000,1000,0101,0000)); // Slave, BCLK=I2S/8,LRCK=32,16bits,I2Smode, Stereo
adac_write_word(AC101_ADDR, I2S1LCK_CTRL, BIN(1000,1000,0101,0000)); // Slave, BCLK=I2S/8,LRCK=32,16bits,I2Smode,Stereo
#endif
adac_write_word(AC101_ADDR, I2S1_SDOUT_CTRL, BIN(1100,0000,0000,0000)); // I2S1ADC (R&L)
adac_write_word(AC101_ADDR, I2S1_SDIN_CTRL, BIN(1100,0000,0000,0000)); // IS21DAC (R&L)

View File

@@ -848,7 +848,7 @@ static void grfa_handler(u8_t *data, int len) {
} else if (artwork.size) GDS_ClearWindow(display, artwork.x, artwork.y, -1, -1, GDS_COLOR_BLACK);
artwork.full = artwork.enable && artwork.x == 0 && artwork.y == 0;
LOG_INFO("gfra en:%u x:%hu, y:%hu", artwork.enable, artwork.x, artwork.y);
LOG_DEBUG("gfra en:%u x:%hu, y:%hu", artwork.enable, artwork.x, artwork.y);
// done in any case
return;
@@ -867,7 +867,7 @@ static void grfa_handler(u8_t *data, int len) {
artwork.y = htons(pkt->y);
artwork.full = artwork.enable && artwork.x == 0 && artwork.y == 0;
if (artwork.data) free(artwork.data);
artwork.data = malloc(length);
artwork.data = malloc(length);
}
// copy artwork data
@@ -875,14 +875,14 @@ static void grfa_handler(u8_t *data, int len) {
artwork.size += size;
if (artwork.size == length) {
GDS_ClearWindow(display, artwork.x, artwork.y, -1, -1, GDS_COLOR_BLACK);
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
GDS_DrawJPEG(display, artwork.data, artwork.x, artwork.y, artwork.y < displayer.height ? (GDS_IMAGE_RIGHT | GDS_IMAGE_TOP) : GDS_IMAGE_CENTER);
xSemaphoreGive(displayer.mutex);
xSemaphoreGive(displayer.mutex);
free(artwork.data);
artwork.data = NULL;
}
LOG_INFO("gfra l:%u x:%hu, y:%hu, o:%u s:%u", length, artwork.x, artwork.y, offset, size);
LOG_DEBUG("gfra l:%u x:%hu, y:%hu, o:%u s:%u", length, artwork.x, artwork.y, offset, size);
}
/****************************************************************************************
@@ -1302,7 +1302,7 @@ static void displayer_task(void *args) {
// need to make sure we own display
if (display && displayer.owned) GDS_Update(display);
else if (!led_display) displayer.wake = LONG_WAKE;
// release semaphore and sleep what's needed
xSemaphoreGive(displayer.mutex);

View File

@@ -40,7 +40,13 @@ static struct {
{ "es8388", true,
"{\"init\":[ \
{\"reg\":8,\"val\":0}, {\"reg\":2,\"val\":243}, {\"reg\":43,\"val\":128}, {\"reg\":0,\"val\":5}, \
{\"reg\":1,\"val\":64}, {\"reg\":4,\"val\":60}, {\"reg\":23,\"val\":24}, {\"reg\":24,\"val\":2}, \
{\"reg\":1,\"val\":64}, {\"reg\":4,\"val\":60},"
#if BYTES_PER_FRAME == 8
"{\"reg\":23,\"val\":32},"
#else
"{\"reg\":23,\"val\":24},"
#endif
"{\"reg\":24,\"val\":2}, \
{\"reg\":26,\"val\":0}, {\"reg\":27,\"val\":0}, {\"reg\":25,\"val\":50}, {\"reg\":38,\"val\":0}, \
{\"reg\":39,\"val\":184}, {\"reg\":42,\"val\":184}, {\"reg\":46,\"val\":30}, {\"reg\":47,\"val\":30}, \
{\"reg\":48,\"val\":30}, {\"reg\":49,\"val\":30}, {\"reg\":2,\"val\":170}]}" },

View File

@@ -0,0 +1,3 @@
idf_component_register( SRC_DIRS .
INCLUDE_DIRS .
)

968
components/tjpgd/tjpgd.c Normal file
View File

@@ -0,0 +1,968 @@
/*----------------------------------------------------------------------------/
/ TJpgDec - Tiny JPEG Decompressor R0.01b (C)ChaN, 2012
/-----------------------------------------------------------------------------/
/ The TJpgDec is a generic JPEG decompressor module for tiny embedded systems.
/ This is a free software that opened for education, research and commercial
/ developments under license policy of following terms.
/
/ Copyright (C) 2012, ChaN, all right reserved.
/
/ * The TJpgDec module is a free software and there is NO WARRANTY.
/ * No restriction on use. You can use, modify and redistribute it for
/ personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY.
/ * Redistributions of source code must retain the above copyright notice.
/
/-----------------------------------------------------------------------------/
/ Oct 04,'11 R0.01 First release.
/ Feb 19,'12 R0.01a Fixed decompression fails when scan starts with an escape seq.
/ Sep 03,'12 R0.01b Added JD_TBLCLIP option.
/----------------------------------------------------------------------------*/
#include "tjpgd.h"
#define SUPPORT_JPEG 1
#ifdef SUPPORT_JPEG
/*-----------------------------------------------*/
/* Zigzag-order to raster-order conversion table */
/*-----------------------------------------------*/
#define ZIG(n) Zig[n]
static
const BYTE Zig[64] = { /* Zigzag-order to raster-order conversion table */
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5,
12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28,
35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63
};
/*-------------------------------------------------*/
/* Input scale factor of Arai algorithm */
/* (scaled up 16 bits for fixed point operations) */
/*-------------------------------------------------*/
#define IPSF(n) Ipsf[n]
static
const WORD Ipsf[64] = { /* See also aa_idct.png */
(WORD)(1.00000*8192), (WORD)(1.38704*8192), (WORD)(1.30656*8192), (WORD)(1.17588*8192), (WORD)(1.00000*8192), (WORD)(0.78570*8192), (WORD)(0.54120*8192), (WORD)(0.27590*8192),
(WORD)(1.38704*8192), (WORD)(1.92388*8192), (WORD)(1.81226*8192), (WORD)(1.63099*8192), (WORD)(1.38704*8192), (WORD)(1.08979*8192), (WORD)(0.75066*8192), (WORD)(0.38268*8192),
(WORD)(1.30656*8192), (WORD)(1.81226*8192), (WORD)(1.70711*8192), (WORD)(1.53636*8192), (WORD)(1.30656*8192), (WORD)(1.02656*8192), (WORD)(0.70711*8192), (WORD)(0.36048*8192),
(WORD)(1.17588*8192), (WORD)(1.63099*8192), (WORD)(1.53636*8192), (WORD)(1.38268*8192), (WORD)(1.17588*8192), (WORD)(0.92388*8192), (WORD)(0.63638*8192), (WORD)(0.32442*8192),
(WORD)(1.00000*8192), (WORD)(1.38704*8192), (WORD)(1.30656*8192), (WORD)(1.17588*8192), (WORD)(1.00000*8192), (WORD)(0.78570*8192), (WORD)(0.54120*8192), (WORD)(0.27590*8192),
(WORD)(0.78570*8192), (WORD)(1.08979*8192), (WORD)(1.02656*8192), (WORD)(0.92388*8192), (WORD)(0.78570*8192), (WORD)(0.61732*8192), (WORD)(0.42522*8192), (WORD)(0.21677*8192),
(WORD)(0.54120*8192), (WORD)(0.75066*8192), (WORD)(0.70711*8192), (WORD)(0.63638*8192), (WORD)(0.54120*8192), (WORD)(0.42522*8192), (WORD)(0.29290*8192), (WORD)(0.14932*8192),
(WORD)(0.27590*8192), (WORD)(0.38268*8192), (WORD)(0.36048*8192), (WORD)(0.32442*8192), (WORD)(0.27590*8192), (WORD)(0.21678*8192), (WORD)(0.14932*8192), (WORD)(0.07612*8192)
};
/*---------------------------------------------*/
/* Conversion table for fast clipping process */
/*---------------------------------------------*/
#if JD_TBLCLIP
#define BYTECLIP(v) Clip8[(UINT)(v) & 0x3FF]
static
const BYTE Clip8[1024] = {
/* 0..255 */
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191,
192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,
/* 256..511 */
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
/* -512..-257 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* -256..-1 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
#else /* JD_TBLCLIP */
inline
BYTE BYTECLIP (
INT val
)
{
if (val < 0) val = 0;
if (val > 255) val = 255;
return (BYTE)val;
}
#endif
/*-----------------------------------------------------------------------*/
/* Allocate a memory block from memory pool */
/*-----------------------------------------------------------------------*/
static
void* alloc_pool ( /* Pointer to allocated memory block (NULL:no memory available) */
JDEC* jd, /* Pointer to the decompressor object */
UINT nd /* Number of bytes to allocate */
)
{
char *rp = 0;
nd = (nd + 3) & ~3; /* Align block size to the word boundary */
if (jd->sz_pool >= nd) {
jd->sz_pool -= nd;
rp = (char*)jd->pool; /* Get start of available memory pool */
jd->pool = (void*)(rp + nd); /* Allocate requierd bytes */
}
return (void*)rp; /* Return allocated memory block (NULL:no memory to allocate) */
}
/*-----------------------------------------------------------------------*/
/* Create de-quantization and prescaling tables with a DQT segment */
/*-----------------------------------------------------------------------*/
static
UINT create_qt_tbl ( /* 0:OK, !0:Failed */
JDEC* jd, /* Pointer to the decompressor object */
const BYTE* data, /* Pointer to the quantizer tables */
UINT ndata /* Size of input data */
)
{
UINT i;
BYTE d, z;
LONG *pb;
while (ndata) { /* Process all tables in the segment */
if (ndata < 65) return JDR_FMT1; /* Err: table size is unaligned */
ndata -= 65;
d = *data++; /* Get table property */
if (d & 0xF0) return JDR_FMT1; /* Err: not 8-bit resolution */
i = d & 3; /* Get table ID */
pb = alloc_pool(jd, 64 * sizeof (LONG));/* Allocate a memory block for the table */
if (!pb) return JDR_MEM1; /* Err: not enough memory */
jd->qttbl[i] = pb; /* Register the table */
for (i = 0; i < 64; i++) { /* Load the table */
z = ZIG(i); /* Zigzag-order to raster-order conversion */
pb[z] = (LONG)((DWORD)*data++ * IPSF(z)); /* Apply scale factor of Arai algorithm to the de-quantizers */
}
}
return JDR_OK;
}
/*-----------------------------------------------------------------------*/
/* Create huffman code tables with a DHT segment */
/*-----------------------------------------------------------------------*/
static
UINT create_huffman_tbl ( /* 0:OK, !0:Failed */
JDEC* jd, /* Pointer to the decompressor object */
const BYTE* data, /* Pointer to the packed huffman tables */
UINT ndata /* Size of input data */
)
{
UINT i, j, b, np, cls, num;
BYTE d, *pb, *pd;
WORD hc, *ph;
while (ndata) { /* Process all tables in the segment */
if (ndata < 17) return JDR_FMT1; /* Err: wrong data size */
ndata -= 17;
d = *data++; /* Get table number and class */
cls = (d >> 4); num = d & 0x0F; /* class = dc(0)/ac(1), table number = 0/1 */
if (d & 0xEE) return JDR_FMT1; /* Err: invalid class/number */
pb = alloc_pool(jd, 16); /* Allocate a memory block for the bit distribution table */
if (!pb) return JDR_MEM1; /* Err: not enough memory */
jd->huffbits[num][cls] = pb;
for (np = i = 0; i < 16; i++) { /* Load number of patterns for 1 to 16-bit code */
pb[i] = b = *data++;
np += b; /* Get sum of code words for each code */
}
ph = alloc_pool(jd, np * sizeof (WORD));/* Allocate a memory block for the code word table */
if (!ph) return JDR_MEM1; /* Err: not enough memory */
jd->huffcode[num][cls] = ph;
hc = 0;
for (j = i = 0; i < 16; i++) { /* Re-build huffman code word table */
b = pb[i];
while (b--) ph[j++] = hc++;
hc <<= 1;
}
if (ndata < np) return JDR_FMT1; /* Err: wrong data size */
ndata -= np;
pd = alloc_pool(jd, np); /* Allocate a memory block for the decoded data */
if (!pd) return JDR_MEM1; /* Err: not enough memory */
jd->huffdata[num][cls] = pd;
for (i = 0; i < np; i++) { /* Load decoded data corresponds to each code ward */
d = *data++;
if (!cls && d > 11) return JDR_FMT1;
*pd++ = d;
}
}
return JDR_OK;
}
/*-----------------------------------------------------------------------*/
/* Extract N bits from input stream */
/*-----------------------------------------------------------------------*/
static
INT bitext ( /* >=0: extracted data, <0: error code */
JDEC* jd, /* Pointer to the decompressor object */
UINT nbit /* Number of bits to extract (1 to 11) */
)
{
BYTE msk, s, *dp;
UINT dc, v, f;
msk = jd->dmsk; dc = jd->dctr; dp = jd->dptr; /* Bit mask, number of data available, read ptr */
s = *dp; v = f = 0;
do {
if (!msk) { /* Next byte? */
if (!dc) { /* No input data is available, re-fill input buffer */
dp = jd->inbuf; /* Top of input buffer */
dc = jd->infunc(jd, dp, JD_SZBUF);
if (!dc) return 0 - (INT)JDR_INP; /* Err: read error or wrong stream termination */
} else {
dp++; /* Next data ptr */
}
dc--; /* Decrement number of available bytes */
if (f) { /* In flag sequence? */
f = 0; /* Exit flag sequence */
if (*dp != 0) return 0 - (INT)JDR_FMT1; /* Err: unexpected flag is detected (may be collapted data) */
*dp = s = 0xFF; /* The flag is a data 0xFF */
} else {
s = *dp; /* Get next data byte */
if (s == 0xFF) { /* Is start of flag sequence? */
f = 1; continue; /* Enter flag sequence */
}
}
msk = 0x80; /* Read from MSB */
}
v <<= 1; /* Get a bit */
if (s & msk) v++;
msk >>= 1;
nbit--;
} while (nbit);
jd->dmsk = msk; jd->dctr = dc; jd->dptr = dp;
return (INT)v;
}
/*-----------------------------------------------------------------------*/
/* Extract a huffman decoded data from input stream */
/*-----------------------------------------------------------------------*/
static
INT huffext ( /* >=0: decoded data, <0: error code */
JDEC* jd, /* Pointer to the decompressor object */
const BYTE* hbits, /* Pointer to the bit distribution table */
const WORD* hcode, /* Pointer to the code word table */
const BYTE* hdata /* Pointer to the data table */
)
{
BYTE msk, s, *dp;
UINT dc, v, f, bl, nd;
msk = jd->dmsk; dc = jd->dctr; dp = jd->dptr; /* Bit mask, number of data available, read ptr */
s = *dp; v = f = 0;
bl = 16; /* Max code length */
do {
if (!msk) { /* Next byte? */
if (!dc) { /* No input data is available, re-fill input buffer */
dp = jd->inbuf; /* Top of input buffer */
dc = jd->infunc(jd, dp, JD_SZBUF);
if (!dc) return 0 - (INT)JDR_INP; /* Err: read error or wrong stream termination */
} else {
dp++; /* Next data ptr */
}
dc--; /* Decrement number of available bytes */
if (f) { /* In flag sequence? */
f = 0; /* Exit flag sequence */
if (*dp != 0)
return 0 - (INT)JDR_FMT1; /* Err: unexpected flag is detected (may be collapted data) */
*dp = s = 0xFF; /* The flag is a data 0xFF */
} else {
s = *dp; /* Get next data byte */
if (s == 0xFF) { /* Is start of flag sequence? */
f = 1; continue; /* Enter flag sequence, get trailing byte */
}
}
msk = 0x80; /* Read from MSB */
}
v <<= 1; /* Get a bit */
if (s & msk) v++;
msk >>= 1;
for (nd = *hbits++; nd; nd--) { /* Search the code word in this bit length */
if (v == *hcode++) { /* Matched? */
jd->dmsk = msk; jd->dctr = dc; jd->dptr = dp;
return *hdata; /* Return the decoded data */
}
hdata++;
}
bl--;
} while (bl);
return 0 - (INT)JDR_FMT1; /* Err: code not found (may be collapted data) */
}
/*-----------------------------------------------------------------------*/
/* Apply Inverse-DCT in Arai Algorithm (see also aa_idct.png) */
/*-----------------------------------------------------------------------*/
static
void block_idct (
LONG* src, /* Input block data (de-quantized and pre-scaled for Arai Algorithm) */
BYTE* dst /* Pointer to the destination to store the block as byte array */
)
{
const LONG M13 = (LONG)(1.41421*4096), M2 = (LONG)(1.08239*4096), M4 = (LONG)(2.61313*4096), M5 = (LONG)(1.84776*4096);
LONG v0, v1, v2, v3, v4, v5, v6, v7;
LONG t10, t11, t12, t13;
UINT i;
/* Process columns */
for (i = 0; i < 8; i++) {
v0 = src[8 * 0]; /* Get even elements */
v1 = src[8 * 2];
v2 = src[8 * 4];
v3 = src[8 * 6];
t10 = v0 + v2; /* Process the even elements */
t12 = v0 - v2;
t11 = (v1 - v3) * M13 >> 12;
v3 += v1;
t11 -= v3;
v0 = t10 + v3;
v3 = t10 - v3;
v1 = t11 + t12;
v2 = t12 - t11;
v4 = src[8 * 7]; /* Get odd elements */
v5 = src[8 * 1];
v6 = src[8 * 5];
v7 = src[8 * 3];
t10 = v5 - v4; /* Process the odd elements */
t11 = v5 + v4;
t12 = v6 - v7;
v7 += v6;
v5 = (t11 - v7) * M13 >> 12;
v7 += t11;
t13 = (t10 + t12) * M5 >> 12;
v4 = t13 - (t10 * M2 >> 12);
v6 = t13 - (t12 * M4 >> 12) - v7;
v5 -= v6;
v4 -= v5;
src[8 * 0] = v0 + v7; /* Write-back transformed values */
src[8 * 7] = v0 - v7;
src[8 * 1] = v1 + v6;
src[8 * 6] = v1 - v6;
src[8 * 2] = v2 + v5;
src[8 * 5] = v2 - v5;
src[8 * 3] = v3 + v4;
src[8 * 4] = v3 - v4;
src++; /* Next column */
}
/* Process rows */
src -= 8;
for (i = 0; i < 8; i++) {
v0 = src[0] + (128L << 8); /* Get even elements (remove DC offset (-128) here) */
v1 = src[2];
v2 = src[4];
v3 = src[6];
t10 = v0 + v2; /* Process the even elements */
t12 = v0 - v2;
t11 = (v1 - v3) * M13 >> 12;
v3 += v1;
t11 -= v3;
v0 = t10 + v3;
v3 = t10 - v3;
v1 = t11 + t12;
v2 = t12 - t11;
v4 = src[7]; /* Get odd elements */
v5 = src[1];
v6 = src[5];
v7 = src[3];
t10 = v5 - v4; /* Process the odd elements */
t11 = v5 + v4;
t12 = v6 - v7;
v7 += v6;
v5 = (t11 - v7) * M13 >> 12;
v7 += t11;
t13 = (t10 + t12) * M5 >> 12;
v4 = t13 - (t10 * M2 >> 12);
v6 = t13 - (t12 * M4 >> 12) - v7;
v5 -= v6;
v4 -= v5;
dst[0] = BYTECLIP((v0 + v7) >> 8); /* Descale the transformed values 8 bits and output */
dst[7] = BYTECLIP((v0 - v7) >> 8);
dst[1] = BYTECLIP((v1 + v6) >> 8);
dst[6] = BYTECLIP((v1 - v6) >> 8);
dst[2] = BYTECLIP((v2 + v5) >> 8);
dst[5] = BYTECLIP((v2 - v5) >> 8);
dst[3] = BYTECLIP((v3 + v4) >> 8);
dst[4] = BYTECLIP((v3 - v4) >> 8);
dst += 8;
src += 8; /* Next row */
}
}
/*-----------------------------------------------------------------------*/
/* Load all blocks in the MCU into working buffer */
/*-----------------------------------------------------------------------*/
static
JRESULT mcu_load (
JDEC* jd /* Pointer to the decompressor object */
)
{
LONG *tmp = (LONG*)jd->workbuf; /* Block working buffer for de-quantize and IDCT */
UINT blk, nby, nbc, i, z, id, cmp;
INT b, d, e;
BYTE *bp;
const BYTE *hb, *hd;
const WORD *hc;
const LONG *dqf;
nby = jd->msx * jd->msy; /* Number of Y blocks (1, 2 or 4) */
nbc = 2; /* Number of C blocks (2) */
bp = jd->mcubuf; /* Pointer to the first block */
for (blk = 0; blk < nby + nbc; blk++) {
cmp = (blk < nby) ? 0 : blk - nby + 1; /* Component number 0:Y, 1:Cb, 2:Cr */
id = cmp ? 1 : 0; /* Huffman table ID of the component */
/* Extract a DC element from input stream */
hb = jd->huffbits[id][0]; /* Huffman table for the DC element */
hc = jd->huffcode[id][0];
hd = jd->huffdata[id][0];
b = huffext(jd, hb, hc, hd); /* Extract a huffman coded data (bit length) */
if (b < 0) return 0 - b; /* Err: invalid code or input */
d = jd->dcv[cmp]; /* DC value of previous block */
if (b) { /* If there is any difference from previous block */
e = bitext(jd, b); /* Extract data bits */
if (e < 0) return 0 - e; /* Err: input */
b = 1 << (b - 1); /* MSB position */
if (!(e & b)) e -= (b << 1) - 1; /* Restore sign if needed */
d += e; /* Get current value */
jd->dcv[cmp] = (SHORT)d; /* Save current DC value for next block */
}
dqf = jd->qttbl[jd->qtid[cmp]]; /* De-quantizer table ID for this component */
tmp[0] = d * dqf[0] >> 8; /* De-quantize, apply scale factor of Arai algorithm and descale 8 bits */
/* Extract following 63 AC elements from input stream */
for (i = 1; i < 64; i++) tmp[i] = 0; /* Clear rest of elements */
hb = jd->huffbits[id][1]; /* Huffman table for the AC elements */
hc = jd->huffcode[id][1];
hd = jd->huffdata[id][1];
i = 1; /* Top of the AC elements */
do {
b = huffext(jd, hb, hc, hd); /* Extract a huffman coded value (zero runs and bit length) */
if (b == 0) break; /* EOB? */
if (b < 0) return 0 - b; /* Err: invalid code or input error */
z = (UINT)b >> 4; /* Number of leading zero elements */
if (z) {
i += z; /* Skip zero elements */
if (i >= 64) return JDR_FMT1; /* Too long zero run */
}
if (b &= 0x0F) { /* Bit length */
d = bitext(jd, b); /* Extract data bits */
if (d < 0) return 0 - d; /* Err: input device */
b = 1 << (b - 1); /* MSB position */
if (!(d & b)) d -= (b << 1) - 1;/* Restore negative value if needed */
z = ZIG(i); /* Zigzag-order to raster-order converted index */
tmp[z] = d * dqf[z] >> 8; /* De-quantize, apply scale factor of Arai algorithm and descale 8 bits */
}
} while (++i < 64); /* Next AC element */
if (JD_USE_SCALE && jd->scale == 3)
*bp = (*tmp / 256) + 128; /* If scale ratio is 1/8, IDCT can be ommited and only DC element is used */
else
block_idct(tmp, bp); /* Apply IDCT and store the block to the MCU buffer */
bp += 64; /* Next block */
}
return JDR_OK; /* All blocks have been loaded successfully */
}
/*-----------------------------------------------------------------------*/
/* Output an MCU: Convert YCrCb to RGB and output it in RGB form */
/*-----------------------------------------------------------------------*/
static
JRESULT mcu_output (
JDEC* jd, /* Pointer to the decompressor object */
UINT (*outfunc)(JDEC*, void*, JRECT*), /* RGB output function */
UINT x, /* MCU position in the image (left of the MCU) */
UINT y /* MCU position in the image (top of the MCU) */
)
{
const INT CVACC = (sizeof (INT) > 2) ? 1024 : 128;
UINT ix, iy, mx, my, rx, ry;
INT yy, cb, cr;
BYTE *py, *pc, *rgb24;
JRECT rect;
mx = jd->msx * 8; my = jd->msy * 8; /* MCU size (pixel) */
rx = (x + mx <= jd->width) ? mx : jd->width - x; /* Output rectangular size (it may be clipped at right/bottom end) */
ry = (y + my <= jd->height) ? my : jd->height - y;
if (JD_USE_SCALE) {
rx >>= jd->scale; ry >>= jd->scale;
if (!rx || !ry) return JDR_OK; /* Skip this MCU if all pixel is to be rounded off */
x >>= jd->scale; y >>= jd->scale;
}
rect.left = x; rect.right = x + rx - 1; /* Rectangular area in the frame buffer */
rect.top = y; rect.bottom = y + ry - 1;
if (!JD_USE_SCALE || jd->scale != 3) { /* Not for 1/8 scaling */
/* Build an RGB MCU from discrete comopnents */
rgb24 = (BYTE*)jd->workbuf;
for (iy = 0; iy < my; iy++) {
pc = jd->mcubuf;
py = pc + iy * 8;
if (my == 16) { /* Double block height? */
pc += 64 * 4 + (iy >> 1) * 8;
if (iy >= 8) py += 64;
} else { /* Single block height */
pc += mx * 8 + iy * 8;
}
for (ix = 0; ix < mx; ix++) {
cb = pc[0] - 128; /* Get Cb/Cr component and restore right level */
cr = pc[64] - 128;
if (mx == 16) { /* Double block width? */
if (ix == 8) py += 64 - 8; /* Jump to next block if double block heigt */
pc += ix & 1; /* Increase chroma pointer every two pixels */
} else { /* Single block width */
pc++; /* Increase chroma pointer every pixel */
}
yy = *py++; /* Get Y component */
/* Convert YCbCr to RGB */
*rgb24++ = /* R */ BYTECLIP(yy + ((INT)(1.402 * CVACC) * cr) / CVACC);
*rgb24++ = /* G */ BYTECLIP(yy - ((INT)(0.344 * CVACC) * cb + (INT)(0.714 * CVACC) * cr) / CVACC);
*rgb24++ = /* B */ BYTECLIP(yy + ((INT)(1.772 * CVACC) * cb) / CVACC);
}
}
/* Descale the MCU rectangular if needed */
if (JD_USE_SCALE && jd->scale) {
UINT x, y, r, g, b, s, w, a;
BYTE *op;
/* Get averaged RGB value of each square correcponds to a pixel */
s = jd->scale * 2; /* Bumber of shifts for averaging */
w = 1 << jd->scale; /* Width of square */
a = (mx - w) * 3; /* Bytes to skip for next line in the square */
op = (BYTE*)jd->workbuf;
for (iy = 0; iy < my; iy += w) {
for (ix = 0; ix < mx; ix += w) {
rgb24 = (BYTE*)jd->workbuf + (iy * mx + ix) * 3;
r = g = b = 0;
for (y = 0; y < w; y++) { /* Accumulate RGB value in the square */
for (x = 0; x < w; x++) {
r += *rgb24++;
g += *rgb24++;
b += *rgb24++;
}
rgb24 += a;
} /* Put the averaged RGB value as a pixel */
*op++ = (BYTE)(r >> s);
*op++ = (BYTE)(g >> s);
*op++ = (BYTE)(b >> s);
}
}
}
} else { /* For only 1/8 scaling (left-top pixel in each block are the DC value of the block) */
/* Build a 1/8 descaled RGB MCU from discrete comopnents */
rgb24 = (BYTE*)jd->workbuf;
pc = jd->mcubuf + mx * my;
cb = pc[0] - 128; /* Get Cb/Cr component and restore right level */
cr = pc[64] - 128;
for (iy = 0; iy < my; iy += 8) {
py = jd->mcubuf;
if (iy == 8) py += 64 * 2;
for (ix = 0; ix < mx; ix += 8) {
yy = *py; /* Get Y component */
py += 64;
/* Convert YCbCr to RGB */
*rgb24++ = /* R */ BYTECLIP(yy + ((INT)(1.402 * CVACC) * cr / CVACC));
*rgb24++ = /* G */ BYTECLIP(yy - ((INT)(0.344 * CVACC) * cb + (INT)(0.714 * CVACC) * cr) / CVACC);
*rgb24++ = /* B */ BYTECLIP(yy + ((INT)(1.772 * CVACC) * cb / CVACC));
}
}
}
/* Squeeze up pixel table if a part of MCU is to be truncated */
mx >>= jd->scale;
if (rx < mx) {
BYTE *s, *d;
UINT x, y;
s = d = (BYTE*)jd->workbuf;
for (y = 0; y < ry; y++) {
for (x = 0; x < rx; x++) { /* Copy effective pixels */
*d++ = *s++;
*d++ = *s++;
*d++ = *s++;
}
s += (mx - rx) * 3; /* Skip truncated pixels */
}
}
/* Convert RGB888 to RGB565 if needed */
if (JD_FORMAT == 1) {
BYTE *s = (BYTE*)jd->workbuf;
WORD w, *d = (WORD*)s;
UINT n = rx * ry;
do {
w = (*s++ & 0xF8) << 8; /* RRRRR----------- */
w |= (*s++ & 0xFC) << 3; /* -----GGGGGG----- */
w |= *s++ >> 3; /* -----------BBBBB */
*d++ = w;
} while (--n);
}
/* Output the RGB rectangular */
return outfunc(jd, jd->workbuf, &rect) ? JDR_OK : JDR_INTR;
}
/*-----------------------------------------------------------------------*/
/* Process restart interval */
/*-----------------------------------------------------------------------*/
static
JRESULT restart (
JDEC* jd, /* Pointer to the decompressor object */
WORD rstn /* Expected restert sequense number */
)
{
UINT i, dc;
WORD d;
BYTE *dp;
/* Discard padding bits and get two bytes from the input stream */
dp = jd->dptr; dc = jd->dctr;
d = 0;
for (i = 0; i < 2; i++) {
if (!dc) { /* No input data is available, re-fill input buffer */
dp = jd->inbuf;
dc = jd->infunc(jd, dp, JD_SZBUF);
if (!dc) return JDR_INP;
} else {
dp++;
}
dc--;
d = (d << 8) | *dp; /* Get a byte */
}
jd->dptr = dp; jd->dctr = dc; jd->dmsk = 0;
/* Check the marker */
if ((d & 0xFFD8) != 0xFFD0 || (d & 7) != (rstn & 7))
return JDR_FMT1; /* Err: expected RSTn marker is not detected (may be collapted data) */
/* Reset DC offset */
jd->dcv[2] = jd->dcv[1] = jd->dcv[0] = 0;
return JDR_OK;
}
/*-----------------------------------------------------------------------*/
/* Analyze the JPEG image and Initialize decompressor object */
/*-----------------------------------------------------------------------*/
#define LDB_WORD(ptr) (WORD)(((WORD)*((BYTE*)(ptr))<<8)|(WORD)*(BYTE*)((ptr)+1))
JRESULT jd_prepare (
JDEC* jd, /* Blank decompressor object */
UINT (*infunc)(JDEC*, BYTE*, UINT), /* JPEG strem input function */
void* pool, /* Working buffer for the decompression session */
UINT sz_pool, /* Size of working buffer */
void* dev /* I/O device identifier for the session */
)
{
BYTE *seg, b;
WORD marker;
DWORD ofs;
UINT n, i, j, len;
JRESULT rc;
if (!pool) return JDR_PAR;
jd->pool = pool; /* Work memroy */
jd->sz_pool = sz_pool; /* Size of given work memory */
jd->infunc = infunc; /* Stream input function */
jd->device = dev; /* I/O device identifier */
jd->nrst = 0; /* No restart interval (default) */
for (i = 0; i < 2; i++) { /* Nulls pointers */
for (j = 0; j < 2; j++) {
jd->huffbits[i][j] = 0;
jd->huffcode[i][j] = 0;
jd->huffdata[i][j] = 0;
}
}
for (i = 0; i < 4; i++) jd->qttbl[i] = 0;
jd->inbuf = seg = alloc_pool(jd, JD_SZBUF); /* Allocate stream input buffer */
if (!seg) return JDR_MEM1;
if (jd->infunc(jd, seg, 2) != 2) return JDR_INP;/* Check SOI marker */
if (LDB_WORD(seg) != 0xFFD8) return JDR_FMT1; /* Err: SOI is not detected */
ofs = 2;
for (;;) {
/* Get a JPEG marker */
if (jd->infunc(jd, seg, 4) != 4) return JDR_INP;
marker = LDB_WORD(seg); /* Marker */
len = LDB_WORD(seg + 2); /* Length field */
if (len <= 2 || (marker >> 8) != 0xFF) return JDR_FMT1;
len -= 2; /* Content size excluding length field */
ofs += 4 + len; /* Number of bytes loaded */
switch (marker & 0xFF) {
case 0xC0: /* SOF0 (baseline JPEG) */
/* Load segment data */
if (len > JD_SZBUF) return JDR_MEM2;
if (jd->infunc(jd, seg, len) != len) return JDR_INP;
jd->width = LDB_WORD(seg+3); /* Image width in unit of pixel */
jd->height = LDB_WORD(seg+1); /* Image height in unit of pixel */
if (seg[5] != 3) return JDR_FMT3; /* Err: Supports only Y/Cb/Cr format */
/* Check three image components */
for (i = 0; i < 3; i++) {
b = seg[7 + 3 * i]; /* Get sampling factor */
if (!i) { /* Y component */
if (b != 0x11 && b != 0x22 && b != 0x21)/* Check sampling factor */
return JDR_FMT3; /* Err: Supports only 4:4:4, 4:2:0 or 4:2:2 */
jd->msx = b >> 4; jd->msy = b & 15; /* Size of MCU [blocks] */
} else { /* Cb/Cr component */
if (b != 0x11) return JDR_FMT3; /* Err: Sampling factor of Cr/Cb must be 1 */
}
b = seg[8 + 3 * i]; /* Get dequantizer table ID for this component */
if (b > 3) return JDR_FMT3; /* Err: Invalid ID */
jd->qtid[i] = b;
}
break;
case 0xDD: /* DRI */
/* Load segment data */
if (len > JD_SZBUF) return JDR_MEM2;
if (jd->infunc(jd, seg, len) != len) return JDR_INP;
/* Get restart interval (MCUs) */
jd->nrst = LDB_WORD(seg);
break;
case 0xC4: /* DHT */
/* Load segment data */
if (len > JD_SZBUF) return JDR_MEM2;
if (jd->infunc(jd, seg, len) != len) return JDR_INP;
/* Create huffman tables */
rc = create_huffman_tbl(jd, seg, len);
if (rc) return rc;
break;
case 0xDB: /* DQT */
/* Load segment data */
if (len > JD_SZBUF) return JDR_MEM2;
if (jd->infunc(jd, seg, len) != len) return JDR_INP;
/* Create de-quantizer tables */
rc = create_qt_tbl(jd, seg, len);
if (rc) return rc;
break;
case 0xDA: /* SOS */
/* Load segment data */
if (len > JD_SZBUF) return JDR_MEM2;
if (jd->infunc(jd, seg, len) != len) return JDR_INP;
if (!jd->width || !jd->height) return JDR_FMT1; /* Err: Invalid image size */
if (seg[0] != 3) return JDR_FMT3; /* Err: Supports only three color components format */
/* Check if all tables corresponding to each components have been loaded */
for (i = 0; i < 3; i++) {
b = seg[2 + 2 * i]; /* Get huffman table ID */
if (b != 0x00 && b != 0x11) return JDR_FMT3; /* Err: Different table number for DC/AC element */
b = i ? 1 : 0;
if (!jd->huffbits[b][0] || !jd->huffbits[b][1]) /* Check huffman table for this component */
return JDR_FMT1; /* Err: Huffman table not loaded */
if (!jd->qttbl[jd->qtid[i]]) return JDR_FMT1; /* Err: Dequantizer table not loaded */
}
/* Allocate working buffer for MCU and RGB */
n = jd->msy * jd->msx; /* Number of Y blocks in the MCU */
if (!n) return JDR_FMT1; /* Err: SOF0 has not been loaded */
len = n * 64 * 2 + 64; /* Allocate buffer for IDCT and RGB output */
if (len < 256) len = 256; /* but at least 256 byte is required for IDCT */
jd->workbuf = alloc_pool(jd, len); /* and it may occupy a part of following MCU working buffer for RGB output */
if (!jd->workbuf) return JDR_MEM1; /* Err: not enough memory */
jd->mcubuf = alloc_pool(jd, (n + 2) * 64); /* Allocate MCU working buffer */
if (!jd->mcubuf) return JDR_MEM1; /* Err: not enough memory */
/* Pre-load the JPEG data to extract it from the bit stream */
jd->dptr = seg; jd->dctr = 0; jd->dmsk = 0; /* Prepare to read bit stream */
if (ofs %= JD_SZBUF) { /* Align read offset to JD_SZBUF */
jd->dctr = jd->infunc(jd, seg + ofs, JD_SZBUF - (UINT)ofs);
jd->dptr = seg + ofs - 1;
}
return JDR_OK; /* Initialization succeeded. Ready to decompress the JPEG image. */
case 0xC1: /* SOF1 */
case 0xC2: /* SOF2 */
case 0xC3: /* SOF3 */
case 0xC5: /* SOF5 */
case 0xC6: /* SOF6 */
case 0xC7: /* SOF7 */
case 0xC9: /* SOF9 */
case 0xCA: /* SOF10 */
case 0xCB: /* SOF11 */
case 0xCD: /* SOF13 */
case 0xCE: /* SOF14 */
case 0xCF: /* SOF15 */
case 0xD9: /* EOI */
return JDR_FMT3; /* Unsuppoted JPEG standard (may be progressive JPEG) */
default: /* Unknown segment (comment, exif or etc..) */
/* Skip segment data */
if (jd->infunc(jd, 0, len) != len) /* Null pointer specifies to skip bytes of stream */
return JDR_INP;
}
}
}
/*-----------------------------------------------------------------------*/
/* Start to decompress the JPEG picture */
/*-----------------------------------------------------------------------*/
JRESULT jd_decomp (
JDEC* jd, /* Initialized decompression object */
UINT (*outfunc)(JDEC*, void*, JRECT*), /* RGB output function */
BYTE scale /* Output de-scaling factor (0 to 3) */
)
{
UINT x, y, mx, my;
WORD rst, rsc;
JRESULT rc;
if (scale > (JD_USE_SCALE ? 3 : 0)) return JDR_PAR;
jd->scale = scale;
mx = jd->msx * 8; my = jd->msy * 8; /* Size of the MCU (pixel) */
jd->dcv[2] = jd->dcv[1] = jd->dcv[0] = 0; /* Initialize DC values */
rst = rsc = 0;
rc = JDR_OK;
for (y = 0; y < jd->height; y += my) { /* Vertical loop of MCUs */
for (x = 0; x < jd->width; x += mx) { /* Horizontal loop of MCUs */
if (jd->nrst && rst++ == jd->nrst) { /* Process restart interval if enabled */
rc = restart(jd, rsc++);
if (rc != JDR_OK) return rc;
rst = 1;
}
rc = mcu_load(jd); /* Load an MCU (decompress huffman coded stream and apply IDCT) */
if (rc != JDR_OK) return rc;
rc = mcu_output(jd, outfunc, x, y); /* Output the MCU (color space conversion, scaling and output) */
if (rc != JDR_OK) return rc;
}
}
return rc;
}
#endif//SUPPORT_JPEG

99
components/tjpgd/tjpgd.h Normal file
View File

@@ -0,0 +1,99 @@
/*----------------------------------------------------------------------------/
/ TJpgDec - Tiny JPEG Decompressor include file (C)ChaN, 2012
/----------------------------------------------------------------------------*/
#ifndef _TJPGDEC
#define _TJPGDEC
/*---------------------------------------------------------------------------*/
/* System Configurations */
#define JD_SZBUF 512 /* Size of stream input buffer */
#define JD_FORMAT 0 /* Output pixel format 0:RGB888 (3 BYTE/pix), 1:RGB565 (1 WORD/pix) */
#define JD_USE_SCALE 1 /* Use descaling feature for output */
#define JD_TBLCLIP 1 /* Use table for saturation (might be a bit faster but increases 1K bytes of code size) */
/*---------------------------------------------------------------------------*/
#ifdef __cplusplus
extern "C" {
#endif
/* These types must be 16-bit, 32-bit or larger integer */
typedef int INT;
typedef unsigned int UINT;
/* These types must be 8-bit integer */
typedef char CHAR;
typedef unsigned char UCHAR;
typedef unsigned char BYTE;
/* These types must be 16-bit integer */
typedef short SHORT;
typedef unsigned short USHORT;
typedef unsigned short WORD;
typedef unsigned short WCHAR;
/* These types must be 32-bit integer */
typedef long LONG;
typedef unsigned long ULONG;
typedef unsigned long DWORD;
/* Error code */
typedef enum {
JDR_OK = 0, /* 0: Succeeded */
JDR_INTR, /* 1: Interrupted by output function */
JDR_INP, /* 2: Device error or wrong termination of input stream */
JDR_MEM1, /* 3: Insufficient memory pool for the image */
JDR_MEM2, /* 4: Insufficient stream input buffer */
JDR_PAR, /* 5: Parameter error */
JDR_FMT1, /* 6: Data format error (may be damaged data) */
JDR_FMT2, /* 7: Right format but not supported */
JDR_FMT3 /* 8: Not supported JPEG standard */
} JRESULT;
/* Rectangular structure */
typedef struct {
WORD left, right, top, bottom;
} JRECT;
/* Decompressor object structure */
typedef struct JDEC JDEC;
struct JDEC {
UINT dctr; /* Number of bytes available in the input buffer */
BYTE* dptr; /* Current data read ptr */
BYTE* inbuf; /* Bit stream input buffer */
BYTE dmsk; /* Current bit in the current read byte */
BYTE scale; /* Output scaling ratio */
BYTE msx, msy; /* MCU size in unit of block (width, height) */
BYTE qtid[3]; /* Quantization table ID of each component */
SHORT dcv[3]; /* Previous DC element of each component */
WORD nrst; /* Restart inverval */
UINT width, height; /* Size of the input image (pixel) */
BYTE* huffbits[2][2]; /* Huffman bit distribution tables [id][dcac] */
WORD* huffcode[2][2]; /* Huffman code word tables [id][dcac] */
BYTE* huffdata[2][2]; /* Huffman decoded data tables [id][dcac] */
LONG* qttbl[4]; /* Dequaitizer tables [id] */
void* workbuf; /* Working buffer for IDCT and RGB output */
BYTE* mcubuf; /* Working buffer for the MCU */
void* pool; /* Pointer to available memory pool */
UINT sz_pool; /* Size of momory pool (bytes available) */
UINT (*infunc)(JDEC*, BYTE*, UINT);/* Pointer to jpeg stream input function */
void* device; /* Pointer to I/O device identifiler for the session */
};
/* TJpgDec API functions */
JRESULT jd_prepare (JDEC*, UINT(*)(JDEC*,BYTE*,UINT), void*, UINT, void*);
JRESULT jd_decomp (JDEC*, UINT(*)(JDEC*,void*,JRECT*), BYTE);
#ifdef __cplusplus
}
#endif
#endif /* _TJPGDEC */

Binary file not shown.

View File

@@ -63,6 +63,9 @@ sub initFirmwareDownload {
$cb->() if $cb;
}
}
elsif ($cb) {
$cb->();
}
},
sub {
my ($http, $error) = @_;
@@ -119,10 +122,15 @@ sub prefetchFirmware {
$cb->($releaseInfo, _gh2lmsUrl($url), $customFwUrl) if $cb;
}
elsif ($cb) {
$cb->();
}
},
sub {
my ($http, $error) = @_;
$log->error("Failed to get releases from Github: $error");
$cb->() if $cb;
},
{
timeout => 10,

View File

@@ -1,3 +1,55 @@
[% IF useExtJS; extJsScripts = BLOCK %]
<script type="text/javascript">
Ext.onReady(function () {
new Ext.util.TaskRunner().start({
run: checkEq,
interval: 1000
});
});
function checkEq() {
var eqValues = [];
this.lastValues = this.lastValues || [];
for (var x = 0; x < 10; x++) {
eqValues[x] = Ext.get('pref_equalizer.' + x).dom.value || 0;
}
if (eqValues.join() != this.lastValues.join()) {
this.lastValues = eqValues;
SqueezeJS.Controller.request({
params: ['[% playerid %]', ['squeezeesp32', 'seteq', eqValues.join()]]
});
}
}
</script>
[% END; ELSIF !useExtJS; pageHeaderScripts = BLOCK %]
<script type="text/javascript">
setInterval(checkEq, 1000);
function checkEq() {
var eqValues = [];
this.lastValues = this.lastValues || [];
for (var x = 0; x < 10; x++) {
eqValues[x] = $('pref_equalizer.' + x).value || 0;
}
if (eqValues.join() != this.lastValues.join()) {
this.lastValues = eqValues;
new Ajax.Request('/jsonrpc.js', {
method: 'post',
postBody: JSON.stringify({
id: 1,
method: 'slim.request',
params: ['[% playerid %]', ['squeezeesp32', 'seteq', eqValues.join()]]
})
});
}
}
</script>
[% END; END %]
[% PROCESS settings/header.html %]
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_FIRMWARE" desc="" %]
@@ -59,62 +111,36 @@
<div>[% "PLUGIN_SQUEEZEESP32_EQUALIZER_SAVE" | string %]</div>
[% END %]
<script TYPE="text/javascript">
if (Ext) {
Ext.onReady(function () {
new Ext.util.TaskRunner().start({
run: checkEq,
interval: 1000
});
});
function checkEq() {
var eqValues = [];
this.lastValues = this.lastValues || [];
for (var x = 0; x < 10; x++) {
eqValues[x] = Ext.get('pref_equalizer.' + x).dom.value || 0;
}
if (eqValues.join() != this.lastValues.join()) {
this.lastValues = eqValues;
SqueezeJS.Controller.request({
params: ['[% playerid %]', ['squeezeesp32', 'seteq', eqValues.join()]]
});
}
}
}
</script>
[% WRAPPER settingSection %]
[% WRAPPER settingGroup title='31Hz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.0" id="pref_equalizer.0" value="[% pref_equalizer.0 %]" size="2"">
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.0" id="pref_equalizer.0" value="[% pref_equalizer.0 || 0 %]" size="2"">
[% END %]
[% WRAPPER settingGroup title='62Hz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.1" id="pref_equalizer.1" value="[% pref_equalizer.1 %]" size="2">
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.1" id="pref_equalizer.1" value="[% pref_equalizer.1 || 0 %]" size="2">
[% END %]
[% WRAPPER settingGroup title='125Hz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.2" id="pref_equalizer.2" value="[% pref_equalizer.2 %]" size="2">
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.2" id="pref_equalizer.2" value="[% pref_equalizer.2 || 0 %]" size="2">
[% END %]
[% WRAPPER settingGroup title='250Hz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.3" id="pref_equalizer.3" value="[% pref_equalizer.3 %]" size="2">
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.3" id="pref_equalizer.3" value="[% pref_equalizer.3 || 0 %]" size="2">
[% END %]
[% WRAPPER settingGroup title='500Hz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.4" id="pref_equalizer.4" value="[% pref_equalizer.4 %]" size="2">
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.4" id="pref_equalizer.4" value="[% pref_equalizer.4 || 0 %]" size="2">
[% END %]
[% WRAPPER settingGroup title='1kHz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.5" id="pref_equalizer.5" value="[% pref_equalizer.5 %]" size="2">
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.5" id="pref_equalizer.5" value="[% pref_equalizer.5 || 0 %]" size="2">
[% END %]
[% WRAPPER settingGroup title='2kHz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.6" id="pref_equalizer.6" value="[% pref_equalizer.6 %]" size="2">
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.6" id="pref_equalizer.6" value="[% pref_equalizer.6 || 0 %]" size="2">
[% END %]
[% WRAPPER settingGroup title='4kHz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.7" id="pref_equalizer.7" value="[% pref_equalizer.7 %]" size="2">
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.7" id="pref_equalizer.7" value="[% pref_equalizer.7 || 0 %]" size="2">
[% END %]
[% WRAPPER settingGroup title='8kHz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.8" id="pref_equalizer.8" value="[% pref_equalizer.8 %]" size="2">
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.8" id="pref_equalizer.8" value="[% pref_equalizer.8 || 0 %]" size="2">
[% END %]
[% WRAPPER settingGroup title='16kHz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.9" id="pref_equalizer.9" value="[% pref_equalizer.9 %]" size="2">
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.9" id="pref_equalizer.9" value="[% pref_equalizer.9 || 0 %]" size="2">
[% END %]
[% END %]
[% END %]

View File

@@ -32,8 +32,8 @@ our $defaultPrefs = {
'analogOutMode' => 0,
'bass' => 0,
'treble' => 0,
'lineInAlwaysOn' => 0,
'lineInLevel' => 50,
'lineInAlwaysOn' => 0,
'lineInLevel' => 50,
'menuItem' => [qw(
NOW_PLAYING
BROWSE_MUSIC
@@ -67,51 +67,66 @@ sub minBass { -13 }
sub init {
my $client = shift;
my ($id, $caps) = @_;
my ($depth) = $caps =~ /Depth=(\d+)/;
$client->depth($depth || 16);
if (!$handlersAdded) {
# Add a handler for line-in/out status changes
Slim::Networking::Slimproto::addHandler( LIOS => \&lineInOutStatus );
# Create a new event for sending LIOS updates
Slim::Control::Request::addDispatch(
['lios', '_state'],
[1, 0, 0, undef],
);
Slim::Control::Request::addDispatch(
['lios', 'linein', '_state'],
[1, 0, 0, undef],
);
Slim::Control::Request::addDispatch(
['lios', 'lineout', '_state'],
[1, 0, 0, undef],
);
$handlersAdded = 1;
}
$client->SUPER::init(@_);
Plugins::SqueezeESP32::FirmwareHelper::init($client);
main::INFOLOG && $log->is_info && $log->info("SqueezeESP player connected: " . $client->id);
}
}
sub initPrefs {
my $client = shift;
$sprefs->client($client)->init($defaultPrefs);
$prefs->client($client)->init( {
$prefs->client($client)->init( {
equalizer => [(0) x 10],
artwork => undef,
} );
$prefs->setValidate({
validator => sub {
my ($pref, $new, $params, $old, $client) = @_;
$new ||= [(0) x 10];
foreach (0..9) {
return 0 if $new->[$_] < $client->minBass;
return 0 if $new->[$_] > $client->maxBass;
}
return 1;
}
}, 'equalizer');
$client->SUPER::initPrefs;
}
@@ -121,15 +136,15 @@ sub power {
my $res = $client->SUPER::power($on, @_);
return $res unless defined $on;
if ($on) {
$client->update_artwork(1);
} else {
$client->clear_artwork(1);
}
return $res;
}
}
# Allow the player to define it's display width (and probably more)
sub playerSettingsFrame {
@@ -162,16 +177,16 @@ sub playerSettingsFrame {
sub bass {
my ($client, $new) = @_;
my $value = $client->SUPER::bass($new);
$client->update_equalizer($value, [2, 1, 3]) if defined $new;
return $value;
}
sub treble {
my ($client, $new) = @_;
my $value = $client->SUPER::treble($new);
$client->update_equalizer($value, [8, 9, 7]) if defined $new;
return $value;
@@ -189,8 +204,8 @@ sub send_equalizer {
sub update_equalizer {
my ($client, $value, $index) = @_;
return if $client->tone_update;
my $equalizer = $prefs->client($client)->get('equalizer');
my $equalizer = $prefs->client($client)->get('equalizer');
$equalizer->[$index->[0]] = $value;
$equalizer->[$index->[1]] = int($value / 2 + 0.5);
$equalizer->[$index->[2]] = int($value / 4 + 0.5);
@@ -203,7 +218,7 @@ sub update_tones {
$client->tone_update(1);
$sprefs->client($client)->set('bass', int(($equalizer->[1] * 2 + $equalizer->[2] + $equalizer->[3] * 4) / 7 + 0.5));
$sprefs->client($client)->set('treble', int(($equalizer->[7] * 4 + $equalizer->[8] + $equalizer->[9] * 2) / 7 + 0.5));
$client->tone_update(0);
$client->tone_update(0);
}
sub update_artwork {
@@ -212,7 +227,7 @@ sub update_artwork {
my $artwork = $cprefs->get('artwork') || return;
return unless $artwork->{'enable'} && $client->display->isa("Plugins::SqueezeESP32::Graphics");
my $header = pack('Nnn', $artwork->{'enable'}, $artwork->{'x'}, $artwork->{'y'});
$client->sendFrame( grfa => \$header );
$client->display->update;
@@ -267,7 +282,7 @@ sub clear_artwork {
if ((!$artwork->{'x'} && !$artwork->{'y'}) || $force) {
$client->sendFrame(grfa => \("\x00"x4));
$client->display->update;
}
}
}
}
@@ -284,7 +299,7 @@ sub config_artwork {
sub reconnect {
my $client = shift;
$client->SUPER::reconnect(@_);
$client->pluginData('artwork_md5', '');
$client->config_artwork if $client->display->isa("Plugins::SqueezeESP32::Graphics");
$client->send_equalizer;
@@ -323,18 +338,18 @@ sub lineOutConnected {
sub lineInOutStatus {
my ( $client, $data_ref ) = @_;
my $state = unpack 'n', $$data_ref;
my $oldState = {
in => $client->lineInConnected(),
out => $client->lineOutConnected(),
};
Slim::Networking::Slimproto::voltage( $client, $state );
Slim::Control::Request::notifyFromArray( $client, [ 'lios', $state ] );
if ($oldState->{in} != $client->lineInConnected()) {
Slim::Control::Request::notifyFromArray( $client, [ 'lios', 'linein', $client->lineInConnected() ] );
if ( Slim::Utils::PluginManager->isEnabled('Slim::Plugin::LineIn::Plugin')) {

View File

@@ -3,7 +3,7 @@ package Plugins::SqueezeESP32::PlayerSettings;
use strict;
use base qw(Slim::Web::Settings);
use JSON::XS::VersionOneAndTwo;
use List::Util qw(first);
use List::Util qw(first min max);
use Slim::Utils::Log;
use Slim::Utils::Prefs;
@@ -79,9 +79,10 @@ sub handler {
if ($client->can('depth') && $client->depth == 16) {
my $equalizer = $cprefs->get('equalizer');
for my $i (0 .. $#{$equalizer}) {
$equalizer->[$i] = $paramRef->{"pref_equalizer.$i"} || 0;
foreach (0 .. 9) {
$equalizer->[$_] = min($client->maxBass, max($client->minBass, $paramRef->{"pref_equalizer.$_"} || 0))
}
$equalizer = [ splice(@$equalizer, 0, 10) ];
$cprefs->set('equalizer', $equalizer);
$client->update_tones($equalizer);
}
@@ -97,6 +98,7 @@ sub handler {
$paramRef->{'pref_equalizer'} = $cprefs->get('equalizer') if $client->can('depth') && $client->depth == 16;
$paramRef->{'player_ip'} = $client->ip;
require Plugins::SqueezeESP32::FirmwareHelper;
Plugins::SqueezeESP32::FirmwareHelper::initFirmwareDownload($client, sub {
my ($currentFWInfo, $newFWUrl, $customFwUrl) = @_;

View File

@@ -10,6 +10,6 @@
<name>PLUGIN_SQUEEZEESP32</name>
<description>PLUGIN_SQUEEZEESP32_DESC</description>
<module>Plugins::SqueezeESP32::Plugin</module>
<version>0.351</version>
<version>0.353</version>
<creator>Philippe</creator>
</extensions>

View File

@@ -1,10 +1,10 @@
<?xml version='1.0' standalone='yes'?>
<extensions>
<plugins>
<plugin version="0.351" name="SqueezeESP32" minTarget="7.9" maxTarget="*">
<plugin version="0.353" name="SqueezeESP32" minTarget="7.9" maxTarget="*">
<link>https://github.com/sle118/squeezelite-esp32</link>
<creator>Philippe</creator>
<sha>3209d93e2b02c1c9161572977f03c93938272b30</sha>
<sha>357d715715e7bf10f83ad15bc3fd794fc45e9e5a</sha>
<email>philippe_44@outlook.com</email>
<desc lang="EN">SqueezeESP32 additional player id (100/101)</desc>
<url>http://github.com/sle118/squeezelite-esp32/raw/master-cmake/plugin/SqueezeESP32.zip</url>