Compare commits

...

43 Commits

Author SHA1 Message Date
Sébastien
01a44be0ca Update certificates - release 2021-03-16 18:03:38 -04:00
Sébastien
4dc6424fed Add github-releases to TLS certs 2021-03-16 17:26:34 -04:00
Philippe G
a989fe06c2 add version - release 2021-03-16 13:37:43 -07:00
philippe44
1b5a877b98 Update CrossBuild.yml 2021-03-16 13:36:33 -07:00
sle118
9c9f79b0b6 Made certificate script an executable for github 2021-03-16 14:33:19 -04:00
Sébastien
6fef6d679e Update CrossBuild.yml 2021-03-16 14:26:40 -04:00
Sébastien
aa54b9dff9 Update CrossBuild.yml 2021-03-16 14:19:05 -04:00
Sébastien
05a704e7ec Update CrossBuild.yml 2021-03-16 14:16:44 -04:00
Sébastien
09e6518870 Update CrossBuild.yml 2021-03-16 14:02:54 -04:00
Sébastien
4d70d0998c Update certificates during build 2021-03-16 13:42:07 -04:00
Sébastien
9d0d957ec3 Update Certificates during build 2021-03-16 13:37:04 -04:00
philippe44
1be4c89f3c release 2021-03-15 23:20:08 -07:00
philippe44
893e67dfa4 release 2021-03-15 23:10:11 -07:00
Philippe G
eced71b2be release 2021-03-14 17:07:39 -07:00
Philippe G
1aa24393e4 release 2021-03-14 17:03:26 -07:00
philippe44
06b0d9aa3e release 2021-03-14 16:49:34 -07:00
Philippe G
6d73ba2d96 release 2021-03-14 16:28:07 -07:00
Philippe G
43785f1a7d Revert "release"
This reverts commit f772b3d07b.
2021-03-14 16:27:41 -07:00
Philippe G
f80e923c2a Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-03-14 16:27:31 -07:00
Philippe G
f772b3d07b release 2021-03-14 16:27:05 -07:00
philippe44
a6ae5d795a Update CrossBuild.yml 2021-03-14 16:23:14 -07:00
philippe44
c08286874a Update CrossBuild.yml 2021-03-14 16:22:13 -07:00
Philippe G
7b10bee68a release 2021-03-14 15:37:11 -07:00
Philippe G
d42098d1c0 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-03-14 14:44:36 -07:00
Philippe G
9563f1ed44 add 32 bits edition - release 2021-03-14 14:42:25 -07:00
philippe44
1f6b24db33 Update CrossBuild.yml 2021-03-14 14:31:15 -07:00
philippe44
5f3a22bb63 Update CrossBuild.yml 2021-03-14 14:26:09 -07:00
philippe44
574c7706a0 Update CrossBuild.yml 2021-03-14 14:18:16 -07:00
Philippe G
46a43efd70 opus & vorbis 32 bits fix 2021-03-11 17:44:23 -08:00
Philippe G
e8bba8af24 AAC 32 bits mode correction 2021-03-11 12:59:34 -08:00
Philippe G
b9466bf7b2 BT non-absolute volume handling 2021-03-07 16:33:26 -08:00
Philippe G
b0d8401274 32 bits mode for AirPlay 2021-03-06 23:04:05 -08:00
Philippe G
1063cd5899 allow -Z even w/o RESAMPLE (32 bits mode) 2021-03-06 14:02:42 -08:00
Philippe G
8f4b1022e2 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-03-05 22:57:35 -08:00
Philippe G
6d45f6d1b6 Tweak 32 bits mode (enable 20 bits spdif) 2021-03-05 22:13:20 -08:00
philippe44
6beafaa747 More bit depth comments 2021-03-05 22:07:37 -08:00
philippe44
fe80ee3c18 Update README.md 2021-03-05 21:59:51 -08:00
philippe44
a5d6600dea Update README.md 2021-03-05 21:59:22 -08:00
philippe44
8132311c75 Update README.md 2021-03-05 21:58:51 -08:00
philippe44
9ecd7e9a79 Bits depth clarification 2021-03-05 09:16:50 -08:00
Philippe G
b3ff717d32 32 bits cleanup 2021-03-04 20:30:06 -08:00
Philippe G
15f1cebcdb ignore 2021-03-04 20:27:16 -08:00
Philippe G
6d9eaf4109 add balance option - release 2021-02-25 06:10:03 -08:00
26 changed files with 363 additions and 239 deletions

View File

@@ -27,6 +27,7 @@ jobs:
max-parallel: 1
matrix:
node: [I2S-4MFlash, ESP32-A1S, SqueezeAmp]
depth: [16, 32]
steps:
- name: Set target name
run: |
@@ -40,21 +41,25 @@ jobs:
id: cache-build
uses: actions/cache@v2
with:
path: build
key: ${{ runner.os }}-${{ matrix.node }}
path: |
build
/var/lib/docker
key: ${{ runner.os }}-${{ matrix.node }}-${{ matrix.depth }}
- name: Set build parameters
run: |
git update-index --chmod=+x ./server_certs/getcert.sh
cd server_certs;./getcert.sh;cat github.pem;cd ..
shopt -s nocasematch
branch_name="${GITHUB_REF//refs\/heads\//}"
branch_name="${branch_name//[^a-zA-Z0-9\-~!@_\.]/}"
BUILD_NUMBER=${{ needs.job1.outputs.build_number }}
echo "BUILD_NUMBER=${BUILD_NUMBER}" >> $GITHUB_ENV
tag="${TARGET_BUILD_NAME}.${BUILD_NUMBER}.${branch_name}"
tag="${TARGET_BUILD_NAME}.${{matrix.depth}}.${BUILD_NUMBER}.${branch_name}"
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="dev.${BUILD_NUMBER}#v4.0#${TARGET_BUILD_NAME}#${branch_name}"
artifact_prefix="squeezelite-esp32-${branch_name}-${TARGET_BUILD_NAME}-${build_version_prefix}${BUILD_NUMBER}"
name="dev.${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"
echo "name=${name}" >> $GITHUB_ENV
@@ -80,7 +85,9 @@ jobs:
run: |
env | grep "artifact\|tag\|GITHUB\|version\|NUMBER\|TARGET" >${TARGET_BUILD_NAME}-env.txt
echo "${tag}" >version.txt
docker run --env-file=${TARGET_BUILD_NAME}-env.txt --rm -v $PWD:/project -w /project sle118/idf:release-v4.0 /bin/bash -c "cp build-scripts/${TARGET_BUILD_NAME}-sdkconfig.defaults sdkconfig && idf.py build && zip -r build_output.zip build && zip build/${artifact_file_name} partitions*.csv build/*.bin build/bootloader/bootloader.bin build/partition_table/partition-table.bin build/flash_project_args build/size_*.txt"
docker pull sle118/idf:release-v4.0
docker info
docker run --env-file=${TARGET_BUILD_NAME}-env.txt -v $PWD:/project -w /project sle118/idf:release-v4.0 /bin/bash -c "cp build-scripts/${TARGET_BUILD_NAME}-sdkconfig.defaults sdkconfig && idf.py build -DDEPTH=${{ matrix.depth }} -DBUILD_NUMBER=${BUILD_NUMBER}-${{ matrix.depth }} && zip -r build_output.zip build && zip build/${artifact_file_name} partitions*.csv build/*.bin build/bootloader/bootloader.bin build/partition_table/partition-table.bin build/flash_project_args build/size_*.txt"
# - name: Build Mock firmware
# run: |
# mkdir -p build

1
.gitignore vendored
View File

