mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 06:33:51 +00:00 
			
		
		
		
	[micro_wake_word] Version 2 (#7032)
This commit is contained in:
		| @@ -9,7 +9,7 @@ import requests | ||||
| import esphome.config_validation as cv | ||||
| import esphome.codegen as cg | ||||
|  | ||||
| from esphome.core import CORE, HexInt, EsphomeError | ||||
| from esphome.core import CORE, HexInt | ||||
|  | ||||
| from esphome.components import esp32, microphone | ||||
| from esphome import automation, git, external_files | ||||
| @@ -41,9 +41,15 @@ CODEOWNERS = ["@kahrendt", "@jesserockz"] | ||||
| DEPENDENCIES = ["microphone"] | ||||
| DOMAIN = "micro_wake_word" | ||||
|  | ||||
|  | ||||
| CONF_FEATURE_STEP_SIZE = "feature_step_size" | ||||
| CONF_MODELS = "models" | ||||
| CONF_ON_WAKE_WORD_DETECTED = "on_wake_word_detected" | ||||
| CONF_PROBABILITY_CUTOFF = "probability_cutoff" | ||||
| CONF_SLIDING_WINDOW_AVERAGE_SIZE = "sliding_window_average_size" | ||||
| CONF_ON_WAKE_WORD_DETECTED = "on_wake_word_detected" | ||||
| CONF_SLIDING_WINDOW_SIZE = "sliding_window_size" | ||||
| CONF_TENSOR_ARENA_SIZE = "tensor_arena_size" | ||||
| CONF_VAD = "vad" | ||||
|  | ||||
| TYPE_HTTP = "http" | ||||
|  | ||||
| @@ -98,12 +104,14 @@ GIT_SCHEMA = cv.All( | ||||
|     _process_git_source, | ||||
| ) | ||||
|  | ||||
| KEY_WAKE_WORD = "wake_word" | ||||
|  | ||||
| KEY_AUTHOR = "author" | ||||
| KEY_WEBSITE = "website" | ||||
| KEY_VERSION = "version" | ||||
| KEY_MICRO = "micro" | ||||
| KEY_MINIMUM_ESPHOME_VERSION = "minimum_esphome_version" | ||||
| KEY_TRAINED_LANGUAGES = "trained_languages" | ||||
| KEY_VERSION = "version" | ||||
| KEY_WAKE_WORD = "wake_word" | ||||
| KEY_WEBSITE = "website" | ||||
|  | ||||
| MANIFEST_SCHEMA_V1 = cv.Schema( | ||||
|     { | ||||
| @@ -125,6 +133,29 @@ MANIFEST_SCHEMA_V1 = cv.Schema( | ||||
|     } | ||||
| ) | ||||
|  | ||||
| MANIFEST_SCHEMA_V2 = cv.Schema( | ||||
|     { | ||||
|         cv.Required(CONF_TYPE): "micro", | ||||
|         cv.Required(CONF_MODEL): cv.string, | ||||
|         cv.Required(KEY_AUTHOR): cv.string, | ||||
|         cv.Required(KEY_VERSION): cv.All(cv.int_, 2), | ||||
|         cv.Required(KEY_WAKE_WORD): cv.string, | ||||
|         cv.Required(KEY_TRAINED_LANGUAGES): cv.ensure_list(cv.string), | ||||
|         cv.Optional(KEY_WEBSITE): cv.url, | ||||
|         cv.Required(KEY_MICRO): cv.Schema( | ||||
|             { | ||||
|                 cv.Required(CONF_FEATURE_STEP_SIZE): cv.int_range(min=0, max=30), | ||||
|                 cv.Required(CONF_TENSOR_ARENA_SIZE): cv.int_, | ||||
|                 cv.Required(CONF_PROBABILITY_CUTOFF): cv.float_, | ||||
|                 cv.Required(CONF_SLIDING_WINDOW_SIZE): cv.positive_int, | ||||
|                 cv.Required(KEY_MINIMUM_ESPHOME_VERSION): cv.All( | ||||
|                     cv.version_number, cv.validate_esphome_version | ||||
|                 ), | ||||
|             } | ||||
|         ), | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| def _compute_local_file_path(config: dict) -> Path: | ||||
|     url = config[CONF_URL] | ||||
| @@ -135,6 +166,24 @@ def _compute_local_file_path(config: dict) -> Path: | ||||
|     return base_dir / key | ||||
|  | ||||
|  | ||||
| def _convert_manifest_v1_to_v2(v1_manifest): | ||||
|     v2_manifest = v1_manifest.copy() | ||||
|  | ||||
|     v2_manifest[KEY_VERSION] = 2 | ||||
|     v2_manifest[KEY_MICRO][CONF_SLIDING_WINDOW_SIZE] = v1_manifest[KEY_MICRO][ | ||||
|         CONF_SLIDING_WINDOW_AVERAGE_SIZE | ||||
|     ] | ||||
|     del v2_manifest[KEY_MICRO][CONF_SLIDING_WINDOW_AVERAGE_SIZE] | ||||
|     v2_manifest[KEY_MICRO][ | ||||
|         CONF_TENSOR_ARENA_SIZE | ||||
|     ] = 45672  # Original Inception-based V1 manifest models require a minimum of 45672 bytes | ||||
|     v2_manifest[KEY_MICRO][ | ||||
|         CONF_FEATURE_STEP_SIZE | ||||
|     ] = 20  # Original Inception-based V1 manifest models use a 20 ms feature step size | ||||
|  | ||||
|     return v2_manifest | ||||
|  | ||||
|  | ||||
| def _download_file(url: str, path: Path) -> bytes: | ||||
|     if not external_files.has_remote_file_changed(url, path): | ||||
|         _LOGGER.debug("Remote file has not changed, skipping download") | ||||
| @@ -155,6 +204,24 @@ def _download_file(url: str, path: Path) -> bytes: | ||||
|     return req.content | ||||
|  | ||||
|  | ||||
| def _validate_manifest_version(manifest_data): | ||||
|     if manifest_version := manifest_data.get(KEY_VERSION): | ||||
|         if manifest_version == 1: | ||||
|             try: | ||||
|                 MANIFEST_SCHEMA_V1(manifest_data) | ||||
|             except cv.Invalid as e: | ||||
|                 raise cv.Invalid(f"Invalid manifest file: {e}") from e | ||||
|         elif manifest_version == 2: | ||||
|             try: | ||||
|                 MANIFEST_SCHEMA_V2(manifest_data) | ||||
|             except cv.Invalid as e: | ||||
|                 raise cv.Invalid(f"Invalid manifest file: {e}") from e | ||||
|         else: | ||||
|             raise cv.Invalid("Invalid manifest version") | ||||
|     else: | ||||
|         raise cv.Invalid("Invalid manifest file, missing 'version' key.") | ||||
|  | ||||
|  | ||||
| def _process_http_source(config): | ||||
|     url = config[CONF_URL] | ||||
|     path = _compute_local_file_path(config) | ||||
| @@ -167,11 +234,6 @@ def _process_http_source(config): | ||||
|     if not isinstance(manifest_data, dict): | ||||
|         raise cv.Invalid("Manifest file must contain a JSON object") | ||||
|  | ||||
|     try: | ||||
|         MANIFEST_SCHEMA_V1(manifest_data) | ||||
|     except cv.Invalid as e: | ||||
|         raise cv.Invalid(f"Invalid manifest file: {e}") from e | ||||
|  | ||||
|     model = manifest_data[CONF_MODEL] | ||||
|     model_url = urljoin(url, model) | ||||
|  | ||||
| @@ -206,7 +268,7 @@ def _validate_source_model_name(value): | ||||
|     return MODEL_SOURCE_SCHEMA( | ||||
|         { | ||||
|             CONF_TYPE: TYPE_HTTP, | ||||
|             CONF_URL: f"https://github.com/esphome/micro-wake-word-models/raw/main/models/{value}.json", | ||||
|             CONF_URL: f"https://github.com/esphome/micro-wake-word-models/raw/main/models/v2/{value}.json", | ||||
|         } | ||||
|     ) | ||||
|  | ||||
| @@ -260,18 +322,55 @@ MODEL_SOURCE_SCHEMA = cv.Any( | ||||
|     msg="Not a valid model name, local path, http(s) url, or github shorthand", | ||||
| ) | ||||
|  | ||||
| MODEL_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.Optional(CONF_MODEL): MODEL_SOURCE_SCHEMA, | ||||
|         cv.Optional(CONF_PROBABILITY_CUTOFF): cv.percentage, | ||||
|         cv.Optional(CONF_SLIDING_WINDOW_SIZE): cv.positive_int, | ||||
|         cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), | ||||
|     } | ||||
| ) | ||||
|  | ||||
| # Provide a default VAD model that could be overridden | ||||
| VAD_MODEL_SCHEMA = MODEL_SCHEMA.extend( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.Optional( | ||||
|                 CONF_MODEL, | ||||
|                 default="vad", | ||||
|             ): MODEL_SOURCE_SCHEMA, | ||||
|         } | ||||
|     ) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def _maybe_empty_vad_schema(value): | ||||
|     # Idea borrowed from uart/__init__.py's ``maybe_empty_debug`` function. Accessed 2 July 2024. | ||||
|     # Loads a default VAD model without any parameters overridden. | ||||
|     if value is None: | ||||
|         value = {} | ||||
|     return VAD_MODEL_SCHEMA(value) | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(MicroWakeWord), | ||||
|             cv.GenerateID(CONF_MICROPHONE): cv.use_id(microphone.Microphone), | ||||
|             cv.Optional(CONF_PROBABILITY_CUTOFF): cv.percentage, | ||||
|             cv.Optional(CONF_SLIDING_WINDOW_AVERAGE_SIZE): cv.positive_int, | ||||
|             cv.Required(CONF_MODELS): cv.ensure_list(MODEL_SCHEMA), | ||||
|             cv.Optional(CONF_ON_WAKE_WORD_DETECTED): automation.validate_automation( | ||||
|                 single=True | ||||
|             ), | ||||
|             cv.Required(CONF_MODEL): MODEL_SOURCE_SCHEMA, | ||||
|             cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), | ||||
|             cv.Optional(CONF_VAD): _maybe_empty_vad_schema, | ||||
|             cv.Optional(CONF_MODEL): cv.invalid( | ||||
|                 f"The {CONF_MODEL} parameter has moved to be a list element under the {CONF_MODELS} parameter." | ||||
|             ), | ||||
|             cv.Optional(CONF_PROBABILITY_CUTOFF): cv.invalid( | ||||
|                 f"The {CONF_PROBABILITY_CUTOFF} parameter has moved to be a list element under the {CONF_MODELS} parameter." | ||||
|             ), | ||||
|             cv.Optional(CONF_SLIDING_WINDOW_AVERAGE_SIZE): cv.invalid( | ||||
|                 f"The {CONF_SLIDING_WINDOW_AVERAGE_SIZE} parameter has been renamed to {CONF_SLIDING_WINDOW_SIZE} and moved to be a list element under the {CONF_MODELS} parameter." | ||||
|             ), | ||||
|         } | ||||
|     ).extend(cv.COMPONENT_SCHEMA), | ||||
|     cv.only_with_esp_idf, | ||||
| @@ -282,45 +381,20 @@ def _load_model_data(manifest_path: Path): | ||||
|     with open(manifest_path, encoding="utf-8") as f: | ||||
|         manifest = json.load(f) | ||||
|  | ||||
|     try: | ||||
|         MANIFEST_SCHEMA_V1(manifest) | ||||
|     except cv.Invalid as e: | ||||
|         raise EsphomeError(f"Invalid manifest file: {e}") from e | ||||
|     _validate_manifest_version(manifest) | ||||
|  | ||||
|     model_path = manifest_path.parent / manifest[CONF_MODEL] | ||||
|  | ||||
|     with open(model_path, "rb") as f: | ||||
|         model = f.read() | ||||
|  | ||||
|     if manifest.get(KEY_VERSION) == 1: | ||||
|         manifest = _convert_manifest_v1_to_v2(manifest) | ||||
|  | ||||
|     return manifest, model | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|  | ||||
|     mic = await cg.get_variable(config[CONF_MICROPHONE]) | ||||
|     cg.add(var.set_microphone(mic)) | ||||
|  | ||||
|     if on_wake_word_detection_config := config.get(CONF_ON_WAKE_WORD_DETECTED): | ||||
|         await automation.build_automation( | ||||
|             var.get_wake_word_detected_trigger(), | ||||
|             [(cg.std_string, "wake_word")], | ||||
|             on_wake_word_detection_config, | ||||
|         ) | ||||
|  | ||||
|     esp32.add_idf_component( | ||||
|         name="esp-tflite-micro", | ||||
|         repo="https://github.com/espressif/esp-tflite-micro", | ||||
|         ref="v1.3.1", | ||||
|     ) | ||||
|  | ||||
|     cg.add_build_flag("-DTF_LITE_STATIC_MEMORY") | ||||
|     cg.add_build_flag("-DTF_LITE_DISABLE_X86_NEON") | ||||
|     cg.add_build_flag("-DESP_NN") | ||||
|  | ||||
|     model_config = config.get(CONF_MODEL) | ||||
|     data = [] | ||||
| def _model_config_to_manifest_data(model_config): | ||||
|     if model_config[CONF_TYPE] == TYPE_GIT: | ||||
|         # compute path to model file | ||||
|         key = f"{model_config[CONF_URL]}@{model_config.get(CONF_REF)}" | ||||
| @@ -338,23 +412,95 @@ async def to_code(config): | ||||
|     else: | ||||
|         raise ValueError("Unsupported config type: {model_config[CONF_TYPE]}") | ||||
|  | ||||
|     manifest, data = _load_model_data(file) | ||||
|     return _load_model_data(file) | ||||
|  | ||||
|     rhs = [HexInt(x) for x in data] | ||||
|     prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) | ||||
|     cg.add(var.set_model_start(prog_arr)) | ||||
|  | ||||
|     probability_cutoff = config.get( | ||||
|         CONF_PROBABILITY_CUTOFF, manifest[KEY_MICRO][CONF_PROBABILITY_CUTOFF] | ||||
| def _feature_step_size_validate(config): | ||||
|     features_step_size = None | ||||
|  | ||||
|     for model_parameters in config[CONF_MODELS]: | ||||
|         model_config = model_parameters.get(CONF_MODEL) | ||||
|         manifest, _ = _model_config_to_manifest_data(model_config) | ||||
|  | ||||
|         model_step_size = manifest[KEY_MICRO][CONF_FEATURE_STEP_SIZE] | ||||
|  | ||||
|         if features_step_size is None: | ||||
|             features_step_size = model_step_size | ||||
|         elif features_step_size != model_step_size: | ||||
|             raise cv.Invalid("Cannot load models with different features step sizes.") | ||||
|  | ||||
|  | ||||
| FINAL_VALIDATE_SCHEMA = _feature_step_size_validate | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|  | ||||
|     mic = await cg.get_variable(config[CONF_MICROPHONE]) | ||||
|     cg.add(var.set_microphone(mic)) | ||||
|  | ||||
|     esp32.add_idf_component( | ||||
|         name="esp-tflite-micro", | ||||
|         repo="https://github.com/espressif/esp-tflite-micro", | ||||
|         ref="v1.3.1", | ||||
|     ) | ||||
|     cg.add(var.set_probability_cutoff(probability_cutoff)) | ||||
|     sliding_window_average_size = config.get( | ||||
|         CONF_SLIDING_WINDOW_AVERAGE_SIZE, | ||||
|         manifest[KEY_MICRO][CONF_SLIDING_WINDOW_AVERAGE_SIZE], | ||||
|     ) | ||||
|     cg.add(var.set_sliding_window_average_size(sliding_window_average_size)) | ||||
|  | ||||
|     cg.add(var.set_wake_word(manifest[KEY_WAKE_WORD])) | ||||
|     cg.add_build_flag("-DTF_LITE_STATIC_MEMORY") | ||||
|     cg.add_build_flag("-DTF_LITE_DISABLE_X86_NEON") | ||||
|     cg.add_build_flag("-DESP_NN") | ||||
|  | ||||
|     if on_wake_word_detection_config := config.get(CONF_ON_WAKE_WORD_DETECTED): | ||||
|         await automation.build_automation( | ||||
|             var.get_wake_word_detected_trigger(), | ||||
|             [(cg.std_string, "wake_word")], | ||||
|             on_wake_word_detection_config, | ||||
|         ) | ||||
|  | ||||
|     if vad_model := config.get(CONF_VAD): | ||||
|         cg.add_define("USE_MICRO_WAKE_WORD_VAD") | ||||
|  | ||||
|         # Use the general model loading code for the VAD codegen | ||||
|         config[CONF_MODELS].append(vad_model) | ||||
|  | ||||
|     for model_parameters in config[CONF_MODELS]: | ||||
|         model_config = model_parameters.get(CONF_MODEL) | ||||
|         data = [] | ||||
|         manifest, data = _model_config_to_manifest_data(model_config) | ||||
|  | ||||
|         rhs = [HexInt(x) for x in data] | ||||
|         prog_arr = cg.progmem_array(model_parameters[CONF_RAW_DATA_ID], rhs) | ||||
|  | ||||
|         probability_cutoff = model_parameters.get( | ||||
|             CONF_PROBABILITY_CUTOFF, manifest[KEY_MICRO][CONF_PROBABILITY_CUTOFF] | ||||
|         ) | ||||
|         sliding_window_size = model_parameters.get( | ||||
|             CONF_SLIDING_WINDOW_SIZE, | ||||
|             manifest[KEY_MICRO][CONF_SLIDING_WINDOW_SIZE], | ||||
|         ) | ||||
|  | ||||
|         if manifest[KEY_WAKE_WORD] == "vad": | ||||
|             cg.add( | ||||
|                 var.add_vad_model( | ||||
|                     prog_arr, | ||||
|                     probability_cutoff, | ||||
|                     sliding_window_size, | ||||
|                     manifest[KEY_MICRO][CONF_TENSOR_ARENA_SIZE], | ||||
|                 ) | ||||
|             ) | ||||
|         else: | ||||
|             cg.add( | ||||
|                 var.add_wake_word_model( | ||||
|                     prog_arr, | ||||
|                     probability_cutoff, | ||||
|                     sliding_window_size, | ||||
|                     manifest[KEY_WAKE_WORD], | ||||
|                     manifest[KEY_MICRO][CONF_TENSOR_ARENA_SIZE], | ||||
|                 ) | ||||
|             ) | ||||
|  | ||||
|     cg.add(var.set_features_step_size(manifest[KEY_MICRO][CONF_FEATURE_STEP_SIZE])) | ||||
|     cg.add_library("kahrendt/ESPMicroSpeechFeatures", "1.0.0") | ||||
|  | ||||
|  | ||||
| MICRO_WAKE_WORD_ACTION_SCHEMA = cv.Schema({cv.GenerateID(): cv.use_id(MicroWakeWord)}) | ||||
|   | ||||
| @@ -1,493 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| #ifdef USE_ESP_IDF | ||||
|  | ||||
| // Converted audio_preprocessor_int8.tflite | ||||
| // From https://github.com/tensorflow/tflite-micro/tree/main/tensorflow/lite/micro/examples/micro_speech/models accessed | ||||
| // January 2024 | ||||
| // | ||||
| // Copyright 2023 The TensorFlow Authors. All Rights Reserved. | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| namespace esphome { | ||||
| namespace micro_wake_word { | ||||
|  | ||||
| const unsigned char G_AUDIO_PREPROCESSOR_INT8_TFLITE[] = { | ||||
|     0x1c, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x14, 0x00, 0x20, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, | ||||
|     0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x88, 0x00, | ||||
|     0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x80, 0x0e, 0x00, 0x00, 0x90, 0x0e, 0x00, 0x00, 0xcc, 0x1f, 0x00, 0x00, 0x03, | ||||
|     0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xe2, 0xeb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, | ||||
|     0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, | ||||
|     0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x94, 0xff, | ||||
|     0xff, 0xff, 0x2a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x6f, 0x75, 0x74, 0x70, 0x75, | ||||
|     0x74, 0x5f, 0x30, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xc2, 0xf5, 0xff, 0xff, | ||||
|     0x04, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, | ||||
|     0x00, 0x02, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xdc, 0xff, 0xff, 0xff, 0x2d, 0x00, | ||||
|     0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x43, 0x4f, 0x4e, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, | ||||
|     0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00, | ||||
|     0x08, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x6d, 0x69, 0x6e, | ||||
|     0x5f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x2e, 0x00, | ||||
|     0x00, 0x00, 0x9c, 0x0d, 0x00, 0x00, 0x94, 0x0d, 0x00, 0x00, 0xc4, 0x09, 0x00, 0x00, 0x6c, 0x09, 0x00, 0x00, 0x48, | ||||
|     0x09, 0x00, 0x00, 0x34, 0x09, 0x00, 0x00, 0x20, 0x09, 0x00, 0x00, 0x0c, 0x09, 0x00, 0x00, 0xf8, 0x08, 0x00, 0x00, | ||||
|     0xec, 0x07, 0x00, 0x00, 0x88, 0x07, 0x00, 0x00, 0x24, 0x07, 0x00, 0x00, 0xc0, 0x06, 0x00, 0x00, 0x38, 0x04, 0x00, | ||||
|     0x00, 0xb0, 0x01, 0x00, 0x00, 0x9c, 0x01, 0x00, 0x00, 0x88, 0x01, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x60, 0x01, | ||||
|     0x00, 0x00, 0x4c, 0x01, 0x00, 0x00, 0x44, 0x01, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x34, 0x01, 0x00, 0x00, 0x2c, | ||||
|     0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x00, 0x1c, 0x01, 0x00, 0x00, 0x14, 0x01, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00, | ||||
|     0x04, 0x01, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0xec, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, | ||||
|     0x00, 0xdc, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0xbc, 0x00, | ||||
|     0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x94, | ||||
|     0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xf2, 0xf6, 0xff, 0xff, | ||||
|     0x04, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, | ||||
|     0x00, 0x08, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00, | ||||
|     0x07, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x0a, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, | ||||
|     0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x32, 0x2e, 0x31, 0x32, 0x2e, 0x30, 0x00, | ||||
|     0x00, 0x56, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x32, 0x2e, 0x38, 0x2e, 0x30, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0xe1, 0xff, 0xff, 0xd8, 0xe1, 0xff, 0xff, 0xdc, | ||||
|     0xe1, 0xff, 0xff, 0xe0, 0xe1, 0xff, 0xff, 0xe4, 0xe1, 0xff, 0xff, 0xe8, 0xe1, 0xff, 0xff, 0xec, 0xe1, 0xff, 0xff, | ||||
|     0xf0, 0xe1, 0xff, 0xff, 0xf4, 0xe1, 0xff, 0xff, 0xf8, 0xe1, 0xff, 0xff, 0xfc, 0xe1, 0xff, 0xff, 0x00, 0xe2, 0xff, | ||||
|     0xff, 0x04, 0xe2, 0xff, 0xff, 0x08, 0xe2, 0xff, 0xff, 0x0c, 0xe2, 0xff, 0xff, 0x10, 0xe2, 0xff, 0xff, 0x14, 0xe2, | ||||
|     0xff, 0xff, 0x18, 0xe2, 0xff, 0xff, 0x1c, 0xe2, 0xff, 0xff, 0x20, 0xe2, 0xff, 0xff, 0x24, 0xe2, 0xff, 0xff, 0x28, | ||||
|     0xe2, 0xff, 0xff, 0x2c, 0xe2, 0xff, 0xff, 0x30, 0xe2, 0xff, 0xff, 0xd2, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, | ||||
|     0x04, 0x00, 0x00, 0x00, 0x9a, 0x02, 0x00, 0x00, 0xe2, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x01, 0x00, 0x00, 0xf2, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x80, 0xff, | ||||
|     0xff, 0xff, 0x02, 0xf8, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x12, | ||||
|     0xf8, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x22, 0xf8, 0xff, 0xff, | ||||
|     0x04, 0x00, 0x00, 0x00, 0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x61, 0x05, 0x00, 0x00, 0x00, 0x00, 0x23, 0x0b, 0x41, | ||||
|     0x01, 0x00, 0x00, 0x00, 0x00, 0xb3, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x0e, 0x80, 0x05, | ||||
|     0x00, 0x00, 0x00, 0x00, 0xd1, 0x0c, 0x63, 0x04, 0x00, 0x00, 0x00, 0x00, 0x34, 0x0c, 0x3f, 0x04, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x81, 0x0c, 0xf7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x9f, 0x0d, 0x77, 0x06, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x0f, | ||||
|     0xa9, 0x08, 0x01, 0x02, 0x7f, 0x0b, 0x22, 0x05, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x0e, 0xd1, 0x08, 0xdb, 0x02, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x03, 0x0d, 0x4a, 0x07, 0xad, 0x01, 0x2c, 0x0c, 0xc6, 0x06, 0x79, 0x01, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x45, 0x0c, 0x29, 0x07, 0x23, 0x02, 0x34, 0x0d, 0x5b, 0x08, 0x96, 0x03, 0x00, 0x00, 0x00, 0x00, 0xe5, 0x0e, 0x48, | ||||
|     0x0a, 0xbd, 0x05, 0x45, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x0c, 0x88, 0x08, 0x43, 0x04, | ||||
|     0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x0b, 0xd3, 0x07, 0xcb, 0x03, 0xd2, 0x0f, 0xe7, | ||||
|     0x0b, 0x09, 0x08, 0x39, 0x04, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x0c, 0x14, 0x09, | ||||
|     0x75, 0x05, 0xe2, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x0e, 0xdd, 0x0a, 0x6b, 0x07, 0x03, | ||||
|     0x04, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x0d, 0x09, 0x0a, 0xc9, 0x06, 0x93, 0x03, 0x65, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x0d, 0x25, 0x0a, 0x12, 0x07, 0x07, 0x04, 0x05, 0x01, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x0a, 0x0e, 0x17, 0x0b, 0x2c, 0x08, 0x49, 0x05, 0x6d, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x98, 0x0f, 0xcb, 0x0c, 0x04, 0x0a, 0x44, 0x07, 0x8b, 0x04, 0xd8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x0f, 0x87, | ||||
|     0x0c, 0xe7, 0x09, 0x4e, 0x07, 0xba, 0x04, 0x2d, 0x02, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x0f, 0x23, 0x0d, 0xa7, 0x0a, | ||||
|     0x30, 0x08, 0xbe, 0x05, 0x52, 0x03, 0xeb, 0x00, 0x89, 0x0e, 0x2c, 0x0c, 0xd4, 0x09, 0x81, 0x07, 0x33, 0x05, 0xe9, | ||||
|     0x02, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x0e, 0x29, 0x0c, 0xf1, 0x09, 0xbe, 0x07, 0x90, 0x05, 0x65, 0x03, | ||||
|     0x3f, 0x01, 0x1d, 0x0f, 0xff, 0x0c, 0xe5, 0x0a, 0xcf, 0x08, 0xbc, 0x06, 0xae, 0x04, 0xa3, 0x02, 0x9c, 0x00, 0x99, | ||||
|     0x0e, 0x99, 0x0c, 0x9d, 0x0a, 0xa4, 0x08, 0xaf, 0x06, 0xbd, 0x04, 0xcf, 0x02, 0xe4, 0x00, 0xfc, 0x0e, 0x17, 0x0d, | ||||
|     0x36, 0x0b, 0x57, 0x09, 0x7c, 0x07, 0xa4, 0x05, 0xcf, 0x03, 0xfd, 0x01, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x62, 0x0e, 0x98, 0x0c, 0xd2, 0x0a, 0x0e, 0x09, 0x4d, 0x07, 0x8f, 0x05, 0xd4, 0x03, 0x1b, 0x02, | ||||
|     0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x0e, 0x00, 0x0d, 0x52, 0x0b, 0xa6, 0x09, 0xfd, 0x07, 0x56, 0x06, 0xb1, | ||||
|     0x04, 0x0f, 0x03, 0x6f, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0x0f, 0x37, 0x0e, 0x9e, 0x0c, | ||||
|     0x08, 0x0b, 0x73, 0x09, 0xe1, 0x07, 0x52, 0x06, 0xc4, 0x04, 0x38, 0x03, 0xaf, 0x01, 0x28, 0x00, 0xa3, 0x0e, 0x1f, | ||||
|     0x0d, 0x9e, 0x0b, 0x1f, 0x0a, 0xa2, 0x08, 0x27, 0x07, 0xae, 0x05, 0x37, 0x04, 0xc2, 0x02, 0x4e, 0x01, 0x00, 0x00, | ||||
|     0x00, 0x00, 0xdd, 0x0f, 0x6d, 0x0e, 0xff, 0x0c, 0x93, 0x0b, 0x29, 0x0a, 0xc1, 0x08, 0x5a, 0x07, 0xf5, 0x05, 0x92, | ||||
|     0x04, 0x30, 0x03, 0xd1, 0x01, 0x73, 0x00, 0x16, 0x0f, 0xbc, 0x0d, 0x62, 0x0c, 0x0b, 0x0b, 0xb5, 0x09, 0x61, 0x08, | ||||
|     0x0e, 0x07, 0xbd, 0x05, 0x6d, 0x04, 0x1f, 0x03, 0xd3, 0x01, 0x88, 0x00, 0x3e, 0x0f, 0xf6, 0x0d, 0xaf, 0x0c, 0x6a, | ||||
|     0x0b, 0x27, 0x0a, 0xe4, 0x08, 0xa3, 0x07, 0x64, 0x06, 0x26, 0x05, 0xe9, 0x03, 0xae, 0x02, 0x74, 0x01, 0x3b, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0f, 0xce, 0x0d, 0x99, 0x0c, 0x66, 0x0b, 0x34, 0x0a, 0x03, | ||||
|     0x09, 0xd3, 0x07, 0xa5, 0x06, 0x78, 0x05, 0x4c, 0x04, 0x22, 0x03, 0xf8, 0x01, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0xa9, 0x0f, 0x83, 0x0e, 0x5f, 0x0d, 0x3b, 0x0c, 0x19, 0x0b, 0xf8, 0x09, 0xd8, 0x08, 0xb9, 0x07, 0x9b, 0x06, 0x7e, | ||||
|     0x05, 0x63, 0x04, 0x48, 0x03, 0x2f, 0x02, 0x17, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa6, 0xfa, 0xff, 0xff, 0x04, 0x00, | ||||
|     0x00, 0x00, 0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x9e, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xdc, 0x04, 0xbe, 0x0e, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x4c, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x01, 0x7f, 0x0a, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x2e, 0x03, 0x9c, 0x0b, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x03, 0xc0, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x7e, | ||||
|     0x03, 0x08, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x60, 0x02, 0x88, 0x09, 0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x56, 0x07, | ||||
|     0xfe, 0x0d, 0x80, 0x04, 0xdd, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x16, 0x01, 0x2e, 0x07, 0x24, 0x0d, 0x00, 0x00, 0x00, | ||||
|     0x00, 0xfc, 0x02, 0xb5, 0x08, 0x52, 0x0e, 0xd3, 0x03, 0x39, 0x09, 0x86, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xba, 0x03, | ||||
|     0xd6, 0x08, 0xdc, 0x0d, 0xcb, 0x02, 0xa4, 0x07, 0x69, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x01, 0xb7, 0x05, 0x42, | ||||
|     0x0a, 0xba, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x03, 0x77, 0x07, 0xbc, 0x0b, 0xf1, 0x0f, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x04, 0x2c, 0x08, 0x34, 0x0c, 0x2d, 0x00, 0x18, 0x04, 0xf6, | ||||
|     0x07, 0xc6, 0x0b, 0x89, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0xeb, 0x06, 0x8a, 0x0a, | ||||
|     0x1d, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x01, 0x22, 0x05, 0x94, 0x08, 0xfc, 0x0b, 0x59, | ||||
|     0x0f, 0x00, 0x00, 0x00, 0x00, 0xac, 0x02, 0xf6, 0x05, 0x36, 0x09, 0x6c, 0x0c, 0x9a, 0x0f, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0xbe, 0x02, 0xda, 0x05, 0xed, 0x08, 0xf8, 0x0b, 0xfa, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xf5, | ||||
|     0x01, 0xe8, 0x04, 0xd3, 0x07, 0xb6, 0x0a, 0x92, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00, | ||||
|     0x34, 0x03, 0xfb, 0x05, 0xbb, 0x08, 0x74, 0x0b, 0x27, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x00, 0x78, 0x03, 0x18, | ||||
|     0x06, 0xb1, 0x08, 0x45, 0x0b, 0xd2, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x00, 0xdc, 0x02, 0x58, 0x05, 0xcf, 0x07, | ||||
|     0x41, 0x0a, 0xad, 0x0c, 0x14, 0x0f, 0x76, 0x01, 0xd3, 0x03, 0x2b, 0x06, 0x7e, 0x08, 0xcc, 0x0a, 0x16, 0x0d, 0x5a, | ||||
|     0x0f, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x01, 0xd6, 0x03, 0x0e, 0x06, 0x41, 0x08, 0x6f, 0x0a, 0x9a, 0x0c, 0xc0, 0x0e, | ||||
|     0xe2, 0x00, 0x00, 0x03, 0x1a, 0x05, 0x30, 0x07, 0x43, 0x09, 0x51, 0x0b, 0x5c, 0x0d, 0x63, 0x0f, 0x66, 0x01, 0x66, | ||||
|     0x03, 0x62, 0x05, 0x5b, 0x07, 0x50, 0x09, 0x42, 0x0b, 0x30, 0x0d, 0x1b, 0x0f, 0x03, 0x01, 0xe8, 0x02, 0xc9, 0x04, | ||||
|     0xa8, 0x06, 0x83, 0x08, 0x5b, 0x0a, 0x30, 0x0c, 0x02, 0x0e, 0xd1, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x9d, 0x01, 0x67, 0x03, 0x2d, 0x05, 0xf1, 0x06, 0xb2, 0x08, 0x70, 0x0a, 0x2b, 0x0c, 0xe4, 0x0d, 0x9a, 0x0f, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x4e, 0x01, 0xff, 0x02, 0xad, 0x04, 0x59, 0x06, 0x02, 0x08, 0xa9, 0x09, 0x4e, 0x0b, 0xf0, | ||||
|     0x0c, 0x90, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0xc8, 0x01, 0x61, 0x03, 0xf7, 0x04, | ||||
|     0x8c, 0x06, 0x1e, 0x08, 0xad, 0x09, 0x3b, 0x0b, 0xc7, 0x0c, 0x50, 0x0e, 0xd7, 0x0f, 0x5c, 0x01, 0xe0, 0x02, 0x61, | ||||
|     0x04, 0xe0, 0x05, 0x5d, 0x07, 0xd8, 0x08, 0x51, 0x0a, 0xc8, 0x0b, 0x3d, 0x0d, 0xb1, 0x0e, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x22, 0x00, 0x92, 0x01, 0x00, 0x03, 0x6c, 0x04, 0xd6, 0x05, 0x3e, 0x07, 0xa5, 0x08, 0x0a, 0x0a, 0x6d, 0x0b, 0xcf, | ||||
|     0x0c, 0x2e, 0x0e, 0x8c, 0x0f, 0xe9, 0x00, 0x43, 0x02, 0x9d, 0x03, 0xf4, 0x04, 0x4a, 0x06, 0x9e, 0x07, 0xf1, 0x08, | ||||
|     0x42, 0x0a, 0x92, 0x0b, 0xe0, 0x0c, 0x2c, 0x0e, 0x77, 0x0f, 0xc1, 0x00, 0x09, 0x02, 0x50, 0x03, 0x95, 0x04, 0xd8, | ||||
|     0x05, 0x1b, 0x07, 0x5c, 0x08, 0x9b, 0x09, 0xd9, 0x0a, 0x16, 0x0c, 0x51, 0x0d, 0x8b, 0x0e, 0xc4, 0x0f, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb, 0x00, 0x31, 0x02, 0x66, 0x03, 0x99, 0x04, 0xcb, 0x05, 0xfc, 0x06, 0x2c, | ||||
|     0x08, 0x5a, 0x09, 0x87, 0x0a, 0xb3, 0x0b, 0xdd, 0x0c, 0x07, 0x0e, 0x2f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x56, 0x00, | ||||
|     0x7c, 0x01, 0xa0, 0x02, 0xc4, 0x03, 0xe6, 0x04, 0x07, 0x06, 0x27, 0x07, 0x46, 0x08, 0x64, 0x09, 0x81, 0x0a, 0x9c, | ||||
|     0x0b, 0xb7, 0x0c, 0xd0, 0x0d, 0xe8, 0x0e, 0x00, 0x10, 0x00, 0x00, 0x2a, 0xfd, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, | ||||
|     0x52, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0e, 0x00, 0x10, | ||||
|     0x00, 0x12, 0x00, 0x16, 0x00, 0x18, 0x00, 0x1a, 0x00, 0x1e, 0x00, 0x20, 0x00, 0x24, 0x00, 0x26, 0x00, 0x2a, 0x00, | ||||
|     0x2e, 0x00, 0x32, 0x00, 0x36, 0x00, 0x3a, 0x00, 0x40, 0x00, 0x44, 0x00, 0x4a, 0x00, 0x4e, 0x00, 0x54, 0x00, 0x5a, | ||||
|     0x00, 0x62, 0x00, 0x68, 0x00, 0x70, 0x00, 0x78, 0x00, 0x80, 0x00, 0x88, 0x00, 0x92, 0x00, 0x9a, 0x00, 0xa6, 0x00, | ||||
|     0xb0, 0x00, 0xbc, 0x00, 0xc8, 0x00, 0xd4, 0x00, 0xe2, 0x00, 0x00, 0x00, 0x8a, 0xfd, 0xff, 0xff, 0x04, 0x00, 0x00, | ||||
|     0x00, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x14, 0x00, 0x18, 0x00, | ||||
|     0x1c, 0x00, 0x20, 0x00, 0x24, 0x00, 0x28, 0x00, 0x2c, 0x00, 0x30, 0x00, 0x34, 0x00, 0x38, 0x00, 0x3c, 0x00, 0x44, | ||||
|     0x00, 0x4c, 0x00, 0x50, 0x00, 0x58, 0x00, 0x60, 0x00, 0x68, 0x00, 0x70, 0x00, 0x78, 0x00, 0x80, 0x00, 0x88, 0x00, | ||||
|     0x90, 0x00, 0x98, 0x00, 0xa0, 0x00, 0xa8, 0x00, 0xb0, 0x00, 0xb8, 0x00, 0xc4, 0x00, 0xd0, 0x00, 0xdc, 0x00, 0xe8, | ||||
|     0x00, 0xf4, 0x00, 0x00, 0x01, 0x0c, 0x01, 0x1c, 0x01, 0x2c, 0x01, 0x00, 0x00, 0xea, 0xfd, 0xff, 0xff, 0x04, 0x00, | ||||
|     0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, | ||||
|     0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, | ||||
|     0x08, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, | ||||
|     0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, | ||||
|     0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x4a, 0xfe, 0xff, 0xff, 0x04, | ||||
|     0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x7c, 0x7f, 0x79, 0x7f, 0x76, 0x7f, 0xfa, 0xff, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x70, 0x7f, 0xf4, 0xff, 0x00, 0x00, 0x00, 0x00, 0x64, 0x7f, 0xe9, 0xff, 0xfe, 0xff, 0x00, 0x00, 0x4b, 0x7f, 0xd0, | ||||
|     0xff, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x7f, 0xa0, 0xff, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x7e, 0x42, 0xff, 0x00, 0x00, | ||||
|     0x00, 0x00, 0xfd, 0x7d, 0x86, 0xfe, 0x04, 0x00, 0x00, 0x00, 0x87, 0x7c, 0x1d, 0xfd, 0x12, 0x00, 0x00, 0x00, 0xb6, | ||||
|     0x79, 0x7f, 0xfa, 0x3e, 0x00, 0x00, 0x00, 0x73, 0x74, 0xf9, 0xf5, 0xca, 0x00, 0x00, 0x00, 0x36, 0x6b, 0x33, 0xef, | ||||
|     0x32, 0x02, 0x00, 0x00, 0x9b, 0x5c, 0x87, 0xe7, 0xce, 0x04, 0x00, 0x00, 0xf0, 0x48, 0xde, 0xe2, 0xa0, 0x07, 0x00, | ||||
|     0x00, 0x6e, 0x33, 0x8a, 0xe4, 0xa4, 0x08, 0x00, 0x00, 0x9c, 0x20, 0x22, 0xeb, 0x4c, 0x07, 0x00, 0x00, 0x0a, 0x13, | ||||
|     0x7d, 0xf2, 0x02, 0x05, 0x00, 0x00, 0x89, 0x0a, 0x17, 0xf8, 0x06, 0x03, 0x00, 0x00, 0xa6, 0x05, 0xa0, 0xfb, 0xb4, | ||||
|     0x01, 0x00, 0x00, 0xfa, 0x02, 0xac, 0xfd, 0xe8, 0x00, 0x00, 0x00, 0x8e, 0x01, 0xc7, 0xfe, 0x7a, 0x00, 0x00, 0x00, | ||||
|     0xcf, 0x00, 0x5c, 0xff, 0x40, 0x00, 0x00, 0x00, 0x6b, 0x00, 0xab, 0xff, 0x22, 0x00, 0x00, 0x00, 0x38, 0x00, 0xd3, | ||||
|     0xff, 0x12, 0x00, 0x00, 0x00, 0x1d, 0x00, 0xea, 0xff, 0x08, 0x00, 0x00, 0x00, 0x0f, 0x00, 0xf3, 0xff, 0x06, 0x00, | ||||
|     0x00, 0x00, 0x08, 0x00, 0xf8, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x02, | ||||
|     0x00, 0xfd, 0xff, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xfd, 0xff, | ||||
|     0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, | ||||
|     0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x62, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, | ||||
|     0x00, 0x00, 0xf1, 0x00, 0x00, 0x00, 0x72, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, | ||||
|     0x00, 0x00, 0x00, 0x82, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x4d, 0x01, 0x00, 0x00, | ||||
|     0x92, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb2, 0xff, 0xff, 0xff, 0x04, 0x00, | ||||
|     0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, | ||||
|     0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, | ||||
|     0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x0a, 0x00, 0x0d, 0x00, 0x10, 0x00, 0x13, 0x00, 0x17, 0x00, | ||||
|     0x1b, 0x00, 0x20, 0x00, 0x25, 0x00, 0x2a, 0x00, 0x30, 0x00, 0x35, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x49, 0x00, 0x51, | ||||
|     0x00, 0x58, 0x00, 0x60, 0x00, 0x68, 0x00, 0x71, 0x00, 0x7a, 0x00, 0x83, 0x00, 0x8d, 0x00, 0x97, 0x00, 0xa1, 0x00, | ||||
|     0xac, 0x00, 0xb7, 0x00, 0xc2, 0x00, 0xcd, 0x00, 0xd9, 0x00, 0xe5, 0x00, 0xf2, 0x00, 0xff, 0x00, 0x0c, 0x01, 0x19, | ||||
|     0x01, 0x27, 0x01, 0x35, 0x01, 0x43, 0x01, 0x52, 0x01, 0x61, 0x01, 0x70, 0x01, 0x7f, 0x01, 0x8f, 0x01, 0x9f, 0x01, | ||||
|     0xaf, 0x01, 0xc0, 0x01, 0xd1, 0x01, 0xe2, 0x01, 0xf3, 0x01, 0x05, 0x02, 0x17, 0x02, 0x29, 0x02, 0x3c, 0x02, 0x4e, | ||||
|     0x02, 0x61, 0x02, 0x75, 0x02, 0x88, 0x02, 0x9c, 0x02, 0xb0, 0x02, 0xc4, 0x02, 0xd8, 0x02, 0xed, 0x02, 0x02, 0x03, | ||||
|     0x17, 0x03, 0x2c, 0x03, 0x41, 0x03, 0x57, 0x03, 0x6d, 0x03, 0x83, 0x03, 0x99, 0x03, 0xb0, 0x03, 0xc7, 0x03, 0xdd, | ||||
|     0x03, 0xf4, 0x03, 0x0c, 0x04, 0x23, 0x04, 0x3b, 0x04, 0x52, 0x04, 0x6a, 0x04, 0x82, 0x04, 0x9a, 0x04, 0xb3, 0x04, | ||||
|     0xcb, 0x04, 0xe4, 0x04, 0xfd, 0x04, 0x16, 0x05, 0x2f, 0x05, 0x48, 0x05, 0x61, 0x05, 0x7a, 0x05, 0x94, 0x05, 0xad, | ||||
|     0x05, 0xc7, 0x05, 0xe1, 0x05, 0xfb, 0x05, 0x15, 0x06, 0x2f, 0x06, 0x49, 0x06, 0x63, 0x06, 0x7e, 0x06, 0x98, 0x06, | ||||
|     0xb2, 0x06, 0xcd, 0x06, 0xe7, 0x06, 0x02, 0x07, 0x1d, 0x07, 0x37, 0x07, 0x52, 0x07, 0x6d, 0x07, 0x87, 0x07, 0xa2, | ||||
|     0x07, 0xbd, 0x07, 0xd8, 0x07, 0xf3, 0x07, 0x0d, 0x08, 0x28, 0x08, 0x43, 0x08, 0x5e, 0x08, 0x79, 0x08, 0x93, 0x08, | ||||
|     0xae, 0x08, 0xc9, 0x08, 0xe3, 0x08, 0xfe, 0x08, 0x19, 0x09, 0x33, 0x09, 0x4e, 0x09, 0x68, 0x09, 0x82, 0x09, 0x9d, | ||||
|     0x09, 0xb7, 0x09, 0xd1, 0x09, 0xeb, 0x09, 0x05, 0x0a, 0x1f, 0x0a, 0x39, 0x0a, 0x53, 0x0a, 0x6c, 0x0a, 0x86, 0x0a, | ||||
|     0x9f, 0x0a, 0xb8, 0x0a, 0xd1, 0x0a, 0xea, 0x0a, 0x03, 0x0b, 0x1c, 0x0b, 0x35, 0x0b, 0x4d, 0x0b, 0x66, 0x0b, 0x7e, | ||||
|     0x0b, 0x96, 0x0b, 0xae, 0x0b, 0xc5, 0x0b, 0xdd, 0x0b, 0xf4, 0x0b, 0x0c, 0x0c, 0x23, 0x0c, 0x39, 0x0c, 0x50, 0x0c, | ||||
|     0x67, 0x0c, 0x7d, 0x0c, 0x93, 0x0c, 0xa9, 0x0c, 0xbf, 0x0c, 0xd4, 0x0c, 0xe9, 0x0c, 0xfe, 0x0c, 0x13, 0x0d, 0x28, | ||||
|     0x0d, 0x3c, 0x0d, 0x50, 0x0d, 0x64, 0x0d, 0x78, 0x0d, 0x8b, 0x0d, 0x9f, 0x0d, 0xb2, 0x0d, 0xc4, 0x0d, 0xd7, 0x0d, | ||||
|     0xe9, 0x0d, 0xfb, 0x0d, 0x0d, 0x0e, 0x1e, 0x0e, 0x2f, 0x0e, 0x40, 0x0e, 0x51, 0x0e, 0x61, 0x0e, 0x71, 0x0e, 0x81, | ||||
|     0x0e, 0x90, 0x0e, 0x9f, 0x0e, 0xae, 0x0e, 0xbd, 0x0e, 0xcb, 0x0e, 0xd9, 0x0e, 0xe7, 0x0e, 0xf4, 0x0e, 0x01, 0x0f, | ||||
|     0x0e, 0x0f, 0x1b, 0x0f, 0x27, 0x0f, 0x33, 0x0f, 0x3e, 0x0f, 0x49, 0x0f, 0x54, 0x0f, 0x5f, 0x0f, 0x69, 0x0f, 0x73, | ||||
|     0x0f, 0x7d, 0x0f, 0x86, 0x0f, 0x8f, 0x0f, 0x98, 0x0f, 0xa0, 0x0f, 0xa8, 0x0f, 0xaf, 0x0f, 0xb7, 0x0f, 0xbe, 0x0f, | ||||
|     0xc4, 0x0f, 0xcb, 0x0f, 0xd0, 0x0f, 0xd6, 0x0f, 0xdb, 0x0f, 0xe0, 0x0f, 0xe5, 0x0f, 0xe9, 0x0f, 0xed, 0x0f, 0xf0, | ||||
|     0x0f, 0xf3, 0x0f, 0xf6, 0x0f, 0xf9, 0x0f, 0xfb, 0x0f, 0xfc, 0x0f, 0xfe, 0x0f, 0xff, 0x0f, 0x00, 0x10, 0x00, 0x10, | ||||
|     0x00, 0x10, 0x00, 0x10, 0xff, 0x0f, 0xfe, 0x0f, 0xfc, 0x0f, 0xfb, 0x0f, 0xf9, 0x0f, 0xf6, 0x0f, 0xf3, 0x0f, 0xf0, | ||||
|     0x0f, 0xed, 0x0f, 0xe9, 0x0f, 0xe5, 0x0f, 0xe0, 0x0f, 0xdb, 0x0f, 0xd6, 0x0f, 0xd0, 0x0f, 0xcb, 0x0f, 0xc4, 0x0f, | ||||
|     0xbe, 0x0f, 0xb7, 0x0f, 0xaf, 0x0f, 0xa8, 0x0f, 0xa0, 0x0f, 0x98, 0x0f, 0x8f, 0x0f, 0x86, 0x0f, 0x7d, 0x0f, 0x73, | ||||
|     0x0f, 0x69, 0x0f, 0x5f, 0x0f, 0x54, 0x0f, 0x49, 0x0f, 0x3e, 0x0f, 0x33, 0x0f, 0x27, 0x0f, 0x1b, 0x0f, 0x0e, 0x0f, | ||||
|     0x01, 0x0f, 0xf4, 0x0e, 0xe7, 0x0e, 0xd9, 0x0e, 0xcb, 0x0e, 0xbd, 0x0e, 0xae, 0x0e, 0x9f, 0x0e, 0x90, 0x0e, 0x81, | ||||
|     0x0e, 0x71, 0x0e, 0x61, 0x0e, 0x51, 0x0e, 0x40, 0x0e, 0x2f, 0x0e, 0x1e, 0x0e, 0x0d, 0x0e, 0xfb, 0x0d, 0xe9, 0x0d, | ||||
|     0xd7, 0x0d, 0xc4, 0x0d, 0xb2, 0x0d, 0x9f, 0x0d, 0x8b, 0x0d, 0x78, 0x0d, 0x64, 0x0d, 0x50, 0x0d, 0x3c, 0x0d, 0x28, | ||||
|     0x0d, 0x13, 0x0d, 0xfe, 0x0c, 0xe9, 0x0c, 0xd4, 0x0c, 0xbf, 0x0c, 0xa9, 0x0c, 0x93, 0x0c, 0x7d, 0x0c, 0x67, 0x0c, | ||||
|     0x50, 0x0c, 0x39, 0x0c, 0x23, 0x0c, 0x0c, 0x0c, 0xf4, 0x0b, 0xdd, 0x0b, 0xc5, 0x0b, 0xae, 0x0b, 0x96, 0x0b, 0x7e, | ||||
|     0x0b, 0x66, 0x0b, 0x4d, 0x0b, 0x35, 0x0b, 0x1c, 0x0b, 0x03, 0x0b, 0xea, 0x0a, 0xd1, 0x0a, 0xb8, 0x0a, 0x9f, 0x0a, | ||||
|     0x86, 0x0a, 0x6c, 0x0a, 0x53, 0x0a, 0x39, 0x0a, 0x1f, 0x0a, 0x05, 0x0a, 0xeb, 0x09, 0xd1, 0x09, 0xb7, 0x09, 0x9d, | ||||
|     0x09, 0x82, 0x09, 0x68, 0x09, 0x4e, 0x09, 0x33, 0x09, 0x19, 0x09, 0xfe, 0x08, 0xe3, 0x08, 0xc9, 0x08, 0xae, 0x08, | ||||
|     0x93, 0x08, 0x79, 0x08, 0x5e, 0x08, 0x43, 0x08, 0x28, 0x08, 0x0d, 0x08, 0xf3, 0x07, 0xd8, 0x07, 0xbd, 0x07, 0xa2, | ||||
|     0x07, 0x87, 0x07, 0x6d, 0x07, 0x52, 0x07, 0x37, 0x07, 0x1d, 0x07, 0x02, 0x07, 0xe7, 0x06, 0xcd, 0x06, 0xb2, 0x06, | ||||
|     0x98, 0x06, 0x7e, 0x06, 0x63, 0x06, 0x49, 0x06, 0x2f, 0x06, 0x15, 0x06, 0xfb, 0x05, 0xe1, 0x05, 0xc7, 0x05, 0xad, | ||||
|     0x05, 0x94, 0x05, 0x7a, 0x05, 0x61, 0x05, 0x48, 0x05, 0x2f, 0x05, 0x16, 0x05, 0xfd, 0x04, 0xe4, 0x04, 0xcb, 0x04, | ||||
|     0xb3, 0x04, 0x9a, 0x04, 0x82, 0x04, 0x6a, 0x04, 0x52, 0x04, 0x3b, 0x04, 0x23, 0x04, 0x0c, 0x04, 0xf4, 0x03, 0xdd, | ||||
|     0x03, 0xc7, 0x03, 0xb0, 0x03, 0x99, 0x03, 0x83, 0x03, 0x6d, 0x03, 0x57, 0x03, 0x41, 0x03, 0x2c, 0x03, 0x17, 0x03, | ||||
|     0x02, 0x03, 0xed, 0x02, 0xd8, 0x02, 0xc4, 0x02, 0xb0, 0x02, 0x9c, 0x02, 0x88, 0x02, 0x75, 0x02, 0x61, 0x02, 0x4e, | ||||
|     0x02, 0x3c, 0x02, 0x29, 0x02, 0x17, 0x02, 0x05, 0x02, 0xf3, 0x01, 0xe2, 0x01, 0xd1, 0x01, 0xc0, 0x01, 0xaf, 0x01, | ||||
|     0x9f, 0x01, 0x8f, 0x01, 0x7f, 0x01, 0x70, 0x01, 0x61, 0x01, 0x52, 0x01, 0x43, 0x01, 0x35, 0x01, 0x27, 0x01, 0x19, | ||||
|     0x01, 0x0c, 0x01, 0xff, 0x00, 0xf2, 0x00, 0xe5, 0x00, 0xd9, 0x00, 0xcd, 0x00, 0xc2, 0x00, 0xb7, 0x00, 0xac, 0x00, | ||||
|     0xa1, 0x00, 0x97, 0x00, 0x8d, 0x00, 0x83, 0x00, 0x7a, 0x00, 0x71, 0x00, 0x68, 0x00, 0x60, 0x00, 0x58, 0x00, 0x51, | ||||
|     0x00, 0x49, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x35, 0x00, 0x30, 0x00, 0x2a, 0x00, 0x25, 0x00, 0x20, 0x00, 0x1b, 0x00, | ||||
|     0x17, 0x00, 0x13, 0x00, 0x10, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x07, 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xee, 0xff, 0xff, 0x38, 0xee, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x4d, 0x4c, | ||||
|     0x49, 0x52, 0x20, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x2e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00, | ||||
|     0x0e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0xf4, 0x05, 0x00, 0x00, 0xf8, 0x05, 0x00, | ||||
|     0x00, 0xfc, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, | ||||
|     0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0x68, 0x05, 0x00, 0x00, 0x24, 0x05, 0x00, 0x00, 0xc8, 0x04, 0x00, 0x00, 0x70, | ||||
|     0x04, 0x00, 0x00, 0x4c, 0x04, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0xc8, 0x03, 0x00, 0x00, 0xa4, 0x03, 0x00, 0x00, | ||||
|     0x4c, 0x03, 0x00, 0x00, 0x14, 0x03, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0xc8, 0x01, 0x00, 0x00, 0x6c, 0x01, 0x00, | ||||
|     0x00, 0x48, 0x01, 0x00, 0x00, 0x14, 0x01, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00, 0x78, 0x00, | ||||
|     0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xf6, 0xfa, 0xff, 0xff, 0x0c, | ||||
|     0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, | ||||
|     0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x16, 0xfb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, | ||||
|     0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x28, 0x00, | ||||
|     0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x3a, 0xfb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, | ||||
|     0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, | ||||
|     0x0e, 0x00, 0x00, 0x00, 0xa6, 0xfc, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x10, 0x00, 0x00, | ||||
|     0x00, 0x14, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x68, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, | ||||
|     0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xd6, 0xfc, 0xff, 0xff, 0x14, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, | ||||
|     0x98, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, | ||||
|     0x00, 0x12, 0x00, 0x00, 0x00, 0x06, 0xfd, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x10, 0x00, | ||||
|     0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xc8, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x25, | ||||
|     0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x36, 0xfd, 0xff, 0xff, | ||||
|     0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, | ||||
|     0x00, 0xf8, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x23, 0x00, | ||||
|     0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x1e, 0xfc, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, | ||||
|     0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, | ||||
|     0x84, 0xfc, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, | ||||
|     0x00, 0x30, 0x00, 0x00, 0x00, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x69, | ||||
|     0x6f, 0x6e, 0x5f, 0x62, 0x69, 0x74, 0x73, 0x00, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x73, 0x63, 0x61, 0x6c, | ||||
|     0x65, 0x00, 0x02, 0x24, 0x0f, 0x02, 0x01, 0x02, 0x03, 0x40, 0x04, 0x04, 0x04, 0x24, 0x01, 0x01, 0x00, 0x00, 0x00, | ||||
|     0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xdc, 0xfc, 0xff, 0xff, 0x10, 0x00, 0x00, | ||||
|     0x00, 0x24, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x73, 0x6e, | ||||
|     0x72, 0x5f, 0x73, 0x68, 0x69, 0x66, 0x74, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x06, 0x04, 0x02, 0x24, 0x01, 0x01, | ||||
|     0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, | ||||
|     0x08, 0x00, 0x00, 0x00, 0x20, 0xfd, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0xec, 0x00, 0x00, | ||||
|     0x00, 0x0a, 0x00, 0x00, 0x00, 0xd2, 0x00, 0x00, 0x00, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x65, 0x5f, | ||||
|     0x6f, 0x6e, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x5f, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, | ||||
|     0x00, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, | ||||
|     0x67, 0x00, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x69, 0x6e, 0x67, 0x00, 0x6d, 0x69, 0x6e, 0x5f, 0x73, 0x69, 0x67, 0x6e, | ||||
|     0x61, 0x6c, 0x5f, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x00, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, | ||||
|     0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x00, 0x6f, 0x6e, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x5f, 0x73, 0x6d, | ||||
|     0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x00, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x00, 0x73, | ||||
|     0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x69, 0x74, 0x73, 0x00, 0x73, 0x70, 0x65, 0x63, 0x74, | ||||
|     0x72, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x69, 0x74, | ||||
|     0x73, 0x00, 0x09, 0xa5, 0x88, 0x75, 0x6d, 0x59, 0x4d, 0x3a, 0x31, 0x23, 0x09, 0x00, 0x01, 0x00, 0x09, 0x00, 0x29, | ||||
|     0x3c, 0xd7, 0x03, 0x00, 0x00, 0x33, 0x03, 0x28, 0x00, 0x67, 0x3e, 0x99, 0x01, 0x0a, 0x00, 0x0e, 0x00, 0x05, 0x05, | ||||
|     0x69, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x1b, 0x25, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, | ||||
|     0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0xfe, 0xff, 0xff, 0x10, 0x00, | ||||
|     0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, | ||||
|     0x1d, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x54, 0xfe, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, | ||||
|     0x00, 0x2c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, | ||||
|     0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x00, 0x01, 0x0e, 0x01, 0x01, 0x01, 0x28, 0x04, 0x02, 0x24, 0x01, 0x00, 0x01, | ||||
|     0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, | ||||
|     0x0c, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x62, 0xfe, 0xff, | ||||
|     0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, | ||||
|     0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0xca, 0xff, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x0a, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x8c, 0xf2, 0xff, 0xff, | ||||
|     0x01, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, | ||||
|     0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x0b, 0x00, | ||||
|     0x04, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x14, | ||||
|     0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xd0, 0xf2, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, | ||||
|     0x04, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, | ||||
|     0x00, 0xfe, 0xfe, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, | ||||
|     0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x64, 0xff, 0xff, 0xff, 0x10, | ||||
|     0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, | ||||
|     0x65, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x64, | ||||
|     0x65, 0x78, 0x00, 0x02, 0x17, 0x0e, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0xf1, 0x00, 0x05, 0x00, 0x05, 0x05, | ||||
|     0x06, 0x25, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, | ||||
|     0x00, 0x00, 0x00, 0xb8, 0xff, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, | ||||
|     0x03, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x54, 0x00, 0x66, 0x66, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, | ||||
|     0x68, 0x00, 0x02, 0x0e, 0x0d, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00, 0x02, 0x05, 0x05, 0x06, 0x25, | ||||
|     0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x10, | ||||
|     0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, | ||||
|     0x10, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, | ||||
|     0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, | ||||
|     0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, | ||||
|     0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, | ||||
|     0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x10, 0x00, | ||||
|     0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73, | ||||
|     0x68, 0x69, 0x66, 0x74, 0x00, 0x01, 0x07, 0x01, 0x01, 0x01, 0x0c, 0x04, 0x02, 0x24, 0x01, 0x01, 0x00, 0x00, 0x00, | ||||
|     0x13, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, | ||||
|     0x00, 0x2a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0xc4, 0x0a, | ||||
|     0x00, 0x00, 0x74, 0x0a, 0x00, 0x00, 0x3c, 0x0a, 0x00, 0x00, 0x04, 0x0a, 0x00, 0x00, 0xd0, 0x09, 0x00, 0x00, 0x88, | ||||
|     0x09, 0x00, 0x00, 0x40, 0x09, 0x00, 0x00, 0xfc, 0x08, 0x00, 0x00, 0xb8, 0x08, 0x00, 0x00, 0x6c, 0x08, 0x00, 0x00, | ||||
|     0x20, 0x08, 0x00, 0x00, 0xd4, 0x07, 0x00, 0x00, 0x88, 0x07, 0x00, 0x00, 0x3c, 0x07, 0x00, 0x00, 0xf8, 0x06, 0x00, | ||||
|     0x00, 0xb8, 0x06, 0x00, 0x00, 0x84, 0x06, 0x00, 0x00, 0x50, 0x06, 0x00, 0x00, 0x1c, 0x06, 0x00, 0x00, 0xd8, 0x05, | ||||
|     0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0x58, 0x05, 0x00, 0x00, 0x14, 0x05, 0x00, 0x00, 0xd8, 0x04, 0x00, 0x00, 0x98, | ||||
|     0x04, 0x00, 0x00, 0x60, 0x04, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0xb0, 0x03, 0x00, 0x00, | ||||
|     0x6c, 0x03, 0x00, 0x00, 0x1c, 0x03, 0x00, 0x00, 0xc4, 0x02, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00, 0x2c, 0x02, 0x00, | ||||
|     0x00, 0xe4, 0x01, 0x00, 0x00, 0xac, 0x01, 0x00, 0x00, 0x78, 0x01, 0x00, 0x00, 0x44, 0x01, 0x00, 0x00, 0x08, 0x01, | ||||
|     0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xfe, | ||||
|     0xf5, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x09, 0x20, 0x00, 0x00, 0x00, 0x44, 0xf5, 0xff, 0xff, 0x11, 0x00, 0x00, 0x00, 0x50, 0x61, 0x72, | ||||
|     0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x65, 0x64, 0x43, 0x61, 0x6c, 0x6c, 0x3a, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, | ||||
|     0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3e, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, | ||||
|     0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x84, 0xf5, 0xff, 0xff, | ||||
|     0x0d, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x70, 0x5f, 0x62, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x00, 0x00, | ||||
|     0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x7a, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, | ||||
|     0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0xc0, | ||||
|     0xf5, 0xff, 0xff, 0x15, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x70, 0x5f, 0x62, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, | ||||
|     0x65, 0x2f, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, | ||||
|     0x00, 0xbe, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x28, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x04, 0xf6, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x61, | ||||
|     0x64, 0x64, 0x5f, 0x31, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xf2, 0xf6, 0xff, 0xff, | ||||
|     0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x02, 0x18, 0x00, 0x00, 0x00, 0x38, 0xf6, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00, 0x54, 0x72, 0x75, 0x6e, 0x63, 0x61, | ||||
|     0x74, 0x65, 0x44, 0x69, 0x76, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2a, 0xf7, 0xff, 0xff, 0x00, | ||||
|     0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, | ||||
|     0x10, 0x00, 0x00, 0x00, 0x70, 0xf6, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x61, 0x64, 0x64, 0x00, 0x01, 0x00, 0x00, | ||||
|     0x00, 0x28, 0x00, 0x00, 0x00, 0x5a, 0xf7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, | ||||
|     0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x10, 0x00, 0x00, 0x00, 0xa0, 0xf6, 0xff, 0xff, 0x03, | ||||
|     0x00, 0x00, 0x00, 0x6d, 0x75, 0x6c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x8a, 0xf7, 0xff, 0xff, | ||||
|     0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x02, 0x14, 0x00, 0x00, 0x00, 0xd0, 0xf6, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x61, 0x73, 0x74, 0x5f, 0x32, | ||||
|     0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xbe, 0xf7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, | ||||
|     0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x24, 0x00, 0x00, 0x00, | ||||
|     0x04, 0xf7, 0xff, 0xff, 0x16, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, | ||||
|     0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x6c, 0x6f, 0x67, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, | ||||
|     0x00, 0x00, 0x02, 0xf8, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x22, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x18, 0x00, 0x00, 0x00, 0x48, 0xf7, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00, | ||||
|     0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x63, 0x61, 0x6e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, | ||||
|     0x00, 0x3a, 0xf8, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x21, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x38, 0x00, 0x00, 0x00, 0x80, 0xf7, 0xff, 0xff, 0x28, 0x00, 0x00, 0x00, 0x73, | ||||
|     0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x73, | ||||
|     0x70, 0x65, 0x63, 0x74, 0x72, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, | ||||
|     0x31, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x92, 0xf8, 0xff, 0xff, 0x00, 0x00, | ||||
|     0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x34, | ||||
|     0x00, 0x00, 0x00, 0xd8, 0xf7, 0xff, 0xff, 0x27, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, | ||||
|     0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x72, 0x61, 0x6c, | ||||
|     0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, | ||||
|     0x00, 0x00, 0xe6, 0xf8, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1f, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x2c, 0x00, 0x00, 0x00, 0x2c, 0xf8, 0xff, 0xff, 0x1e, 0x00, 0x00, 0x00, | ||||
|     0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, | ||||
|     0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, | ||||
|     0x00, 0x00, 0x32, 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1e, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x20, 0x00, 0x00, 0x00, 0x78, 0xf8, 0xff, 0xff, 0x12, 0x00, 0x00, 0x00, | ||||
|     0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x00, | ||||
|     0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x72, 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, | ||||
|     0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x14, 0x00, 0x00, 0x00, 0xb8, | ||||
|     0xf8, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x61, 0x73, 0x74, 0x5f, 0x31, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, | ||||
|     0x01, 0x01, 0x00, 0x00, 0xa6, 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, | ||||
|     0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xec, 0xf8, 0xff, 0xff, 0x06, 0x00, | ||||
|     0x00, 0x00, 0x63, 0x6f, 0x6e, 0x63, 0x61, 0x74, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0xda, | ||||
|     0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x20, 0xf9, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, | ||||
|     0x69, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xec, 0x00, | ||||
|     0x00, 0x00, 0x16, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1a, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x5c, 0xf9, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, | ||||
|     0x43, 0x61, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x4a, 0xfa, 0xff, | ||||
|     0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x0f, 0x1c, 0x00, 0x00, 0x00, 0x90, 0xf9, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, | ||||
|     0x6c, 0x5f, 0x65, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, | ||||
|     0x86, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x07, 0x18, 0x00, 0x00, 0x00, 0xcc, 0xf9, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00, 0x73, 0x69, | ||||
|     0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x66, 0x66, 0x74, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0xbe, | ||||
|     0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0x04, 0xfa, 0xff, 0xff, 0x16, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, | ||||
|     0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x66, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x31, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, | ||||
|     0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x24, 0x00, 0x00, 0x00, 0x44, 0xfa, 0xff, 0xff, | ||||
|     0x15, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x66, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x6f, | ||||
|     0x5f, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x42, 0xfb, | ||||
|     0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x07, 0x14, 0x00, 0x00, 0x00, 0x88, 0xfa, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x52, 0x65, 0x73, 0x68, | ||||
|     0x61, 0x70, 0x65, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x76, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00, | ||||
|     0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1c, 0x00, | ||||
|     0x00, 0x00, 0xbc, 0xfa, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x77, 0x69, | ||||
|     0x6e, 0x64, 0x6f, 0x77, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, | ||||
|     0xb6, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xfc, 0xfa, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x43, 0x6f, | ||||
|     0x6e, 0x73, 0x74, 0x5f, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, | ||||
|     0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, | ||||
|     0x2c, 0xfb, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x16, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x11, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x5c, 0xfb, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x43, | ||||
|     0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, | ||||
|     0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, | ||||
|     0x00, 0x8c, 0xfb, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x52, 0x65, 0x73, 0x68, 0x61, 0x70, 0x65, 0x2f, 0x73, 0x68, | ||||
|     0x61, 0x70, 0x65, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x82, 0xfc, 0xff, 0xff, 0x00, | ||||
|     0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, | ||||
|     0x24, 0x00, 0x00, 0x00, 0xc8, 0xfb, 0xff, 0xff, 0x17, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x70, 0x5f, 0x62, 0x79, | ||||
|     0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2f, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x2f, 0x79, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0xc2, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0e, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0x08, 0xfc, 0xff, 0xff, 0x18, 0x00, 0x00, 0x00, | ||||
|     0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, | ||||
|     0x43, 0x6f, 0x6e, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x0a, 0xfd, | ||||
|     0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0x50, 0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, | ||||
|     0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, | ||||
|     0x74, 0x5f, 0x31, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x52, 0xfd, 0xff, 0xff, 0x00, 0x00, | ||||
|     0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, | ||||
|     0x00, 0x00, 0x00, 0x98, 0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, | ||||
|     0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x32, 0x00, | ||||
|     0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x9a, 0xfd, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, | ||||
|     0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0xe0, | ||||
|     0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, | ||||
|     0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x33, 0x00, 0x00, 0x01, 0x00, 0x00, | ||||
|     0x00, 0x29, 0x00, 0x00, 0x00, 0xe2, 0xfd, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, | ||||
|     0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0x28, 0xfd, 0xff, 0xff, 0x1a, | ||||
|     0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, | ||||
|     0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x34, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, | ||||
|     0x00, 0x2a, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x20, 0x00, 0x00, 0x00, 0x70, 0xfd, 0xff, 0xff, 0x11, 0x00, 0x00, 0x00, 0x73, | ||||
|     0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x63, 0x61, 0x6e, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x00, 0x00, 0x00, | ||||
|     0x01, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x6a, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, | ||||
|     0x00, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x00, 0xb0, 0xfd, | ||||
|     0xff, 0xff, 0x13, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, | ||||
|     0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xaa, 0xfe, 0xff, 0xff, | ||||
|     0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x02, 0x24, 0x00, 0x00, 0x00, 0xf0, 0xfd, 0xff, 0xff, 0x15, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, | ||||
|     0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x31, 0x00, 0x00, 0x00, 0x01, | ||||
|     0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xee, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, | ||||
|     0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0x34, 0xfe, 0xff, | ||||
|     0xff, 0x15, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x2f, | ||||
|     0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x32, | ||||
|     0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x78, 0xfe, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x61, 0x73, | ||||
|     0x74, 0x5f, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, | ||||
|     0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xa8, | ||||
|     0xfe, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x7a, 0x65, 0x72, 0x6f, 0x73, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, | ||||
|     0x05, 0x00, 0x00, 0x00, 0x96, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, | ||||
|     0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xdc, 0xfe, 0xff, 0xff, 0x07, 0x00, | ||||
|     0x00, 0x00, 0x7a, 0x65, 0x72, 0x6f, 0x73, 0x5f, 0x31, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xca, | ||||
|     0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x07, 0x14, 0x00, 0x00, 0x00, 0x10, 0xff, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x6e, | ||||
|     0x73, 0x74, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x1c, 0x00, | ||||
|     0x18, 0x00, 0x17, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x16, | ||||
|     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x07, 0x2c, 0x00, 0x00, 0x00, 0x5c, 0xff, 0xff, 0xff, 0x1d, 0x00, 0x00, 0x00, 0x73, 0x65, 0x72, | ||||
|     0x76, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x5f, | ||||
|     0x66, 0x72, 0x61, 0x6d, 0x65, 0x3a, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, | ||||
|     0x01, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1c, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0xc8, 0x01, 0x00, 0x00, | ||||
|     0xa4, 0x01, 0x00, 0x00, 0x7c, 0x01, 0x00, 0x00, 0x68, 0x01, 0x00, 0x00, 0x4c, 0x01, 0x00, 0x00, 0x3c, 0x01, 0x00, | ||||
|     0x00, 0x10, 0x01, 0x00, 0x00, 0xdc, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x50, 0x00, | ||||
|     0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, | ||||
|     0x00, 0x00, 0x00, 0x50, 0xfe, 0xff, 0xff, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x5c, 0xfe, 0xff, 0xff, | ||||
|     0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x68, 0xfe, 0xff, 0xff, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x2a, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7c, 0xfe, 0xff, 0xff, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x12, 0x70, 0xfe, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x13, | ||||
|     0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x6b, | ||||
|     0x4c, 0x6f, 0x67, 0x00, 0x98, 0xfe, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x20, 0x0a, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x50, 0x43, 0x41, 0x4e, 0x00, 0x00, 0xb8, 0xfe, | ||||
|     0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x23, 0x00, 0x00, 0x00, 0x53, | ||||
|     0x69, 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x6b, 0x53, 0x70, 0x65, 0x63, | ||||
|     0x74, 0x72, 0x61, 0x6c, 0x53, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0xf0, 0xfe, 0xff, | ||||
|     0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x1a, 0x00, 0x00, 0x00, 0x53, 0x69, | ||||
|     0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x6b, 0x53, 0x71, 0x75, 0x61, 0x72, | ||||
|     0x65, 0x52, 0x6f, 0x6f, 0x74, 0x00, 0x00, 0x20, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, | ||||
|     0x72, 0x42, 0x61, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x60, 0xff, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x02, 0x6c, 0xff, 0xff, 0xff, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x0c, 0x00, 0x10, 0x00, 0x0f, | ||||
|     0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x00, 0x35, 0x7c, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x20, 0x0c, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x45, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x00, 0x00, | ||||
|     0x00, 0x00, 0xa0, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0a, | ||||
|     0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x52, 0x66, 0x66, 0x74, 0x00, 0x00, 0xc0, 0xff, 0xff, 0xff, | ||||
|     0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x12, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, | ||||
|     0x6e, 0x61, 0x6c, 0x46, 0x66, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x00, 0x00, 0x0c, 0x00, | ||||
|     0x0c, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, | ||||
|     0x00, 0x00, 0x16, 0x0c, 0x00, 0x10, 0x00, 0x0f, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, | ||||
|     0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0c, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, | ||||
|     0x6e, 0x61, 0x6c, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x00, 0x00, 0x00, 0x00}; | ||||
|  | ||||
| }  // namespace micro_wake_word | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif  // USE_ESP_IDF | ||||
| @@ -1,12 +1,5 @@ | ||||
| #include "micro_wake_word.h" | ||||
|  | ||||
| /** | ||||
|  * This is a workaround until we can figure out a way to get | ||||
|  * the tflite-micro idf component code available in CI | ||||
|  * | ||||
|  * */ | ||||
| // | ||||
| #ifndef CLANG_TIDY | ||||
| #include "streaming_model.h" | ||||
|  | ||||
| #ifdef USE_ESP_IDF | ||||
|  | ||||
| @@ -14,13 +7,13 @@ | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #include "audio_preprocessor_int8_model_data.h" | ||||
| #include <frontend.h> | ||||
| #include <frontend_util.h> | ||||
|  | ||||
| #include <tensorflow/lite/core/c/common.h> | ||||
| #include <tensorflow/lite/micro/micro_interpreter.h> | ||||
| #include <tensorflow/lite/micro/micro_mutable_op_resolver.h> | ||||
|  | ||||
| #include <cinttypes> | ||||
| #include <cmath> | ||||
|  | ||||
| namespace esphome { | ||||
| @@ -29,9 +22,9 @@ namespace micro_wake_word { | ||||
| static const char *const TAG = "micro_wake_word"; | ||||
|  | ||||
| static const size_t SAMPLE_RATE_HZ = 16000;  // 16 kHz | ||||
| static const size_t BUFFER_LENGTH = 500;     // 0.5 seconds | ||||
| static const size_t BUFFER_LENGTH = 64;      // 0.064 seconds | ||||
| static const size_t BUFFER_SIZE = SAMPLE_RATE_HZ / 1000 * BUFFER_LENGTH; | ||||
| static const size_t INPUT_BUFFER_SIZE = 32 * SAMPLE_RATE_HZ / 1000;  // 32ms * 16kHz / 1000ms | ||||
| static const size_t INPUT_BUFFER_SIZE = 16 * SAMPLE_RATE_HZ / 1000;  // 16ms * 16kHz / 1000ms | ||||
|  | ||||
| float MicroWakeWord::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } | ||||
|  | ||||
| @@ -56,58 +49,56 @@ static const LogString *micro_wake_word_state_to_string(State state) { | ||||
|  | ||||
| void MicroWakeWord::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "microWakeWord:"); | ||||
|   ESP_LOGCONFIG(TAG, "  Wake Word: %s", this->get_wake_word().c_str()); | ||||
|   ESP_LOGCONFIG(TAG, "  Probability cutoff: %.3f", this->probability_cutoff_); | ||||
|   ESP_LOGCONFIG(TAG, "  Sliding window size: %d", this->sliding_window_average_size_); | ||||
|   ESP_LOGCONFIG(TAG, "  models:"); | ||||
|   for (auto &model : this->wake_word_models_) { | ||||
|     model.log_model_config(); | ||||
|   } | ||||
| #ifdef USE_MICRO_WAKE_WORD_VAD | ||||
|   this->vad_model_->log_model_config(); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void MicroWakeWord::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up microWakeWord..."); | ||||
|  | ||||
|   if (!this->initialize_models()) { | ||||
|     ESP_LOGE(TAG, "Failed to initialize models"); | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   ExternalRAMAllocator<int16_t> allocator(ExternalRAMAllocator<int16_t>::ALLOW_FAILURE); | ||||
|   this->input_buffer_ = allocator.allocate(INPUT_BUFFER_SIZE * sizeof(int16_t)); | ||||
|   if (this->input_buffer_ == nullptr) { | ||||
|     ESP_LOGW(TAG, "Could not allocate input buffer"); | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   this->ring_buffer_ = RingBuffer::create(BUFFER_SIZE * sizeof(int16_t)); | ||||
|   if (this->ring_buffer_ == nullptr) { | ||||
|     ESP_LOGW(TAG, "Could not allocate ring buffer"); | ||||
|   if (!this->register_streaming_ops_(this->streaming_op_resolver_)) { | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   ESP_LOGCONFIG(TAG, "Micro Wake Word initialized"); | ||||
|  | ||||
|   this->frontend_config_.window.size_ms = FEATURE_DURATION_MS; | ||||
|   this->frontend_config_.window.step_size_ms = this->features_step_size_; | ||||
|   this->frontend_config_.filterbank.num_channels = PREPROCESSOR_FEATURE_SIZE; | ||||
|   this->frontend_config_.filterbank.lower_band_limit = 125.0; | ||||
|   this->frontend_config_.filterbank.upper_band_limit = 7500.0; | ||||
|   this->frontend_config_.noise_reduction.smoothing_bits = 10; | ||||
|   this->frontend_config_.noise_reduction.even_smoothing = 0.025; | ||||
|   this->frontend_config_.noise_reduction.odd_smoothing = 0.06; | ||||
|   this->frontend_config_.noise_reduction.min_signal_remaining = 0.05; | ||||
|   this->frontend_config_.pcan_gain_control.enable_pcan = 1; | ||||
|   this->frontend_config_.pcan_gain_control.strength = 0.95; | ||||
|   this->frontend_config_.pcan_gain_control.offset = 80.0; | ||||
|   this->frontend_config_.pcan_gain_control.gain_bits = 21; | ||||
|   this->frontend_config_.log_scale.enable_log = 1; | ||||
|   this->frontend_config_.log_scale.scale_shift = 6; | ||||
| } | ||||
|  | ||||
| int MicroWakeWord::read_microphone_() { | ||||
|   size_t bytes_read = this->microphone_->read(this->input_buffer_, INPUT_BUFFER_SIZE * sizeof(int16_t)); | ||||
|   if (bytes_read == 0) { | ||||
|     return 0; | ||||
|   } | ||||
|  | ||||
|   size_t bytes_free = this->ring_buffer_->free(); | ||||
|  | ||||
|   if (bytes_free < bytes_read) { | ||||
|     ESP_LOGW(TAG, | ||||
|              "Not enough free bytes in ring buffer to store incoming audio data (free bytes=%d, incoming bytes=%d). " | ||||
|              "Resetting the ring buffer. Wake word detection accuracy will be reduced.", | ||||
|              bytes_free, bytes_read); | ||||
|  | ||||
|     this->ring_buffer_->reset(); | ||||
|   } | ||||
|  | ||||
|   return this->ring_buffer_->write((void *) this->input_buffer_, bytes_read); | ||||
| void MicroWakeWord::add_wake_word_model(const uint8_t *model_start, float probability_cutoff, | ||||
|                                         size_t sliding_window_average_size, const std::string &wake_word, | ||||
|                                         size_t tensor_arena_size) { | ||||
|   this->wake_word_models_.emplace_back(model_start, probability_cutoff, sliding_window_average_size, wake_word, | ||||
|                                        tensor_arena_size); | ||||
| } | ||||
|  | ||||
| #ifdef USE_MICRO_WAKE_WORD_VAD | ||||
| void MicroWakeWord::add_vad_model(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_size, | ||||
|                                   size_t tensor_arena_size) { | ||||
|   this->vad_model_ = make_unique<VADModel>(model_start, probability_cutoff, sliding_window_size, tensor_arena_size); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| void MicroWakeWord::loop() { | ||||
|   switch (this->state_) { | ||||
|     case State::IDLE: | ||||
| @@ -124,9 +115,12 @@ void MicroWakeWord::loop() { | ||||
|       } | ||||
|       break; | ||||
|     case State::DETECTING_WAKE_WORD: | ||||
|       this->read_microphone_(); | ||||
|       if (this->detect_wake_word_()) { | ||||
|         ESP_LOGD(TAG, "Wake Word Detected"); | ||||
|       while (!this->has_enough_samples_()) { | ||||
|         this->read_microphone_(); | ||||
|       } | ||||
|       this->update_model_probabilities_(); | ||||
|       if (this->detect_wake_words_()) { | ||||
|         ESP_LOGD(TAG, "Wake Word '%s' Detected", (this->detected_wake_word_).c_str()); | ||||
|         this->detected_ = true; | ||||
|         this->set_state_(State::STOP_MICROPHONE); | ||||
|       } | ||||
| @@ -136,13 +130,16 @@ void MicroWakeWord::loop() { | ||||
|       this->microphone_->stop(); | ||||
|       this->set_state_(State::STOPPING_MICROPHONE); | ||||
|       this->high_freq_.stop(); | ||||
|       this->unload_models_(); | ||||
|       this->deallocate_buffers_(); | ||||
|       break; | ||||
|     case State::STOPPING_MICROPHONE: | ||||
|       if (this->microphone_->is_stopped()) { | ||||
|         this->set_state_(State::IDLE); | ||||
|         if (this->detected_) { | ||||
|           this->wake_word_detected_trigger_->trigger(this->detected_wake_word_); | ||||
|           this->detected_ = false; | ||||
|           this->wake_word_detected_trigger_->trigger(this->wake_word_); | ||||
|           this->detected_wake_word_ = ""; | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
| @@ -150,14 +147,34 @@ void MicroWakeWord::loop() { | ||||
| } | ||||
|  | ||||
| void MicroWakeWord::start() { | ||||
|   if (!this->is_ready()) { | ||||
|     ESP_LOGW(TAG, "Wake word detection can't start as the component hasn't been setup yet"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (this->is_failed()) { | ||||
|     ESP_LOGW(TAG, "Wake word component is marked as failed. Please check setup logs"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!this->load_models_() || !this->allocate_buffers_()) { | ||||
|     ESP_LOGE(TAG, "Failed to load the wake word model(s) or allocate buffers"); | ||||
|     this->status_set_error(); | ||||
|   } else { | ||||
|     this->status_clear_error(); | ||||
|   } | ||||
|  | ||||
|   if (this->status_has_error()) { | ||||
|     ESP_LOGW(TAG, "Wake word component has an error. Please check logs"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (this->state_ != State::IDLE) { | ||||
|     ESP_LOGW(TAG, "Wake word is already running"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   this->reset_states_(); | ||||
|   this->set_state_(State::START_MICROPHONE); | ||||
| } | ||||
|  | ||||
| @@ -179,289 +196,218 @@ void MicroWakeWord::set_state_(State state) { | ||||
|   this->state_ = state; | ||||
| } | ||||
|  | ||||
| bool MicroWakeWord::initialize_models() { | ||||
|   ExternalRAMAllocator<uint8_t> arena_allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); | ||||
|   ExternalRAMAllocator<int8_t> features_allocator(ExternalRAMAllocator<int8_t>::ALLOW_FAILURE); | ||||
| size_t MicroWakeWord::read_microphone_() { | ||||
|   size_t bytes_read = this->microphone_->read(this->input_buffer_, INPUT_BUFFER_SIZE * sizeof(int16_t)); | ||||
|   if (bytes_read == 0) { | ||||
|     return 0; | ||||
|   } | ||||
|  | ||||
|   size_t bytes_free = this->ring_buffer_->free(); | ||||
|  | ||||
|   if (bytes_free < bytes_read) { | ||||
|     ESP_LOGW(TAG, | ||||
|              "Not enough free bytes in ring buffer to store incoming audio data (free bytes=%d, incoming bytes=%d). " | ||||
|              "Resetting the ring buffer. Wake word detection accuracy will be reduced.", | ||||
|              bytes_free, bytes_read); | ||||
|  | ||||
|     this->ring_buffer_->reset(); | ||||
|   } | ||||
|  | ||||
|   return this->ring_buffer_->write((void *) this->input_buffer_, bytes_read); | ||||
| } | ||||
|  | ||||
| bool MicroWakeWord::allocate_buffers_() { | ||||
|   ExternalRAMAllocator<int16_t> audio_samples_allocator(ExternalRAMAllocator<int16_t>::ALLOW_FAILURE); | ||||
|  | ||||
|   this->streaming_tensor_arena_ = arena_allocator.allocate(STREAMING_MODEL_ARENA_SIZE); | ||||
|   if (this->streaming_tensor_arena_ == nullptr) { | ||||
|     ESP_LOGE(TAG, "Could not allocate the streaming model's tensor arena."); | ||||
|     return false; | ||||
|   if (this->input_buffer_ == nullptr) { | ||||
|     this->input_buffer_ = audio_samples_allocator.allocate(INPUT_BUFFER_SIZE * sizeof(int16_t)); | ||||
|     if (this->input_buffer_ == nullptr) { | ||||
|       ESP_LOGE(TAG, "Could not allocate input buffer"); | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   this->streaming_var_arena_ = arena_allocator.allocate(STREAMING_MODEL_VARIABLE_ARENA_SIZE); | ||||
|   if (this->streaming_var_arena_ == nullptr) { | ||||
|     ESP_LOGE(TAG, "Could not allocate the streaming model variable's tensor arena."); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   this->preprocessor_tensor_arena_ = arena_allocator.allocate(PREPROCESSOR_ARENA_SIZE); | ||||
|   if (this->preprocessor_tensor_arena_ == nullptr) { | ||||
|     ESP_LOGE(TAG, "Could not allocate the audio preprocessor model's tensor arena."); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   this->new_features_data_ = features_allocator.allocate(PREPROCESSOR_FEATURE_SIZE); | ||||
|   if (this->new_features_data_ == nullptr) { | ||||
|     ESP_LOGE(TAG, "Could not allocate the audio features buffer."); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   this->preprocessor_audio_buffer_ = audio_samples_allocator.allocate(SAMPLE_DURATION_COUNT); | ||||
|   if (this->preprocessor_audio_buffer_ == nullptr) { | ||||
|     ESP_LOGE(TAG, "Could not allocate the audio preprocessor's buffer."); | ||||
|     return false; | ||||
|     this->preprocessor_audio_buffer_ = audio_samples_allocator.allocate(this->new_samples_to_get_()); | ||||
|     if (this->preprocessor_audio_buffer_ == nullptr) { | ||||
|       ESP_LOGE(TAG, "Could not allocate the audio preprocessor's buffer."); | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   this->preprocessor_model_ = tflite::GetModel(G_AUDIO_PREPROCESSOR_INT8_TFLITE); | ||||
|   if (this->preprocessor_model_->version() != TFLITE_SCHEMA_VERSION) { | ||||
|     ESP_LOGE(TAG, "Wake word's audio preprocessor model's schema is not supported"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   this->streaming_model_ = tflite::GetModel(this->model_start_); | ||||
|   if (this->streaming_model_->version() != TFLITE_SCHEMA_VERSION) { | ||||
|     ESP_LOGE(TAG, "Wake word's streaming model's schema is not supported"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   static tflite::MicroMutableOpResolver<18> preprocessor_op_resolver; | ||||
|   static tflite::MicroMutableOpResolver<17> streaming_op_resolver; | ||||
|  | ||||
|   if (!this->register_preprocessor_ops_(preprocessor_op_resolver)) | ||||
|     return false; | ||||
|   if (!this->register_streaming_ops_(streaming_op_resolver)) | ||||
|     return false; | ||||
|  | ||||
|   tflite::MicroAllocator *ma = | ||||
|       tflite::MicroAllocator::Create(this->streaming_var_arena_, STREAMING_MODEL_VARIABLE_ARENA_SIZE); | ||||
|   this->mrv_ = tflite::MicroResourceVariables::Create(ma, 15); | ||||
|  | ||||
|   static tflite::MicroInterpreter static_preprocessor_interpreter( | ||||
|       this->preprocessor_model_, preprocessor_op_resolver, this->preprocessor_tensor_arena_, PREPROCESSOR_ARENA_SIZE); | ||||
|  | ||||
|   static tflite::MicroInterpreter static_streaming_interpreter(this->streaming_model_, streaming_op_resolver, | ||||
|                                                                this->streaming_tensor_arena_, | ||||
|                                                                STREAMING_MODEL_ARENA_SIZE, this->mrv_); | ||||
|  | ||||
|   this->preprocessor_interperter_ = &static_preprocessor_interpreter; | ||||
|   this->streaming_interpreter_ = &static_streaming_interpreter; | ||||
|  | ||||
|   // Allocate tensors for each models. | ||||
|   if (this->preprocessor_interperter_->AllocateTensors() != kTfLiteOk) { | ||||
|     ESP_LOGE(TAG, "Failed to allocate tensors for the audio preprocessor"); | ||||
|     return false; | ||||
|   } | ||||
|   if (this->streaming_interpreter_->AllocateTensors() != kTfLiteOk) { | ||||
|     ESP_LOGE(TAG, "Failed to allocate tensors for the streaming model"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   // Verify input tensor matches expected values | ||||
|   TfLiteTensor *input = this->streaming_interpreter_->input(0); | ||||
|   if ((input->dims->size != 3) || (input->dims->data[0] != 1) || (input->dims->data[0] != 1) || | ||||
|       (input->dims->data[1] != 1) || (input->dims->data[2] != PREPROCESSOR_FEATURE_SIZE)) { | ||||
|     ESP_LOGE(TAG, "Wake word detection model tensor input dimensions is not 1x1x%u", input->dims->data[2]); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   if (input->type != kTfLiteInt8) { | ||||
|     ESP_LOGE(TAG, "Wake word detection model tensor input is not int8."); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   // Verify output tensor matches expected values | ||||
|   TfLiteTensor *output = this->streaming_interpreter_->output(0); | ||||
|   if ((output->dims->size != 2) || (output->dims->data[0] != 1) || (output->dims->data[1] != 1)) { | ||||
|     ESP_LOGE(TAG, "Wake word detection model tensor output dimensions is not 1x1."); | ||||
|   } | ||||
|  | ||||
|   if (output->type != kTfLiteUInt8) { | ||||
|     ESP_LOGE(TAG, "Wake word detection model tensor input is not uint8."); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   this->recent_streaming_probabilities_.resize(this->sliding_window_average_size_, 0.0); | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool MicroWakeWord::update_features_() { | ||||
|   // Retrieve strided audio samples | ||||
|   int16_t *audio_samples = nullptr; | ||||
|   if (!this->stride_audio_samples_(&audio_samples)) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   // Compute the features for the newest audio samples | ||||
|   if (!this->generate_single_feature_(audio_samples, SAMPLE_DURATION_COUNT, this->new_features_data_)) { | ||||
|     return false; | ||||
|   if (this->ring_buffer_ == nullptr) { | ||||
|     this->ring_buffer_ = RingBuffer::create(BUFFER_SIZE * sizeof(int16_t)); | ||||
|     if (this->ring_buffer_ == nullptr) { | ||||
|       ESP_LOGE(TAG, "Could not allocate ring buffer"); | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| float MicroWakeWord::perform_streaming_inference_() { | ||||
|   TfLiteTensor *input = this->streaming_interpreter_->input(0); | ||||
|  | ||||
|   size_t bytes_to_copy = input->bytes; | ||||
|  | ||||
|   memcpy((void *) (tflite::GetTensorData<int8_t>(input)), (const void *) (this->new_features_data_), bytes_to_copy); | ||||
|  | ||||
|   uint32_t prior_invoke = millis(); | ||||
|  | ||||
|   TfLiteStatus invoke_status = this->streaming_interpreter_->Invoke(); | ||||
|   if (invoke_status != kTfLiteOk) { | ||||
|     ESP_LOGW(TAG, "Streaming Interpreter Invoke failed"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   ESP_LOGV(TAG, "Streaming Inference Latency=%" PRIu32 " ms", (millis() - prior_invoke)); | ||||
|  | ||||
|   TfLiteTensor *output = this->streaming_interpreter_->output(0); | ||||
|  | ||||
|   return static_cast<float>(output->data.uint8[0]) / 255.0; | ||||
| void MicroWakeWord::deallocate_buffers_() { | ||||
|   ExternalRAMAllocator<int16_t> audio_samples_allocator(ExternalRAMAllocator<int16_t>::ALLOW_FAILURE); | ||||
|   audio_samples_allocator.deallocate(this->input_buffer_, INPUT_BUFFER_SIZE * sizeof(int16_t)); | ||||
|   this->input_buffer_ = nullptr; | ||||
|   audio_samples_allocator.deallocate(this->preprocessor_audio_buffer_, this->new_samples_to_get_()); | ||||
|   this->preprocessor_audio_buffer_ = nullptr; | ||||
| } | ||||
|  | ||||
| bool MicroWakeWord::detect_wake_word_() { | ||||
|   // Preprocess the newest audio samples into features | ||||
|   if (!this->update_features_()) { | ||||
| bool MicroWakeWord::load_models_() { | ||||
|   // Setup preprocesor feature generator | ||||
|   if (!FrontendPopulateState(&this->frontend_config_, &this->frontend_state_, AUDIO_SAMPLE_FREQUENCY)) { | ||||
|     ESP_LOGD(TAG, "Failed to populate frontend state"); | ||||
|     FrontendFreeStateContents(&this->frontend_state_); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   // Perform inference | ||||
|   float streaming_prob = this->perform_streaming_inference_(); | ||||
|   // Setup streaming models | ||||
|   for (auto &model : this->wake_word_models_) { | ||||
|     if (!model.load_model(this->streaming_op_resolver_)) { | ||||
|       ESP_LOGE(TAG, "Failed to initialize a wake word model."); | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
| #ifdef USE_MICRO_WAKE_WORD_VAD | ||||
|   if (!this->vad_model_->load_model(this->streaming_op_resolver_)) { | ||||
|     ESP_LOGE(TAG, "Failed to initialize VAD model."); | ||||
|     return false; | ||||
|   } | ||||
| #endif | ||||
|  | ||||
|   // Add the most recent probability to the sliding window | ||||
|   this->recent_streaming_probabilities_[this->last_n_index_] = streaming_prob; | ||||
|   ++this->last_n_index_; | ||||
|   if (this->last_n_index_ == this->sliding_window_average_size_) | ||||
|     this->last_n_index_ = 0; | ||||
|   return true; | ||||
| } | ||||
|  | ||||
|   float sum = 0.0; | ||||
|   for (auto &prob : this->recent_streaming_probabilities_) { | ||||
|     sum += prob; | ||||
| void MicroWakeWord::unload_models_() { | ||||
|   FrontendFreeStateContents(&this->frontend_state_); | ||||
|  | ||||
|   for (auto &model : this->wake_word_models_) { | ||||
|     model.unload_model(); | ||||
|   } | ||||
| #ifdef USE_MICRO_WAKE_WORD_VAD | ||||
|   this->vad_model_->unload_model(); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void MicroWakeWord::update_model_probabilities_() { | ||||
|   int8_t audio_features[PREPROCESSOR_FEATURE_SIZE]; | ||||
|  | ||||
|   if (!this->generate_features_for_window_(audio_features)) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   float sliding_window_average = sum / static_cast<float>(this->sliding_window_average_size_); | ||||
|  | ||||
|   // Ensure we have enough samples since the last positive detection | ||||
|   // Increase the counter since the last positive detection | ||||
|   this->ignore_windows_ = std::min(this->ignore_windows_ + 1, 0); | ||||
|  | ||||
|   for (auto &model : this->wake_word_models_) { | ||||
|     // Perform inference | ||||
|     model.perform_streaming_inference(audio_features); | ||||
|   } | ||||
| #ifdef USE_MICRO_WAKE_WORD_VAD | ||||
|   this->vad_model_->perform_streaming_inference(audio_features); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| bool MicroWakeWord::detect_wake_words_() { | ||||
|   // Verify we have processed samples since the last positive detection | ||||
|   if (this->ignore_windows_ < 0) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   // Detect the wake word if the sliding window average is above the cutoff | ||||
|   if (sliding_window_average > this->probability_cutoff_) { | ||||
|     this->ignore_windows_ = -MIN_SLICES_BEFORE_DETECTION; | ||||
|     for (auto &prob : this->recent_streaming_probabilities_) { | ||||
|       prob = 0; | ||||
|     } | ||||
| #ifdef USE_MICRO_WAKE_WORD_VAD | ||||
|   bool vad_state = this->vad_model_->determine_detected(); | ||||
| #endif | ||||
|  | ||||
|     ESP_LOGD(TAG, "Wake word sliding average probability is %.3f and most recent probability is %.3f", | ||||
|              sliding_window_average, streaming_prob); | ||||
|     return true; | ||||
|   for (auto &model : this->wake_word_models_) { | ||||
|     if (model.determine_detected()) { | ||||
| #ifdef USE_MICRO_WAKE_WORD_VAD | ||||
|       if (vad_state) { | ||||
| #endif | ||||
|         this->detected_wake_word_ = model.get_wake_word(); | ||||
|         return true; | ||||
| #ifdef USE_MICRO_WAKE_WORD_VAD | ||||
|       } else { | ||||
|         ESP_LOGD(TAG, "Wake word model predicts %s, but VAD model doesn't.", model.get_wake_word().c_str()); | ||||
|       } | ||||
| #endif | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| void MicroWakeWord::set_sliding_window_average_size(size_t size) { | ||||
|   this->sliding_window_average_size_ = size; | ||||
|   this->recent_streaming_probabilities_.resize(this->sliding_window_average_size_, 0.0); | ||||
| bool MicroWakeWord::has_enough_samples_() { | ||||
|   return this->ring_buffer_->available() >= | ||||
|          (this->features_step_size_ * (AUDIO_SAMPLE_FREQUENCY / 1000)) * sizeof(int16_t); | ||||
| } | ||||
|  | ||||
| bool MicroWakeWord::slice_available_() { | ||||
|   size_t available = this->ring_buffer_->available(); | ||||
|  | ||||
|   return available > (NEW_SAMPLES_TO_GET * sizeof(int16_t)); | ||||
| } | ||||
|  | ||||
| bool MicroWakeWord::stride_audio_samples_(int16_t **audio_samples) { | ||||
|   if (!this->slice_available_()) { | ||||
| bool MicroWakeWord::generate_features_for_window_(int8_t features[PREPROCESSOR_FEATURE_SIZE]) { | ||||
|   // Ensure we have enough new audio samples in the ring buffer for a full window | ||||
|   if (!this->has_enough_samples_()) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   // Copy the last 320 bytes (160 samples over 10 ms) from the audio buffer to the start of the audio buffer | ||||
|   memcpy((void *) (this->preprocessor_audio_buffer_), (void *) (this->preprocessor_audio_buffer_ + NEW_SAMPLES_TO_GET), | ||||
|          HISTORY_SAMPLES_TO_KEEP * sizeof(int16_t)); | ||||
|  | ||||
|   // Copy 640 bytes (320 samples over 20 ms) from the ring buffer into the audio buffer offset 320 bytes (160 samples | ||||
|   // over 10 ms) | ||||
|   size_t bytes_read = this->ring_buffer_->read((void *) (this->preprocessor_audio_buffer_ + HISTORY_SAMPLES_TO_KEEP), | ||||
|                                                NEW_SAMPLES_TO_GET * sizeof(int16_t), pdMS_TO_TICKS(200)); | ||||
|   size_t bytes_read = this->ring_buffer_->read((void *) (this->preprocessor_audio_buffer_), | ||||
|                                                this->new_samples_to_get_() * sizeof(int16_t), pdMS_TO_TICKS(200)); | ||||
|  | ||||
|   if (bytes_read == 0) { | ||||
|     ESP_LOGE(TAG, "Could not read data from Ring Buffer"); | ||||
|   } else if (bytes_read < NEW_SAMPLES_TO_GET * sizeof(int16_t)) { | ||||
|   } else if (bytes_read < this->new_samples_to_get_() * sizeof(int16_t)) { | ||||
|     ESP_LOGD(TAG, "Partial Read of Data by Model"); | ||||
|     ESP_LOGD(TAG, "Could only read %d bytes when required %d bytes ", bytes_read, | ||||
|              (int) (NEW_SAMPLES_TO_GET * sizeof(int16_t))); | ||||
|              (int) (this->new_samples_to_get_() * sizeof(int16_t))); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   *audio_samples = this->preprocessor_audio_buffer_; | ||||
|   return true; | ||||
| } | ||||
|   size_t num_samples_read; | ||||
|   struct FrontendOutput frontend_output = FrontendProcessSamples( | ||||
|       &this->frontend_state_, this->preprocessor_audio_buffer_, this->new_samples_to_get_(), &num_samples_read); | ||||
|  | ||||
| bool MicroWakeWord::generate_single_feature_(const int16_t *audio_data, const int audio_data_size, | ||||
|                                              int8_t feature_output[PREPROCESSOR_FEATURE_SIZE]) { | ||||
|   TfLiteTensor *input = this->preprocessor_interperter_->input(0); | ||||
|   TfLiteTensor *output = this->preprocessor_interperter_->output(0); | ||||
|   std::copy_n(audio_data, audio_data_size, tflite::GetTensorData<int16_t>(input)); | ||||
|  | ||||
|   if (this->preprocessor_interperter_->Invoke() != kTfLiteOk) { | ||||
|     ESP_LOGE(TAG, "Failed to preprocess audio for local wake word."); | ||||
|     return false; | ||||
|   for (size_t i = 0; i < frontend_output.size; ++i) { | ||||
|     // These scaling values are set to match the TFLite audio frontend int8 output. | ||||
|     // The feature pipeline outputs 16-bit signed integers in roughly a 0 to 670 | ||||
|     // range. In training, these are then arbitrarily divided by 25.6 to get | ||||
|     // float values in the rough range of 0.0 to 26.0. This scaling is performed | ||||
|     // for historical reasons, to match up with the output of other feature | ||||
|     // generators. | ||||
|     // The process is then further complicated when we quantize the model. This | ||||
|     // means we have to scale the 0.0 to 26.0 real values to the -128 to 127 | ||||
|     // signed integer numbers. | ||||
|     // All this means that to get matching values from our integer feature | ||||
|     // output into the tensor input, we have to perform: | ||||
|     // input = (((feature / 25.6) / 26.0) * 256) - 128 | ||||
|     // To simplify this and perform it in 32-bit integer math, we rearrange to: | ||||
|     // input = (feature * 256) / (25.6 * 26.0) - 128 | ||||
|     constexpr int32_t value_scale = 256; | ||||
|     constexpr int32_t value_div = 666;  // 666 = 25.6 * 26.0 after rounding | ||||
|     int32_t value = ((frontend_output.values[i] * value_scale) + (value_div / 2)) / value_div; | ||||
|     value -= 128; | ||||
|     if (value < -128) { | ||||
|       value = -128; | ||||
|     } | ||||
|     if (value > 127) { | ||||
|       value = 127; | ||||
|     } | ||||
|     features[i] = value; | ||||
|   } | ||||
|   std::memcpy(feature_output, tflite::GetTensorData<int8_t>(output), PREPROCESSOR_FEATURE_SIZE * sizeof(int8_t)); | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool MicroWakeWord::register_preprocessor_ops_(tflite::MicroMutableOpResolver<18> &op_resolver) { | ||||
|   if (op_resolver.AddReshape() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddCast() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddStridedSlice() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddConcatenation() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddMul() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddAdd() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddDiv() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddMinimum() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddMaximum() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddWindow() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddFftAutoScale() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddRfft() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddEnergy() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddFilterBank() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddFilterBankSquareRoot() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddFilterBankSpectralSubtraction() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddPCAN() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddFilterBankLog() != kTfLiteOk) | ||||
|     return false; | ||||
|  | ||||
|   return true; | ||||
| void MicroWakeWord::reset_states_() { | ||||
|   ESP_LOGD(TAG, "Resetting buffers and probabilities"); | ||||
|   this->ring_buffer_->reset(); | ||||
|   this->ignore_windows_ = -MIN_SLICES_BEFORE_DETECTION; | ||||
|   for (auto &model : this->wake_word_models_) { | ||||
|     model.reset_probabilities(); | ||||
|   } | ||||
| #ifdef USE_MICRO_WAKE_WORD_VAD | ||||
|   this->vad_model_->reset_probabilities(); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<17> &op_resolver) { | ||||
| bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<20> &op_resolver) { | ||||
|   if (op_resolver.AddCallOnce() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddVarHandle() != kTfLiteOk) | ||||
| @@ -496,6 +442,12 @@ bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<17> & | ||||
|     return false; | ||||
|   if (op_resolver.AddMaxPool2D() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddPad() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddPack() != kTfLiteOk) | ||||
|     return false; | ||||
|   if (op_resolver.AddSplitV() != kTfLiteOk) | ||||
|     return false; | ||||
|  | ||||
|   return true; | ||||
| } | ||||
| @@ -504,5 +456,3 @@ bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<17> & | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif  // USE_ESP_IDF | ||||
|  | ||||
| #endif  // CLANG_TIDY | ||||
|   | ||||
| @@ -1,21 +1,18 @@ | ||||
| #pragma once | ||||
|  | ||||
| /** | ||||
|  * This is a workaround until we can figure out a way to get | ||||
|  * the tflite-micro idf component code available in CI | ||||
|  * | ||||
|  * */ | ||||
| // | ||||
| #ifndef CLANG_TIDY | ||||
|  | ||||
| #ifdef USE_ESP_IDF | ||||
|  | ||||
| #include "preprocessor_settings.h" | ||||
| #include "streaming_model.h" | ||||
|  | ||||
| #include "esphome/core/automation.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/ring_buffer.h" | ||||
|  | ||||
| #include "esphome/components/microphone/microphone.h" | ||||
|  | ||||
| #include <frontend_util.h> | ||||
|  | ||||
| #include <tensorflow/lite/core/c/common.h> | ||||
| #include <tensorflow/lite/micro/micro_interpreter.h> | ||||
| #include <tensorflow/lite/micro/micro_mutable_op_resolver.h> | ||||
| @@ -23,35 +20,6 @@ | ||||
| namespace esphome { | ||||
| namespace micro_wake_word { | ||||
|  | ||||
| // The following are dictated by the preprocessor model | ||||
| // | ||||
| // The number of features the audio preprocessor generates per slice | ||||
| static const uint8_t PREPROCESSOR_FEATURE_SIZE = 40; | ||||
| // How frequently the preprocessor generates a new set of features | ||||
| static const uint8_t FEATURE_STRIDE_MS = 20; | ||||
| // Duration of each slice used as input into the preprocessor | ||||
| static const uint8_t FEATURE_DURATION_MS = 30; | ||||
| // Audio sample frequency in hertz | ||||
| static const uint16_t AUDIO_SAMPLE_FREQUENCY = 16000; | ||||
| // The number of old audio samples that are saved to be part of the next feature window | ||||
| static const uint16_t HISTORY_SAMPLES_TO_KEEP = | ||||
|     ((FEATURE_DURATION_MS - FEATURE_STRIDE_MS) * (AUDIO_SAMPLE_FREQUENCY / 1000)); | ||||
| // The number of new audio samples to receive to be included with the next feature window | ||||
| static const uint16_t NEW_SAMPLES_TO_GET = (FEATURE_STRIDE_MS * (AUDIO_SAMPLE_FREQUENCY / 1000)); | ||||
| // The total number of audio samples included in the feature window | ||||
| static const uint16_t SAMPLE_DURATION_COUNT = FEATURE_DURATION_MS * AUDIO_SAMPLE_FREQUENCY / 1000; | ||||
| // Number of bytes in memory needed for the preprocessor arena | ||||
| static const uint32_t PREPROCESSOR_ARENA_SIZE = 9528; | ||||
|  | ||||
| // The following configure the streaming wake word model | ||||
| // | ||||
| // The number of audio slices to process before accepting a positive detection | ||||
| static const uint8_t MIN_SLICES_BEFORE_DETECTION = 74; | ||||
|  | ||||
| // Number of bytes in memory needed for the streaming wake word model | ||||
| static const uint32_t STREAMING_MODEL_ARENA_SIZE = 64000; | ||||
| static const uint32_t STREAMING_MODEL_VARIABLE_ARENA_SIZE = 1024; | ||||
|  | ||||
| enum State { | ||||
|   IDLE, | ||||
|   START_MICROPHONE, | ||||
| @@ -61,6 +29,9 @@ enum State { | ||||
|   STOPPING_MICROPHONE, | ||||
| }; | ||||
|  | ||||
| // The number of audio slices to process before accepting a positive detection | ||||
| static const uint8_t MIN_SLICES_BEFORE_DETECTION = 74; | ||||
|  | ||||
| class MicroWakeWord : public Component { | ||||
|  public: | ||||
|   void setup() override; | ||||
| @@ -73,28 +44,21 @@ class MicroWakeWord : public Component { | ||||
|  | ||||
|   bool is_running() const { return this->state_ != State::IDLE; } | ||||
|  | ||||
|   bool initialize_models(); | ||||
|  | ||||
|   std::string get_wake_word() { return this->wake_word_; } | ||||
|  | ||||
|   // Increasing either of these will reduce the rate of false acceptances while increasing the false rejection rate | ||||
|   void set_probability_cutoff(float probability_cutoff) { this->probability_cutoff_ = probability_cutoff; } | ||||
|   void set_sliding_window_average_size(size_t size); | ||||
|   void set_features_step_size(uint8_t step_size) { this->features_step_size_ = step_size; } | ||||
|  | ||||
|   void set_microphone(microphone::Microphone *microphone) { this->microphone_ = microphone; } | ||||
|  | ||||
|   Trigger<std::string> *get_wake_word_detected_trigger() const { return this->wake_word_detected_trigger_; } | ||||
|  | ||||
|   void set_model_start(const uint8_t *model_start) { this->model_start_ = model_start; } | ||||
|   void set_wake_word(const std::string &wake_word) { this->wake_word_ = wake_word; } | ||||
|   void add_wake_word_model(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_average_size, | ||||
|                            const std::string &wake_word, size_t tensor_arena_size); | ||||
|  | ||||
| #ifdef USE_MICRO_WAKE_WORD_VAD | ||||
|   void add_vad_model(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_size, | ||||
|                      size_t tensor_arena_size); | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   void set_state_(State state); | ||||
|   int read_microphone_(); | ||||
|  | ||||
|   const uint8_t *model_start_; | ||||
|   std::string wake_word_; | ||||
|  | ||||
|   microphone::Microphone *microphone_{nullptr}; | ||||
|   Trigger<std::string> *wake_word_detected_trigger_ = new Trigger<std::string>(); | ||||
|   State state_{State::IDLE}; | ||||
| @@ -102,85 +66,93 @@ class MicroWakeWord : public Component { | ||||
|  | ||||
|   std::unique_ptr<RingBuffer> ring_buffer_; | ||||
|  | ||||
|   int16_t *input_buffer_; | ||||
|   std::vector<WakeWordModel> wake_word_models_; | ||||
|  | ||||
|   const tflite::Model *preprocessor_model_{nullptr}; | ||||
|   const tflite::Model *streaming_model_{nullptr}; | ||||
|   tflite::MicroInterpreter *streaming_interpreter_{nullptr}; | ||||
|   tflite::MicroInterpreter *preprocessor_interperter_{nullptr}; | ||||
| #ifdef USE_MICRO_WAKE_WORD_VAD | ||||
|   std::unique_ptr<VADModel> vad_model_; | ||||
| #endif | ||||
|  | ||||
|   std::vector<float> recent_streaming_probabilities_; | ||||
|   size_t last_n_index_{0}; | ||||
|   tflite::MicroMutableOpResolver<20> streaming_op_resolver_; | ||||
|  | ||||
|   float probability_cutoff_{0.5}; | ||||
|   size_t sliding_window_average_size_{10}; | ||||
|   // Audio frontend handles generating spectrogram features | ||||
|   struct FrontendConfig frontend_config_; | ||||
|   struct FrontendState frontend_state_; | ||||
|  | ||||
|   // When the wake word detection first starts or after the word has been detected once, we ignore this many audio | ||||
|   // feature slices before accepting a positive detection again | ||||
|   // When the wake word detection first starts, we ignore this many audio | ||||
|   // feature slices before accepting a positive detection | ||||
|   int16_t ignore_windows_{-MIN_SLICES_BEFORE_DETECTION}; | ||||
|  | ||||
|   uint8_t *streaming_var_arena_{nullptr}; | ||||
|   uint8_t *streaming_tensor_arena_{nullptr}; | ||||
|   uint8_t *preprocessor_tensor_arena_{nullptr}; | ||||
|   int8_t *new_features_data_{nullptr}; | ||||
|   uint8_t features_step_size_; | ||||
|  | ||||
|   tflite::MicroResourceVariables *mrv_{nullptr}; | ||||
|  | ||||
|   // Stores audio fed into feature generator preprocessor | ||||
|   int16_t *preprocessor_audio_buffer_; | ||||
|   // Stores audio read from the microphone before being added to the ring buffer. | ||||
|   int16_t *input_buffer_{nullptr}; | ||||
|   // Stores audio to be fed into the audio frontend for generating features. | ||||
|   int16_t *preprocessor_audio_buffer_{nullptr}; | ||||
|  | ||||
|   bool detected_{false}; | ||||
|   std::string detected_wake_word_{""}; | ||||
|  | ||||
|   /** Detects if wake word has been said | ||||
|   void set_state_(State state); | ||||
|  | ||||
|   /// @brief Tests if there are enough samples in the ring buffer to generate new features. | ||||
|   /// @return True if enough samples, false otherwise. | ||||
|   bool has_enough_samples_(); | ||||
|  | ||||
|   /** Reads audio from microphone into the ring buffer | ||||
|    * | ||||
|    * Audio data (16000 kHz with int16 samples) is read into the input_buffer_. | ||||
|    * Verifies the ring buffer has enough space for all audio data. If not, it logs | ||||
|    * a warning and resets the ring buffer entirely. | ||||
|    * @return Number of bytes written to the ring buffer | ||||
|    */ | ||||
|   size_t read_microphone_(); | ||||
|  | ||||
|   /// @brief Allocates memory for input_buffer_, preprocessor_audio_buffer_, and ring_buffer_ | ||||
|   /// @return True if successful, false otherwise | ||||
|   bool allocate_buffers_(); | ||||
|  | ||||
|   /// @brief Frees memory allocated for input_buffer_ and preprocessor_audio_buffer_ | ||||
|   void deallocate_buffers_(); | ||||
|  | ||||
|   /// @brief Loads streaming models and prepares the feature generation frontend | ||||
|   /// @return True if successful, false otherwise | ||||
|   bool load_models_(); | ||||
|  | ||||
|   /// @brief Deletes each model's TFLite interpreters and frees tensor arena memory. Frees memory used by the feature | ||||
|   /// generation frontend. | ||||
|   void unload_models_(); | ||||
|  | ||||
|   /** Performs inference with each configured model | ||||
|    * | ||||
|    * If enough audio samples are available, it will generate one slice of new features. | ||||
|    * If the streaming model predicts the wake word, then the nonstreaming model confirms it. | ||||
|    * @param ring_Buffer Ring buffer containing raw audio samples | ||||
|    * @return True if the wake word is detected, false otherwise | ||||
|    * It then loops through and performs inference with each of the loaded models. | ||||
|    */ | ||||
|   bool detect_wake_word_(); | ||||
|   void update_model_probabilities_(); | ||||
|  | ||||
|   /// @brief Returns true if there are enough audio samples in the buffer to generate another slice of features | ||||
|   bool slice_available_(); | ||||
|  | ||||
|   /** Shifts previous feature slices over by one and generates a new slice of features | ||||
|   /** Checks every model's recent probabilities to determine if the wake word has been predicted | ||||
|    * | ||||
|    * @param ring_buffer ring buffer containing raw audio samples | ||||
|    * @return True if a new slice of features was generated, false otherwise | ||||
|    * Verifies the models have processed enough new samples for accurate predictions. | ||||
|    * Sets detected_wake_word_ to the wake word, if one is detected. | ||||
|    * @return True if a wake word is predicted, false otherwise | ||||
|    */ | ||||
|   bool update_features_(); | ||||
|   bool detect_wake_words_(); | ||||
|  | ||||
|   /** Generates features from audio samples | ||||
|   /** Generates features for a window of audio samples | ||||
|    * | ||||
|    * Adapted from TFLite micro speech example | ||||
|    * @param audio_data Pointer to array with the audio samples | ||||
|    * @param audio_data_size The number of samples to use as input to the preprocessor model | ||||
|    * @param feature_output Array that will store the features | ||||
|    * Reads samples from the ring buffer and feeds them into the preprocessor frontend. | ||||
|    * Adapted from TFLite microspeech frontend. | ||||
|    * @param features int8_t array to store the audio features | ||||
|    * @return True if successful, false otherwise. | ||||
|    */ | ||||
|   bool generate_single_feature_(const int16_t *audio_data, int audio_data_size, | ||||
|                                 int8_t feature_output[PREPROCESSOR_FEATURE_SIZE]); | ||||
|   bool generate_features_for_window_(int8_t features[PREPROCESSOR_FEATURE_SIZE]); | ||||
|  | ||||
|   /** Performs inference over the most recent feature slice with the streaming model | ||||
|    * | ||||
|    * @return Probability of the wake word between 0.0 and 1.0 | ||||
|    */ | ||||
|   float perform_streaming_inference_(); | ||||
|  | ||||
|   /** Strides the audio samples by keeping the last 10 ms of the previous slice | ||||
|    * | ||||
|    * Adapted from the TFLite micro speech example | ||||
|    * @param ring_buffer Ring buffer containing raw audio samples | ||||
|    * @param audio_samples Pointer to an array that will store the strided audio samples | ||||
|    * @return True if successful, false otherwise | ||||
|    */ | ||||
|   bool stride_audio_samples_(int16_t **audio_samples); | ||||
|  | ||||
|   /// @brief Returns true if successfully registered the preprocessor's TensorFlow operations | ||||
|   bool register_preprocessor_ops_(tflite::MicroMutableOpResolver<18> &op_resolver); | ||||
|   /// @brief Resets the ring buffer, ignore_windows_, and sliding window probabilities | ||||
|   void reset_states_(); | ||||
|  | ||||
|   /// @brief Returns true if successfully registered the streaming model's TensorFlow operations | ||||
|   bool register_streaming_ops_(tflite::MicroMutableOpResolver<17> &op_resolver); | ||||
|   bool register_streaming_ops_(tflite::MicroMutableOpResolver<20> &op_resolver); | ||||
|  | ||||
|   inline uint16_t new_samples_to_get_() { return (this->features_step_size_ * (AUDIO_SAMPLE_FREQUENCY / 1000)); } | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class StartAction : public Action<Ts...>, public Parented<MicroWakeWord> { | ||||
| @@ -202,5 +174,3 @@ template<typename... Ts> class IsRunningCondition : public Condition<Ts...>, pub | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif  // USE_ESP_IDF | ||||
|  | ||||
| #endif  // CLANG_TIDY | ||||
|   | ||||
							
								
								
									
										20
									
								
								esphome/components/micro_wake_word/preprocessor_settings.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								esphome/components/micro_wake_word/preprocessor_settings.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| #pragma once | ||||
|  | ||||
| #ifdef USE_ESP_IDF | ||||
|  | ||||
| #include <cstdint> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace micro_wake_word { | ||||
|  | ||||
| // The number of features the audio preprocessor generates per slice | ||||
| static const uint8_t PREPROCESSOR_FEATURE_SIZE = 40; | ||||
| // Duration of each slice used as input into the preprocessor | ||||
| static const uint8_t FEATURE_DURATION_MS = 30; | ||||
| // Audio sample frequency in hertz | ||||
| static const uint16_t AUDIO_SAMPLE_FREQUENCY = 16000; | ||||
|  | ||||
| }  // namespace micro_wake_word | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										189
									
								
								esphome/components/micro_wake_word/streaming_model.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								esphome/components/micro_wake_word/streaming_model.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,189 @@ | ||||
| #ifdef USE_ESP_IDF | ||||
|  | ||||
| #include "streaming_model.h" | ||||
|  | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| static const char *const TAG = "micro_wake_word"; | ||||
|  | ||||
| namespace esphome { | ||||
| namespace micro_wake_word { | ||||
|  | ||||
| void WakeWordModel::log_model_config() { | ||||
|   ESP_LOGCONFIG(TAG, "    - Wake Word: %s", this->wake_word_.c_str()); | ||||
|   ESP_LOGCONFIG(TAG, "      Probability cutoff: %.3f", this->probability_cutoff_); | ||||
|   ESP_LOGCONFIG(TAG, "      Sliding window size: %d", this->sliding_window_size_); | ||||
| } | ||||
|  | ||||
| void VADModel::log_model_config() { | ||||
|   ESP_LOGCONFIG(TAG, "    - VAD Model"); | ||||
|   ESP_LOGCONFIG(TAG, "      Probability cutoff: %.3f", this->probability_cutoff_); | ||||
|   ESP_LOGCONFIG(TAG, "      Sliding window size: %d", this->sliding_window_size_); | ||||
| } | ||||
|  | ||||
| bool StreamingModel::load_model(tflite::MicroMutableOpResolver<20> &op_resolver) { | ||||
|   ExternalRAMAllocator<uint8_t> arena_allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); | ||||
|  | ||||
|   if (this->tensor_arena_ == nullptr) { | ||||
|     this->tensor_arena_ = arena_allocator.allocate(this->tensor_arena_size_); | ||||
|     if (this->tensor_arena_ == nullptr) { | ||||
|       ESP_LOGE(TAG, "Could not allocate the streaming model's tensor arena."); | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (this->var_arena_ == nullptr) { | ||||
|     this->var_arena_ = arena_allocator.allocate(STREAMING_MODEL_VARIABLE_ARENA_SIZE); | ||||
|     if (this->var_arena_ == nullptr) { | ||||
|       ESP_LOGE(TAG, "Could not allocate the streaming model's variable tensor arena."); | ||||
|       return false; | ||||
|     } | ||||
|     this->ma_ = tflite::MicroAllocator::Create(this->var_arena_, STREAMING_MODEL_VARIABLE_ARENA_SIZE); | ||||
|     this->mrv_ = tflite::MicroResourceVariables::Create(this->ma_, 20); | ||||
|   } | ||||
|  | ||||
|   const tflite::Model *model = tflite::GetModel(this->model_start_); | ||||
|   if (model->version() != TFLITE_SCHEMA_VERSION) { | ||||
|     ESP_LOGE(TAG, "Streaming model's schema is not supported"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   if (this->interpreter_ == nullptr) { | ||||
|     this->interpreter_ = make_unique<tflite::MicroInterpreter>( | ||||
|         tflite::GetModel(this->model_start_), op_resolver, this->tensor_arena_, this->tensor_arena_size_, this->mrv_); | ||||
|     if (this->interpreter_->AllocateTensors() != kTfLiteOk) { | ||||
|       ESP_LOGE(TAG, "Failed to allocate tensors for the streaming model"); | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     // Verify input tensor matches expected values | ||||
|     // Dimension 3 will represent the first layer stride, so skip it may vary | ||||
|     TfLiteTensor *input = this->interpreter_->input(0); | ||||
|     if ((input->dims->size != 3) || (input->dims->data[0] != 1) || | ||||
|         (input->dims->data[2] != PREPROCESSOR_FEATURE_SIZE)) { | ||||
|       ESP_LOGE(TAG, "Streaming model tensor input dimensions has improper dimensions."); | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     if (input->type != kTfLiteInt8) { | ||||
|       ESP_LOGE(TAG, "Streaming model tensor input is not int8."); | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     // Verify output tensor matches expected values | ||||
|     TfLiteTensor *output = this->interpreter_->output(0); | ||||
|     if ((output->dims->size != 2) || (output->dims->data[0] != 1) || (output->dims->data[1] != 1)) { | ||||
|       ESP_LOGE(TAG, "Streaming model tensor output dimension is not 1x1."); | ||||
|     } | ||||
|  | ||||
|     if (output->type != kTfLiteUInt8) { | ||||
|       ESP_LOGE(TAG, "Streaming model tensor output is not uint8."); | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void StreamingModel::unload_model() { | ||||
|   this->interpreter_.reset(); | ||||
|  | ||||
|   ExternalRAMAllocator<uint8_t> arena_allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); | ||||
|  | ||||
|   arena_allocator.deallocate(this->tensor_arena_, this->tensor_arena_size_); | ||||
|   this->tensor_arena_ = nullptr; | ||||
|   arena_allocator.deallocate(this->var_arena_, STREAMING_MODEL_VARIABLE_ARENA_SIZE); | ||||
|   this->var_arena_ = nullptr; | ||||
| } | ||||
|  | ||||
| bool StreamingModel::perform_streaming_inference(const int8_t features[PREPROCESSOR_FEATURE_SIZE]) { | ||||
|   if (this->interpreter_ != nullptr) { | ||||
|     TfLiteTensor *input = this->interpreter_->input(0); | ||||
|  | ||||
|     std::memmove( | ||||
|         (int8_t *) (tflite::GetTensorData<int8_t>(input)) + PREPROCESSOR_FEATURE_SIZE * this->current_stride_step_, | ||||
|         features, PREPROCESSOR_FEATURE_SIZE); | ||||
|     ++this->current_stride_step_; | ||||
|  | ||||
|     uint8_t stride = this->interpreter_->input(0)->dims->data[1]; | ||||
|  | ||||
|     if (this->current_stride_step_ >= stride) { | ||||
|       this->current_stride_step_ = 0; | ||||
|  | ||||
|       TfLiteStatus invoke_status = this->interpreter_->Invoke(); | ||||
|       if (invoke_status != kTfLiteOk) { | ||||
|         ESP_LOGW(TAG, "Streaming interpreter invoke failed"); | ||||
|         return false; | ||||
|       } | ||||
|  | ||||
|       TfLiteTensor *output = this->interpreter_->output(0); | ||||
|  | ||||
|       ++this->last_n_index_; | ||||
|       if (this->last_n_index_ == this->sliding_window_size_) | ||||
|         this->last_n_index_ = 0; | ||||
|       this->recent_streaming_probabilities_[this->last_n_index_] = output->data.uint8[0];  // probability; | ||||
|     } | ||||
|     return true; | ||||
|   } | ||||
|   ESP_LOGE(TAG, "Streaming interpreter is not initialized."); | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| void StreamingModel::reset_probabilities() { | ||||
|   for (auto &prob : this->recent_streaming_probabilities_) { | ||||
|     prob = 0; | ||||
|   } | ||||
| } | ||||
|  | ||||
| WakeWordModel::WakeWordModel(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_average_size, | ||||
|                              const std::string &wake_word, size_t tensor_arena_size) { | ||||
|   this->model_start_ = model_start; | ||||
|   this->probability_cutoff_ = probability_cutoff; | ||||
|   this->sliding_window_size_ = sliding_window_average_size; | ||||
|   this->recent_streaming_probabilities_.resize(sliding_window_average_size, 0); | ||||
|   this->wake_word_ = wake_word; | ||||
|   this->tensor_arena_size_ = tensor_arena_size; | ||||
| }; | ||||
|  | ||||
| bool WakeWordModel::determine_detected() { | ||||
|   int32_t sum = 0; | ||||
|   for (auto &prob : this->recent_streaming_probabilities_) { | ||||
|     sum += prob; | ||||
|   } | ||||
|  | ||||
|   float sliding_window_average = static_cast<float>(sum) / static_cast<float>(255 * this->sliding_window_size_); | ||||
|  | ||||
|   // Detect the wake word if the sliding window average is above the cutoff | ||||
|   if (sliding_window_average > this->probability_cutoff_) { | ||||
|     ESP_LOGD(TAG, "The '%s' model sliding average probability is %.3f and most recent probability is %.3f", | ||||
|              this->wake_word_.c_str(), sliding_window_average, | ||||
|              this->recent_streaming_probabilities_[this->last_n_index_] / (255.0)); | ||||
|     return true; | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| VADModel::VADModel(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_size, | ||||
|                    size_t tensor_arena_size) { | ||||
|   this->model_start_ = model_start; | ||||
|   this->probability_cutoff_ = probability_cutoff; | ||||
|   this->sliding_window_size_ = sliding_window_size; | ||||
|   this->recent_streaming_probabilities_.resize(sliding_window_size, 0); | ||||
|   this->tensor_arena_size_ = tensor_arena_size; | ||||
| }; | ||||
|  | ||||
| bool VADModel::determine_detected() { | ||||
|   uint8_t max = 0; | ||||
|   for (auto &prob : this->recent_streaming_probabilities_) { | ||||
|     max = std::max(prob, max); | ||||
|   } | ||||
|  | ||||
|   return max > this->probability_cutoff_; | ||||
| } | ||||
|  | ||||
| }  // namespace micro_wake_word | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										84
									
								
								esphome/components/micro_wake_word/streaming_model.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								esphome/components/micro_wake_word/streaming_model.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| #pragma once | ||||
|  | ||||
| #ifdef USE_ESP_IDF | ||||
|  | ||||
| #include "preprocessor_settings.h" | ||||
|  | ||||
| #include <tensorflow/lite/core/c/common.h> | ||||
| #include <tensorflow/lite/micro/micro_interpreter.h> | ||||
| #include <tensorflow/lite/micro/micro_mutable_op_resolver.h> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace micro_wake_word { | ||||
|  | ||||
| static const uint32_t STREAMING_MODEL_VARIABLE_ARENA_SIZE = 1024; | ||||
|  | ||||
| class StreamingModel { | ||||
|  public: | ||||
|   virtual void log_model_config() = 0; | ||||
|   virtual bool determine_detected() = 0; | ||||
|  | ||||
|   bool perform_streaming_inference(const int8_t features[PREPROCESSOR_FEATURE_SIZE]); | ||||
|  | ||||
|   /// @brief Sets all recent_streaming_probabilities to 0 | ||||
|   void reset_probabilities(); | ||||
|  | ||||
|   /// @brief Allocates tensor and variable arenas and sets up the model interpreter | ||||
|   /// @param op_resolver MicroMutableOpResolver object that must exist until the model is unloaded | ||||
|   /// @return True if successful, false otherwise | ||||
|   bool load_model(tflite::MicroMutableOpResolver<20> &op_resolver); | ||||
|  | ||||
|   /// @brief Destroys the TFLite interpreter and frees the tensor and variable arenas' memory | ||||
|   void unload_model(); | ||||
|  | ||||
|  protected: | ||||
|   uint8_t current_stride_step_{0}; | ||||
|  | ||||
|   float probability_cutoff_; | ||||
|   size_t sliding_window_size_; | ||||
|   size_t last_n_index_{0}; | ||||
|   size_t tensor_arena_size_; | ||||
|   std::vector<uint8_t> recent_streaming_probabilities_; | ||||
|  | ||||
|   const uint8_t *model_start_; | ||||
|   uint8_t *tensor_arena_{nullptr}; | ||||
|   uint8_t *var_arena_{nullptr}; | ||||
|   std::unique_ptr<tflite::MicroInterpreter> interpreter_; | ||||
|   tflite::MicroResourceVariables *mrv_{nullptr}; | ||||
|   tflite::MicroAllocator *ma_{nullptr}; | ||||
| }; | ||||
|  | ||||
| class WakeWordModel final : public StreamingModel { | ||||
|  public: | ||||
|   WakeWordModel(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_average_size, | ||||
|                 const std::string &wake_word, size_t tensor_arena_size); | ||||
|  | ||||
|   void log_model_config() override; | ||||
|  | ||||
|   /// @brief Checks for the wake word by comparing the mean probability in the sliding window with the probability | ||||
|   /// cutoff | ||||
|   /// @return True if wake word is detected, false otherwise | ||||
|   bool determine_detected() override; | ||||
|  | ||||
|   const std::string &get_wake_word() const { return this->wake_word_; } | ||||
|  | ||||
|  protected: | ||||
|   std::string wake_word_; | ||||
| }; | ||||
|  | ||||
| class VADModel final : public StreamingModel { | ||||
|  public: | ||||
|   VADModel(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_size, size_t tensor_arena_size); | ||||
|  | ||||
|   void log_model_config() override; | ||||
|  | ||||
|   /// @brief Checks for voice activity by comparing the max probability in the sliding window with the probability | ||||
|   /// cutoff | ||||
|   /// @return True if voice activity is detected, false otherwise | ||||
|   bool determine_detected() override; | ||||
| }; | ||||
|  | ||||
| }  // namespace micro_wake_word | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
| @@ -86,6 +86,7 @@ | ||||
| #define USE_ESP32_BLE_SERVER | ||||
| #define USE_ESP32_CAMERA | ||||
| #define USE_IMPROV | ||||
| #define USE_MICRO_WAKE_WORD_VAD | ||||
| #define USE_MICROPHONE | ||||
| #define USE_PSRAM | ||||
| #define USE_SOCKET_IMPL_BSD_SOCKETS | ||||
|   | ||||
| @@ -142,7 +142,8 @@ platform_packages = | ||||
| framework = espidf | ||||
| lib_deps = | ||||
|     ${common:idf.lib_deps} | ||||
|     droscy/esp_wireguard@0.4.2    ; wireguard | ||||
|     droscy/esp_wireguard@0.4.2              ; wireguard | ||||
|     kahrendt/ESPMicroSpeechFeatures@1.0.0   ; micro_wake_word | ||||
| build_flags = | ||||
|     ${common:idf.build_flags} | ||||
|     -Wno-nonnull-compare | ||||
|   | ||||
| @@ -10,6 +10,10 @@ microphone: | ||||
|     pdm: true | ||||
|  | ||||
| micro_wake_word: | ||||
|   model: hey_jarvis | ||||
|   on_wake_word_detected: | ||||
|     - logger.log: "Wake word detected" | ||||
|   models: | ||||
|     - model: hey_jarvis | ||||
|       probability_cutoff: 0.7 | ||||
|     - model: okay_nabu | ||||
|       sliding_window_size: 5 | ||||
|   | ||||
							
								
								
									
										1
									
								
								tests/components/micro_wake_word/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/components/micro_wake_word/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <<: !include common.yaml | ||||
		Reference in New Issue
	
	Block a user