@@ -97,3 +97,4 @@ components/wifi-manager/res/backup/
test/.vscode/
node_modules/*
esp-dsp/

View File

@@ -23,6 +23,18 @@ Other features include
- Full web interface for further configuration/management
- Firmware over-the-air update
**Important note** (philippe44 writing)
The main build of squeezelite-esp32 is a 16 bits internal core with all calculations in 32 bits or float precision. This is a design choice I've made to preserve CPU performances (it is already stretching a lot the esp32 chipset) and optimize memory usage as we only have 4MB of usable RAM. Now, when I did the porting of squeezelite to esp32, I've also made the core 16 or 32 bits compatible at compile-time. So far, it works in 32 bits but very little tests have been done. You can chose to compile it in 32 bits mode by changing the cmake file in components/squeezelite. Note the following limitation in 32 bits
- no resampling
- no equalizer
- buffer are smaller, so crossfade will be at best 5s at 44.1 kHz
- SPDIF is 20 bits maximum *(1)*
- display will be slower
I've not tested all codecs, I've only verified it with TAS57xx DAC and in general I've not tested that mode more than a few minutes. I'm not very interested above 16 bits samples because it does not bring anything (I have an engineering background in theory of information). On memory Some might correctly comment that wrover module have 8MB of RAM, but the processor is only able to address 4MB and the remaining 4MB must be paginated by smaller blocks and I don't have patience to that.
## 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))
### Raw WROVER module
@@ -470,3 +482,6 @@ Use 'idf monitor' to monitor the application (see esp-idf documentation)
- add DEPS_CFLAGS and DEPS_LIBS to avoid pkg-config to be required
- stack consumption can be very high with some codec variants, so set NONTHREADSAFE_PSEUDOSTACK and GLOBAL_STACK_SIZE=32000 and unset VAR_ARRAYS in config.h
- libmad has been patched to avoid using a lot of stack and is not provided here. There is an issue with sync detection in 1.15.1b from where the original stack patch was done but since a few fixes have been made wrt sync detection. This 1.15.1b-10 found on debian fixes the issue where mad thinks it has reached sync but has not and so returns a wrong sample rate. It comes at the expense of 8KB (!) of code where a simple check in squeezelite/mad.c that next_frame[0] is 0xff and next_frame[1] & 0xf0 is 0xf0 does the trick ...
# Footnotes
(1) SPDIF is made by tricking the I2S bus but this consumes a fair bit of CPU as it multiplies by four the throughput on the i2s bus. To optimize some computation, the parity of the spdif frames must always be 0, so at least one bit has to be available to force it. As SPDIF samples are 20+4 bits length maximum, the LSB is used for that purpose, so the bit 24 is randomly toggling. It does not matter for 16 bits samples but it has been chosen to truncate the last 4 bits for 24 bits samples. I'm sure that some smart dude can further optimize spdif_convert() and use the user bit instead. You're welcome to do a PR but, as said above, I (philippe44) am not interested by 24 bits mental illness :-) and I've already made an effort to provide 20 bits which already way more what's needed :-)

View File

@@ -295,6 +295,9 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) {
s_audio = AUDIO_CONNECTED;
// send memorized volume for devices that can't do absolute volume
(*bt_app_a2d_cmd_cb)(BT_SINK_VOLUME, s_volume);
// verify that we can take control
if ((*bt_app_a2d_cmd_cb)(BT_SINK_AUDIO_STARTED, s_sample_rate)) {

View File

@@ -1,5 +1,3 @@
idf_component_register( SRC_DIRS . external ac101 tas57xx
INCLUDE_DIRS . ac101
PRIV_REQUIRES
@@ -17,8 +15,6 @@ idf_component_register( SRC_DIRS . external ac101 tas57xx
EMBED_FILES vu.data
)
set_source_files_properties(mad.c
PROPERTIES COMPILE_FLAGS
-Wno-maybe-uninitialized
@@ -33,9 +29,11 @@ set_source_files_properties(flac.c
-Wno-maybe-uninitialized
)
add_definitions(-DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ONLY -DBYTES_PER_FRAME=4)
add_definitions(-DLINKALL -DLOOPBACK -DNO_FAAD -DEMBEDDED -DTREMOR_ONLY -DCUSTOM_VERSION=${BUILD_NUMBER})
if(${DEPTH} EQUAL "32")
add_definitions(-DBYTES_PER_FRAME=8)
else()
add_definitions(-DBYTES_PER_FRAME=4)
endif()
add_compile_options (-O3 )

View File

@@ -31,7 +31,7 @@ extern log_level loglevel;
static bool enable_bt_sink;
static bool enable_airplay;
#define RAOP_OUTPUT_SIZE (RAOP_SAMPLE_RATE * 2 * 2 * 2 * 1.2)
#define RAOP_OUTPUT_SIZE (((RAOP_SAMPLE_RATE * BYTES_PER_FRAME * 2 * 120) / 100) & ~BYTES_PER_FRAME)
#define SYNC_WIN_SLOW 32
#define SYNC_WIN_CHECK 8
#define SYNC_WIN_FAST 2
@@ -63,19 +63,19 @@ static void sink_data_handler(const uint8_t *data, uint32_t len)
while (len) {
LOCK_O;
bytes = min(_buf_space(outputbuf), _buf_cont_write(outputbuf));
bytes = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / (BYTES_PER_FRAME / 4);
bytes = min(len, bytes);
#if BYTES_PER_FRAME == 4
memcpy(outputbuf->writep, data, bytes);
#else
{
s16_t *iptr = (s16_t*) data;
ISAMPLE_T *optr = (ISAMPLE_T*) outputbuf->writep;
size_t n = bytes / BYTES_PER_FRAME * 2;
ISAMPLE_T *optr = (ISAMPLE_T *) outputbuf->writep;
size_t n = bytes / 2;
while (n--) *optr++ = *iptr++ << 16;
}
#endif
_buf_inc_writep(outputbuf, bytes);
_buf_inc_writep(outputbuf, bytes * BYTES_PER_FRAME / 4);
space = _buf_space(outputbuf);
len -= bytes;
@@ -188,7 +188,6 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
// this is async, so player might have been deleted
switch (event) {
case RAOP_TIMING: {
if (!raop_sync.enabled || output.state != OUTPUT_RUNNING || output.frames_played_dmp < output.device_frames) break;
u32_t ms, now = gettime_ms();

View File

@@ -853,8 +853,8 @@ static void visu_update(void) {
int mode = visu.mode & ~VISU_ESP32;
// not enough samples
if (visu_export.level < (mode == VISU_VUMETER ? RMS_LEN : FFT_LEN) * 2 && visu_export.running) {
// not enough frames
if (visu_export.level < (mode == VISU_VUMETER ? RMS_LEN : FFT_LEN) && visu_export.running) {
pthread_mutex_unlock(&visu_export.mutex);
return;
}
@@ -865,14 +865,14 @@ static void visu_update(void) {
if (visu_export.running) {
if (mode == VISU_VUMETER) {
s16_t *iptr = visu_export.buffer;
s16_t *iptr = (s16_t*) visu_export.buffer + (BYTES_PER_FRAME / 4) - 1;
// calculate sum(L²+R²), try to not overflow at the expense of some precision
for (int i = RMS_LEN; --i >= 0;) {
visu.bars[0].current += (*iptr * *iptr + (1 << (RMS_LEN_BIT - 2))) >> (RMS_LEN_BIT - 1);
iptr++;
iptr += BYTES_PER_FRAME / 4;
visu.bars[1].current += (*iptr * *iptr + (1 << (RMS_LEN_BIT - 2))) >> (RMS_LEN_BIT - 1);
iptr++;
iptr += BYTES_PER_FRAME / 4;
}
// convert to dB (1 bit remaining for getting X²/N, 60dB dynamic starting from 0dBFS = 3 bits back-off)
@@ -882,11 +882,13 @@ static void visu_update(void) {
else if (visu.bars[i].current < 0) visu.bars[i].current = 0;
}
} else {
s16_t *iptr = (s16_t*) visu_export.buffer + (BYTES_PER_FRAME / 4) - 1;
// on xtensa/esp32 the floating point FFT takes 1/2 cycles of the fixed point
for (int i = 0 ; i < FFT_LEN ; i++) {
// don't normalize here, but we are due INT16_MAX and FFT_LEN / 2 / 2
visu.samples[i * 2 + 0] = (float) (visu_export.buffer[2*i] + visu_export.buffer[2*i + 1]) * visu.hanning[i];
visu.samples[i * 2 + 0] = (float) (*iptr + *(iptr+BYTES_PER_FRAME/4)) * visu.hanning[i];
visu.samples[i * 2 + 1] = 0;
iptr += 2 * BYTES_PER_FRAME / 4;
}
// actual FFT that might be less cycle than all the crap below

View File

@@ -22,7 +22,7 @@ typedef int16_t s16_t;
typedef int32_t s32_t;
typedef int64_t s64_t;
typedef unsigned long long u64_t;
#ifndef PTHREAD_STACK_MIN
#define PTHREAD_STACK_MIN 256
#endif
@@ -42,7 +42,12 @@ typedef unsigned long long u64_t;
#define PLAYER_ID custom_player_id
extern u8_t custom_player_id;
#define BASE_CAP "Model=squeezeesp32,AccuratePlayPoints=1,HasDigitalOut=1,HasPolarityInversion=1,Firmware=" VERSION
#if BYTES_PER_FRAME == 8
#define BASE_CAP "Model=squeezeesp32,AccuratePlayPoints=1,HasDigitalOut=1,HasPolarityInversion=1,Balance=1,Depth=32,Firmware=" VERSION
#else
#define BASE_CAP "Model=squeezeesp32,AccuratePlayPoints=1,HasDigitalOut=1,HasPolarityInversion=1,Balance=1,Depth=16,Firmware=" VERSION
#endif
// to force some special buffer attribute
#define EXT_BSS __attribute__((section(".ext_ram.bss")))
@@ -78,10 +83,10 @@ u8_t get_battery(void); // must provide 0..15 or define as 0x0
extern struct visu_export_s {
pthread_mutex_t mutex;
u32_t level, size, rate, gain;
s16_t *buffer;
void *buffer;
bool running;
} visu_export;
void output_visu_export(s16_t *frames, frames_t out_frames, u32_t rate, bool silence, u32_t gain);
void output_visu_export(void *frames, frames_t out_frames, u32_t rate, bool silence, u32_t gain);
void output_visu_init(log_level level);
void output_visu_close(void);

View File

@@ -30,7 +30,7 @@
#if BYTES_PER_FRAME == 4
#define ALIGN(n) (n)
#else
#define ALIGN(n) (n << 8)
#define ALIGN(n) (n << 16)
#endif
#define WRAPBUF_LEN 2048
@@ -332,7 +332,7 @@ static decode_state helixaac_decode(void) {
size_t bytes_total, bytes_wrap;
int res, bytes;
static AACFrameInfo info;
ISAMPLE_T *iptr;
s16_t *iptr;
u8_t *sptr;
bool endstream;
frames_t frames;
@@ -372,7 +372,7 @@ static decode_state helixaac_decode(void) {
u8_t *p = streambuf->readp + n;
int bytes = bytes_wrap - n;
if (!HAAC(a, Decode, a->hAac, &p, &bytes, (short*) a->write_buf)) {
if (!HAAC(a, Decode, a->hAac, &p, &bytes, (s16_t*) a->write_buf)) {
HAAC(a, GetLastFrameInfo, a->hAac, &info);
channels = info.nChans;
samplerate = info.sampRateOut;
@@ -443,13 +443,13 @@ static decode_state helixaac_decode(void) {
}
// decode function changes iptr, so can't use streambuf->readp (same for bytes)
res = HAAC(a, Decode, a->hAac, &sptr, &bytes, (short*) a->write_buf);
res = HAAC(a, Decode, a->hAac, &sptr, &bytes, (s16_t*) a->write_buf);
if (res < 0) {
LOG_WARN("AAC decode error %d", res);
}
HAAC(a, GetLastFrameInfo, a->hAac, &info);
iptr = (ISAMPLE_T *) a->write_buf;
iptr = (s16_t*) a->write_buf;
bytes = bytes_wrap - bytes;
endstream = false;
@@ -543,8 +543,8 @@ static decode_state helixaac_decode(void) {
iptr += count * 2;
#else
while (count--) {
*optr++ = *iptr++ << 8;
*optr++ = *iptr++ << 8;
*optr++ = ALIGN(*iptr++);
*optr++ = ALIGN(*iptr++);
}
#endif
} else if (info.nChans == 1) {
@@ -597,7 +597,7 @@ static void helixaac_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
// always free decoder as flush only works when no parameter has changed
HAAC(a, FreeDecoder, a->hAac);
} else {
a->write_buf = malloc(FRAME_BUF * BYTES_PER_FRAME);
a->write_buf = malloc(FRAME_BUF * 4);
a->wrap_buf = malloc(WRAPBUF_LEN);
}

View File

@@ -343,16 +343,9 @@ int main(int argc, char **argv) {
while (optind < argc && strlen(argv[optind]) >= 2 && argv[optind][0] == '-') {
char *opt = argv[optind] + 1;
if (strstr("oabcCdefmMnNpPrs"
if (strstr("oabcCdefmMnNpPrsZ"
#if ALSA
"UVO"
#endif
/*
* only allow '-Z <rate>' override of maxSampleRate
* reported by client if built with the capability to resample!
*/
#if RESAMPLE || RESAMPLE16
"Z"
#endif
, opt) && optind < argc - 1) {
optarg = argv[optind + 1];
@@ -519,6 +512,9 @@ int main(int argc, char **argv) {
case 'N':
namefile = optarg;
break;
case 'Z':
maxSampleRate = atoi(optarg);
break;
case 'W':
pcm_check_header = true;
break;
@@ -552,9 +548,6 @@ int main(int argc, char **argv) {
resample = "";
}
break;
case 'Z':
maxSampleRate = atoi(optarg);
break;
#endif
#if DSD
case 'D':

View File

@@ -188,7 +188,12 @@ static decode_state opus_decompress(void) {
// work backward to unpack samples (if needed)
iptr = (s16_t *) write_buf + count;
optr = (ISAMPLE_T *) write_buf + frames * 2;
IF_DIRECT(
optr = (ISAMPLE_T *) outputbuf->writep + frames * 2;
)
IF_PROCESS(
optr = (ISAMPLE_T *) write_buf + frames * 2;
)
if (channels == 2) {
#if BYTES_PER_FRAME == 4

View File

@@ -253,7 +253,7 @@ frames_t _output_frames(frames_t avail) {
}
out_frames = !silence ? min(size, cont_frames) : size;
if (output.channels & 0x01) gainR |= MONO_FLAG;
if (output.channels & 0x02) gainL |= MONO_FLAG;

View File

@@ -112,7 +112,7 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g
memcpy(btout + oframes * BYTES_PER_FRAME, buf, out_frames * BYTES_PER_FRAME);
}
output_visu_export((s16_t*) (btout + oframes * BYTES_PER_FRAME), out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2);
output_visu_export(btout + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, ((gainL & ~MONO_FLAG) + (gainR & ~MONO_FLAG)) / 2);
oframes += out_frames;

View File

@@ -400,42 +400,18 @@ bool output_volume_i2s(unsigned left, unsigned right) {
*/
static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,
s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr) {
#if BYTES_PER_FRAME == 8
s32_t *optr;
#endif
if (!silence) {
if (output.fade == FADE_ACTIVE && output.fade_dir == FADE_CROSS && *cross_ptr) {
_apply_cross(outputbuf, out_frames, cross_gain_in, cross_gain_out, cross_ptr);
}
#if BYTES_PER_FRAME == 4
_apply_gain(outputbuf, out_frames, gainL, gainR);
memcpy(obuf + oframes * BYTES_PER_FRAME, outputbuf->readp, out_frames * BYTES_PER_FRAME);
#else
optr = (s32_t*) outputbuf->readp;
#endif
} else {
#if BYTES_PER_FRAME == 4
memcpy(obuf + oframes * BYTES_PER_FRAME, silencebuf, out_frames * BYTES_PER_FRAME);
#else
optr = (s32_t*) silencebuf;
#endif
}
#if BYTES_PER_FRAME == 8
IF_DSD(
if (output.outfmt == DOP) {
update_dop((u32_t *) optr, out_frames, output.invert);
} else if (output.outfmt != PCM && output.invert)
dsd_invert((u32_t *) optr, out_frames);
)
_scale_and_pack_frames(obuf + oframes * BYTES_PER_FRAME, optr, out_frames, gainL, gainR, output.format);
#endif
output_visu_export((s16_t*) (obuf + oframes * BYTES_PER_FRAME), out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2);
output_visu_export(obuf + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, ((gainL & ~MONO_FLAG) + (gainR & ~MONO_FLAG)) / 2);
oframes += out_frames;
return out_frames;
@@ -556,14 +532,16 @@ static void *output_thread_i2s(void *arg) {
//return;
}
#if BYTES_PER_FRAME == 4
// run equalizer
equalizer_process(obuf, oframes * BYTES_PER_FRAME, output.current_sample_rate);
#endif
// we assume that here we have been able to entirely fill the DMA buffers
if (spdif) {
spdif_convert((ISAMPLE_T*) obuf, oframes, (u32_t*) sbuf, &count);
i2s_write(CONFIG_I2S_NUM, sbuf, oframes * 16, &bytes, portMAX_DELAY);
bytes /= 4;
bytes /= 16 / BYTES_PER_FRAME;
#if BYTES_PER_FRAME == 4
} else if (i2s_config.bits_per_sample == 32) {
i2s_write_expand(CONFIG_I2S_NUM, obuf, oframes * BYTES_PER_FRAME, 16, 32, &bytes, portMAX_DELAY);
@@ -643,8 +621,9 @@ extern const u16_t spdif_bmclookup[256];
*/
void spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst, size_t *count) {
u16_t hi, lo, aux;
register size_t cnt = *count;
// frames are 2 channels of 16 bits
// frames are 2 channels of 16/32 bits
frames *= 2;
while (frames--) {
@@ -653,28 +632,38 @@ void spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst, size_t *count) {
lo = spdif_bmclookup[(u8_t) *src];
#else
hi = spdif_bmclookup[(u8_t)(*src >> 24)];
lo = spdif_bmclookup[(u8_t) *src >> 16];
lo = spdif_bmclookup[(u8_t)(*src >> 16)];
#endif
// invert if last preceeding bit is 1
lo ^= ~((s16_t)hi) >> 16;
// 16 bits sample:
// first 16 bits
*(dst+0) = ((u32_t)lo << 16) | hi;
// 4 bits auxillary-audio-databits, the first used as parity
#if BYTES_PER_FRAME == 4
aux = 0xb333 ^ (((u32_t)((s16_t)lo)) >> 17);
#else
// we use 20 bits samples as we need to force parity
aux = spdif_bmclookup[(u8_t)(*src >> 12)];
aux = (u8_t) (aux ^ (~((s16_t)lo) >> 16));
aux |= (0xb3 ^ (((u16_t)((s8_t)aux)) >> 9)) << 8;
#endif
// VUCP-Bits: Valid, Subcode, Channelstatus, Parity = 0
// As parity is always 0, we can use fixed preambles
if (++(*count) > 383) {
if (++cnt > 383) {
*(dst+1) = VUCP | (PREAMBLE_B << 16 ) | aux; //special preamble for one of 192 frames
*count = 0;
cnt = 0;
} else {
*(dst+1) = VUCP | ((((*count) & 0x01) ? PREAMBLE_W : PREAMBLE_M) << 16) | aux;
*(dst+1) = VUCP | (((cnt & 0x01) ? PREAMBLE_W : PREAMBLE_M) << 16) | aux;
}
src++;
dst += 2;
}
*count = cnt;
}
const u16_t spdif_bmclookup[256] = { //biphase mark encoded values (least significant bit first)

View File

@@ -23,14 +23,8 @@
#include "squeezelite.h"
#if BYTES_PER_FRAM == 4
#define MAX_VAL16 0x7fffffffLL
#define MAX_SCALESAMPLE 0x7fffffffffffLL
#define MIN_SCALESAMPLE -MAX_SCALESAMPLE
#else
#define MAX_SCALESAMPLE 0x7fffffffffffLL
#define MIN_SCALESAMPLE -MAX_SCALESAMPLE
#endif
// inlining these on windows prevents them being linkable...
#if !WIN
@@ -57,7 +51,7 @@ void _scale_and_pack_frames(void *outputptr, s32_t *inputptr, frames_t cnt, s32_
gainL &= ~MONO_FLAG; gainR &= ~MONO_FLAG;
while (count--) {
// use 64 bits integer for purists but should really not care
*ptr = *(ptr + 1) = ((s64_t) gain(gainL, *ptr) + (s64_t) gain(gainR, *(ptr + 1))) / 2;
*ptr = *(ptr + 1) = ((s64_t) *ptr + (s64_t) *(ptr + 1)) / 2;
ptr += 2;
}
} else if (gainL & MONO_FLAG) {

View File

@@ -29,7 +29,7 @@ static struct visu_export_s *visu = &visu_export;
static log_level loglevel = lINFO;
void output_visu_export(s16_t *frames, frames_t out_frames, u32_t rate, bool silence, u32_t gain) {
void output_visu_export(void *frames, frames_t out_frames, u32_t rate, bool silence, u32_t gain) {
// no data to process
if (silence) {
@@ -44,10 +44,10 @@ void output_visu_export(s16_t *frames, frames_t out_frames, u32_t rate, bool sil
// stuff buffer up and wait for consumer to read it (should reset level)
if (visu->level < visu->size) {
u32_t space = min(visu->size - visu->level, out_frames * 2) * 2;
u32_t space = min(visu->size - visu->level, out_frames) * BYTES_PER_FRAME;
memcpy(visu->buffer + visu->level, frames, space);
visu->level += space / 2;
visu->level += space / BYTES_PER_FRAME;
visu->running = true;
visu->rate = rate ? rate : 44100;
visu->gain = gain;
@@ -71,7 +71,7 @@ void output_visu_init(log_level level) {
visu->size = VISUEXPORT_SIZE;
visu->running = false;
visu->rate = 44100;
visu->buffer = malloc(VISUEXPORT_SIZE * sizeof(s16_t) * 2);
LOG_INFO("Initialize VISUEXPORT %u 16 bits samples", VISUEXPORT_SIZE);
visu->buffer = malloc(VISUEXPORT_SIZE * BYTES_PER_FRAME);
LOG_INFO("Initialize VISUEXPORT %u %u bits samples", VISUEXPORT_SIZE, BYTES_PER_FRAME * 4);
}

View File

@@ -122,7 +122,7 @@ void send_packet(u8_t *packet, size_t len) {
static void sendHELO(bool reconnect, const char *fixed_cap, const char *var_cap, u8_t mac[6]) {
#ifndef BASE_CAP
#define BASE_CAP "Model=squeezelite,AccuratePlayPoints=1,HasDigitalOut=1,HasPolarityInversion=1,Firmware=" VERSION
#define BASE_CAP "Model=squeezelite,AccuratePlayPoints=1,HasDigitalOut=1,HasPolarityInversion=1,Balance=1,Firmware=" VERSION
#endif
#define SSL_CAP "CanHTTPS=1"
const char *base_cap;
@@ -914,7 +914,11 @@ void slimproto(log_level level, char *server, u8_t mac[6], const char *name, con
LOCK_O;
snprintf(fixed_cap, FIXED_CAP_LEN, ",ModelName=%s,MaxSampleRate=%u", modelname ? modelname : MODEL_NAME_STRING,
#if RESAMPLE || RESAMPLE16
((maxSampleRate > 0) ? maxSampleRate : output.supported_rates[0]));
#else
((maxSampleRate > 0 && maxSampleRate < output.supported_rates[0]) ? maxSampleRate : output.supported_rates[0]));
#endif
for (i = 0; i < MAX_CODECS; i++) {
if (codecs[i] && codecs[i]->id && strlen(fixed_cap) < FIXED_CAP_LEN - 10) {

View File

@@ -24,9 +24,9 @@
// make may define: PORTAUDIO, SELFPIPE, RESAMPLE, RESAMPLE_MP, VISEXPORT, GPIO, IR, DSD, LINKALL to influence build
#define MAJOR_VERSION "1.9"
#define MINOR_VERSION "2"
#define MICRO_VERSION "1145"
#define MAJOR_VERSION "0"
#define MINOR_VERSION "0"
#define MICRO_VERSION ""
#if defined(CUSTOM_VERSION)
#define VERSION "v" MAJOR_VERSION "." MINOR_VERSION "-" MICRO_VERSION STR(CUSTOM_VERSION)

View File

@@ -453,7 +453,7 @@ void stream_file(const char *header, size_t header_len, unsigned threshold) {
buf_flush(streambuf);
LOCK;
stream.header_len = header_len;
memcpy(stream.header, header, header_len);
*(stream.header+header_len) = '\0';
@@ -492,7 +492,7 @@ void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, un
// wait till we are not polling anymore
while (polling && running) { usleep(10000); }
#endif
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {

View File

@@ -236,7 +236,12 @@ static decode_state vorbis_decode(void) {
// work backward to unpack samples (if needed)
iptr = (s16_t *) write_buf + count;
optr = (ISAMPLE_T *) write_buf + frames * 2;
IF_DIRECT(
optr = (ISAMPLE_T *) outputbuf->writep + frames * 2;
)
IF_PROCESS(
optr = (ISAMPLE_T *) write_buf + frames * 2;
)
if (channels == 2) {
#if BYTES_PER_FRAME == 4

View File

@@ -38,67 +38,69 @@
<hr>
[% END %]
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_EQUALIZER" desc="" %]
<div>[% "PLUGIN_SQUEEZEESP32_EQUALIZER_SAVE" | string %]</div>
[% END %]
[% IF pref_equalizer %]
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_EQUALIZER" desc="" %]
<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 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="" %]
</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"">
[% 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">
[% 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">
[% 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">
[% 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">
[% 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">
[% 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">
[% 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">
[% 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">
[% 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">
[% END %]
[% 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">
[% 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">
[% 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">
[% 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">
[% 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">
[% 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">
[% 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">
[% 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">
[% 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">
[% END %]
[% END %]
[% END %]
[% PROCESS settings/footer.html %]

View File

@@ -14,7 +14,7 @@ my $prefs = preferences('plugin.squeezeesp32');
my $log = logger('plugin.squeezeesp32');
{
__PACKAGE__->mk_accessor('rw', 'tone_update');
__PACKAGE__->mk_accessor('rw', qw(tone_update depth));
}
sub new {
@@ -64,6 +64,10 @@ sub minBass { -13 }
sub init {
my $client = shift;
my ($id, $caps) = @_;
my ($depth) = $caps =~ /Depth=(\d+)/;
$client->depth($depth || 16);
if (!$handlersAdded) {
@@ -107,6 +111,22 @@ sub initPrefs {
$client->SUPER::initPrefs;
}
sub power {
my $client = shift;
my $on = shift;
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 {
my $client = shift;
@@ -232,16 +252,16 @@ sub send_artwork {
}
sub clear_artwork {
my ($client, $request) = @_;
my ($client, $force, $from) = @_;
my $artwork = $prefs->client($client)->get('artwork');
if ($artwork && $artwork->{'enable'}) {
main::INFOLOG && $log->is_info && $log->info("artwork stop/clear " . $request->getRequestString());
main::INFOLOG && $log->is_info && $log->info("artwork stop/clear " . ($from || ""));
$client->pluginData('artwork_md5', '');
# refresh screen and disable artwork when artwork was full screen (hack)
if (!$artwork->{'x'} && !$artwork->{'y'}) {
$client->sendFrame(grfa => \("\x00"x4)) unless $artwork->{'x'} || $artwork->{'y'};
if ((!$artwork->{'x'} && !$artwork->{'y'}) || $force) {
$client->sendFrame(grfa => \("\x00"x4));
$client->display->update;
}
}

View File

@@ -76,12 +76,14 @@ sub handler {
}
my $equalizer = $cprefs->get('equalizer');
for my $i (0 .. $#{$equalizer}) {
$equalizer->[$i] = $paramRef->{"pref_equalizer.$i"} || 0;
}
$cprefs->set('equalizer', $equalizer);
$client->update_tones($equalizer);
if ($client->depth == 16) {
my $equalizer = $cprefs->get('equalizer');
for my $i (0 .. $#{$equalizer}) {
$equalizer->[$i] = $paramRef->{"pref_equalizer.$i"} || 0;
}
$cprefs->set('equalizer', $equalizer);
$client->update_tones($equalizer);
}
}
if ($client->displayWidth) {
@@ -91,7 +93,7 @@ sub handler {
$paramRef->{'pref_artwork'} = $cprefs->get('artwork');
}
$paramRef->{'pref_equalizer'} = $cprefs->get('equalizer');
$paramRef->{'pref_equalizer'} = $cprefs->get('equalizer') if $client->depth == 16;
return $class->SUPER::handler($client, $paramRef);
}

View File

@@ -65,7 +65,7 @@ sub onStopClear {
my $client = $request->client || return;
if ($client->isa('Plugins::SqueezeESP32::Player')) {
$client->clear_artwork($request);
$client->clear_artwork(0, $request->getRequestString());
}
}

1
server_certs/getcert.sh Normal file → Executable file
View File

@@ -43,4 +43,5 @@ rm *.txt
# seed the start pem
get_all_pem github.com github-com
get_all_pem s3.amazonaws.com s3-amazon-com
get_all_pem github-releases.githubusercontent.com githubusercontent-com
cat *.pem >github.pem

View File

@@ -25,6 +25,28 @@ bxcQKegOw+FUllSlkZUIII1pLJ4vP1C0LuVXH6+kc9KhJLsNkP5FEx2noSnYZgvD
upcHi9nzBhDFKdT3uhaQqNBU4UtJx5g=
-----END CERTIFICATE-----
-----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-----
-----BEGIN CERTIFICATE-----
MIIEsTCCA5mgAwIBAgIQBOHnpNxc8vNtwCtCuF0VnzANBgkqhkiG9w0BAQsFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
@@ -53,59 +75,115 @@ xICaEnL6VpPX/78whQYwvwt/Tv9XBZ0k7YXDK/umdaisLRbvfXknsuvCnQsH6qqF
cPUeybQ=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIG1TCCBb2gAwIBAgIQBVfICygmg6F7ChFEkylreTANBgkqhkiG9w0BAQsFADBw
MIIEQzCCAyugAwIBAgIQCidf5wTW7ssj1c1bSxpOBDANBgkqhkiG9w0BAQwFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMS8wLQYDVQQDEyZEaWdpQ2VydCBTSEEyIEhpZ2ggQXNz
dXJhbmNlIFNlcnZlciBDQTAeFw0yMDA1MDUwMDAwMDBaFw0yMjA1MTAxMjAwMDBa
MGYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T
YW4gRnJhbmNpc2NvMRUwEwYDVQQKEwxHaXRIdWIsIEluYy4xEzARBgNVBAMTCmdp
dGh1Yi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7MrTQ2J6a
nox5KUwrqO9cQ9STO5R4/zBUxxvI5S8bmc0QjWfIVAwHWuT0Bn/H1oS0LM0tTkQm
ARrqN77v9McVB8MWTGsmGQnS/1kQRFuKiYGUHf7iX5pfijbYsOkfb4AiVKysKUNV
UtgVvpJoe5RWURjQp9XDWkeo2DzGHXLcBDadrM8VLC6H1/D9SXdVruxKqduLKR41
Z/6dlSDdeY1gCnhz3Ch1pYbfMfsTCTamw+AtRtwlK3b2rfTHffhowjuzM15UKt+b
rr/cEBlAjQTva8rutYU9K9ONgl+pG2u7Bv516DwmNy8xz9wOjTeOpeh0M9N/ewq8
cgbR87LFaxi1AgMBAAGjggNzMIIDbzAfBgNVHSMEGDAWgBRRaP+QrwIHdTzM2WVk
YqISuFlyOzAdBgNVHQ4EFgQUYwLSXQJf943VWhKedhE2loYsikgwJQYDVR0RBB4w
HIIKZ2l0aHViLmNvbYIOd3d3LmdpdGh1Yi5jb20wDgYDVR0PAQH/BAQDAgWgMB0G
A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjB1BgNVHR8EbjBsMDSgMqAwhi5o
dHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1oYS1zZXJ2ZXItZzYuY3JsMDSg
MqAwhi5odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1oYS1zZXJ2ZXItZzYu
Y3JsMEwGA1UdIARFMEMwNwYJYIZIAYb9bAEBMCowKAYIKwYBBQUHAgEWHGh0dHBz
Oi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQICMIGDBggrBgEFBQcBAQR3
MHUwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBNBggrBgEF
BQcwAoZBaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkhp
Z2hBc3N1cmFuY2VTZXJ2ZXJDQS5jcnQwDAYDVR0TAQH/BAIwADCCAXwGCisGAQQB
1nkCBAIEggFsBIIBaAFmAHUAKXm+8J45OSHwVnOfY6V35b5XfZxgCvj5TV0mXCVd
x4QAAAFx5ltprwAABAMARjBEAiAuWGCWxN/M0Ms3KOsqFjDMHT8Aq0SlHfQ68KDg
rVU6AAIgDA+2EB0D5W5r0i4Nhljx6ABlIByzrEdfcxiOD/o6//EAdQAiRUUHWVUk
VpY/oS/x922G4CMmY63AS39dxoNcbuIPAgAAAXHmW2nTAAAEAwBGMEQCIBp+XQKa
UDiPHwjBxdv5qvgyALKaysKqMF60gqem8iPRAiAk9Dp5+VBUXfSHqyW+tVShUigh
ndopccf8Gs21KJ4jXgB2AFGjsPX9AXmcVm24N3iPDKR6zBsny/eeiEKaDf7UiwXl
AAABceZbahsAAAQDAEcwRQIgd/5HcxT4wfNV8zavwxjYkw2TYBAuRCcqp1SjWKFn
4EoCIQDHSTHxnbpxWFbP6v5Y6nGFZCDjaHgd9HrzUv2J/DaacDANBgkqhkiG9w0B
AQsFAAOCAQEAhjKPnBW4r+jR3gg6RA5xICTW/A5YMcyqtK0c1QzFr8S7/l+skGpC
yCHrJfFrLDeyKqgabvLRT6YvvM862MGfMMDsk+sKWtzLbDIcYG7sbviGpU+gtG1q
B0ohWNApfWWKyNpquqvwdSEzAEBvhcUT5idzbK7q45bQU9vBIWgQz+PYULAU7KmY
z7jOYV09o22TNMQT+hFmo92+EBlwSeIETYEsHy5ZxixTRTvu9hP00CyEbiht5OTK
5EiJG6vsIh/uEtRsdenMCxV06W2f20Af4iSFo0uk6c1ryHefh08FcwA4pSNUaPyi
Pb8YGQ6o/blejFzo/OSiUnDueafSJ0p6SQ==
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0yMDA5MjMwMDAwMDBaFw0zMDA5MjIyMzU5NTlaMFYxCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMDAuBgNVBAMTJ0RpZ2lDZXJ0IFRMUyBI
eWJyaWQgRUNDIFNIQTM4NCAyMDIwIENBMTB2MBAGByqGSM49AgEGBSuBBAAiA2IA
BMEbxppbmNmkKaDp1AS12+umsmxVwP/tmMZJLwYnUcu/cMEFesOxnYeJuq20ExfJ
qLSDyLiQ0cx0NTY8g3KwtdD3ImnI8YDEe0CPz2iHJlw5ifFNkU3aiYvkA8ND5b8v
c6OCAa4wggGqMB0GA1UdDgQWBBQKvAgpF4ylOW16Ds4zxy6z7fvDejAfBgNVHSME
GDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0l
BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8CAQAwdgYI
KwYBBQUHAQEEajBoMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j
b20wQAYIKwYBBQUHMAKGNGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp
Q2VydEdsb2JhbFJvb3RDQS5jcnQwewYDVR0fBHQwcjA3oDWgM4YxaHR0cDovL2Ny
bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDA3oDWgM4Yx
aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNy
bDAwBgNVHSAEKTAnMAcGBWeBDAEBMAgGBmeBDAECATAIBgZngQwBAgIwCAYGZ4EM
AQIDMA0GCSqGSIb3DQEBDAUAA4IBAQDeOpcbhb17jApY4+PwCwYAeq9EYyp/3YFt
ERim+vc4YLGwOWK9uHsu8AjJkltz32WQt960V6zALxyZZ02LXvIBoa33llPN1d9R
JzcGRvJvPDGJLEoWKRGC5+23QhST4Nlg+j8cZMsywzEXJNmvPlVv/w+AbxsBCMqk
BGPI2lNM8hkmxPad31z6n58SXqJdH/bYF462YvgdgbYKOytobPAyTgr3mYI5sUje
CzqJx1+NLyc8nAK8Ib2HxnC+IrrWzfRLvVNve8KaN9EtBH7TuMwNW4SpDCmGr6fY
1h3tDjHhkTb9PA36zoaJzu0cIw265vZt6hCmYWJC+/j+fgZwcPwL
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIH9jCCBt6gAwIBAgIQBLLrctRBObyjf4KVINV68DANBgkqhkiG9w0BAQsFADBk
MIIE7jCCBHSgAwIBAgIQBGv4V/rhZO4SCgtfMpPGOTAKBggqhkjOPQQDAzBWMQsw
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMTAwLgYDVQQDEydEaWdp
Q2VydCBUTFMgSHlicmlkIEVDQyBTSEEzODQgMjAyMCBDQTEwHhcNMjEwMzEyMDAw
MDAwWhcNMjIwMzIzMjM1OTU5WjBmMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2Fs
aWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHVi
LCBJbmMuMRMwEQYDVQQDEwpnaXRodWIuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D
AQcDQgAE2NKiDAGKpIeEu5Zc3atS11fP5PWbOdYtBDm7XKkGoHlyHIxPuuKc0wqo
9o4PK4BzXugwFGiwR/ycuVr/ZTcTUqOCAxIwggMOMB8GA1UdIwQYMBaAFAq8CCkX
jKU5bXoOzjPHLrPt+8N6MB0GA1UdDgQWBBS08xJCml9AyxzY6VeFeTwgBhfaWDAl
BgNVHREEHjAcggpnaXRodWIuY29tgg53d3cuZ2l0aHViLmNvbTAOBgNVHQ8BAf8E
BAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMIGXBgNVHR8EgY8w
gYwwRKBCoECGPmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRMU0h5
YnJpZEVDQ1NIQTM4NDIwMjBDQTEuY3JsMESgQqBAhj5odHRwOi8vY3JsNC5kaWdp
Y2VydC5jb20vRGlnaUNlcnRUTFNIeWJyaWRFQ0NTSEEzODQyMDIwQ0ExLmNybDA+
BgNVHSAENzA1MDMGBmeBDAECAjApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3LmRp
Z2ljZXJ0LmNvbS9DUFMwgYMGCCsGAQUFBwEBBHcwdTAkBggrBgEFBQcwAYYYaHR0
cDovL29jc3AuZGlnaWNlcnQuY29tME0GCCsGAQUFBzAChkFodHRwOi8vY2FjZXJ0
cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUTFNIeWJyaWRFQ0NTSEEzODQyMDIwQ0Ex
LmNydDAMBgNVHRMBAf8EAjAAMIIBBgYKKwYBBAHWeQIEAgSB9wSB9ADyAHcAKXm+
8J45OSHwVnOfY6V35b5XfZxgCvj5TV0mXCVdx4QAAAF4J1lR5QAABAMASDBGAiEA
9PC30XzJepYPompNYMDjq9oow8xEAbDAzxZbF0e2HDQCIQC20gF9ohk20paUvsq+
oTXhIwwoyq4TAPprf4Ii5d2M9AB3ACJFRQdZVSRWlj+hL/H3bYbgIyZjrcBLf13G
g1xu4g8CAAABeCdZUjQAAAQDAEgwRgIhANnbglRYcck1OgSk9B6ItU7CpDBJnbdP
SxcIyyAY1If8AiEA0G8HucONc8DcsAQg8+MB7Ms0gTpz5Qs41LLiJ+LLyb4wCgYI
KoZIzj0EAwMDaAAwZQIwflk1MqBiRbkppu+8NqxY3TPzP5XwfZLS75/4aGNigtoY
53Ui/QwQmKtZII7AB7V1AjEAntfvfSN1M9pshLFMV2CCDctp0955sxexDBasHXSN
VRFc5ZyUMUiWhyo8siO3bcxF
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIHMDCCBhigAwIBAgIQAkk+B/qeN1otu8YdlEMPzzANBgkqhkiG9w0BAQsFADBw
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMS8wLQYDVQQDEyZEaWdpQ2VydCBTSEEyIEhpZ2ggQXNz
dXJhbmNlIFNlcnZlciBDQTAeFw0yMDA1MDYwMDAwMDBaFw0yMjA0MTQxMjAwMDBa
MGoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T
YW4gRnJhbmNpc2NvMRUwEwYDVQQKEwxHaXRIdWIsIEluYy4xFzAVBgNVBAMTDnd3
dy5naXRodWIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsj49
6jJ99veEXO7WdxGQZ7idtCnDcjZqQeDiy6057SwXj9yDUVnqhwo/yII8+y6Jpk3g
75LpPpYNjiOwYp/JkpWbpBAd1FWlvXJo/eZS+TwuIYb7JSc2H3NDDKt2VV5SSKQd
XOkDNqq7BisOFp2/TYwCMZboLufwRR5fKxL0nTKIOCwpnH8k//UdWpvTgIixDGLY
QCwHt0fYEo49jFeDaKD4WMBPq6Tx1iKWBhw3HVc/OyvI3yjRAx4Anf/DCSt9YTW6
f/ND4O/fOowcfW5T7zii1Kw0yw+ulBrE/xe6taVhL+QR0MXNkQV2iHNN85swidwM
tcdGI8g3fYL48bSRywIDAQABo4IDyjCCA8YwHwYDVR0jBBgwFoAUUWj/kK8CB3U8
zNllZGKiErhZcjswHQYDVR0OBBYEFIygCmlH3IkysE3GEUViXxovlk46MHsGA1Ud
EQR0MHKCDnd3dy5naXRodWIuY29tggwqLmdpdGh1Yi5jb22CCmdpdGh1Yi5jb22C
CyouZ2l0aHViLmlvgglnaXRodWIuaW+CFyouZ2l0aHVidXNlcmNvbnRlbnQuY29t
ghVnaXRodWJ1c2VyY29udGVudC5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQW
MBQGCCsGAQUFBwMBBggrBgEFBQcDAjB1BgNVHR8EbjBsMDSgMqAwhi5odHRwOi8v
Y3JsMy5kaWdpY2VydC5jb20vc2hhMi1oYS1zZXJ2ZXItZzYuY3JsMDSgMqAwhi5o
dHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1oYS1zZXJ2ZXItZzYuY3JsMEwG
A1UdIARFMEMwNwYJYIZIAYb9bAEBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3
LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQICMIGDBggrBgEFBQcBAQR3MHUwJAYI
KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBNBggrBgEFBQcwAoZB
aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkhpZ2hBc3N1
cmFuY2VTZXJ2ZXJDQS5jcnQwDAYDVR0TAQH/BAIwADCCAX0GCisGAQQB1nkCBAIE
ggFtBIIBaQFnAHYARqVV63X6kSAwtaKJafTzfREsQXS+/Um4havy/HD+bUcAAAFx
6y8fFgAABAMARzBFAiEA59y6w9oaoAoM2fvFq6KofYWRh0xRm4VEEaMHBtsBYUgC
IBZxJhjA7SGWUlo57YslG8u6clHngDNvoTNVw1HQtTr3AHUAIkVFB1lVJFaWP6Ev
8fdthuAjJmOtwEt/XcaDXG7iDwIAAAFx6y8evwAABAMARjBEAiBmEjiioTbc1//h
CInYIX6O8hph5oLRVGCTxrTBfSRT2wIgZz7x3ZNIKQkWPKOFaaW3AxcB0DzhFsD6
gxhkbl1p0AgAdgBRo7D1/QF5nFZtuDd4jwykeswbJ8v3nohCmg3+1IsF5QAAAXHr
Lx8JAAAEAwBHMEUCIBQ/6El+TCCtWuop7IderN0+byn5sDreTu+Xz3GiY8cLAiEA
7S83HxFFdQhQqpjjbWbIVBA88Nn/riaf5Jb8h3oJV8cwDQYJKoZIhvcNAQELBQAD
ggEBAADzu/I/4dMPwG4QzMFHZmgQFlnc/xqXtaNLqONIzXPznBQmHQi481xKgAR4
jZOTTknlwOLBXnDXvV6rJQZXut3pxHSvVJk2kvuyDO3RC0uudd81AXIUsd6Pnjt2
D6Xd/ypUAoMkyE+8euYESEFk4HlnrpXtN7OSTGVYZQk0aJrDINslXdmUL9E6AQiI
YaRIpRMRdj4stG6CkPJpfSauWa19kReZ6hTQR5f89L6x50us7GuWlmH6EmVFIbhf
9EO02QA3CcU7bE1iLWMHmKcU6ythmgsvNRU5TikxvF77JFv7n1/y8GLrprmKpB6Q
Df4PA8S9ROX9Rzgwe3KTIM6qeKU=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIIGTCCBwGgAwIBAgIQDWRQa0XzDONabC3fLBi0NzANBgkqhkiG9w0BAQsFADBk
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSMwIQYDVQQDExpEaWdpQ2VydCBCYWx0aW1vcmUgQ0Et
MiBHMjAeFw0xOTExMDkwMDAwMDBaFw0yMDEyMDIxMjAwMDBaMGoxCzAJBgNVBAYT
MiBHMjAeFw0yMDA4MDQwMDAwMDBaFw0yMTA4MDkxMjAwMDBaMGoxCzAJBgNVBAYT
AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdTZWF0dGxlMRkwFwYD
VQQKExBBbWF6b24uY29tLCBJbmMuMRkwFwYDVQQDExBzMy5hbWF6b25hd3MuY29t
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjPufyACUmfDnxFBd2mD0
Mo9rTInLIf/z1Ow9OSeZP5pzIzJEwXlEmxYpqpfEm4dUEb90NhTXHMNTHn858ztb
2cH+0aRkmdCLN5z5F7gCX0fUSyh5zQs6OaUTBZZQnx4aK1BlYgyOo5fQ8ix0DOkL
oSWSorwjfjGqMSbl6sn+NqrUdCPe7rb7/CSiusB15AfgfaRKUh4IY7wmvnruE/xv
rz0YC6G5w040quV4bzUVXfux+z0HNYVPguQx4Rqqf0kx84jeI1U+4KuxToQNN1K3
U7MQyI3gH3hBbN1iIsWe8eJ4dXQqNqeeUGWISxXbC3FKuvZZjlyLFNV/5XUsGqzG
CQIDAQABo4IEnDCCBJgwHwYDVR0jBBgwFoAUwBKyKHRoRmfpcCV0GgBFWwZ9XEQw
HQYDVR0OBBYEFCB6dgTvZ8mMCcd7Vj3IKNu+80aLMIIBwQYDVR0RBIIBuDCCAbSC
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlLzYszLxTSSEdEQT7Lx7
yw1HDpWUKCO58oupRlEkpJqZcKpUa5n05zpVUQVERfseZx5MV7yaKZD+Pf2Cm373
nA8P0IkeLe7ZyURH1f0OdkU9y740Fn2BgA4Zs0bEPbKyp2J5pJsEBiDWX139PR9q
Obqp66lhS7Z6P9smMLxWFPx3Hg6oWUrYYnsXBPZD1/DsqKqhB6x4y4D01yeGpDVp
da+Xe04LM28ti5XJTmWpzp8+ZbYNWBYcvIvnBAfvTXSnCGQz1JRaOyBO/kKPrXWx
WkWE5EpR2wgk7PjqGXct/Bm6l8bpWc3zZ5Sap8iSpcbdibwEu1cYYDkHjlwgPiXE
awIDAQABo4IEvzCCBLswHwYDVR0jBBgwFoAUwBKyKHRoRmfpcCV0GgBFWwZ9XEQw
HQYDVR0OBBYEFIVEjpBQCk5Tm2dsfZt5LHMOx3+LMIIB5AYDVR0RBIIB2zCCAdeC
EHMzLmFtYXpvbmF3cy5jb22CEiouczMuYW1hem9uYXdzLmNvbYImKi5zMy5kdWFs
c3RhY2sudXMtZWFzdC0xLmFtYXpvbmF3cy5jb22CJHMzLmR1YWxzdGFjay51cy1l
YXN0LTEuYW1hem9uYXdzLmNvbYIcKi5zMy51cy1lYXN0LTEuYW1hem9uYXdzLmNv
@@ -115,24 +193,25 @@ YXdzLmNvbYIuKi5zMy1jb250cm9sLmR1YWxzdGFjay51cy1lYXN0LTEuYW1hem9u
YXdzLmNvbYIsczMtY29udHJvbC5kdWFsc3RhY2sudXMtZWFzdC0xLmFtYXpvbmF3
cy5jb22CKCouczMtYWNjZXNzcG9pbnQudXMtZWFzdC0xLmFtYXpvbmF3cy5jb22C
MiouczMtYWNjZXNzcG9pbnQuZHVhbHN0YWNrLnVzLWVhc3QtMS5hbWF6b25hd3Mu
Y29tMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH
AwIwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9E
aWdpQ2VydEJhbHRpbW9yZUNBLTJHMi5jcmwwOqA4oDaGNGh0dHA6Ly9jcmw0LmRp
Z2ljZXJ0LmNvbS9EaWdpQ2VydEJhbHRpbW9yZUNBLTJHMi5jcmwwTAYDVR0gBEUw
QzA3BglghkgBhv1sAQEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNl
cnQuY29tL0NQUzAIBgZngQwBAgIweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzAB
hhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9j
YWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEJhbHRpbW9yZUNBLTJHMi5jcnQw
DAYDVR0TAQH/BAIwADCCAQUGCisGAQQB1nkCBAIEgfYEgfMA8QB2ALvZ37wfinG1
k5Qjl6qSe0c4V5UKq1LoGpCWZDaOHtGFAAABbk2G29QAAAQDAEcwRQIgAed3N8sk
ohtjfh62k+G9Ko8rE9Dxulud26Whri4Wdu4CIQDKiPQ86THLwG19xTfIl4OCtpax
/96NQb+iKV0ocme1YwB3AId1v+dZfPiMQ5lfvfNu/1aNR1Y2/0q1YMG06v9eoIMP
AAABbk2G3CAAAAQDAEgwRgIhAPnRoKotFe+0VEznyOCGXrCRXPqOFm4fsl1yZ2iP
a1PKAiEAxhuoUzUkjk9qXPGcYKE3XvAqNtOPt2rWySvOXzcZAtEwDQYJKoZIhvcN
AQELBQADggEBAFnYbd/xMt/wq2i+P3fOBOL54fM0i+yPON6XbHkySTlElHbeJ6e6
Mgl/bmCRkk3LKnkJ5sGP48ix+RfY3KztzRYnaEZQpN5rRjJWktTch81gpJbrTY5Q
MNWfq2MtLmshRXiHF5jCZcccZhNb7ELHQip/5BXez0hfO0GBTGdnTqknBaW7xBAA
PYqv0MuvolkWfablAWfYSnmcVytwmBXwBhdtxF+HnzFyW8AKzqBjaXnORTEqE1y/
hnvtdZiN5YW4gFau+ci87tSJ3cxGj2yyJs2hLhZuIiPGVrJNvJcRcxxIyIVZmDVt
oRzKmaM8SQDRPMHZZLYD3jjxG2SEG8gY9F0=
Y29tgiEqLnMzLnVzLWVhc3QtMS52cGNlLmFtYXpvbmF3cy5jb20wDgYDVR0PAQH/
BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjCBgQYDVR0fBHow
eDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QmFsdGlt
b3JlQ0EtMkcyLmNybDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0Rp
Z2lDZXJ0QmFsdGltb3JlQ0EtMkcyLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwB
ATAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgG
BmeBDAECAjB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3Nw
LmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNl
cnQuY29tL0RpZ2lDZXJ0QmFsdGltb3JlQ0EtMkcyLmNydDAMBgNVHRMBAf8EAjAA
MIIBBQYKKwYBBAHWeQIEAgSB9gSB8wDxAHcA9lyUL9F3MCIUVBgIMJRWjuNNExkz
v98MLyALzE7xZOMAAAFzu071tgAABAMASDBGAiEAlGDJVuKxRHHlN/O3J6MYmENC
4vnJqp3SyGAexyhlE1cCIQDfXlm8NW4fGb/zCb4CDHrQcrJUDv/s8ORi5/M5aqQl
GAB2AFzcQ5L+5qtFRLFemtRW5hA3+9X6R9yhc5SyXub2xw7KAAABc7tO9eYAAAQD
AEcwRQIhAOfJXPwhpRvdgbLeu6l7pJ23OIvkpcczPjj9mdZBcYPtAiBCqDSLNRPF
dxdmdR+VBN4dOmbFGH4iCHYDDmybFvPFszANBgkqhkiG9w0BAQsFAAOCAQEAPE/F
VWxMK+CDCiGYXy1ND65HQDFC/lU6lbmywR4E4Lv9x6gpQj875wMG0RosWq1xT9i2
/2EGrcqDor7ER2to70K8Yv75/M9EzsY1wbdqfd5M3PUqccMLaMgmMKugqUqx90SG
nNsxJrRxJeuZpfWfjtAfZ+EyU650FlZ1m25KcJVaOuYDdL+XnxPKm7YShOwFs9mx
vBBUL4qDKKjROc7LkUvqoqa6QnXN92twtkMBnALF8GP24y+CLINS8rJCA117NMXf
x+JAorfCzDKa+P1lgCh3+V5Lnqvla2hwCyCnYAy1RR0y1UEUB8FUYj1/PIDs9RJX
cVq+ZBjAtIrm6j5b+Q==
-----END CERTIFICATE-----