commit b68c60841534c44bf0c73dea1049e963b6426faf Author: thead_admin Date: Tue Sep 13 10:34:38 2022 +0800 Linux_SDK_V0.9.5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c8b6402 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +/.auto.deps +*.tmp +*.bak +*.depend +*.o +*.a +*.o.d +*.o.cmd +*.a.cmd +*.ko.cmd +*.ko +*.mod +*.mod.c +*.mod.cmd +*.order +*.orig +*.log +Module.symvers +modules.order +output/ +obj/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9c8db90 --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## + +include build.param + +all: info common platform tests examples + +info: + @echo $(BUILD_LOG_START) + @echo " ========== Build Info ==========" + @echo " Platform: "$(PLATFORM) + @echo " ROOT_DIR: "$(ROOT_DIR) + @echo $(BUILD_LOG_END) + +common: + @echo $(BUILD_LOG_START) + make -C src/common + @echo $(BUILD_LOG_END) + +platform: common + @echo $(BUILD_LOG_START) + make -C src/platform + @echo $(BUILD_LOG_END) + +libs: platform + @echo $(BUILD_LOG_START) + make -C src/lib_camera + @echo $(BUILD_LOG_END) + +tests: platform libs + @echo $(BUILD_LOG_START) + make -C tests + @echo $(BUILD_LOG_END) + +examples: platform libs + @echo $(BUILD_LOG_START) + make -C examples + @echo $(BUILD_LOG_END) + +clean: + rm -rf .obj + make -C src/common/ clean + make -C src/platform/ clean + make -C src/lib_camera clean + make -C tests/ clean + make -C examples/ clean + +.PHONY: clean all info common platform tests examples diff --git a/README.md b/README.md new file mode 100644 index 0000000..e00f582 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# How to get the code +- git clone git@gitlab.alibaba-inc.com:thead-linux/csi_hal.git + +# How to build +- https://yuque.antfin.com/occ/software/fhcekb + +# Description +- Maintainer: Lu Chongzhi (chongzhi.lcz@alibaba-inc.com) diff --git a/build.param b/build.param new file mode 100644 index 0000000..2a7ed71 --- /dev/null +++ b/build.param @@ -0,0 +1,63 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## + +BUILD_LOG_START="\n\033[47;30m>>> $(MODULE_NAME) $@ begin >>>\033[0m" +BUILD_LOG_END ="\033[47;30m<<< $(MODULE_NAME) $@ end <<<\033[0m" + +PLATFORM ?= simulator + +ROOT_DIR=$(dir $(abspath $(lastword $(MAKEFILE_LIST)))) +SO_LIB_DIR=lib_so + +ifeq ($(PLATFORM),simulator) + CFLAGS := -O0 -g -Werror -fPIC -DPLATFORM_SIMULATOR +else + CFLAGS := -O2 -g -Werror -fPIC +endif +LFLAGS := -no-pie + +LIBS := -L$(ROOT_DIR)/output/common \ + -L$(ROOT_DIR)/output/hal + +ifeq ($(PLATFORM),light) +INCLUDE := -I$(ROOT_DIR)/include/common \ + -I$(ROOT_DIR)/include/hal \ + -I$(ROOT_DIR)/include/lib_camera \ + -I$(ROOT_DIR)/src/lib_camera/camera_action/include\ + -I$(ROOT_DIR)/examples/camera/camera_demo3_srcs \ + -I$(ROOT_DIR)/src/lib_camera \ + -I$(ROOT_DIR)/../recipe-sysroot/usr/include/csi_hal/ + +else +INCLUDE := -I$(ROOT_DIR)/include/common \ + -I$(ROOT_DIR)/include/hal \ + -I$(ROOT_DIR)/include/lib_camera \ + -I$(ROOT_DIR)/src/lib_camera/camera_action/include +endif + +ifeq ($(PLATFORM),simulator) + CC := gcc + CXX := g++ + CPP := g++ + AR := ar + LD := ld + RAN := ranlib + LIBOPENCV_INC = $(shell pkg-config --cflags opencv) + LIBOPENCV_LIBS = $(shell pkg-config --libs opencv) +else ifeq ($(PLATFORM),light) +# PARAM_FILE = ../.param +# -include $(PARAM_FILE) +test = $(shell if [ -f "../.param" ]; then echo "exist"; else echo "noexist"; fi) +ifeq ("$(test)", "exist") + -include ../.param +endif +else + $(error PLATFORM undefined) +endif + diff --git a/common_target.mk b/common_target.mk new file mode 100644 index 0000000..cdcbee7 --- /dev/null +++ b/common_target.mk @@ -0,0 +1,107 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## + +OBJS_1 = $(SRCS_1:.c=.o) +$(TARGET_1): $(OBJS_1) $(PREPARE) + @mkdir -p $(OUTPUT_DIR) + @echo ">>> Linking" $@ "..." + $(CC) $(LFLAGS) -o $@ $(addprefix .obj/,$(notdir $(OBJS_1))) -Wl,--start-group $(LIBS) $(LIBS_1) -Wl,--end-group + cp $@ $(OUTPUT_DIR) + +$(OBJS_1): %.o:%.c + @mkdir -p .obj + @echo ">>> Compiling" $< "..." + $(CC) $(CFLAGS) $(INCLUDE) -c -o .obj/$(notdir $@) $< + +OBJS_2 = $(SRCS_2:.c=.o) +$(TARGET_2): $(OBJS_2) $(PREPARE) + @mkdir -p $(OUTPUT_DIR) + @echo ">>> Linking" $@ "..." + $(CC) $(LFLAGS) -o $@ $(addprefix .obj/,$(notdir $(OBJS_2))) -Wl,--start-group $(LIBS) $(LIBS_2) -Wl,--end-group + cp $@ $(OUTPUT_DIR) + +$(OBJS_2): %.o:%.c + @mkdir -p .obj + @echo ">>> Compiling" $< "..." + $(CC) $(CFLAGS) $(INCLUDE) -c -o .obj/$(notdir $@) $< + +OBJS_3 = $(SRCS_3:.c=.o) +$(TARGET_3): $(OBJS_3) $(PREPARE) + @mkdir -p $(OUTPUT_DIR) + @echo ">>> Linking" $@ "..." + $(CC) $(LFLAGS) -o $@ $(addprefix .obj/,$(notdir $(OBJS_3))) -Wl,--start-group $(LIBS) $(LIBS_3) -Wl,--end-group + cp $@ $(OUTPUT_DIR) + +$(OBJS_3): %.o:%.c + @mkdir -p .obj + @echo ">>> Compiling" $< "..." + $(CC) $(CFLAGS) $(INCLUDE) -c -o .obj/$(notdir $@) $< + +OBJS_4 = $(SRCS_4:.c=.o) +$(TARGET_4): $(OBJS_4) $(PREPARE) + @mkdir -p $(OUTPUT_DIR) + @echo ">>> Linking" $@ "..." + $(CC) $(LFLAGS) -o $@ $(addprefix .obj/,$(notdir $(OBJS_4))) -Wl,--start-group $(LIBS) $(LIBS_4) -Wl,--end-group + cp $@ $(OUTPUT_DIR) + +$(OBJS_4): %.o:%.c + @mkdir -p .obj + @echo ">>> Compiling" $< "..." + $(CC) $(CFLAGS) $(INCLUDE) -c -o .obj/$(notdir $@) $< + + +OBJS_5 = $(SRCS_5:.c=.o) +$(TARGET_5): $(OBJS_5) $(PREPARE) + @mkdir -p $(OUTPUT_DIR) + @echo ">>> Linking" $@ "..." + $(CC) $(LFLAGS) -o $@ $(addprefix .obj/,$(notdir $(OBJS_5))) -Wl,--start-group $(LIBS) $(LIBS_5) -Wl,--end-group + cp $@ $(OUTPUT_DIR) + +$(OBJS_5): %.o:%.c + @mkdir -p .obj + @echo ">>> Compiling" $< "..." + $(CC) $(CFLAGS) $(INCLUDE) -c -o .obj/$(notdir $@) $< + +OBJS_6 = $(SRCS_6:.c=.o) +$(TARGET_6): $(OBJS_6) $(PREPARE) + @mkdir -p $(OUTPUT_DIR) + @echo ">>> Linking" $@ "..." + $(CC) $(LFLAGS) -o $@ $(addprefix .obj/,$(notdir $(OBJS_6))) -Wl,--start-group $(LIBS) $(LIBS_6) -Wl,--end-group + cp $@ $(OUTPUT_DIR) + +$(OBJS_6): %.o:%.c + @mkdir -p .obj + @echo ">>> Compiling" $< "..." + $(CC) $(CFLAGS) $(INCLUDE) -c -o .obj/$(notdir $@) $< + + +OBJS_7 = $(SRCS_7:.c=.o) +$(TARGET_7): $(OBJS_7) $(PREPARE) + @mkdir -p $(OUTPUT_DIR) + @echo ">>> Linking" $@ "..." + $(CC) $(LFLAGS) -o $@ $(addprefix .obj/,$(notdir $(OBJS_7))) -Wl,--start-group $(LIBS) $(LIBS_7) -Wl,--end-group + cp $@ $(OUTPUT_DIR) + +$(OBJS_7): %.o:%.c + @mkdir -p .obj + @echo ">>> Compiling" $< "..." + $(CC) $(CFLAGS) $(INCLUDE) -c -o .obj/$(notdir $@) $< + +OBJS_8 = $(SRCS_8:.c=.o) +$(TARGET_8): $(OBJS_8) $(PREPARE) + @mkdir -p $(OUTPUT_DIR) + @echo ">>> Linking" $@ "..." + $(CC) $(LFLAGS) -o $@ $(addprefix .obj/,$(notdir $(OBJS_8))) -Wl,--start-group $(LIBS) $(LIBS_8) -Wl,--end-group + cp $@ $(OUTPUT_DIR) + +$(OBJS_8): %.o:%.c + @mkdir -p .obj + @echo ">>> Compiling" $< "..." + $(CC) $(CFLAGS) $(INCLUDE) -c -o .obj/$(notdir $@) $< + diff --git a/examples/Makefile b/examples/Makefile new file mode 100644 index 0000000..82ee6fc --- /dev/null +++ b/examples/Makefile @@ -0,0 +1,52 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=.. +include $(DIR_TO_ROOT)/build.param + +ALL_TARGETS := common camera frame +ifeq ($(PLATFORM),simulator) + ALL_TARGETS += vdec venc +endif + +all: $(ALL_TARGETS) + +common: + @echo $(BUILD_LOG_START) + make -C common/ + @echo $(BUILD_LOG_END) + +camera: + @echo $(BUILD_LOG_START) + make -C camera/ + @echo $(BUILD_LOG_END) + +frame: + @echo $(BUILD_LOG_START) + make -C frame/ + @echo $(BUILD_LOG_END) + +vdec: + @echo $(BUILD_LOG_START) + make -C vdec/ + @echo $(BUILD_LOG_END) + +venc: + @echo $(BUILD_LOG_START) + make -C venc/ + @echo $(BUILD_LOG_END) + +clean: + make -C common/ clean + make -C camera/ clean + make -C frame/ clean + make -C vdec/ clean + make -C venc/ clean + rm -rf $(DIR_TO_ROOT)/output/examples/resource + +.PHONY: clean all common camera frame vdec venc diff --git a/examples/camera/.gitignore b/examples/camera/.gitignore new file mode 100644 index 0000000..0980d6e --- /dev/null +++ b/examples/camera/.gitignore @@ -0,0 +1,5 @@ +camera_demo1 +camera_demo2 +camera_demo3 +camera_demo4 +camera_demo5 diff --git a/examples/camera/Makefile b/examples/camera/Makefile new file mode 100644 index 0000000..aa94d16 --- /dev/null +++ b/examples/camera/Makefile @@ -0,0 +1,85 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../.. +include $(DIR_TO_ROOT)/build.param + +CFLAGS += -Wno-error=unused-result -Icamera_demo3_srcs -I./camera_demo3_srcs -I$(DIR_TO_ROOT)/src/lib_camera/ +LIBS += -lhal_common -lhal_camera -lhal_platform -lpthread -lcamera_action +OUTPUT_DIR := $(DIR_TO_ROOT)/output/examples/camera +PREPARE := lib_camera + +ifeq ($(PLATFORM),light) + CFLAGS += -I$(VISYS_SYM_PATH)/usr/include/plink/ + CFLAGS += -I$(VISYS_SYM_PATH)/usr/include/vidmem/ + LIBS += -lstdc++ -lpthread + LIBS += -lvmem -lplink + +endif + +ifeq ($(PLATFORM),simulator) + LIBS += -lcamera_platform -lapp_utilities -lcamera_utilities $(LIBOPENCV_LIBS) -lstdc++ + CFLAGS += $(LIBOPENCV_INC) -I ./opencv + PREPARE += opencv +endif + +TARGET_1 := camera_demo1 +SRCS_1 = camera_demo1.c + +TARGET_2 := camera_demo2 +SRCS_2 = camera_demo2.c + +TARGET_3 := camera_demo3 +SRCS_3 := camera_demo3.c $(wildcard camera_demo3_srcs/*.c) +LIBS_3 += -lcurses + +TARGET_4 := camera_demo4 +SRCS_4 = camera_demo4.c + +TARGET_5 := camera_test1 +SRCS_5 = camera_test1.c + +TARGET_6 := cam_demo_simple +SRCS_6 = cam_demo_simple.c camera_frame_display.c + +TARGET_7 := cam_demo_ir +SRCS_7 = cam_demo_ir.c camera_frame_display.c + +TARGET_8 := cam_demo_multi +SRCS_8 = cam_demo_multi.c camera_frame_display.c + +ifeq ($(PLATFORM),simulator) +TARGET_ALL := $(TARGET_1) $(TARGET_2) $(TARGET_3) $(TARGET_4) +else +TARGET_ALL := $(TARGET_1) $(TARGET_2) $(TARGET_3) $(TARGET_5) $(TARGET_6) $(TARGET_7) $(TARGET_8) +endif + +all: $(TARGET_ALL) + cp run_camera_demo3.sh $(OUTPUT_DIR) + cp camera_demo3.conf $(OUTPUT_DIR) + +lib_camera: + make -C $(DIR_TO_ROOT)/src/lib_camera + +opencv: + make -C opencv + +clean: + rm -rf .obj + rm -f $(TARGET_1) $(OUTPUT_DIR)/$(TARGET_1) + rm -f $(TARGET_2) $(OUTPUT_DIR)/$(TARGET_2) + rm -f $(TARGET_3) $(OUTPUT_DIR)/$(TARGET_3) + rm -f $(TARGET_3) $(OUTPUT_DIR)/run_camera_demo3.sh + rm -f $(TARGET_3) $(OUTPUT_DIR)/camera_demo3.conf + rm -f $(TARGET_4) $(OUTPUT_DIR)/$(TARGET_4) + rm -f $(TARGET_5) $(OUTPUT_DIR)/$(TARGET_5) + make -C ./opencv clean + +include $(DIR_TO_ROOT)/common_target.mk + +.PHONY: opencv clean all diff --git a/examples/camera/cam_demo_ir.c b/examples/camera/cam_demo_ir.c new file mode 100644 index 0000000..046d872 --- /dev/null +++ b/examples/camera/cam_demo_ir.c @@ -0,0 +1,678 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include "pthread.h" +#include +#define LOG_LEVEL 2 +#define LOG_PREFIX "cam_demo_ir" +#include + + +#include +#include +#include "csi_camera_dev_api.h" +#define TEST_DEVICE_NAME "/dev/video12" +typedef struct frame_fps { + uint64_t frameNumber; + struct timeval initTick; + float fps; +} frame_fps_t; +static void dump_camera_meta(csi_frame_s *frame); +static void dump_camera_frame(csi_frame_s *frame, int ch_id); + + +typedef struct event_queue_item{ + struct csi_camera_event evet; + struct event_queue_item *next; +}event_queue_item_t; + +typedef struct channel_handle_ctx{ + + struct event_queue{ + event_queue_item_t *head; + event_queue_item_t *tail; + int exit; + pthread_mutex_t mutex; + pthread_cond_t cond; + }event_queue; + csi_cam_handle_t cam_handle; + pthread_t chan_thread; + int frame_num; +}channel_handle_ctx_t; + +static event_queue_item_t *dequeue_event(channel_handle_ctx_t *ctx) +{ + event_queue_item_t *ev = ctx->event_queue.head; + pthread_mutex_lock(&ctx->event_queue.mutex); + if(!ev) + { + pthread_mutex_unlock(&ctx->event_queue.mutex); + return NULL; + } + + if(ev == ctx->event_queue.tail) + ctx->event_queue.tail = NULL; + ctx->event_queue.head = ev->next; + pthread_mutex_unlock(&ctx->event_queue.mutex); + return ev; +} + +static int test_flag =0; +static void enqueue_event(channel_handle_ctx_t *ctx,event_queue_item_t * item) +{ + + if(!item || !ctx) + return; + item->next = NULL; + LOG_D("%s enter \n", __func__); + pthread_mutex_lock(&ctx->event_queue.mutex); + if (ctx->event_queue.tail) { + ctx->event_queue.tail->next = item; + } else { + ctx->event_queue.head = item; + // pthread_cond_broadcast(&ctx->cond.cond); + } + ctx->event_queue.tail = item; + pthread_mutex_unlock(&ctx->event_queue.mutex); +} +static void* camera_channle_process(void* arg) +{ + + struct timeval init_time, cur_time; + frame_fps_t demo_fps; + csi_frame_s frame; + memset(&init_time, 0, sizeof(init_time)); + memset(&cur_time, 0, sizeof(cur_time)); + channel_handle_ctx_t * ctx = (channel_handle_ctx_t *)arg; + int timeout = -1; // unit: ms, -1 means wait forever, or until error occurs + LOG_D("Channel process running........\n"); + while(ctx->event_queue.exit == 0) + { + event_queue_item_t *item = dequeue_event(ctx); + if(item==NULL) + { + continue; + } + + switch (item->evet.type) { + case CSI_CAMERA_EVENT_TYPE_CHANNEL0: + case CSI_CAMERA_EVENT_TYPE_CHANNEL1: + switch (item->evet.id) { + case CSI_CAMERA_CHANNEL_EVENT_FRAME_READY: { + csi_camera_channel_id_e ch_id = item->evet.type-CSI_CAMERA_EVENT_TYPE_CHANNEL0+CSI_CAMERA_CHANNEL_0; + int read_frame_count = csi_camera_get_frame_count(ctx->cam_handle, + ch_id); + for (int i = 0; i < read_frame_count; i++) { + csi_camera_get_frame(ctx->cam_handle, ch_id, &frame, timeout); + demo_fps.frameNumber++; + ctx->frame_num++; + dump_camera_meta(&frame); + if(test_flag ==0) + { + dump_camera_frame(&frame,ch_id); + } + + // csi_camera_frame_unlock(cam_handle, &frame); + csi_camera_put_frame(&frame); + csi_frame_release(&frame); + } + unsigned long diff; + if (init_time.tv_usec == 0) + gettimeofday(&init_time,0);//osGetTick(); + gettimeofday(&cur_time, 0); + diff = 1000000 * (cur_time.tv_sec-init_time.tv_sec)+ cur_time.tv_usec-init_time.tv_usec; + + if (diff != 0) + demo_fps.fps = (float)demo_fps.frameNumber / diff * 1000000.0f; + LOG_O("ch id:%d read_frame_count = %d, frame_count = %d, fps = %.2f diff = %ld\n",ch_id, read_frame_count, ctx->frame_num, demo_fps.fps, diff); + + break; + } + default: + break; + } + default: + break; + } + free(item); + } + LOG_O("exit\n"); + pthread_exit(0); +} +channel_handle_ctx_t channel_ctx[2]; +/***********************DSP ALGO *******************************/ + +typedef struct cb_context{ + int id; + csi_cam_handle_t cam_handle; + int dsp_id; + int dsp_path; + void *ref_buf; + int buf_size; +}cb_context_t; + +typedef struct cb_args{ + char lib_name[8]; + char setting[16]; + uint32_t frame_id; + uint64_t timestap; + uint32_t temp_projector; //投射器温度 + uint32_t temp_sensor; //sensor 温度 +}cb_args_t; +cb_context_t cb_context[2]; +void dsp_algo_result_cb(void*context,void*arg) +{ + cb_context_t *ctx = (cb_context_t *)context; + cb_args_t *cb_args= (cb_args_t*)arg; + char update_setting[16]; + struct timeval times_value; + uint32_t frame_id = cb_args->frame_id; + uint64_t frame_timestap = cb_args->timestap; + times_value.tv_sec = cb_args->timestap/1000000; + times_value.tv_usec = cb_args->timestap%1000000; + + LOG_O("cb:%d,%s,setting:%s,frame :%d,timestad:(%ld s,%ld us)\n",ctx->id,cb_args->lib_name,cb_args->setting, + frame_id,times_value.tv_sec,times_value.tv_usec); + LOG_O("temperate projector:%d,sensor:%d\n",cb_args->temp_projector,cb_args->temp_sensor); + + if(test_flag!=0) + { + sprintf(update_setting, "update_%d", frame_id); + csi_camera_update_dsp_algo_setting(ctx->cam_handle,ctx->dsp_id,ctx->dsp_path,update_setting); + if(frame_id%20==0) + { + void *replace_buf; + *((uint32_t*)ctx->ref_buf) = frame_id; + csi_camera_update_dsp_algo_buf(ctx->cam_handle, ctx->dsp_id,ctx->dsp_path,ctx->ref_buf,&replace_buf); + LOG_D("cb:%d,old buffer:0x%llx,new buffer:0x%llx\n",ctx->id,ctx->ref_buf,replace_buf); + ctx->ref_buf = replace_buf; + } + } +} +static void usage(void) +{ + printf(" 1 : video id cases\n"); + printf(" 2 : [0]dump frame or [1] algo loop test\n"); + printf(" 3 : frame num to stop ,0: for not stop\n"); + printf(" 4 : channel 0 algo\n"); + printf(" 5 : channel 1 algo\n"); + +} + +int main(int argc, char *argv[]) +{ + uint32_t i; + bool running = true; + csi_camera_info_s camera_info; + frame_fps_t demo_fps; + char *algo_1; + char *algo_2; + int video_id = -1; + char dev_name[64]; + int frame_num = 0; + algo_2 = "dsp1_dummy_algo_flo"; + algo_1 = "dsp1_dummy_algo_flo_1"; + switch(argc) + { + case 0: + case 1: + LOG_E("input ERROR:vidoe id is mandatory\n"); + usage(); + return -1; + + case 6: + algo_2 = (argv[5]); + case 5: + algo_1 = (argv[4]); + case 4: + frame_num = atoi(argv[3]); + case 3: + test_flag = atoi(argv[2]); + case 2: + video_id = atoi(argv[1]); + break; + default: + LOG_E("input ERROR:too many param\n"); + usage(); + return -1; + } + LOG_O("Demo: video id:%d,test type:%d,frame number:%d,algo1:%s,algo2:%s\n",video_id,test_flag,frame_num,algo_1,algo_2); + sprintf(dev_name, "/dev/video%d", video_id); + memset(&demo_fps, 0, sizeof(demo_fps)); + struct timeval init_time, cur_time; + memset(&init_time, 0, sizeof(init_time)); + memset(&cur_time, 0, sizeof(cur_time)); + // 获取设备中,所有的Camera + struct csi_camera_infos camera_infos; + memset(&camera_infos, 0, sizeof(camera_infos)); + csi_camera_query_list(&camera_infos); + LOG_O("csi_camera_query_list() OK\n"); + + // 打印所有设备所支持的Camera + for (i = 0; i < camera_infos.count; i++) { + printf("Camera[%d]: camera_name='%s', device_name='%s', bus_info='%s', capabilities=0x%08x\n", + i, camera_infos.info[i].camera_name, camera_infos.info[i].device_name, + camera_infos.info[i].bus_info, camera_infos.info[i].capabilities); + } + /* Camera[0]: camera_name='RGB_Camera', device_name='/dev/video0', bus_info='CSI-MIPI', capabilities=0x00800001 + * Camera[1]: camera_name:'Mono_Camera', device_name:'/dev/video8', bus_info='USB', capabilities=0x00000001 + */ + + bool found_camera = false; + for (i = 0; i < camera_infos.count; i++) { + if (strcmp(camera_infos.info[i].device_name, dev_name) == 0) { + camera_info = camera_infos.info[i]; + printf("found device_name:'%s'\n", camera_info.device_name); + found_camera = true; + break; + } + } + if (!found_camera) { + LOG_E("Can't find camera_name:'%s'\n", dev_name); + exit(-1); + } + + printf("The caps are:\n"); + for (i = 1; i < 0x80000000; i = i << 1) { + switch (camera_info.capabilities & i) { + case CSI_CAMERA_CAP_VIDEO_CAPTURE: + printf("\t Video capture\n"); + break; + case CSI_CAMERA_CAP_META_CAPTURE: + printf("\t metadata capture\n"); + break; + default: + if (camera_info.capabilities & i) { + printf("\t unknown capabilities(0x%08x)\n", camera_info.capabilities & i); + } + break; + } + } + + /* The caps are: Video capture, metadata capture */ + // 打开Camera设备获取句柄,作为后续操对象 + csi_cam_handle_t cam_handle; + csi_camera_open(&cam_handle, camera_info.device_name); + LOG_O("csi_camera_open() OK\n"); + + // 获取Camera支持的工作模式 + struct csi_camera_modes camera_modes; + camera_modes.count = 0; + csi_camera_get_modes(cam_handle, &camera_modes); + LOG_O("csi_camera_get_modes() OK\n"); + + // 打印camera所支持的所有工作模式 + printf(" Camera:'%s' modes are:{\n", dev_name); + for (i = 0; i < camera_modes.count; i++) { + printf("\t mode_id=%d: description:'%s'\n", + camera_modes.modes[i].mode_id, camera_modes.modes[i].description); + } + printf("}\n"); + + + csi_camera_channel_cfg_s chn_cfg; + + chn_cfg.chn_id = CSI_CAMERA_CHANNEL_0; + chn_cfg.frm_cnt = 4; + chn_cfg.img_fmt.width = 1080; + chn_cfg.img_fmt.height = 1280; + chn_cfg.img_fmt.pix_fmt = CSI_PIX_FMT_RAW_12BIT; + chn_cfg.img_type = CSI_IMG_TYPE_DMA_BUF; + chn_cfg.meta_fields = CSI_CAMERA_META_DEFAULT_FIELDS; + chn_cfg.capture_type = CSI_CAMERA_CHANNEL_CAPTURE_VIDEO | + CSI_CAMERA_CHANNEL_CAPTURE_META; + csi_camera_channel_open(cam_handle, &chn_cfg); + LOG_O("CSI_CAMERA_CHANNEL_0: csi_camera_channel_open() OK\n"); + + + // 打开输出channel_1 + chn_cfg.chn_id = CSI_CAMERA_CHANNEL_1; + chn_cfg.frm_cnt = 4; + chn_cfg.img_fmt.width = 1080; + chn_cfg.img_fmt.height = 1280; + chn_cfg.img_fmt.pix_fmt = CSI_PIX_FMT_RAW_12BIT; + chn_cfg.img_type = CSI_IMG_TYPE_DMA_BUF; + chn_cfg.meta_fields = CSI_CAMERA_META_DEFAULT_FIELDS; + chn_cfg.capture_type = CSI_CAMERA_CHANNEL_CAPTURE_VIDEO | + CSI_CAMERA_CHANNEL_CAPTURE_META; + csi_camera_channel_open(cam_handle, &chn_cfg); + LOG_O("CSI_CAMERA_CHANNEL_1: csi_camera_channel_open() OK\n"); + + // 订阅Event + csi_cam_event_handle_t event_handle; + csi_camera_create_event(&event_handle, cam_handle); + struct csi_camera_event_subscription subscribe; + subscribe.type = + CSI_CAMERA_EVENT_TYPE_CAMERA; // 订阅Camera的ERROR事件 + subscribe.id = CSI_CAMERA_EVENT_WARNING | CSI_CAMERA_EVENT_ERROR; + csi_camera_subscribe_event(event_handle, &subscribe); + + subscribe.type = + CSI_CAMERA_EVENT_TYPE_CHANNEL0; // 订阅Channel0的FRAME_READY事件 + subscribe.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY | + CSI_CAMERA_CHANNEL_EVENT_OVERFLOW; + csi_camera_subscribe_event(event_handle, &subscribe); + memset(&channel_ctx[0],0x0,sizeof(channel_handle_ctx_t)); + channel_ctx[0].cam_handle = cam_handle; + pthread_mutex_init(&channel_ctx[0].event_queue.mutex,NULL); + pthread_cond_init(&channel_ctx[0].event_queue.cond,NULL); + pthread_create(&channel_ctx[0].chan_thread,NULL,camera_channle_process,&channel_ctx[0]); + + subscribe.type = + CSI_CAMERA_EVENT_TYPE_CHANNEL1; // 订阅Channel0的FRAME_READY事件 + subscribe.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY | + CSI_CAMERA_CHANNEL_EVENT_OVERFLOW; + csi_camera_subscribe_event(event_handle, &subscribe); + memset(&channel_ctx[1],0x0,sizeof(channel_handle_ctx_t)); + channel_ctx[1].cam_handle = cam_handle; + pthread_mutex_init(&channel_ctx[1].event_queue.mutex,NULL); + pthread_cond_init(&channel_ctx[1].event_queue.cond,NULL); + pthread_create(&channel_ctx[1].chan_thread,NULL,camera_channle_process,&channel_ctx[1]); + LOG_O("Event subscript OK\n"); + + cb_context[0].id=0; + cb_context[0].cam_handle = cam_handle; + cb_context[0].dsp_id = 1; + cb_context[0].dsp_path = 3; + char test_set[16]="init"; + cb_context[0].buf_size = 1920*1080; + cb_context[0].ref_buf=NULL; + csi_camera_dsp_algo_param_t algo_param_1={ + .algo_name = algo_1, + .algo_cb.cb = dsp_algo_result_cb, + .algo_cb.context = &cb_context[0], + .algo_cb.arg_size = sizeof(cb_args_t), + .sett_ptr = test_set, + .sett_size =sizeof(test_set), + .extra_buf_num =1, + .extra_buf_sizes = &cb_context[0].buf_size, + .extra_bufs = &cb_context[0].ref_buf, + }; + + if(csi_camera_set_dsp_algo_param(cam_handle, cb_context[0].dsp_id,cb_context[0].dsp_path, &algo_param_1)) + { + LOG_E("set DSP algo fail\n"); + + } + + + cb_context[1].id=1; + cb_context[1].cam_handle = cam_handle; + cb_context[1].dsp_id = 1; + cb_context[1].dsp_path = 4; + cb_context[1].buf_size = 1920*1080; + cb_context[1].ref_buf=NULL; + csi_camera_dsp_algo_param_t algo_param_2={ + .algo_name = algo_2, + .algo_cb.cb = dsp_algo_result_cb, + .algo_cb.context = &cb_context[1], + .algo_cb.arg_size = sizeof(cb_args_t), + .sett_ptr = test_set, + .sett_size =sizeof(test_set), + .extra_buf_num =1, + .extra_buf_sizes = &cb_context[1].buf_size, + .extra_bufs = &cb_context[1].ref_buf, + }; + + if(csi_camera_set_dsp_algo_param(cam_handle, cb_context[1].dsp_id,cb_context[1].dsp_path, &algo_param_2)) + { + LOG_E("set DSP algo fail\n"); + + } + + csi_camera_floodlight_led_set_flash_bright(cam_handle, 500); //500ma + csi_camera_projection_led_set_flash_bright(cam_handle, 500); //500ma + csi_camera_projection_led_set_mode(cam_handle, LED_IR_ENABLE); + csi_camera_floodlight_led_set_mode(cam_handle, LED_IR_ENABLE); + csi_camera_led_enable(cam_handle, LED_FLOODLIGHT_PROJECTION); + + // 开始从channel中取出准备好的frame + csi_camera_channel_start(cam_handle, CSI_CAMERA_CHANNEL_0); + csi_camera_channel_start(cam_handle, CSI_CAMERA_CHANNEL_1); + LOG_O("Channel start OK\n"); + + // 处理订阅的Event + csi_frame_s frame; + event_queue_item_t *event_item; + + LOG_O("Starting get frame...\n"); + while (running) { + int timeout = -1; // unit: ms, -1 means wait forever, or until error occurs + event_item = malloc(sizeof( event_queue_item_t)); + csi_camera_get_event(event_handle, &event_item->evet, timeout); + LOG_O("event.type = %d, event.id = %d\n",event_item->evet.type, event_item->evet.id); + + switch (event_item->evet.type) { + case CSI_CAMERA_EVENT_TYPE_CAMERA: + switch (event_item->evet.id) { + case CSI_CAMERA_EVENT_ERROR: + // do sth. + break; + default: + break; + free(event_item); + break; + } + + case CSI_CAMERA_EVENT_TYPE_CHANNEL0: + case CSI_CAMERA_EVENT_TYPE_CHANNEL1: + switch (event_item->evet.id) { + case CSI_CAMERA_CHANNEL_EVENT_FRAME_READY: { + csi_camera_channel_id_e ch_id = event_item->evet.type-CSI_CAMERA_EVENT_TYPE_CHANNEL0+CSI_CAMERA_CHANNEL_0; + + if(event_item && ch_id<2) + { + enqueue_event(&channel_ctx[ch_id],event_item); + } + break; + } + default: + free(event_item); + break; + } + break; + default: + free(event_item); + break; + } + + // LOG_O("stop:%d,frame:(%d,%d)(%llx,%llx)\n",frame_num ,channel_ctx[0].frame_num ,channel_ctx[1].frame_num,&(channel_ctx[0].frame_num),&(channel_ctx[1].frame_num)); + + if((frame_num >0) && + (channel_ctx[0].frame_num >frame_num) && + (channel_ctx[1].frame_num >frame_num)) + { + csi_camera_channel_stop(cam_handle, CSI_CAMERA_CHANNEL_0); + csi_camera_channel_stop(cam_handle, CSI_CAMERA_CHANNEL_1); + channel_ctx[0].event_queue.exit = 1; + channel_ctx[1].event_queue.exit = 1; + pthread_join(channel_ctx[0].chan_thread,NULL); + pthread_join(channel_ctx[1].chan_thread,NULL); + running = false; + } + } + + // 取消订阅某一个event, 也可以直接调用csi_camera_destory_event,结束所有的订阅 + subscribe.type = CSI_CAMERA_EVENT_TYPE_CHANNEL0; + subscribe.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY | + CSI_CAMERA_CHANNEL_EVENT_OVERFLOW;; + csi_camera_unsubscribe_event(event_handle, &subscribe); + + subscribe.type = CSI_CAMERA_EVENT_TYPE_CHANNEL1; + subscribe.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY | + CSI_CAMERA_CHANNEL_EVENT_OVERFLOW;; + csi_camera_unsubscribe_event(event_handle, &subscribe); + + subscribe.type = CSI_CAMERA_EVENT_TYPE_CAMERA; + subscribe.id = CSI_CAMERA_EVENT_WARNING | CSI_CAMERA_EVENT_ERROR; + csi_camera_unsubscribe_event(event_handle, &subscribe); + + csi_camera_destory_event(event_handle); + + csi_camera_channel_close(cam_handle, CSI_CAMERA_CHANNEL_0); + csi_camera_close(cam_handle); +} + +static void dump_camera_meta(csi_frame_s *frame) +{ + int i; + if (frame->meta.type != CSI_META_TYPE_CAMERA) + return; + + csi_camera_meta_s *meta_data = (csi_camera_meta_s *)frame->meta.data; + csi_camrea_meta_unit_s meta_frame_id,meta_frame_ts; + + + csi_camera_frame_get_meta_unit( + &meta_frame_id, meta_data, CSI_CAMERA_META_ID_FRAME_ID); + csi_camera_frame_get_meta_unit( + &meta_frame_ts, meta_data, CSI_CAMERA_META_ID_TIMESTAMP); + LOG_O("meta frame id=%d,time stap:(%ld,%ld)\n", + meta_frame_id.int_value,meta_frame_ts.time_value.tv_sec,meta_frame_ts.time_value.tv_usec); +} + +static void dump_camera_frame(csi_frame_s *frame, int ch_id) +{ + char file[128]; + static int file_indx=0; + int size,buf_size; + uint32_t indexd, j; + void *buf[3]; + + sprintf(file,"demo_save_img_ch_%d_%d", ch_id,file_indx++%6); + int fd = open(file, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IROTH); + if(fd == -1) { + LOG_E("%s, %d, open file error!!!!!!!!!!!!!!!\n", __func__, __LINE__); + return ; + } + if (frame->img.type == CSI_IMG_TYPE_DMA_BUF) { + + buf_size = frame->img.strides[0]*frame->img.height; + switch(frame->img.pix_format) { + case CSI_PIX_FMT_NV12: + case CSI_PIX_FMT_YUV_SEMIPLANAR_420: + if(frame->img.num_planes ==2) + { + buf_size+=frame->img.strides[1]*frame->img.height/2; + } + else{ + LOG_E("img fomrat is not match with frame planes:%d\n",frame->img.num_planes); + return; + } + break; + case CSI_PIX_FMT_YUV_SEMIPLANAR_422: + if(frame->img.num_planes ==2) + { + buf_size+=frame->img.strides[1]*frame->img.height; + } + else{ + LOG_E("img fomrat is not match with frame planes:%d\n",frame->img.num_planes); + return; + } + break; + default: + break; + + } + + buf[0]= mmap(0, buf_size, PROT_READ | PROT_WRITE, + MAP_SHARED, frame->img.dmabuf[0].fds, frame->img.dmabuf[0].offset); + // plane_size[1] = frame->img.strides[1]*frame->img.height; + // printf("frame plane 1 stride:%d,offset:%d\n", frame->img.strides[1],(uint32_t)frame->img.dmabuf[1].offset); + if(frame->img.num_planes ==2) + { + // buf[1] = mmap(0, plane_size[1]/2, PROT_READ | PROT_WRITE, + // MAP_SHARED, frame->img.dmabuf[1].fds, frame->img.dmabuf[1].offset); + buf[1] = buf[0]+frame->img.dmabuf[1].offset; + } + + } + else{ + buf[0] = frame->img.usr_addr[0]; + buf[1] = frame->img.usr_addr[1]; + } + + LOG_O("save img from :%d, to %s, fmt:%d width:%d stride:%d height:%d\n",frame->img.dmabuf[0].fds, file, frame->img.pix_format , frame->img.width, frame->img.strides[0], frame->img.height); + frame->img.pix_format = CSI_PIX_FMT_RAW_12BIT; + switch(frame->img.pix_format) { + case CSI_PIX_FMT_NV12: + case CSI_PIX_FMT_YUV_SEMIPLANAR_420: + if (frame->img.strides[0] == 0) { + frame->img.strides[0] = frame->img.width; + } + for (j = 0; j < frame->img.height; j++) { //Y + indexd = j*frame->img.strides[0]; + write(fd, buf[0] + indexd, frame->img.width); + } + for (j = 0; j < frame->img.height / 2; j++) { //UV + indexd = j*frame->img.strides[0]; + write(fd, buf[1] + indexd, frame->img.width); + } + break; + case CSI_PIX_FMT_RGB_INTEVLEAVED_888: + case CSI_PIX_FMT_YUV_TEVLEAVED_444: + size = frame->img.width*3; + for (j = 0; j < frame->img.height; j++) { + indexd = j*frame->img.strides[0]; + write(fd, buf[0] + indexd, size); + } + break; + case CSI_PIX_FMT_BGR: + case CSI_PIX_FMT_RGB_PLANAR_888: + case CSI_PIX_FMT_YUV_PLANAR_444: + size = frame->img.width * frame->img.height * 3; + write(fd, buf[0], size); + break; + case CSI_PIX_FMT_RAW_8BIT: + size = frame->img.width; + for (j = 0; j < frame->img.height; j++) { + indexd = j*frame->img.strides[0]; + write(fd, buf[0] + indexd, size); + } + break; + case CSI_PIX_FMT_YUV_TEVLEAVED_422: + case CSI_PIX_FMT_RAW_10BIT: + case CSI_PIX_FMT_RAW_12BIT: + case CSI_PIX_FMT_RAW_14BIT: + case CSI_PIX_FMT_RAW_16BIT: + size = frame->img.width*2; + for (j = 0; j < frame->img.height; j++) { + indexd = j*frame->img.strides[0]; + write(fd, buf[0] + indexd, size); + } + break; + case CSI_PIX_FMT_YUV_SEMIPLANAR_422: + if (frame->img.strides[0] == 0) { + frame->img.strides[0] = frame->img.width; + } + for (j = 0; j < frame->img.height; j++) { //Y + indexd = j*frame->img.strides[0]; + write(fd,buf[0] + indexd, frame->img.width); + } + for (j = 0; j < frame->img.height; j++) { //UV + indexd = j*frame->img.strides[0]; + write(fd, buf[1]+ indexd, frame->img.width); + } + break; + default: + LOG_E("%s unsupported format to save\n", __func__); + exit(-1); + break; + } + + close(fd); + munmap(buf[0],buf_size); + // munmap(buf[1],plane_size[1]); + LOG_O("%s exit\n", __func__); +} \ No newline at end of file diff --git a/examples/camera/cam_demo_multi.c b/examples/camera/cam_demo_multi.c new file mode 100644 index 0000000..3a85f2f --- /dev/null +++ b/examples/camera/cam_demo_multi.c @@ -0,0 +1,807 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: ShenWuYi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + + +#include +#include +#include +#include +#include +#include +#define LOG_LEVEL 2 +#define LOG_PREFIX "cam_demo_multi" +#include + +#include +#include +#include +#ifdef PLATFORM_SIMULATOR +#include "apputilities.h" +#endif +#define MAX_CAM_NUM 3 +#define MAX_CHANNEL_NUM 3 +static void dump_camera_meta(csi_frame_s *frame); +static void dump_camera_frame(csi_frame_s *frame, csi_pixel_fmt_e fmt, int camera_id); + +// extern int csi_camera_frame_unlock(csi_cam_handle_t cam_handle, csi_frame_s *frame); + +#define TEST_DEVICE_NAME "/dev/video0" +#define CSI_CAMERA_TRUE 1 +#define CSI_CAMERA_FALSE 0 + +typedef struct frame_fps { + uint64_t frameNumber; + struct timeval initTick; + float fps; +} frame_fps_t; +typedef enum _frame_mode{ + FRAME_NONE =0, + FRAME_SAVE_IMG =1, + FRAME_SAVE_STREAM =2, + FRAME_SAVE_DISPLAY, + FRAME_INVALID, +}frame_mode_t; + +typedef enum _cam_type{ + CAM_TYEP_RGB =0, + CAM_TYEP_IR, + CAM_TYEP_INVALID, + +}cam_type_e; + + +typedef struct _camera_param{ + int video_id; + int channel_num; + struct { + int width; + int height; + csi_pixel_fmt_e fmt; + + }out_pic[MAX_CHANNEL_NUM]; + + cam_type_e type; + frame_mode_t mode; + int frames_to_stop; +}camera_param_t; + +typedef struct event_queue_item{ + struct csi_camera_event evet; + struct event_queue_item *next; +}event_queue_item_t; + +typedef struct _camera_ctx{ + + int cam_id; + pthread_t cam_thread; + int exit; + csi_cam_handle_t cam_handle; + int channel_num; + csi_cam_event_handle_t event_handle; + csi_camera_event_subscription_s event_subscribe; + csi_camera_channel_cfg_s chn_cfg[MAX_CHANNEL_NUM]; + void * disaply_plink[MAX_CHANNEL_NUM]; + int frame_num; + frame_mode_t frame_mode[MAX_CHANNEL_NUM]; +}camera_ctx_t; + +int file_id = 0; +extern void *vi_plink_create(csi_camera_channel_cfg_s *chn_cfg); +extern void vi_plink_release(void * plink); +extern void display_camera_frame(void * plink, csi_frame_s *frame); + +static void usage(void) +{ + printf(" 1 : RGB Camera id \n"); + printf(" 2 : RGB Camera id channel num\n"); + printf(" 3 : RGB Camera frame mode :0 none ,1 dump enable,2 display \n"); + printf(" 4 : RGB Camera frame num :0 not stop ,else frame num to stop \n"); + printf(" 5 : IR Camera id \n"); + printf(" 6 : IR Camera id channel num\n"); + printf(" 7 : IR Camera frame mode :0 none ,1 dump enable,2 display \n"); + printf(" 8 : IR Camera frame num :0 not stop ,else frame num to stop \n"); +} + +void printUsage(char *name) +{ + printf("usage: %s [options]\n" + "\n" + " Available options:\n" + " -f fisrt camera device 5 param \n" + " 1:video id 2: channel num 3: camrea type (0->rgb ;1->IR) (mandatory)\n" + " 3: mode【0->none ,1->save image,2->display 】(default 1) \n" + " 4: frame num to stop 【0 not stop】(default 0) \n" + " -s second camera device 5 param)\n" + " same param setting as above)\n" + " -t third camera device 5 param)\n" + " same param setting above)\n" + + "\n", name); +} + +static int parseParams(int argc, char **argv, camera_param_t *params) +{ + int index =0; + params[index].video_id = 2; + params[index].type = CAM_TYEP_RGB; + + params[index].channel_num =1; + params[index].out_pic[0].width = 1920; + params[index].out_pic[0].height = 1080; + params[index].out_pic[0].fmt = CSI_PIX_FMT_NV12; + params[index].mode = FRAME_SAVE_IMG; + params[index].frames_to_stop = 0; + index++; + + params[index].video_id = 6; + params[index].type = CAM_TYEP_IR; + + params[index].channel_num = 2; + params[index].out_pic[0].width = 1080; + params[index].out_pic[0].height = 1280; + params[index].out_pic[0].fmt =CSI_PIX_FMT_RAW_12BIT; + params[index].out_pic[1].width = 1080; + params[index].out_pic[1].height = 1280; + params[index].out_pic[1].fmt =CSI_PIX_FMT_RAW_12BIT; + + params[index].mode = FRAME_SAVE_IMG; + params[index].frames_to_stop = 0; + index++; + return index; +} + +static void get_system_time(const char *func, int line_num) +{ + struct timeval cur_time; + + memset(&cur_time, 0, sizeof(cur_time)); + gettimeofday(&cur_time, 0); + printf("%s %s line_num = %d, cur_time.tv_sec = %ld, cur_time.tv_usec = %ld\n", + __func__, func, line_num, cur_time.tv_sec, cur_time.tv_usec); +} + + +static int camera_get_chl_id(int env_type, int *chl_id) +{ + if (chl_id == NULL) { + LOG_E("fail to check param chl_id = %p\n", chl_id); + return -1; + } + switch (env_type) { + case CSI_CAMERA_EVENT_TYPE_CHANNEL0: + *chl_id = CSI_CAMERA_CHANNEL_0; + break; + case CSI_CAMERA_EVENT_TYPE_CHANNEL1: + *chl_id = CSI_CAMERA_CHANNEL_1; + break; + case CSI_CAMERA_EVENT_TYPE_CHANNEL2: + *chl_id = CSI_CAMERA_CHANNEL_2; + break; + default: + LOG_D("%s fail to check env_type = %d unsupport\n", env_type); + break; + } + return 0; +} + +/***********************DSP ALGO *******************************/ + +typedef struct cb_context{ + int id; + csi_cam_handle_t cam_handle; + int dsp_id; + int dsp_path; + void *ref_buf; + int buf_size; +}cb_context_t; + +typedef struct cb_args{ + char lib_name[8]; + char setting[16]; + uint32_t frame_id; + uint64_t timestap; + uint32_t temp_projector; //投射器温度 + uint32_t temp_sensor; //sensor 温度 +}cb_args_t; +cb_context_t cb_context[2]; +void dsp_algo_result_cb(void*context,void*arg) +{ + cb_context_t *ctx = (cb_context_t *)context; + cb_args_t *cb_args= (cb_args_t*)arg; + char update_setting[16]; + struct timeval times_value; + uint32_t frame_id = cb_args->frame_id; + uint64_t frame_timestap = cb_args->timestap; + times_value.tv_sec = cb_args->timestap/1000000; + times_value.tv_usec = cb_args->timestap%1000000; + + LOG_O("cb:%d,%s,setting:%s,frame :%d,timestad:(%ld s,%ld us)\n",ctx->id,cb_args->lib_name,cb_args->setting, + frame_id,times_value.tv_sec,times_value.tv_usec); + LOG_O("temperate projector:%d,sensor:%d\n",cb_args->temp_projector,cb_args->temp_sensor); + + // if(test_flag!=0) + { + sprintf(update_setting, "update_%d", frame_id); + csi_camera_update_dsp_algo_setting(ctx->cam_handle,ctx->dsp_id,ctx->dsp_path,update_setting); + if(frame_id%20==0) + { + void *replace_buf; + *((uint32_t*)ctx->ref_buf) = frame_id; + csi_camera_update_dsp_algo_buf(ctx->cam_handle, ctx->dsp_id,ctx->dsp_path,ctx->ref_buf,&replace_buf); + LOG_D("cb:%d,old buffer:0x%llx,new buffer:0x%llx\n",ctx->id,ctx->ref_buf,replace_buf); + ctx->ref_buf = replace_buf; + } + } +} +static void *camera_event_thread(void *arg) +{ + int ret = 0; + if (arg == NULL) { + LOG_E("NULL Ptr\n"); + pthread_exit(0); + } + camera_ctx_t* ctx = (camera_ctx_t*)arg; + csi_cam_event_handle_t ev_handle = ctx->event_handle; + csi_camera_channel_cfg_s *ch_cfg=NULL; + struct timeval init_time, cur_time; + memset(&init_time, 0, sizeof(init_time)); + memset(&cur_time, 0, sizeof(cur_time)); + + frame_fps_t demo_fps; + memset(&demo_fps, 0, sizeof(demo_fps)); + csi_frame_s frame; + + if (ev_handle == NULL) { + LOG_E("fail to get ev_handle ev_handle\n"); + pthread_exit(0); + } + int timeout = -1; // unit: ms, -1 means wait forever, or until error occurs + int loop_count = 0; + struct csi_camera_event event; + LOG_O("Theard is runing............\n"); + while (ctx->exit==0) { + int timeout = -1; // unit: ms, -1 means wait forever, or until error occurs + csi_camera_get_event(ev_handle, &event, timeout); + LOG_D("Camera_%d event.type = %d, event.id = %d\n",ctx->cam_id ,event.type, event.id); + switch (event.type) { + case CSI_CAMERA_EVENT_TYPE_CAMERA: + switch (event.id) { + case CSI_CAMERA_EVENT_ERROR: + // do sth. + LOG_E("get CAMERA EVENT CSI_CAMERA_EVENT_ERROR!\n"); + break; + case CSI_CAMERA_EVENT_WARNING: + LOG_W("get CAMERA EVENT CSI_CAMERA_EVENT_WRN,RC: %s\n",event.bin); + break; + default: + break; + } + break; + case CSI_CAMERA_EVENT_TYPE_CHANNEL0: + case CSI_CAMERA_EVENT_TYPE_CHANNEL1: + case CSI_CAMERA_EVENT_TYPE_CHANNEL2: + case CSI_CAMERA_EVENT_TYPE_CHANNEL3: + switch (event.id) { + case CSI_CAMERA_CHANNEL_EVENT_FRAME_READY: { + int chn_id = 0; + ret = camera_get_chl_id(event.type, &chn_id); + if (ret) { + LOG_E("fail to get chl_id = %d\n", chn_id); + continue; + } + ch_cfg = &ctx->chn_cfg[chn_id]; + get_system_time(__func__, __LINE__); + int read_frame_count = csi_camera_get_frame_count(ctx->cam_handle, + chn_id); + + unsigned long diff; + if (init_time.tv_usec == 0) + gettimeofday(&init_time,0);//osGetTick(); + gettimeofday(&cur_time, 0); + diff = 1000000 * (cur_time.tv_sec-init_time.tv_sec)+ cur_time.tv_usec-init_time.tv_usec; + demo_fps.frameNumber++; + if (diff != 0) + demo_fps.fps = (float)demo_fps.frameNumber / diff * 1000000.0f; + LOG_O("cam:%d,channle:%d ,read_frame_count = %d, frame_count = %ld, fps = %.2f diff = %ld\n", ctx->cam_id, chn_id, read_frame_count, demo_fps.frameNumber, demo_fps.fps, diff); + + for (int i = 0; i < read_frame_count; i++) { + csi_camera_get_frame(ctx->cam_handle, chn_id, &frame, timeout); + dump_camera_meta(&frame); + if (ctx->frame_mode[chn_id] == FRAME_SAVE_IMG) { + dump_camera_frame(&frame, ch_cfg->img_fmt.pix_fmt, chn_id); + } + if (ctx->disaply_plink && ctx->frame_mode[chn_id]==FRAME_SAVE_DISPLAY) { + frame.img.pix_format = ch_cfg->img_fmt.pix_fmt; + display_camera_frame(ctx->disaply_plink, &frame); + } + csi_camera_put_frame(&frame); + ctx->frame_num++; + } + break; + } + default: + break; + } + break; + default: + break; + } + } + LOG_O("exit!\n"); + pthread_exit(0); +} + + +static void camera_start_all(camera_ctx_t * ctx) +{ + camera_ctx_t * cam_ctx = ctx; + int loop_ch; + for(loop_ch=0;loop_chchannel_num;loop_ch++) + { + csi_camera_channel_start(cam_ctx->cam_handle, cam_ctx->chn_cfg[loop_ch].chn_id); + } +} + +static void camera_stop_all(camera_ctx_t * ctx) +{ + camera_ctx_t * cam_ctx = ctx; + int loop_ch; + for(loop_ch=0;loop_chchannel_num;loop_ch++) + { + csi_camera_channel_stop(cam_ctx->cam_handle, cam_ctx->chn_cfg[loop_ch].chn_id); + } +} +static camera_ctx_t * camera_open(camera_param_t *params) +{ + int ret = 0; + char dev_name[128]; + camera_ctx_t * cam_ctx = NULL; + int loop_ch; + LOG_O("Open Camera %d\n",params->video_id); + if(params==NULL || params->video_id <0) + { + LOG_E("param err\n"); + return NULL; + } + if(params->channel_num> MAX_CHANNEL_NUM) + { + LOG_E("unsupoort channle num:%d \n",params->channel_num); + return NULL; + } + + if(params->mode >=FRAME_INVALID) + { + LOG_E("unsupoort frame process mode:%d \n",params->mode); + return NULL; + } + + cam_ctx = malloc(sizeof(camera_ctx_t)); + if(!cam_ctx) + { + return NULL; + } + memset(cam_ctx,0x0,sizeof(camera_ctx_t)); + + + // 打开Camera设备获取句柄,作为后续操对象 + sprintf(dev_name, "/dev/video%d", params->video_id); + if(csi_camera_open(&cam_ctx->cam_handle, dev_name)) + { + LOG_E("Fail to open cam :%s\n",dev_name); + goto ONE_ERR; + } + + for(loop_ch= CSI_CAMERA_CHANNEL_0;loop_chchannel_num;loop_ch++) + { + cam_ctx->chn_cfg[loop_ch].chn_id = loop_ch; + cam_ctx->chn_cfg[loop_ch].img_fmt.pix_fmt = params->out_pic[loop_ch].fmt; + cam_ctx->chn_cfg[loop_ch].img_fmt.width= params->out_pic[loop_ch].width; + cam_ctx->chn_cfg[loop_ch].img_fmt.height = params->out_pic[loop_ch].height; + cam_ctx->chn_cfg[loop_ch].img_type = CSI_IMG_TYPE_DMA_BUF; + cam_ctx->chn_cfg[loop_ch].meta_fields = CSI_CAMERA_META_DEFAULT_FIELDS; + if(csi_camera_channel_open(cam_ctx->cam_handle,&cam_ctx->chn_cfg[loop_ch])) + { + LOG_E("Fail to open cam %s,channel :%d\n",dev_name,loop_ch); + goto TWO_ERR; + } + + if(params->mode == FRAME_SAVE_DISPLAY) + { + cam_ctx->disaply_plink[loop_ch] = vi_plink_create(&cam_ctx->chn_cfg[loop_ch]); + if(!cam_ctx->disaply_plink[loop_ch]&& loop_ch== CSI_CAMERA_CHANNEL_0) //temp only channel to display + { + LOG_E("Fail to create plink for cam %s,channel :%d\n",dev_name,loop_ch); + goto TWO_ERR; + } + } + cam_ctx->frame_mode[loop_ch]=params->mode; + cam_ctx->channel_num++; + } + + if(csi_camera_create_event(&cam_ctx->event_handle,cam_ctx->cam_handle)) + { + LOG_E("Fail to create event handler for cam %s\n",dev_name); + goto TWO_ERR; + } + + cam_ctx->event_subscribe.type = CSI_CAMERA_EVENT_TYPE_CAMERA; + cam_ctx->event_subscribe.id = CSI_CAMERA_EVENT_WARNING | CSI_CAMERA_EVENT_ERROR; + if(csi_camera_subscribe_event(cam_ctx->event_handle,&cam_ctx->event_subscribe)) + { + LOG_E("Fail to subscribe eventfor cam %s\n",dev_name); + goto TWO_ERR; + } + + + for(loop_ch=0;loop_chchannel_num;loop_ch++) + { + cam_ctx->event_subscribe.type = CSI_CAMERA_EVENT_TYPE_CHANNEL0+loop_ch; + cam_ctx->event_subscribe.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY; + if(csi_camera_subscribe_event(cam_ctx->event_handle,&cam_ctx->event_subscribe)) + { + LOG_E("Fail to subscribe eventfor cam %s\n",dev_name); + goto TWO_ERR; + } + } + + LOG_O("%s open successfully\n",dev_name); + ret = pthread_create(&cam_ctx->cam_thread, NULL, + (void *)camera_event_thread, cam_ctx); + if (ret != 0) { + LOG_E("pthread_create() failed, ret=%d", ret); + goto TWO_ERR; + } + + if(params->type == CAM_TYEP_IR) + { + cb_context[0].id=0; + cb_context[0].cam_handle = cam_ctx->cam_handle; + cb_context[0].dsp_id = 1; + cb_context[0].dsp_path = 3; + char test_set[16]="init"; + cb_context[0].buf_size = 1920*1080; + cb_context[0].ref_buf=NULL; + csi_camera_dsp_algo_param_t algo_param_1={ + .algo_name = "dsp1_dummy_algo_flo_1", + .algo_cb.cb = dsp_algo_result_cb, + .algo_cb.context = &cb_context[0], + .algo_cb.arg_size = sizeof(cb_args_t), + .sett_ptr = test_set, + .sett_size =sizeof(test_set), + .extra_buf_num =1, + .extra_buf_sizes = &cb_context[0].buf_size, + .extra_bufs = &cb_context[0].ref_buf, + }; + + if(csi_camera_set_dsp_algo_param(cam_ctx->cam_handle, cb_context[0].dsp_id,cb_context[0].dsp_path, &algo_param_1)) + { + LOG_E("set DSP algo fail\n"); + goto TWO_ERR; + } + + cb_context[1].id=1; + cb_context[1].cam_handle = cam_ctx->cam_handle; + cb_context[1].dsp_id = 1; + cb_context[1].dsp_path = 4; + cb_context[1].buf_size = 1920*1080; + cb_context[1].ref_buf=NULL; + csi_camera_dsp_algo_param_t algo_param_2={ + .algo_name = "dsp1_dummy_algo_flo", + .algo_cb.cb = dsp_algo_result_cb, + .algo_cb.context = &cb_context[1], + .algo_cb.arg_size = sizeof(cb_args_t), + .sett_ptr = test_set, + .sett_size =sizeof(test_set), + .extra_buf_num =1, + .extra_buf_sizes = &cb_context[1].buf_size, + .extra_bufs = &cb_context[1].ref_buf, + }; + + if(csi_camera_set_dsp_algo_param(cam_ctx->cam_handle, cb_context[1].dsp_id,cb_context[1].dsp_path, &algo_param_2)) + { + LOG_E("set DSP algo fail\n"); + goto TWO_ERR; + } + csi_camera_floodlight_led_set_flash_bright(cam_ctx->cam_handle, 500); //500ma + csi_camera_projection_led_set_flash_bright(cam_ctx->cam_handle, 500); //500ma + csi_camera_projection_led_set_mode(cam_ctx->cam_handle, LED_IR_ENABLE); + csi_camera_floodlight_led_set_mode(cam_ctx->cam_handle, LED_IR_ENABLE); + csi_camera_led_enable(cam_ctx->cam_handle, LED_FLOODLIGHT_PROJECTION); + } + + // for(loop_ch=0;loop_chchannel_num;loop_ch++) + // { + // csi_camera_channel_start(cam_ctx->cam_handle, cam_ctx->chn_cfg[loop_ch].chn_id); + // } + + get_system_time(__func__, __LINE__); + + + return cam_ctx; + + +TWO_ERR: + for(loop_ch=0;loop_chchannel_num;loop_ch++) + { + csi_camera_channel_close(cam_ctx->cam_handle, cam_ctx->chn_cfg[loop_ch].chn_id); + } + csi_camera_close(cam_ctx->cam_handle); +ONE_ERR: + free(cam_ctx); + return NULL; +} + +static void camera_close(camera_ctx_t *ctx) +{ + int loop_ch; + + if(ctx->cam_handle==NULL) + { + return; + } + + if (pthread_join(ctx->cam_thread, NULL)) { + LOG_E("pthread_join() failed\n"); + } + + csi_camera_unsubscribe_event(ctx->event_handle, &ctx->event_subscribe); + + csi_camera_destory_event(ctx->event_handle); + // int loop_ch; + // for(loop_ch=0;loop_chchannel_num;loop_ch++) + // { + // csi_camera_channel_stop(cam_ctx->cam_handle, cam_ctx->chn_cfg[loop_ch].chn_id); + // } + + for(loop_ch=0;loop_chchannel_num;loop_ch++) + { + csi_camera_channel_close(ctx->cam_handle, ctx->chn_cfg[loop_ch].chn_id); + } + csi_camera_close(ctx->cam_handle); + free(ctx); +} + + +int main(int argc, char *argv[]) +{ + + char dev_name[128]; + int camera_id = 0; + // 打印HAL接口版本号 + csi_api_version_u version; + csi_camera_get_version(&version); + camera_param_t params[MAX_CAM_NUM]; + camera_ctx_t * ctx[MAX_CAM_NUM]={NULL,NULL,NULL}; + int cam_num=0; + bool running = false; + cam_num =parseParams(argc,argv,params); + if(cam_num <=0 || cam_num>MAX_CAM_NUM) + { + LOG_E("not camera is active\n"); + exit(0); + } + for(int i =0;icam_id = i; + ctx[i]->frame_num =0; + ctx[i]->exit = 0; + camera_start_all(ctx[i]); + running = true; + } + int all_exit; + do{ + sleep(1); + all_exit = 1; + for(int i =0;iexit==0 && + params[i].frames_to_stop>0 && + ctx[i]->frame_num >=params[i].frames_to_stop) + { + camera_stop_all(ctx[i]); + ctx[i]->exit=1; + } + if(ctx[i]) + all_exit &= ctx[i]->exit; + } + if(all_exit ==1) + { + LOG_O("all_exit\n"); + running = false; + } + + }while(running); + +ONR_ERR: + for(int i =0;imeta.type != CSI_META_TYPE_CAMERA) + return; + + csi_camera_meta_s *meta_data = (csi_camera_meta_s *)frame->meta.data; + int meta_count = meta_data->count; + csi_camrea_meta_unit_s meta_unit; + + + csi_camera_frame_get_meta_unit( + &meta_unit, meta_data, CSI_CAMERA_META_ID_FRAME_ID); + LOG_I("meta_id=%d, meta_type=%d, meta_value=%d\n", + meta_unit.id, meta_unit.type, meta_unit.int_value); +} + +static void dump_camera_frame(csi_frame_s *frame, csi_pixel_fmt_e fmt, int chan_id) +{ + char file[128]; + static int file_indx=0; + int size; + uint32_t indexd, j; + void *buf[3]; + int buf_size; + csi_camera_meta_s *meta_data = (csi_camera_meta_s *)frame->meta.data; + csi_camrea_meta_unit_s meta_unit; + + + csi_camera_frame_get_meta_unit( + &meta_unit, meta_data, CSI_CAMERA_META_ID_CAMERA_NAME); + + + sprintf(file,"demo_save_img%s_%d_%d",meta_unit.str_value,chan_id, file_indx++%10); + int fd = open(file, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IROTH); + if(fd == -1) { + LOG_E("%s, %d, open file error!!!!!!!!!!!!!!!\n", __func__, __LINE__); + return ; + } + if (frame->img.type == CSI_IMG_TYPE_DMA_BUF) { + + buf_size = frame->img.strides[0]*frame->img.height; + switch(fmt) { + case CSI_PIX_FMT_NV12: + case CSI_PIX_FMT_YUV_SEMIPLANAR_420: + if(frame->img.num_planes ==2) + { + buf_size+=frame->img.strides[1]*frame->img.height/2; + } + else{ + LOG_O("img fomrat is not match with frame planes:%d\n",frame->img.num_planes); + return; + } + break; + case CSI_PIX_FMT_YUV_SEMIPLANAR_422: + if(frame->img.num_planes ==2) + { + buf_size+=frame->img.strides[1]*frame->img.height; + } + else{ + LOG_O("img fomrat is not match with frame planes:%d\n",frame->img.num_planes); + return; + } + break; + default: + break; + + } + + buf[0]= mmap(0, buf_size, PROT_READ | PROT_WRITE, + MAP_SHARED, frame->img.dmabuf[0].fds, frame->img.dmabuf[0].offset); + // plane_size[1] = frame->img.strides[1]*frame->img.height; + // printf("frame plane 1 stride:%d,offset:%d\n", frame->img.strides[1],(uint32_t)frame->img.dmabuf[1].offset); + if(frame->img.num_planes ==2) + { + // buf[1] = mmap(0, plane_size[1]/2, PROT_READ | PROT_WRITE, + // MAP_SHARED, frame->img.dmabuf[1].fds, frame->img.dmabuf[1].offset); + buf[1] = buf[0]+frame->img.dmabuf[1].offset; + } + + } + else{ + buf[0] = frame->img.usr_addr[0]; + buf[1] = frame->img.usr_addr[1]; + } + + LOG_O("%s,save img from : (%lx,%lx)size:%d to %s, fmt:%d width:%d stride:%d height:%d\n",__FUNCTION__, (uint64_t)buf[0],(uint64_t)buf[1],size, file, fmt, frame->img.width, frame->img.strides[0], frame->img.height); + + if (frame->img.strides[0] == 0) { + frame->img.strides[0] = frame->img.width; + } + switch(fmt) { + case CSI_PIX_FMT_NV12: + case CSI_PIX_FMT_YUV_SEMIPLANAR_420: + for (j = 0; j < frame->img.height; j++) { //Y + indexd = j*frame->img.strides[0]; + write(fd, buf[0] + indexd, frame->img.width); + } + for (j = 0; j < frame->img.height / 2; j++) { //UV + indexd = j*frame->img.strides[0]; + write(fd, buf[1] + indexd, frame->img.width); + } + break; + case CSI_PIX_FMT_RGB_INTEVLEAVED_888: + case CSI_PIX_FMT_YUV_TEVLEAVED_444: + size = frame->img.width*3; + for (j = 0; j < frame->img.height; j++) { + indexd = j*frame->img.strides[0]; + write(fd, buf[0] + indexd, size); + } + break; + case CSI_PIX_FMT_BGR: + case CSI_PIX_FMT_RGB_PLANAR_888: + case CSI_PIX_FMT_YUV_PLANAR_444: + size = frame->img.width * frame->img.height * 3; + write(fd, buf[0], size); + break; + case CSI_PIX_FMT_RAW_8BIT: + size = frame->img.width; + for (j = 0; j < frame->img.height; j++) { + indexd = j*frame->img.strides[0]; + write(fd, buf[0] + indexd, size); + } + break; + case CSI_PIX_FMT_YUV_TEVLEAVED_422: + case CSI_PIX_FMT_RAW_10BIT: + case CSI_PIX_FMT_RAW_12BIT: + case CSI_PIX_FMT_RAW_14BIT: + case CSI_PIX_FMT_RAW_16BIT: + size = frame->img.width*2; + for (j = 0; j < frame->img.height; j++) { + indexd = j*frame->img.strides[0]; + write(fd, buf[0] + indexd, size); + } + break; + case CSI_PIX_FMT_YUV_SEMIPLANAR_422: + if (frame->img.strides[0] == 0) { + frame->img.strides[0] = frame->img.width; + } + for (j = 0; j < frame->img.height; j++) { //Y + indexd = j*frame->img.strides[0]; + write(fd,buf[0] + indexd, frame->img.width); + } + for (j = 0; j < frame->img.height; j++) { //UV + indexd = j*frame->img.strides[0]; + write(fd, buf[1]+ indexd, frame->img.width); + } + break; + default: + LOG_E("%s unsupported format to save\n", __func__); + exit(-1); + break; + } + + close(fd); + munmap(buf[0],buf_size); + // munmap(buf[1],plane_size[1]); + LOG_O("%s exit\n", __func__); +} diff --git a/examples/camera/cam_demo_simple.c b/examples/camera/cam_demo_simple.c new file mode 100644 index 0000000..04a03e4 --- /dev/null +++ b/examples/camera/cam_demo_simple.c @@ -0,0 +1,710 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#define LOG_LEVEL 2 +#define LOG_PREFIX "cam_demo_simple" +#include + +#include +#include +#include "csi_camera_dev_api.h" +// #include "vi_mem.h" +#ifdef PLATFORM_SIMULATOR +#include "apputilities.h" +#endif + +static void dump_camera_meta(csi_frame_s *frame); +static void dump_camera_frame(csi_frame_s *frame, csi_pixel_fmt_e fmt); + + +#define TEST_DEVICE_NAME "/dev/video0" +#define CSI_CAMERA_TRUE 1 +#define CSI_CAMERA_FALSE 0 + +typedef struct frame_fps { + uint64_t frameNumber; + struct timeval initTick; + float fps; +} frame_fps_t; + +typedef struct cb_context{ + int id; + csi_cam_handle_t cam_handle; + int dsp_id; + int dsp_path; + void *ref_buf; + int buf_size; +}cb_context_t; + +typedef struct cb_args{ + char setting[16]; + uint32_t frame_id; + uint32_t timestap; +}cb_args_t; +cb_context_t cb_context[2]; + +void dsp_algo_result_cb(void*context,void*arg) +{ + cb_context_t *ctx = (cb_context_t *)context; + cb_args_t *cb_args= (cb_args_t*)arg; + char update_setting[16]; + uint32_t frame_id = cb_args->frame_id; + uint32_t frame_timestap = cb_args->timestap; + LOG_O("cb:%d,setting:%s,frame :%d,timestad:0x%x\n",ctx->id,cb_args->setting,frame_id,frame_timestap); + sprintf(update_setting, "update_%d", frame_id); + csi_camera_update_dsp_algo_setting(ctx->cam_handle,ctx->dsp_id,ctx->dsp_path,update_setting); + if(frame_id%20) + { + void *replace_buf; + csi_camera_update_dsp_algo_buf(ctx->cam_handle, ctx->dsp_id,ctx->dsp_path,ctx->ref_buf,&replace_buf); + LOG_O("cb:%d,old buffer:0x%lx,new buffer:0x%lx\n",ctx->id,(uint64_t)ctx->ref_buf,(uint64_t)replace_buf); + ctx->ref_buf = replace_buf; + } +} + + +static void usage(void) +{ + printf(" 1 : video id cases\n"); + printf(" 2 : channel cases\n"); + printf(" 3 : dump enable\n"); + printf(" 4 : display enable\n"); + printf(" 5 : width\n"); + printf(" 6 : heithg\n"); + printf(" 7 : fmt\n"); + printf(" 8 : frame_num\n"); + printf(" 9 : file_id\n"); +} + +static void get_system_time(const char *func, int line_num) +{ + struct timeval cur_time; + + memset(&cur_time, 0, sizeof(cur_time)); + gettimeofday(&cur_time, 0); + LOG_O("%s %s line_num = %d, cur_time.tv_sec = %ld, cur_time.tv_usec = %ld\n", + __func__, func, line_num, cur_time.tv_sec, cur_time.tv_usec); +} + + +int file_id = 0; +extern void *vi_plink_create(csi_camera_channel_cfg_s *chn_cfg); +extern void vi_plink_release(void * plink); +extern void display_camera_frame(void * plink, csi_frame_s *frame); +int main(int argc, char *argv[]) +{ + bool running = true; + int channel_id = -1; + int video_id = -1; + int dump_enable = -1; + int display_enable = -1; + int width = 0, height = 0, fmt = -1; + int ret = 0; + uint64_t frame_num = 0; + csi_camera_info_s camera_info; + memset(&camera_info, 0, sizeof(camera_info)); + int chn_id = CSI_CAMERA_CHANNEL_0; + int env_type = CSI_CAMERA_EVENT_TYPE_CHANNEL0; + frame_fps_t demo_fps; + memset(&demo_fps, 0, sizeof(demo_fps)); + struct timeval init_time, cur_time; + memset(&init_time, 0, sizeof(init_time)); + memset(&cur_time, 0, sizeof(cur_time)); + get_system_time(__func__, __LINE__); + char dev_name[128]; + // 打印HAL接口版本号 + csi_api_version_u version; + csi_camera_get_version(&version); + if (argc == 5) + { + video_id = atoi(argv[1]); + channel_id = atoi(argv[2]); + dump_enable = atoi(argv[3]); + display_enable = atoi(argv[4]); + width = 640; + height = 480; + fmt = 1; + printf("%s, %s, %s, %s \n", argv[1],argv[2],argv[3],argv[4]); + printf("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$, %d, display_enable is %d\n", __LINE__, display_enable ); + } else if (argc == 8) { + video_id = atoi(argv[1]); + channel_id = atoi(argv[2]); + dump_enable = atoi(argv[3]); + display_enable = atoi(argv[4]); + width = atoi(argv[5]); + height = atoi(argv[6]); + fmt = atoi(argv[7]); + }else if (argc == 9) { + video_id = atoi(argv[1]); + channel_id = atoi(argv[2]); + dump_enable = atoi(argv[3]); + display_enable = atoi(argv[4]); + width = atoi(argv[5]); + height = atoi(argv[6]); + fmt = atoi(argv[7]); + frame_num = atoi(argv[8]); + }else if (argc == 10) { + video_id = atoi(argv[1]); + channel_id = atoi(argv[2]); + dump_enable = atoi(argv[3]); + display_enable = atoi(argv[4]); + width = atoi(argv[5]); + height = atoi(argv[6]); + fmt = atoi(argv[7]); + frame_num = atoi(argv[8]); + file_id = atoi(argv[9]); + } else { + usage(); + video_id = 0; + channel_id = 0; + dump_enable = 0; + display_enable = 0; + width = 640; + height = 480; + fmt = 1; + printf("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$, %d, display_enable is %d\n", __LINE__, display_enable ); + } + sprintf(dev_name, "/dev/video%d", video_id); + LOG_O("Camera HAL version: %d.%d video_id = %d channel_id = %d size{%d, %d} fmt = %d\n", version.major, version.minor, video_id, channel_id, width, height, fmt); + + // 获取设备中,所有的Camera + struct csi_camera_infos camera_infos; + memset(&camera_infos, 0, sizeof(camera_infos)); + csi_camera_query_list(&camera_infos); + + // 打印所有设备所支持的Camera + for (int i = 0; i < camera_infos.count; i++) { + camera_info = camera_infos.info[i]; + printf("Camera[%d]: camera_name='%s', device_name='%s', bus_info='%s', capabilities=0x%08x\n", + i, + camera_info.camera_name, camera_info.device_name, camera_info.bus_info, + camera_info.capabilities); + printf("Camera[%d] caps are:\n", i); /* The caps are: Video capture, metadata capture */ + for (int j = 1; j <= 0x08000000; j = j << 1) { + switch (camera_info.capabilities & j) { + case CSI_CAMERA_CAP_VIDEO_CAPTURE: + printf("\t camera_infos.info[%d]:Video capture,\n", i); + break; + case CSI_CAMERA_CAP_META_CAPTURE: + printf("\t camera_infos.info[%d] metadata capture,\n", i); + break; + default: + if (camera_info.capabilities & j) { + printf("\t camera_infos.info[%d] unknown capabilities(0x%08x)\n", i, + camera_info.capabilities & j); + } + break; + } + } + } + /* Camera[0]: camera_name='RGB_Camera', device_name='/dev/video0', bus_info='CSI-MIPI', capabilities=0x00800001 + * Camera[1]: camera_name:'Mono_Camera', device_name:'/dev/video8', bus_info='USB', capabilities=0x00000001 + */ + + bool found_camera = false; + for (int i = 0; i < camera_infos.count; i++) { + if (strcmp(camera_infos.info[i].device_name, dev_name) == 0) { + camera_info = camera_infos.info[i]; + printf("found device_name:'%s'\n", camera_info.device_name); + found_camera = true; + break; + } + } + if (!found_camera) { + LOG_E("Can't find camera_name:'%s'\n", dev_name); + exit(-1); + } + get_system_time(__func__, __LINE__); + // 打开Camera设备获取句柄,作为后续操对象 + csi_cam_handle_t cam_handle; + csi_camera_open(&cam_handle, camera_info.device_name); + get_system_time(__func__, __LINE__); + // 获取Camera支持的工作模式 + struct csi_camera_modes camera_modes; + csi_camera_get_modes(cam_handle, &camera_modes); + + // 打印camera所支持的所有工作模式 + printf("Camera:'%s' modes are:\n", dev_name); + printf("{\n"); + for (int i = 0; i < camera_modes.count; i++) { + printf("\t mode_id=%d: description:'%s'\n", + camera_modes.modes[i].mode_id, camera_modes.modes[i].description); + } + printf("}\n"); + + // 设置camera的工作模式及其配置 + csi_camera_mode_cfg_s camera_cfg; + camera_cfg.mode_id = 1; + camera_cfg.calibriation = NULL; // 采用系统默认配置 + camera_cfg.lib3a = NULL; // 采用系统默认配置 + csi_camera_set_mode(cam_handle, &camera_cfg); + + // 获取单个可控单元的属性 + csi_camera_property_description_s description; + /* id=0x0098090x, type=2 default=0 value=1 */ + /* Other example: + * id=0x0098090y, type=3 min=0 max=255 step=1 default=127 value=116 + * id=0x0098090z, type=4 min=0 max=3 default=0 value=2 + * 0: IDLE + * 1: BUSY + * 2: REACHED + * 3: FAILED + */ + + // 轮询获取所有可控制的单元 + printf("all properties are:\n"); + description.id = CSI_CAMERA_PID_HFLIP; + while (!csi_camera_query_property(cam_handle, &description)) { + switch (description.type) { + case (CSI_CAMERA_PROPERTY_TYPE_INTEGER): + printf("id=0x%08x type=%d default=%d value=%d\n", + description.id, description.type, + description.default_value.int_value, description.value.int_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_BOOLEAN): + printf("id=0x%08x type=%d default=%d value=%d\n", + description.id, description.type, + description.default_value.bool_value, description.value.bool_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_ENUM): + printf("id=0x%08x type=%d default=%d value=%d\n", + description.id, description.type, + description.default_value.enum_value, description.value.enum_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_STRING): + printf("id=0x%08x type=%d default=%s value=%s\n", + description.id, description.type, + description.default_value.str_value, description.value.str_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_BITMASK): + printf("id=0x%08x type=%d default=%x value=%x\n", + description.id, description.type, + description.default_value.bitmask_value, description.value.bitmask_value); + break; + default: + LOG_E("error type!\n"); + break; + } + description.id |= CSI_CAMERA_FLAG_NEXT_CTRL; + } + + // 同时配置多个参数 + csi_camera_properties_s properties; + csi_camera_property_s property[3]; + property[0].id = CSI_CAMERA_PID_HFLIP; + property[0].type = CSI_CAMERA_PROPERTY_TYPE_BOOLEAN; + property[0].value.bool_value = false; + property[1].id = CSI_CAMERA_PID_VFLIP; + property[1].type = CSI_CAMERA_PROPERTY_TYPE_BOOLEAN; + property[1].value.bool_value = false; + property[2].id = CSI_CAMERA_PID_ROTATE; + property[2].type = CSI_CAMERA_PROPERTY_TYPE_INTEGER; + property[2].value.int_value = 0; + properties.count = 3; + properties.property = property; + if (csi_camera_set_property(cam_handle, &properties) < 0) { + LOG_O("set_property fail!\n"); + } + LOG_O("set_property ok!\n"); + + + + // extern int csi_camera_set_pp_path_param(csi_cam_handle_t cam_handle, uint16_t line_num,uint16_t buf_mode); + // csi_camera_set_pp_path_param(cam_handle,80,0); + // 查询输出channel + switch (channel_id) { + case 0: + chn_id = CSI_CAMERA_CHANNEL_0; + env_type = CSI_CAMERA_EVENT_TYPE_CHANNEL0; + break; + case 1: + chn_id = CSI_CAMERA_CHANNEL_1; + env_type = CSI_CAMERA_EVENT_TYPE_CHANNEL1; + break; + case 2: + chn_id = CSI_CAMERA_CHANNEL_2; + env_type = CSI_CAMERA_EVENT_TYPE_CHANNEL2; + break; + case 3: + chn_id = CSI_CAMERA_CHANNEL_3; + env_type = CSI_CAMERA_EVENT_TYPE_CHANNEL3; + break; + case 4: + chn_id = CSI_CAMERA_CHANNEL_4; + env_type = CSI_CAMERA_EVENT_TYPE_CHANNEL4; + break; + case 5: + chn_id = CSI_CAMERA_CHANNEL_5; + env_type = CSI_CAMERA_EVENT_TYPE_CHANNEL5; + break; + case 6: + chn_id = CSI_CAMERA_CHANNEL_6; + env_type = CSI_CAMERA_EVENT_TYPE_CHANNEL6; + break; + case 7: + chn_id = CSI_CAMERA_CHANNEL_7; + env_type = CSI_CAMERA_EVENT_TYPE_CHANNEL7; + break; + default: + LOG_E("fail to check chn_id = %d unsupport\n", chn_id); + exit(-1); + } + csi_camera_channel_cfg_s chn_cfg; + chn_cfg.chn_id = chn_id; + csi_camera_channel_query(cam_handle, &chn_cfg); + if (chn_cfg.status != CSI_CAMERA_CHANNEL_CLOSED) { + LOG_E("Can't open chn_id = %d\n", chn_id); + exit(-1); + } + + extern int csi_camera_set_pp_path_param(csi_cam_handle_t cam_handle, uint16_t line_num,uint16_t buf_mode); + /*set PP line num to default and enable sram*/ + csi_camera_set_pp_path_param(cam_handle,0,1); + // 打开输出channel + chn_cfg.chn_id = chn_id; + chn_cfg.frm_cnt = 1; + chn_cfg.img_fmt.width = width; + chn_cfg.img_fmt.height = height; + + chn_cfg.img_fmt.pix_fmt = fmt; + chn_cfg.img_type = CSI_IMG_TYPE_DMA_BUF; + chn_cfg.meta_fields = CSI_CAMERA_META_DEFAULT_FIELDS; + chn_cfg.capture_type = CSI_CAMERA_CHANNEL_CAPTURE_VIDEO | + CSI_CAMERA_CHANNEL_CAPTURE_META; + ret = csi_camera_channel_open(cam_handle, &chn_cfg); + if (ret) { + exit(-1); + } + get_system_time(__func__, __LINE__); + /*init for vi->vo plink*/ + void * display_plink = NULL; + if (display_enable == 1) { + chn_cfg.chn_id = chn_id; + display_plink = vi_plink_create(&chn_cfg); + if(display_plink==NULL) + { + LOG_E("plink create fail\n"); + exit(-1); + } + } + // 订阅Event + csi_cam_event_handle_t event_handle; + csi_camera_create_event(&event_handle, cam_handle); + csi_camera_event_subscription_s subscribe; + subscribe.type = + CSI_CAMERA_EVENT_TYPE_CAMERA; // 订阅Camera的ERROR事件 + subscribe.id = CSI_CAMERA_EVENT_WARNING | CSI_CAMERA_EVENT_ERROR; + csi_camera_subscribe_event(event_handle, &subscribe); + subscribe.type = + env_type; // 订阅Channel0的FRAME_READY事件 + subscribe.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY | + CSI_CAMERA_CHANNEL_EVENT_OVERFLOW; + csi_camera_subscribe_event(event_handle, &subscribe); + +/* for dsp algo setting + cb_context[0].id=0; + cb_context[0].cam_handle = cam_handle; + cb_context[0].dsp_id = 1; + cb_context[0].dsp_path = 2; + char test_set[16]="init"; + cb_context[0].buf_size = 1920*1080; + cb_context[0].ref_buf=NULL; + csi_camera_dsp_algo_param_t algo_param_1={ + .algo_name = "dsp1_dummy_algo_flo", + .algo_cb.cb = dsp_algo_result_cb, + .algo_cb.context = &cb_context[0], + .algo_cb.arg_size = sizeof(cb_args_t), + .sett_ptr = test_set, + .sett_size =16, + .extra_buf_num =1, + .extra_buf_sizes = &cb_context[0].buf_size, + .extra_bufs = &cb_context[0].ref_buf, + }; + + if(csi_camera_set_dsp_algo_param(cam_handle,cb_context[0].dsp_id,cb_context[0].dsp_path, &algo_param_1)) + { + LOG_E("set DSP algo fail\n"); + + } +*/ + // 开始从channel中取出准备好的frame + csi_camera_channel_start(cam_handle, chn_id); + get_system_time(__func__, __LINE__); + // 处理订阅的Event + csi_frame_s frame; + struct csi_camera_event event; + + while (running) { + if (frame_num != 0) { + if (demo_fps.frameNumber > frame_num) { + running = false; + } + } + int timeout = -1; // unit: ms, -1 means wait forever, or until error occurs + csi_camera_get_event(event_handle, &event, timeout); + // printf("%s event.type = %d, event.id = %d\n", __func__, event.type, event.id); + switch (event.type) { + case CSI_CAMERA_EVENT_TYPE_CAMERA: + switch (event.id) { + case CSI_CAMERA_EVENT_ERROR: + // do sth. + LOG_O("get CAMERA EVENT CSI_CAMERA_EVENT_ERROR!\n"); + break; + case CSI_CAMERA_EVENT_WARNING: + LOG_O("get CAMERA EVENT CSI_CAMERA_EVENT_WRN,RC: %s\n",event.bin); + break; + default: + break; + } + break; + case CSI_CAMERA_EVENT_TYPE_CHANNEL0: + case CSI_CAMERA_EVENT_TYPE_CHANNEL1: + case CSI_CAMERA_EVENT_TYPE_CHANNEL2: + case CSI_CAMERA_EVENT_TYPE_CHANNEL3: + case CSI_CAMERA_EVENT_TYPE_CHANNEL4: + case CSI_CAMERA_EVENT_TYPE_CHANNEL5: + case CSI_CAMERA_EVENT_TYPE_CHANNEL6: + case CSI_CAMERA_EVENT_TYPE_CHANNEL7: + switch (event.id) { + case CSI_CAMERA_CHANNEL_EVENT_FRAME_READY: { + get_system_time(__func__, __LINE__); + int read_frame_count = csi_camera_get_frame_count(cam_handle, + chn_id); + unsigned long diff; + if (init_time.tv_usec == 0) + gettimeofday(&init_time,0);//osGetTick(); + gettimeofday(&cur_time, 0); + diff = 1000000 * (cur_time.tv_sec-init_time.tv_sec)+ cur_time.tv_usec-init_time.tv_usec; + demo_fps.frameNumber++; + if (diff != 0) + demo_fps.fps = (float)demo_fps.frameNumber / diff * 1000000.0f; + LOG_O("%s %d read_frame_count = %d, frame_count = %ld, fps = %.2f diff = %ld\n", __func__, __LINE__, read_frame_count, demo_fps.frameNumber, demo_fps.fps, diff); + + for (int i = 0; i < read_frame_count; i++) { + csi_camera_get_frame(cam_handle, chn_id, &frame, timeout); +#ifdef PLATFORM_SIMULATOR + if (frame.img.type == CSI_IMG_TYPE_DMA_BUF) { + void *phyaddr = vi_mem_import(frame.img.dmabuf[0].fds); + void *p = vi_mem_map(phyaddr) + frame.img.dmabuf[0].offset; + show_frame_image(p, frame.img.height, frame.img.width); + vi_mem_release(phyaddr); + } else { + show_frame_image(frame.img.usr_addr[0], frame.img.height, frame.img.width); + } +#endif + // printf("main 443 phyaddr=%p\n", vi_mem_import(frame.img.dmabuf[0].fds)); + dump_camera_meta(&frame); + chn_cfg.chn_id = chn_id; + csi_camera_channel_query(cam_handle, &chn_cfg); + frame.img.pix_format = chn_cfg.img_fmt.pix_fmt; + + if (dump_enable) { + dump_camera_frame(&frame, chn_cfg.img_fmt.pix_fmt); + } + if (display_enable == 1) { + display_camera_frame(display_plink, &frame); + } + // csi_camera_frame_unlock(cam_handle, &frame); + csi_camera_put_frame(&frame); + + csi_frame_release(&frame); + } + break; + } + default: + break; + } + break; + default: + break; + } + } + csi_camera_channel_stop(cam_handle, chn_id); + usleep (1000000); + // 取消订阅某一个event, 也可以直接调用csi_camera_destory_event,结束所有的订阅 + subscribe.type = env_type; + subscribe.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY; + csi_camera_unsubscribe_event(event_handle, &subscribe); + + csi_camera_destory_event(event_handle); + + csi_camera_channel_close(cam_handle, chn_id); + csi_camera_close(cam_handle); + if(display_plink) + { + vi_plink_release(display_plink); + } +} + +static void dump_camera_meta(csi_frame_s *frame) +{ + int i; + + if (frame->meta.type != CSI_META_TYPE_CAMERA) + return; + + csi_camera_meta_s *meta_data = (csi_camera_meta_s *)frame->meta.data; + int meta_count = meta_data->count; + csi_camrea_meta_unit_s meta_unit; + + + csi_camera_frame_get_meta_unit( + &meta_unit, meta_data, CSI_CAMERA_META_ID_FRAME_ID); + LOG_O("meta_id=%d, meta_type=%d, meta_value=%d\n", + meta_unit.id, meta_unit.type, meta_unit.int_value); +} + +static void dump_camera_frame(csi_frame_s *frame, csi_pixel_fmt_e fmt) +{ + char file[128]; + static int file_indx=0; + int size; + uint32_t indexd, j; + void *buf[3]; + int buf_size; + csi_camera_meta_s *meta_data = (csi_camera_meta_s *)frame->meta.data; + csi_camrea_meta_unit_s meta_unit; + + + csi_camera_frame_get_meta_unit( + &meta_unit, meta_data, CSI_CAMERA_META_ID_CAMERA_NAME); + + + sprintf(file,"demo_save_img%s_%d_%d",meta_unit.str_value,file_id, file_indx++%10); + int fd = open(file, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IROTH); + if(fd == -1) { + LOG_E("%s, %d, open file error!!!!!!!!!!!!!!!\n", __func__, __LINE__); + return ; + } + if (frame->img.type == CSI_IMG_TYPE_DMA_BUF) { + + buf_size = frame->img.strides[0]*frame->img.height; + switch(fmt) { + case CSI_PIX_FMT_NV12: + case CSI_PIX_FMT_YUV_SEMIPLANAR_420: + if(frame->img.num_planes ==2) + { + buf_size+=frame->img.strides[1]*frame->img.height/2; + } + else{ + LOG_E("img fomrat is not match with frame planes:%d\n",frame->img.num_planes); + return; + } + break; + case CSI_PIX_FMT_YUV_SEMIPLANAR_422: + if(frame->img.num_planes ==2) + { + buf_size+=frame->img.strides[1]*frame->img.height; + } + else{ + LOG_E("img fomrat is not match with frame planes:%d\n",frame->img.num_planes); + return; + } + break; + default: + break; + + } + + buf[0]= mmap(0, buf_size, PROT_READ | PROT_WRITE, + MAP_SHARED, frame->img.dmabuf[0].fds, frame->img.dmabuf[0].offset); + // plane_size[1] = frame->img.strides[1]*frame->img.height; + // printf("frame plane 1 stride:%d,offset:%d\n", frame->img.strides[1],(uint32_t)frame->img.dmabuf[1].offset); + if(frame->img.num_planes ==2) + { + // buf[1] = mmap(0, plane_size[1]/2, PROT_READ | PROT_WRITE, + // MAP_SHARED, frame->img.dmabuf[1].fds, frame->img.dmabuf[1].offset); + buf[1] = buf[0]+frame->img.dmabuf[1].offset; + } + + } + else{ + buf[0] = frame->img.usr_addr[0]; + buf[1] = frame->img.usr_addr[1]; + } + + LOG_O("%s,save img from : (%lx,%lx)size:%d to %s, fmt:%d width:%d stride:%d height:%d\n",__FUNCTION__, (uint64_t)buf[0],(uint64_t)buf[1],size, file, fmt, frame->img.width, frame->img.strides[0], frame->img.height); + + if (frame->img.strides[0] == 0) { + frame->img.strides[0] = frame->img.width; + } + switch(fmt) { + case CSI_PIX_FMT_NV12: + case CSI_PIX_FMT_YUV_SEMIPLANAR_420: + for (j = 0; j < frame->img.height; j++) { //Y + indexd = j*frame->img.strides[0]; + write(fd, buf[0] + indexd, frame->img.width); + } + for (j = 0; j < frame->img.height / 2; j++) { //UV + indexd = j*frame->img.strides[0]; + write(fd, buf[1] + indexd, frame->img.width); + } + break; + case CSI_PIX_FMT_RGB_INTEVLEAVED_888: + case CSI_PIX_FMT_YUV_TEVLEAVED_444: + size = frame->img.width*3; + for (j = 0; j < frame->img.height; j++) { + indexd = j*frame->img.strides[0]; + write(fd, buf[0] + indexd, size); + } + break; + case CSI_PIX_FMT_BGR: + case CSI_PIX_FMT_RGB_PLANAR_888: + case CSI_PIX_FMT_YUV_PLANAR_444: + size = frame->img.width * frame->img.height * 3; + write(fd, buf[0], size); + break; + case CSI_PIX_FMT_RAW_8BIT: + size = frame->img.width; + for (j = 0; j < frame->img.height; j++) { + indexd = j*frame->img.strides[0]; + write(fd, buf[0] + indexd, size); + } + break; + case CSI_PIX_FMT_YUV_TEVLEAVED_422: + case CSI_PIX_FMT_RAW_10BIT: + case CSI_PIX_FMT_RAW_12BIT: + case CSI_PIX_FMT_RAW_14BIT: + case CSI_PIX_FMT_RAW_16BIT: + size = frame->img.width*2; + for (j = 0; j < frame->img.height; j++) { + indexd = j*frame->img.strides[0]; + write(fd, buf[0] + indexd, size); + } + break; + case CSI_PIX_FMT_YUV_SEMIPLANAR_422: + if (frame->img.strides[0] == 0) { + frame->img.strides[0] = frame->img.width; + } + for (j = 0; j < frame->img.height; j++) { //Y + indexd = j*frame->img.strides[0]; + write(fd,buf[0] + indexd, frame->img.width); + } + for (j = 0; j < frame->img.height; j++) { //UV + indexd = j*frame->img.strides[0]; + write(fd, buf[1]+ indexd, frame->img.width); + } + break; + default: + LOG_E("%s unsupported format to save\n", __func__); + exit(-1); + break; + } + + close(fd); + munmap(buf[0],buf_size); + // munmap(buf[1],plane_size[1]); + LOG_O("%s exit\n", __func__); +} diff --git a/examples/camera/camera_demo1.c b/examples/camera/camera_demo1.c new file mode 100644 index 0000000..4360098 --- /dev/null +++ b/examples/camera/camera_demo1.c @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "camera_demo1" +#include + +#include +#include + +#ifdef PLATFORM_SIMULATOR +#include "apputilities.h" +#include "platform_action.h" +#endif + +static void dump_camera_meta(csi_frame_s *frame); + +//#define TEST_DEVICE_NAME "/dev/fake" +#define TEST_DEVICE_NAME "/dev/video0" + +int main(int argc, char *argv[]) +{ + bool running = true; + csi_camera_info_s camera_info; + + // 打印HAL接口版本号 + csi_api_version_u version; + csi_camera_get_version(&version); + printf("Camera HAL version: %d.%d\n", version.major, version.minor); + + // 获取设备中,所有的Camera + struct csi_camera_infos camera_infos; + csi_camera_query_list(&camera_infos); + + // 打印所有设备所支持的Camera + for (int i = 0; i < camera_infos.count; i++) { + camera_info = camera_infos.info[i]; + printf("Camera[%d]: camera_name='%s', device_name='%s', bus_info='%s', capabilities=0x%08x\n", + i, + camera_info.camera_name, camera_info.device_name, camera_info.bus_info, + camera_info.capabilities); + printf("Camera[%d] caps are:\n", + i); /* The caps are: Video capture, metadata capture */ + for (int j = 1; j <= 0x08000000; j = j << 1) { + switch (camera_info.capabilities & j) { + case CSI_CAMERA_CAP_VIDEO_CAPTURE: + printf("\t camera_infos.info[%d]:Video capture,\n", i); + break; + case CSI_CAMERA_CAP_META_CAPTURE: + printf("\t camera_infos.info[%d] metadata capture,\n", i); + break; + default: + if (camera_info.capabilities & j) { + printf("\t camera_infos.info[%d] unknown capabilities(0x%08x)\n", i, + camera_info.capabilities & j); + } + break; + } + } + } + /* Camera[0]: camera_name='RGB_Camera', device_name='/dev/video0', bus_info='CSI-MIPI', capabilities=0x00800001 + * Camera[1]: camera_name:'Mono_Camera', device_name:'/dev/video8', bus_info='USB', capabilities=0x00000001 + */ + + bool found_camera = false; + for (int i = 0; i < camera_infos.count; i++) { + if (strcmp(camera_infos.info[i].device_name, TEST_DEVICE_NAME) == 0) { + camera_info = camera_infos.info[i]; + printf("found device_name:'%s'\n", camera_info.device_name); + found_camera = true; + break; + } + } + if (!found_camera) { + LOG_E("Can't find camera_name:'%s'\n", TEST_DEVICE_NAME); + exit(-1); + } + + + // 打开Camera设备获取句柄,作为后续操对象 + csi_cam_handle_t cam_handle; + csi_camera_open(&cam_handle, camera_info.device_name); + + // 获取Camera支持的工作模式 + struct csi_camera_modes camera_modes; + csi_camera_get_modes(cam_handle, &camera_modes); + + // 打印camera所支持的所有工作模式 + printf("Camera:'%s' modes are:\n", TEST_DEVICE_NAME); + printf("{\n"); + for (int i = 0; i < camera_modes.count; i++) { + printf("\t mode_id=%d: description:'%s'\n", + camera_modes.modes[i].mode_id, camera_modes.modes[i].description); + } + printf("}\n"); + + // 设置camera的工作模式及其配置 + csi_camera_mode_cfg_s camera_cfg; + camera_cfg.mode_id = 1; + camera_cfg.calibriation = NULL; // 采用系统默认配置 + camera_cfg.lib3a = NULL; // 采用系统默认配置 + csi_camera_set_mode(cam_handle, &camera_cfg); + + // 获取单个可控单元的属性 + csi_camera_property_description_s description; + description.id = CSI_CAMERA_PID_HFLIP; + csi_camera_query_property(cam_handle, &description); + printf("id=0x%08x type=%d default=%d value=%d\n", + description.id, description.type, + description.default_value.bool_value, description.value.bool_value); + /* id=0x0098090x, type=2 default=0 value=1 */ + /* Other example: + * id=0x0098090y, type=3 min=0 max=255 step=1 default=127 value=116 + * id=0x0098090z, type=4 min=0 max=3 default=0 value=2 + * 0: IDLE + * 1: BUSY + * 2: REACHED + * 3: FAILED + */ + + // 轮询获取所有可控制的单元 + printf("all properties are:\n"); + description.id = CSI_CAMERA_PID_HFLIP; + while (!csi_camera_query_property(cam_handle, &description)) { + switch (description.type) { + case (CSI_CAMERA_PROPERTY_TYPE_INTEGER): + printf("id=0x%08x type=%d default=%d value=%d\n", + description.id, description.type, + description.default_value.int_value, description.value.int_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_BOOLEAN): + printf("id=0x%08x type=%d default=%d value=%d\n", + description.id, description.type, + description.default_value.bool_value, description.value.bool_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_ENUM): + printf("id=0x%08x type=%d default=%d value=%d\n", + description.id, description.type, + description.default_value.enum_value, description.value.enum_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_STRING): + printf("id=0x%08x type=%d default=%s value=%s\n", + description.id, description.type, + description.default_value.str_value, description.value.str_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_BITMASK): + printf("id=0x%08x type=%d default=%x value=%x\n", + description.id, description.type, + description.default_value.bitmask_value, description.value.bitmask_value); + break; + default: + LOG_E("error type!\n"); + break; + } + description.id |= CSI_CAMERA_FLAG_NEXT_CTRL; + } + + // 同时配置多个参数 + csi_camera_properties_s properties; + csi_camera_property_s property[2]; + property[0].id = CSI_CAMERA_PID_HFLIP; + property[0].type = CSI_CAMERA_PROPERTY_TYPE_BOOLEAN; + property[0].value.bool_value = true; + property[1].id = CSI_CAMERA_PID_VFLIP; + property[1].type = CSI_CAMERA_PROPERTY_TYPE_BOOLEAN; + property[1].value.bool_value = false; + properties.count = 2; + properties.property = property; + csi_camera_set_property(cam_handle, &properties); + LOG_O("set_property ok!\n"); + + csi_camera_get_property(cam_handle, &properties); + + + + // 查询输出channel + csi_camera_channel_cfg_s chn_cfg; + chn_cfg.chn_id = CSI_CAMERA_CHANNEL_0; + csi_camera_channel_query(cam_handle, &chn_cfg); + if (chn_cfg.status != CSI_CAMERA_CHANNEL_CLOSED) { + printf("Can't open CSI_CAMERA_CHANNEL_0\n"); + exit(-1); + } + + // 打开输出channel + chn_cfg.chn_id = CSI_CAMERA_CHANNEL_0; + chn_cfg.frm_cnt = 4; + chn_cfg.img_fmt.width = 1280; + chn_cfg.img_fmt.height = 720; + chn_cfg.img_fmt.pix_fmt = CSI_PIX_FMT_BGR; + chn_cfg.img_type = CSI_IMG_TYPE_DMA_BUF; + chn_cfg.meta_fields = CSI_CAMERA_META_DEFAULT_FIELDS; + chn_cfg.capture_type = CSI_CAMERA_CHANNEL_CAPTURE_VIDEO | + CSI_CAMERA_CHANNEL_CAPTURE_META; + csi_camera_channel_open(cam_handle, &chn_cfg); + + // 订阅Event + csi_cam_event_handle_t event_handle; + csi_camera_create_event(&event_handle, cam_handle); + csi_camera_event_subscription_s subscribe; + subscribe.type = + CSI_CAMERA_EVENT_TYPE_CAMERA; // 订阅Camera的ERROR事件 + subscribe.id = CSI_CAMERA_EVENT_WARNING | CSI_CAMERA_EVENT_ERROR; + csi_camera_subscribe_event(event_handle, &subscribe); + subscribe.type = + CSI_CAMERA_EVENT_TYPE_CHANNEL0; // 订阅Channel0的FRAME_READY事件 + subscribe.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY | + CSI_CAMERA_CHANNEL_EVENT_OVERFLOW; + csi_camera_subscribe_event(event_handle, &subscribe); + + // 开始从channel中取出准备好的frame + csi_camera_channel_start(cam_handle, CSI_CAMERA_CHANNEL_0); + + // 处理订阅的Event + csi_frame_s frame; + struct csi_camera_event event; + + while (running) { + int timeout = -1; // unit: ms, -1 means wait forever, or until error occurs + csi_camera_get_event(event_handle, &event, timeout); + + switch (event.type) { + case CSI_CAMERA_EVENT_TYPE_CAMERA: + switch (event.id) { + case CSI_CAMERA_EVENT_ERROR: + // do sth. + LOG_D("get CAMERA EVENT CSI_CAMERA_EVENT_ERROR!\n"); + break; + default: + break; + } + case CSI_CAMERA_EVENT_TYPE_CHANNEL0: + switch (event.id) { + case CSI_CAMERA_CHANNEL_EVENT_FRAME_READY: { + int read_frame_count = csi_camera_get_frame_count(cam_handle, + CSI_CAMERA_CHANNEL_0); + for (int i = 0; i < read_frame_count; i++) { + csi_camera_get_frame(cam_handle, CSI_CAMERA_CHANNEL_0, &frame, timeout); + + // frame.image: {.width, .height, .pix_fmt, .dma-buf} + //show_frame_image(frame.image); // 伪代码 +#ifdef PLATFORM_SIMULATOR + camera_action_image_display(&frame); + csi_camera_put_frame(&frame); +#endif + + // frame.meta: {.count, + // .meta_data[]={meta_id, type, + // union{int_value, str_value, ...}}} + + dump_camera_meta(&frame); + + //csi_camera_put_frame(&frame); + } + break; + } + default: + break; + } + default: + break; + } + } + + // 取消订阅某一个event, 也可以直接调用csi_camera_destory_event,结束所有的订阅 + subscribe.type = CSI_CAMERA_EVENT_TYPE_CHANNEL0; + subscribe.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY; + csi_camera_unsubscribe_event(event_handle, &subscribe); + + csi_camera_destory_event(event_handle); + csi_camera_channel_stop(cam_handle, CSI_CAMERA_CHANNEL_0); + csi_camera_channel_close(cam_handle, CSI_CAMERA_CHANNEL_0); + csi_camera_close(cam_handle); +} + +static void dump_camera_meta(csi_frame_s *frame) +{ + int i; + if (frame->meta.type != CSI_META_TYPE_CAMERA) + return; + + csi_camera_meta_s *meta_data = (csi_camera_meta_s *)frame->meta.data; + int meta_count = meta_data->count; + csi_camrea_meta_unit_s meta_unit; + + for (i = 0; i < meta_count; i++) { + csi_camera_frame_get_meta_unit( + &meta_unit, meta_data, CSI_CAMERA_META_ID_FRAME_ID); + printf("meta_id=%d, meta_type=%d, meta_value=%d", + meta_unit.id, meta_unit.type, meta_unit.int_value); + } +} + diff --git a/examples/camera/camera_demo2.c b/examples/camera/camera_demo2.c new file mode 100644 index 0000000..15b641a --- /dev/null +++ b/examples/camera/camera_demo2.c @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#define LOG_LEVEL 2 +#define LOG_PREFIX "camera_demo2" +#include + + +#include +#include + +#ifdef PLATFORM_SIMULATOR +#include "apputilities.h" +#include "platform_action.h" +#endif + +static void dump_camera_meta(csi_frame_s *frame); + +#define TEST_DEVICE_NAME "/dev/video0" +int main(int argc, char *argv[]) +{ + uint32_t i; + bool running = true; + csi_camera_info_s camera_info; + + // 获取设备中,所有的Camera + struct csi_camera_infos camera_infos; + csi_camera_query_list(&camera_infos); + LOG_O("csi_camera_query_list() OK\n"); + + // 打印所有设备所支持的Camera + for (i = 0; i < camera_infos.count; i++) { + printf("Camera[%d]: camera_name='%s', device_name='%s', bus_info='%s', capabilities=0x%08x\n", + i, camera_infos.info[i].camera_name, camera_infos.info[i].device_name, + camera_infos.info[i].bus_info, camera_infos.info[i].capabilities); + } + /* Camera[0]: camera_name='RGB_Camera', device_name='/dev/video0', bus_info='CSI-MIPI', capabilities=0x00800001 + * Camera[1]: camera_name:'Mono_Camera', device_name:'/dev/video8', bus_info='USB', capabilities=0x00000001 + */ + + bool found_camera = false; + for (i = 0; i < camera_infos.count; i++) { + if (strcmp(camera_infos.info[i].device_name, TEST_DEVICE_NAME) == 0) { + camera_info = camera_infos.info[i]; + printf("found device_name:'%s'\n", camera_info.device_name); + found_camera = true; + break; + } + } + if (!found_camera) { + LOG_E("Can't find camera_name:'%s'\n", TEST_DEVICE_NAME); + exit(-1); + } + + printf("The caps are:\n"); + for (i = 1; i < 0x80000000; i = i << 1) { + switch (camera_info.capabilities & i) { + case CSI_CAMERA_CAP_VIDEO_CAPTURE: + printf("\t Video capture\n"); + break; + case CSI_CAMERA_CAP_META_CAPTURE: + printf("\t metadata capture\n"); + break; + default: + if (camera_info.capabilities & i) { + printf("\t unknown capabilities(0x%08x)\n", camera_info.capabilities & i); + } + break; + } + } + /* The caps are: Video capture, metadata capture */ + + // 打开Camera设备获取句柄,作为后续操对象 + csi_cam_handle_t cam_handle; + csi_camera_open(&cam_handle, camera_info.device_name); + LOG_O("csi_camera_open() OK\n"); + + // 获取Camera支持的工作模式 + struct csi_camera_modes camera_modes; + camera_modes.count = 0; + csi_camera_get_modes(cam_handle, &camera_modes); + LOG_O("csi_camera_get_modes() OK\n"); + + // 打印camera所支持的所有工作模式 + printf(" Camera:'%s' modes are:{\n", TEST_DEVICE_NAME); + for (i = 0; i < camera_modes.count; i++) { + printf("\t mode_id=%d: description:'%s'\n", + camera_modes.modes[i].mode_id, camera_modes.modes[i].description); + } + printf("}\n"); + + // 设置camera的工作模式及其配置 + csi_camera_mode_cfg_s camera_cfg; + camera_cfg.mode_id = 1; + camera_cfg.calibriation = NULL; // 采用系统默认配置 + camera_cfg.lib3a = NULL; // 采用系统默认配置 + csi_camera_set_mode(cam_handle, &camera_cfg); + + // 获取单个可控单元的属性 + csi_camera_property_description_s description; + description.id = CSI_CAMERA_PID_HFLIP; + csi_camera_query_property(cam_handle, &description); + printf("properity id=0x%08x type=%d default=%d value=%d\n", + description.id, description.type, + description.default_value.bool_value, description.value.bool_value); + /* id=0x0098090x, type=2 default=0 value=1 */ + /* Other example: + * id=0x0098090y, type=3 min=0 max=255 step=1 default=127 value=116 + * id=0x0098090z, type=4 min=0 max=3 default=0 value=2 + * 0: IDLE + * 1: BUSY + * 2: REACHED + * 3: FAILED + */ + + // 轮询获取所有可控制的单元 + description.id = CSI_CAMERA_FLAG_NEXT_CTRL; + while (!csi_camera_query_property(cam_handle, &description)) { + //printf(...); // 打印属性 + switch (description.type) { + case (CSI_CAMERA_PROPERTY_TYPE_INTEGER): + printf("id=0x%08x type=%d default=%d value=%d\n", + description.id, description.type, + description.default_value.int_value, description.value.int_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_BOOLEAN): + printf("id=0x%08x type=%d default=%d value=%d\n", + description.id, description.type, + description.default_value.bool_value, description.value.bool_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_ENUM): + printf("id=0x%08x type=%d default=%d value=%d\n", + description.id, description.type, + description.default_value.enum_value, description.value.enum_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_STRING): + printf("id=0x%08x type=%d default=%s value=%s\n", + description.id, description.type, + description.default_value.str_value, description.value.str_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_BITMASK): + printf("id=0x%08x type=%d default=%x value=%x\n", + description.id, description.type, + description.default_value.bitmask_value, description.value.bitmask_value); + break; + default: + LOG_E("error type!\n"); + break; + } + description.id |= CSI_CAMERA_FLAG_NEXT_CTRL; + } + LOG_O("csi_camera_query_property() OK\n"); + + // 同时配置多个参数 + csi_camera_properties_s properties; + csi_camera_property_s property[2]; + property[0].id = CSI_CAMERA_PID_EXPOSURE_MODE; + property[0].type = CSI_CAMERA_PROPERTY_TYPE_ENUM; + property[0].value.enum_value = CSI_CAMERA_EXPOSURE_MODE_AUTO; + property[1].id = CSI_CAMERA_PID_EXPOSURE_MODE; + property[1].type = CSI_CAMERA_PROPERTY_TYPE_ENUM; + property[1].value.enum_value = CSI_CAMERA_EXPOSURE_MANUAL; + properties.count = 2; + properties.property = property; + csi_camera_get_property(cam_handle, &properties); + LOG_O("csi_camera_get_property() OK\n"); + + // 查询输出channel + csi_camera_channel_cfg_s chn_cfg; + chn_cfg.chn_id = CSI_CAMERA_CHANNEL_0; + csi_camera_channel_query(cam_handle, &chn_cfg); + if (chn_cfg.status != CSI_CAMERA_CHANNEL_CLOSED) { + printf("Can't open CSI_CAMERA_CHANNEL_0\n"); + exit(-1); + } + LOG_O("csi_camera_channel_query() OK\n"); + + // 打开输出channel_0 + chn_cfg.chn_id = CSI_CAMERA_CHANNEL_0; + chn_cfg.frm_cnt = 4; + chn_cfg.img_fmt.width = 640; + chn_cfg.img_fmt.height = 480; + chn_cfg.img_fmt.pix_fmt = CSI_PIX_FMT_I420; + chn_cfg.img_type = CSI_IMG_TYPE_DMA_BUF; + chn_cfg.meta_fields = CSI_CAMERA_META_DEFAULT_FIELDS; + chn_cfg.capture_type = CSI_CAMERA_CHANNEL_CAPTURE_VIDEO | + CSI_CAMERA_CHANNEL_CAPTURE_META; + csi_camera_channel_open(cam_handle, &chn_cfg); + LOG_O("CSI_CAMERA_CHANNEL_0: csi_camera_channel_open() OK\n"); + + + // 打开输出channel_1 + chn_cfg.chn_id = CSI_CAMERA_CHANNEL_1; + chn_cfg.frm_cnt = 4; + chn_cfg.img_fmt.width = 1280; + chn_cfg.img_fmt.height = 720; + chn_cfg.img_fmt.pix_fmt = CSI_PIX_FMT_BGR; + chn_cfg.img_type = CSI_IMG_TYPE_DMA_BUF; + chn_cfg.meta_fields = CSI_CAMERA_META_DEFAULT_FIELDS; + chn_cfg.capture_type = CSI_CAMERA_CHANNEL_CAPTURE_VIDEO | + CSI_CAMERA_CHANNEL_CAPTURE_META; + csi_camera_channel_open(cam_handle, &chn_cfg); + LOG_O("CSI_CAMERA_CHANNEL_1: csi_camera_channel_open() OK\n"); + + // 订阅Event + csi_cam_event_handle_t event_handle; + csi_camera_create_event(&event_handle, cam_handle); + struct csi_camera_event_subscription subscribe; + subscribe.type = + CSI_CAMERA_EVENT_TYPE_CAMERA; // 订阅Camera的ERROR事件 + subscribe.id = CSI_CAMERA_EVENT_WARNING | CSI_CAMERA_EVENT_ERROR; + csi_camera_subscribe_event(event_handle, &subscribe); + + subscribe.type = + CSI_CAMERA_EVENT_TYPE_CHANNEL0; // 订阅Channel0的FRAME_READY事件 + subscribe.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY | + CSI_CAMERA_CHANNEL_EVENT_OVERFLOW; + csi_camera_subscribe_event(event_handle, &subscribe); + + subscribe.type = + CSI_CAMERA_EVENT_TYPE_CHANNEL1; // 订阅Channel0的FRAME_READY事件 + subscribe.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY | + CSI_CAMERA_CHANNEL_EVENT_OVERFLOW; + csi_camera_subscribe_event(event_handle, &subscribe); + LOG_O("Event subscript OK\n"); + + // 开始从channel中取出准备好的frame + csi_camera_channel_start(cam_handle, CSI_CAMERA_CHANNEL_0); + csi_camera_channel_start(cam_handle, CSI_CAMERA_CHANNEL_1); + LOG_O("Channel start OK\n"); + + // 处理订阅的Event + csi_frame_s frame; + struct csi_camera_event event; + + LOG_O("Starting get frame...\n"); + while (running) { + int timeout = -1; // unit: ms, -1 means wait forever, or until error occurs + + csi_camera_get_event(event_handle, &event, timeout); + + switch (event.type) { + case CSI_CAMERA_EVENT_TYPE_CAMERA: + switch (event.id) { + case CSI_CAMERA_EVENT_ERROR: + // do sth. + break; + default: + break; + } + case CSI_CAMERA_EVENT_TYPE_CHANNEL0: + switch (event.id) { + case CSI_CAMERA_CHANNEL_EVENT_FRAME_READY: { + int read_frame_count = csi_camera_get_frame_count(cam_handle, + CSI_CAMERA_CHANNEL_0); + csi_camera_get_frame(cam_handle,CSI_CAMERA_CHANNEL_0, &frame, timeout); + + // frame.image: {.width, .height, .pix_fmt, .dma-buf} + //show_frame_image(frame.image); // 伪代码 + // frame.meta: {.count, + // .meta_data[]={meta_id, type, + // union{int_value, str_value, ...}}} + +#ifdef PLATFORM_SIMULATOR + camera_action_image_display(&frame); + csi_camera_put_frame(&frame); +#endif + dump_camera_meta(&frame); + + csi_frame_release(&frame); + break; + } + default: + break; + } + break; + case CSI_CAMERA_EVENT_TYPE_CHANNEL1: + switch (event.id) { + case CSI_CAMERA_CHANNEL_EVENT_FRAME_READY: { + int read_frame_count = csi_camera_get_frame_count(cam_handle, + CSI_CAMERA_CHANNEL_1); + for (int i = 0; i < read_frame_count; i++) { + csi_camera_get_frame(cam_handle,CSI_CAMERA_CHANNEL_1, &frame, timeout); + + // frame.image: {.width, .height, .pix_fmt, .dma-buf} + //show_frame_image(frame.image); // 伪代码 + // frame.meta: {.count, + // .meta_data[]={meta_id, type, + // union{int_value, str_value, ...}}} + +#ifdef PLATFORM_SIMULATOR + camera_action_image_display(&frame); + csi_camera_put_frame(&frame); +#endif + dump_camera_meta(&frame); + + csi_frame_release(&frame); + } + break; + } + default: + break; + } + default: + break; + } + } + + // 取消订阅某一个event, 也可以直接调用csi_camera_destory_event,结束所有的订阅 + subscribe.type = CSI_CAMERA_EVENT_TYPE_CHANNEL0; + subscribe.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY | + CSI_CAMERA_CHANNEL_EVENT_OVERFLOW;; + csi_camera_unsubscribe_event(event_handle, &subscribe); + + subscribe.type = CSI_CAMERA_EVENT_TYPE_CHANNEL1; + subscribe.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY | + CSI_CAMERA_CHANNEL_EVENT_OVERFLOW;; + csi_camera_unsubscribe_event(event_handle, &subscribe); + + subscribe.type = CSI_CAMERA_EVENT_TYPE_CAMERA; + subscribe.id = CSI_CAMERA_EVENT_WARNING | CSI_CAMERA_EVENT_ERROR; + csi_camera_unsubscribe_event(event_handle, &subscribe); + + csi_camera_destory_event(event_handle); + csi_camera_channel_stop(cam_handle, CSI_CAMERA_CHANNEL_0); + csi_camera_channel_close(cam_handle, CSI_CAMERA_CHANNEL_0); + csi_camera_close(cam_handle); +} + +static void dump_camera_meta(csi_frame_s *frame) +{ + int i; + if (frame->meta.type != CSI_META_TYPE_CAMERA) + return; + + csi_camera_meta_s *meta_data = (csi_camera_meta_s *)frame->meta.data; + int meta_count = meta_data->count; + csi_camrea_meta_unit_s meta_unit; + + for (i = 0; i < meta_count; i++) { + csi_camera_frame_get_meta_unit( + &meta_unit, meta_data, CSI_CAMERA_META_ID_FRAME_ID); + printf("meta_id=%d, meta_type=%d, meta_value=%d", + meta_unit.id, meta_unit.type, meta_unit.int_value); + } +} + diff --git a/examples/camera/camera_demo3.c b/examples/camera/camera_demo3.c new file mode 100644 index 0000000..b6b4ac8 --- /dev/null +++ b/examples/camera/camera_demo3.c @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "camera_demo3" +#include + +#include "camera_manager.h" +#include "param.h" +#include "app_dialogs.h" +#include "menu_process.h" + +cams_t *cam_session; + +WINDOW *menubar; +WINDOW *messagebar; +WINDOW *win_border; +WINDOW *win_content; + +void init_curses() +{ + initscr(); /* init curses */ + + FILE *f = fopen("/dev/tty", "r+"); + SCREEN *screen = newterm(NULL, f, f); + set_term(screen); + + start_color(); /* color init, then init color pair */ + + init_pair(1, COLOR_WHITE, COLOR_BLACK); + init_pair(2, COLOR_BLACK, COLOR_WHITE); + init_pair(3, COLOR_RED, COLOR_WHITE); + init_pair(4, COLOR_WHITE, COLOR_BLUE); // menu font + init_pair(5, COLOR_YELLOW, COLOR_BLACK); + init_pair(6, COLOR_GREEN, COLOR_WHITE); + init_pair(7, COLOR_YELLOW, COLOR_WHITE); + + curs_set(0); /* Cursor mode: 0:hide/1:normal/2:hilight */ + noecho(); /* Do not display on screen */ + keypad(stdscr, TRUE); /* allow keypad map */ +} + +void uninit_curses() +{ + echo(); + endwin(); +} + +void draw_menubar(WINDOW *menubar) +{ + wbkgd(menubar, COLOR_PAIR(2)); + + for (int i = 0; i < atoi(param[0][0]); i++) { + wattron(menubar, COLOR_PAIR(3)); + mvwprintw(menubar, 0, i * 14 + 2, "%1d.", i + 1); + wattroff(menubar, COLOR_PAIR(3)); + mvwprintw(menubar, 0, i * 14 + 4, "%-12s", param[0][i + 1]); + } +} + +WINDOW **draw_menu(int menu) +{ + int i, start_col; + WINDOW **items; + items = (WINDOW **)malloc((atoi(param[menu][0]) + 1) * sizeof(WINDOW *)); + start_col = (menu - 1) * 14 + 2; + items[0] = newwin(atoi(param[menu][0]) + 2, MENU_ITEM_MAX_LEN+2, 3, start_col); + wbkgd(items[0], COLOR_PAIR(2)); + box(items[0], ACS_VLINE, ACS_HLINE); + for (i = 1; i <= atoi(param[menu][0]); i++) { + items[i] = subwin(items[0], 1, MENU_ITEM_MAX_LEN, 3 + i, start_col + 1); + wprintw(items[i], "%s", param[menu][i]); + } + wbkgd(items[1], COLOR_PAIR(4)); + wrefresh(items[0]); + return items; +} + +void delete_menu(WINDOW **items, int count) +{ + int i; + for (i = 0; i < count; i++) + delwin(items[i]); + free(items); +} + +int scroll_menu(WINDOW **items, int menu, int *menu_returned) +{ + int key, count, selected = 0; + count = atoi(param[menu][0]); + LOG_D("Active: menu=%d, item=%d\n", menu, selected); + + while (1) { + key = getch(); + if (key == KEY_DOWN || key == KEY_UP) { + wbkgd(items[selected + 1], COLOR_PAIR(2)); + + wnoutrefresh(items[selected + 1]); + if (key == KEY_DOWN) + selected = (selected + 1) % count; + else + selected = (selected + count - 1) % count; + wbkgd(items[selected + 1], COLOR_PAIR(4)); + wnoutrefresh(items[selected + 1]); + doupdate(); + } else if (key == KEY_LEFT || key == KEY_RIGHT) { + delete_menu(items, count + 1); + touchwin(stdscr); + refresh(); + if (key == KEY_LEFT) { + menu -= 1; + if (menu <= 0) menu = atoi(param[0][0]); + items = draw_menu(menu); + *menu_returned = menu; + LOG_D("Active: menu=%d, item=%d\n", *menu_returned, selected); + return scroll_menu(items, menu, menu_returned); + } + if (key == KEY_RIGHT) { + menu += 1; + if (menu > atoi(param[0][0])) menu = 1; + items = draw_menu(menu); + *menu_returned = menu; + LOG_D("Active: menu=%d, item=%d\n", *menu_returned, selected); + return scroll_menu(items, menu, menu_returned); + } + } else if (key == ESCAPE || key == '0' || key == 'q') { + delete_menu(items, count + 1); + return -1; + } else if (key == ENTER) { + delete_menu(items, count + 1); + return selected; + } + LOG_D("Active: menu=%d, item=%d\n", *menu_returned, selected); + } +} + +/* ss: display string, + * status: 0:Normal, 1:OK, 2:Failed, 3:Warning */ +void message(char *ss, int status) +{ + int color_pair; + switch (status) { + case 1: + color_pair = 6; // GREEN:WHITE + break; + case 2: + color_pair = 3; // RED:WHITE + break; + case 3: + color_pair = 7; // YELLOW:WHITE + break; + default: + color_pair = 2; // BLACK:WHITE + break; + }; + wbkgd(messagebar, COLOR_PAIR(2)); + wattron(messagebar, COLOR_PAIR(color_pair)); + mvwprintw(messagebar, 0, 0, "%80s", " "); + mvwprintw(messagebar, 0, 1, "%s", ss); + wattroff(messagebar, COLOR_PAIR(color_pair)); + wrefresh(messagebar); +} + +void copyright() +{ + char *str[] = { "Copyright (C) 2021 Alibaba Group Holding Limited", + "Author: LuChongzhi, T-Head / IOT / OS Team", + "mailto:[chongzhi.lcz@alibaba-inc.com]"}; + + attron(A_UNDERLINE | COLOR_PAIR(1)); + for (int i = 0; i < sizeof(str) / sizeof(char *); i++) + mvaddstr((WIN_ROWS - 2) / 2 + i, (WIN_COLS - strlen(str[0])) / 2, str[i]); + + attroff(A_UNDERLINE | COLOR_PAIR(1)); + refresh(); +} + +int main(int argc, char **argv) +{ + int ret = -1; + int key; + WINDOW **menu_items; + + if (get_param(argv[0])) { + LOG_E("\n Open %s.conf failed!\n", argv[0]); + goto LAB_EXIT; + } + + if (camera_create_session(&cam_session) != 0 || cam_session == NULL) { + LOG_E("camera_create_session() failed\n"); + goto LAB_EXIT; + } + + init_curses(); + + if (init_dialog(NULL)) { + fprintf(stderr, N_("Your display is too small to run Menuconfig!\n")); + fprintf(stderr, N_("It must be at least 19 lines by 80 columns.\n")); + goto LAB_EXIT_CURSES_INITED; + } + + bkgd(COLOR_PAIR(1)); /* COLOR_WHITE, COLOR_BLACK */ + + /* set windows postion: (win, lines, cols, begin_y, begin_x) */ + menubar = subwin(stdscr, 1, WIN_COLS, 1, 0); /* Menu bar line */ + messagebar = subwin(stdscr, 1, WIN_COLS, WIN_ROWS - 1, 0); /* Bottom line */ + win_border = subwin(stdscr, WIN_ROWS - 3, WIN_COLS, 2, 0); /* Content win with border */ + win_content = subwin(stdscr, WIN_ROWS - 5, WIN_COLS - 2, 3, 1); /* Content win without border */ + + /* Set window title */ + char str_title[81]; + strcpy(str_title, "<<< CSI Camera Test Tool >>>"); + wattron(stdscr, COLOR_PAIR(5)); + mvwprintw(stdscr, 0, (80 - strlen(str_title)) / 2 - 1, "%s", str_title); + wattroff(stdscr, COLOR_PAIR(5)); + + /* Draw menu */ + draw_menubar(menubar); + message("Use number key to choses menu. 'q' or '0' to Exit", 0); + + /* Draw box border */ + box(win_border, ACS_VLINE, ACS_HLINE); + + /* Show copyright */ + copyright(); + + refresh(); + + /* menu operate loop */ + do { + key = wgetch(stdscr); + if (isdigit(key) && key > '0' && key <= atoi(param[0][0]) + '0') { + werase(messagebar); + wrefresh(messagebar); + + int menu_init = key - '0'; // first press menu id + int menu_final = menu_init; // return from scroll_menu + menu_items = draw_menu(menu_init); + int item_selected = scroll_menu(menu_items, menu_init, &menu_final); + LOG_D("menu_final=%d, selected_item=%d\n", + menu_final, item_selected); + + switch(menu_final) { + case MENU_CAMERA: + menu_camera_process(item_selected); + break; + case MENU_CHANNEL: + menu_channel_process(item_selected); + break; + case MENU_EVENT_RUN: + menu_event_run_process(item_selected); + break; + default: + LOG_W("Not supported menu: %d\n", menu_final); + break; + } + + if (item_selected == 13) { + LOG_D("Test dialog_inputbox\n"); + dialog_inputbox("inputbox", "prompt", 16, 80, + "const char init"); + } + + touchwin(stdscr); + refresh(); + } else if (key == KEY_RESIZE) { + box(win_border, ACS_VLINE, ACS_HLINE); + wrefresh(messagebar); + } + } while (key != 'q' && key != '0'); + + ret = 0; + +LAB_EXIT_WIN_CREATED: + delwin(win_content); + delwin(win_border); + delwin(menubar); + delwin(messagebar); + +LAB_EXIT_CURSES_INITED: + uninit_curses(); + +LAB_EXIT_CAM_MGR_CREATED: + camera_destory_session(&cam_session); + +LAB_EXIT: + return ret; +} + diff --git a/examples/camera/camera_demo3.conf b/examples/camera/camera_demo3.conf new file mode 100644 index 0000000..38f9113 --- /dev/null +++ b/examples/camera/camera_demo3.conf @@ -0,0 +1,11 @@ +# +# 格式为: 菜单号|项目个数|项目名称...... +# 菜单数量最大为10个 +# +0|5|Camera|Channel|Event&Run|Capture|Help| +1|5|List Cameras|Open Camera|Set Mode|Set Property|Close Camera| +2|3|List Channels|Open Channel|Close Channel| +3|2|Subscribe&Action|Start/Stop| +4|4|menu41|menu42|menu43|menu44| +5|2|menu51|menu52| +6|1|menu61| diff --git a/examples/camera/camera_demo3_srcs/app_dialogs.h b/examples/camera/camera_demo3_srcs/app_dialogs.h new file mode 100644 index 0000000..2d93ba1 --- /dev/null +++ b/examples/camera/camera_demo3_srcs/app_dialogs.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __APP_DIALOGS_H__ +#define __APP_DIALOGS_H__ + +#include +#include + +#include "dialog.h" +#include "param.h" +#include "camera_manager.h" + +extern cams_t *cam_session; /* camera manager */ + +/* curses window */ +#define WIN_COLS 100 +#define WIN_ROWS 40 + +/* key defination */ +#define ENTER 10 +#define ESCAPE 27 + +/* windoes created by main */ +extern WINDOW *menubar; +extern WINDOW *messagebar; +extern WINDOW *win_border; +extern WINDOW *win_content; + +void message(char *ss, int status); + +int dialog_camera_list(void); +int dialog_camera_open(void); +int dialog_camera_set_mode(void); +int dialog_camera_property_list(void); +int dialog_camera_close(void); + +int dialog_camera_property_boolean(csi_camera_property_description_s *property); +int dialog_camera_property_integer(csi_camera_property_description_s *property); +int dialog_camera_property_enum(csi_camera_property_description_s *property); +int dialog_camera_property_bitmask(csi_camera_property_description_s *property); + +int dialog_channel_list(void); +int dialog_channel_select(csi_camera_channel_cfg_s **selected_chn, int action); +int dialog_channel_open(csi_camera_channel_cfg_s *channel); + +int dialog_event_subscribe_action_list(camera_event_action_union_t **event_action); +int dialog_event_subscribe_action_camera(camera_event_action_union_t *event_action); +int dialog_event_subscribe_action_channel(camera_event_action_union_t *event_action); + +int dialog_channel_run(void); + +#endif /* __APP_DIALOGS_H__ */ diff --git a/examples/camera/camera_demo3_srcs/camera_ops.c b/examples/camera/camera_demo3_srcs/camera_ops.c new file mode 100644 index 0000000..90ee03a --- /dev/null +++ b/examples/camera/camera_demo3_srcs/camera_ops.c @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "camera_ops" +#include + +#include "camera_ops.h" + diff --git a/examples/camera/camera_demo3_srcs/camera_ops.h b/examples/camera/camera_demo3_srcs/camera_ops.h new file mode 100644 index 0000000..cfabcab --- /dev/null +++ b/examples/camera/camera_demo3_srcs/camera_ops.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CAMERA_OPS_H__ +#define __CAMERA_OPS_H__ + +#include +#include +#include + + +#endif /* __CAMERA_OPS_H__ */ diff --git a/examples/camera/camera_demo3_srcs/checkbox.c b/examples/camera/camera_demo3_srcs/checkbox.c new file mode 100644 index 0000000..74e4aa7 --- /dev/null +++ b/examples/camera/camera_demo3_srcs/checkbox.c @@ -0,0 +1,337 @@ +/* + * checklist.c -- implements the checklist box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * Stuart Herbert - S.Herbert@sheffield.ac.uk: radiolist extension + * Alessandro Rubini - rubini@ipvvis.unipv.it: merged the two + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "dialog.h" +#include "app_dialogs.h" + +#define LOG_LEVEL 2 +#define LOG_PREFIX "camera_demo3" +#include + + +static int list_width, check_x, item_x; + +/* + * Print list item + */ +static void print_item(WINDOW * win, int choice, int selected) +{ + int i; + char *list_item = malloc(list_width + 1); + + strncpy(list_item, item_str(), list_width - item_x); + list_item[list_width - item_x] = '\0'; + + /* Clear 'residue' of last item */ + wattrset(win, dlg.menubox.atr); + wmove(win, choice, 0); + for (i = 0; i < list_width; i++) + waddch(win, ' '); + + wmove(win, choice, check_x); + wattrset(win, selected ? dlg.check_selected.atr + : dlg.check.atr); + if (!item_is_tag(':')) + wprintw(win, "[%c]", item_is_tag('*') ? '*' : ' '); + + wattrset(win, selected ? dlg.tag_selected.atr : dlg.tag.atr); + mvwaddch(win, choice, item_x, list_item[0]); + wattrset(win, selected ? dlg.item_selected.atr : dlg.item.atr); + waddstr(win, list_item + 1); + if (selected) { + wmove(win, choice, check_x + 1); + wrefresh(win); + } + free(list_item); +} + +/* + * Print the scroll indicators. + */ +static void print_arrows(WINDOW * win, int choice, int item_no, int scroll, + int y, int x, int height) +{ + wmove(win, y, x); + + if (scroll > 0) { + wattrset(win, dlg.uarrow.atr); + waddch(win, ACS_UARROW); + waddstr(win, "[-]"); + } else { + wattrset(win, dlg.menubox.atr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } + + y = y + height + 1; + wmove(win, y, x); + + if ((height < item_no) && (scroll + choice < item_no - 1)) { + wattrset(win, dlg.darrow.atr); + waddch(win, ACS_DARROW); + waddstr(win, "[+]"); + } else { + wattrset(win, dlg.menubox_border.atr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } +} + +/* + * Display the termination buttons + */ +static void print_buttons(WINDOW * dialog, int height, int width, int selected) +{ + int x = width / 2 - 11; + int y = height - 2; + + print_button(dialog, gettext("Select"), y, x - 7, selected == 0); + print_button(dialog, gettext("Return"), y, x + 7, selected == 1); + print_button(dialog, gettext(" Help "), y, x + 21, selected == 2); + + wmove(dialog, y, x - 7 + 1 + 14 * selected); + wrefresh(dialog); +} + +/* + * Display a dialog box with a list of options that can be turned on or off + * in the style of radiolist (only one option turned on at a time). + */ +int dialog_checkbox(const char *title, const char *prompt, int height, + int width, int list_height, int init_choice) +{ + int i, x, y, box_x, box_y; + int key = 0, button = 0, choice = init_choice, scroll = 0, max_choice; + WINDOW *dialog, *list; + +do_resize: + if (WIN_ROWS < (height + CHECKLIST_HEIGTH_MIN)) { + LOG_E("WIN_ROWS(%d) < (height(%d) + CHECKLIST_HEIGTH_MIN(%d)\n", + WIN_ROWS, height, CHECKLIST_HEIGTH_MIN); + return -ERRDISPLAYTOOSMALL; + } + if (WIN_COLS < (width + CHECKLIST_WIDTH_MIN)) { + LOG_E("WIN_COLS(%d) < (width(%d) + CHECKLIST_WIDTH_MIN(%d)\n", + WIN_COLS, width, CHECKLIST_WIDTH_MIN); + return -ERRDISPLAYTOOSMALL; + } + + max_choice = MIN(list_height, item_count()); + + /* center dialog box on screen */ + x = (WIN_COLS - width) / 2; + y = (WIN_ROWS - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, + dlg.dialog.atr, dlg.border.atr); + wattrset(dialog, dlg.border.atr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dlg.dialog.atr); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dlg.dialog.atr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + list_width = width - 6; + box_y = height - list_height - 5; + box_x = (width - list_width) / 2 - 1; + + /* create new window for the list */ + list = subwin(dialog, list_height, list_width, y + box_y + 1, + x + box_x + 1); + + keypad(list, TRUE); + + /* draw a box around the list items */ + draw_box(dialog, box_y, box_x, list_height + 2, list_width + 2, + dlg.menubox_border.atr, dlg.menubox.atr); + + /* Find length of longest item in order to center checklist */ + check_x = 0; + item_foreach() + check_x = MAX(check_x, strlen(item_str()) + 4); + check_x = MIN(check_x, list_width); + + check_x = (list_width - check_x) / 2 - 4; + item_x = check_x + 4; + + if (choice >= list_height) { + scroll = choice - list_height + 1; + choice -= scroll; + } + + /* Print the list */ + for (i = 0; i < max_choice; i++) { + item_set(scroll + i); + print_item(list, i, i == choice); + } + + print_arrows(dialog, choice, item_count(), scroll, + box_y, box_x + check_x + 5, list_height); + + print_buttons(dialog, height, width, 0); + + wnoutrefresh(dialog); + wnoutrefresh(list); + doupdate(); + + while (key != KEY_ESC) { + key = wgetch(dialog); + + for (i = 0; i < max_choice; i++) { + item_set(i + scroll); + if (toupper(key) == toupper(item_str()[0])) + break; + } + + if (i < max_choice || key == KEY_UP || key == KEY_DOWN || + key == '+' || key == '-') { + if (key == KEY_UP || key == '-') { + if (!choice) { + if (!scroll) + continue; + /* Scroll list down */ + if (list_height > 1) { + /* De-highlight current first item */ + item_set(scroll); + print_item(list, 0, FALSE); + scrollok(list, TRUE); + wscrl(list, -1); + scrollok(list, FALSE); + } + scroll--; + item_set(scroll); + print_item(list, 0, TRUE); + print_arrows(dialog, choice, item_count(), + scroll, box_y, box_x + check_x + 5, list_height); + + wnoutrefresh(dialog); + wrefresh(list); + + continue; /* wait for another key press */ + } else + i = choice - 1; + } else if (key == KEY_DOWN || key == '+') { + if (choice == max_choice - 1) { + if (scroll + choice >= item_count() - 1) + continue; + /* Scroll list up */ + if (list_height > 1) { + /* De-highlight current last item before scrolling up */ + item_set(scroll + max_choice - 1); + print_item(list, + max_choice - 1, + FALSE); + scrollok(list, TRUE); + wscrl(list, 1); + scrollok(list, FALSE); + } + scroll++; + item_set(scroll + max_choice - 1); + print_item(list, max_choice - 1, TRUE); + + print_arrows(dialog, choice, item_count(), + scroll, box_y, box_x + check_x + 5, list_height); + + wnoutrefresh(dialog); + wrefresh(list); + + continue; /* wait for another key press */ + } else + i = choice + 1; + } + if (i != choice) { + /* De-highlight current item */ + item_set(scroll + choice); + print_item(list, choice, FALSE); + /* Highlight new item */ + choice = i; + item_set(scroll + choice); + print_item(list, choice, TRUE); + wnoutrefresh(dialog); + wrefresh(list); + } + continue; /* wait for another key press */ + } + switch (key) { + case 'H': + case 'h': + button = 1; + /* fall-through */ + case 'S': + case 's': + case ' ': + case '\n': + item_set(scroll + choice); + if (item_is_selected()) { + item_set_selected(0); + } + else { + item_set_selected(1); + } + delwin(list); + delwin(dialog); + return button; + case TAB: + case KEY_LEFT: + case KEY_RIGHT: + button = ((key == KEY_LEFT ? --button : ++button) < 0) + ? 2 : (button > 2 ? 0 : button); + + print_buttons(dialog, height, width, button); + wrefresh(dialog); + break; + case 'X': + case 'x': + key = KEY_ESC; + break; + case KEY_ESC: + key = on_key_esc(dialog); + break; + case KEY_RESIZE: + delwin(list); + delwin(dialog); + on_key_resize(); + goto do_resize; + } + + /* Now, update everything... */ + doupdate(); + } + delwin(list); + delwin(dialog); + return key; /* ESC pressed */ +} diff --git a/examples/camera/camera_demo3_srcs/checklist.c b/examples/camera/camera_demo3_srcs/checklist.c new file mode 100644 index 0000000..7646afe --- /dev/null +++ b/examples/camera/camera_demo3_srcs/checklist.c @@ -0,0 +1,355 @@ +/* + * checklist.c -- implements the checklist box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * Stuart Herbert - S.Herbert@sheffield.ac.uk: radiolist extension + * Alessandro Rubini - rubini@ipvvis.unipv.it: merged the two + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "dialog.h" +#include "app_dialogs.h" + +#define LOG_LEVEL 2 +#define LOG_PREFIX "camera_demo3" +#include + + +static int list_width, check_x, item_x; + +/* + * Print list item + */ +static void print_item(WINDOW * win, int choice, int selected) +{ + int i; + char *list_item = malloc(list_width + 1); + + strncpy(list_item, item_str(), list_width - item_x); + list_item[list_width - item_x] = '\0'; + + /* Clear 'residue' of last item */ + wattrset(win, dlg.menubox.atr); + wmove(win, choice, 0); + for (i = 0; i < list_width; i++) + waddch(win, ' '); + + wmove(win, choice, check_x); + wattrset(win, selected ? dlg.check_selected.atr + : dlg.check.atr); + if (!item_is_tag(':')) + wprintw(win, "(%c)", item_is_tag('X') ? 'X' : ' '); + + wattrset(win, selected ? dlg.tag_selected.atr : dlg.tag.atr); + mvwaddch(win, choice, item_x, list_item[0]); + wattrset(win, selected ? dlg.item_selected.atr : dlg.item.atr); + waddstr(win, list_item + 1); + if (selected) { + wmove(win, choice, check_x + 1); + wrefresh(win); + } + free(list_item); +} + +/* + * Print the scroll indicators. + */ +static void print_arrows(WINDOW * win, int choice, int item_no, int scroll, + int y, int x, int height) +{ + wmove(win, y, x); + + if (scroll > 0) { + wattrset(win, dlg.uarrow.atr); + waddch(win, ACS_UARROW); + waddstr(win, "(-)"); + } else { + wattrset(win, dlg.menubox.atr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } + + y = y + height + 1; + wmove(win, y, x); + + if ((height < item_no) && (scroll + choice < item_no - 1)) { + wattrset(win, dlg.darrow.atr); + waddch(win, ACS_DARROW); + waddstr(win, "(+)"); + } else { + wattrset(win, dlg.menubox_border.atr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } +} + +/* + * Display the termination buttons + */ +static void print_buttons(WINDOW * dialog, int height, int width, int selected, + char *button_names[], int button_count) +{ + int x = width / 2 - 11; + int y = height - 2; + + if (button_names == NULL) { + print_button(dialog, gettext("Select"), y, x, selected == 0); + print_button(dialog, gettext(" Help "), y, x + 14, selected == 1); + } else { + for (int i = 0; i < button_count; i++) { + print_button(dialog, gettext(button_names[i]), y, x + 14*i, selected == i); + } + } + + wmove(dialog, y, x + 1 + 14 * selected); + wrefresh(dialog); +} + +/* + * Display a dialog box with a list of options that can be turned on or off + * in the style of radiolist (only one option turned on at a time). + */ +int dialog_checklist(const char *title, const char *prompt, int height, + int width, int list_height, char *button_names[], int button_count) +{ + int i, x, y, box_x, box_y; + int key = 0, button = 0, choice = 0, scroll = 0, max_choice; + WINDOW *dialog, *list; + + /* which item to highlight */ + item_foreach() { + if (item_is_tag('X')) + choice = item_n(); + if (item_is_selected()) { + choice = item_n(); + break; + } + } + +do_resize: + if (WIN_ROWS < (height + CHECKLIST_HEIGTH_MIN)) { + LOG_E("WIN_ROWS(%d) < (height(%d) + CHECKLIST_HEIGTH_MIN(%d)\n", + WIN_ROWS, height, CHECKLIST_HEIGTH_MIN); + return -ERRDISPLAYTOOSMALL; + } + if (WIN_COLS < (width + CHECKLIST_WIDTH_MIN)) { + LOG_E("WIN_COLS(%d) < (width(%d) + CHECKLIST_WIDTH_MIN(%d)\n", + WIN_COLS, width, CHECKLIST_WIDTH_MIN); + return -ERRDISPLAYTOOSMALL; + } + + max_choice = MIN(list_height, item_count()); + + /* center dialog box on screen */ + x = (WIN_COLS - width) / 2; + y = (WIN_ROWS - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, + dlg.dialog.atr, dlg.border.atr); + wattrset(dialog, dlg.border.atr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dlg.dialog.atr); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dlg.dialog.atr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + list_width = width - 6; + box_y = height - list_height - 5; + box_x = (width - list_width) / 2 - 1; + + /* create new window for the list */ + list = subwin(dialog, list_height, list_width, y + box_y + 1, + x + box_x + 1); + + keypad(list, TRUE); + + /* draw a box around the list items */ + draw_box(dialog, box_y, box_x, list_height + 2, list_width + 2, + dlg.menubox_border.atr, dlg.menubox.atr); + + /* Find length of longest item in order to center checklist */ + check_x = 0; + item_foreach() + check_x = MAX(check_x, strlen(item_str()) + 4); + check_x = MIN(check_x, list_width); + + check_x = (list_width - check_x) / 2 - 4; + item_x = check_x + 4; + + if (choice >= list_height) { + scroll = choice - list_height + 1; + choice -= scroll; + } + + /* Print the list */ + for (i = 0; i < max_choice; i++) { + item_set(scroll + i); + print_item(list, i, i == choice); + } + + print_arrows(dialog, choice, item_count(), scroll, + box_y, box_x + check_x + 5, list_height); + + print_buttons(dialog, height, width, 0, button_names, button_count); + + wnoutrefresh(dialog); + wnoutrefresh(list); + doupdate(); + + while (key != KEY_ESC) { + key = wgetch(dialog); + + for (i = 0; i < max_choice; i++) { + item_set(i + scroll); + if (toupper(key) == toupper(item_str()[0])) + break; + } + + if (i < max_choice || key == KEY_UP || key == KEY_DOWN || + key == '+' || key == '-') { + if (key == KEY_UP || key == '-') { + if (!choice) { + if (!scroll) + continue; + /* Scroll list down */ + if (list_height > 1) { + /* De-highlight current first item */ + item_set(scroll); + print_item(list, 0, FALSE); + scrollok(list, TRUE); + wscrl(list, -1); + scrollok(list, FALSE); + } + scroll--; + item_set(scroll); + print_item(list, 0, TRUE); + print_arrows(dialog, choice, item_count(), + scroll, box_y, box_x + check_x + 5, list_height); + + wnoutrefresh(dialog); + wrefresh(list); + + continue; /* wait for another key press */ + } else + i = choice - 1; + } else if (key == KEY_DOWN || key == '+') { + if (choice == max_choice - 1) { + if (scroll + choice >= item_count() - 1) + continue; + /* Scroll list up */ + if (list_height > 1) { + /* De-highlight current last item before scrolling up */ + item_set(scroll + max_choice - 1); + print_item(list, + max_choice - 1, + FALSE); + scrollok(list, TRUE); + wscrl(list, 1); + scrollok(list, FALSE); + } + scroll++; + item_set(scroll + max_choice - 1); + print_item(list, max_choice - 1, TRUE); + + print_arrows(dialog, choice, item_count(), + scroll, box_y, box_x + check_x + 5, list_height); + + wnoutrefresh(dialog); + wrefresh(list); + + continue; /* wait for another key press */ + } else + i = choice + 1; + } + if (i != choice) { + /* De-highlight current item */ + item_set(scroll + choice); + print_item(list, choice, FALSE); + /* Highlight new item */ + choice = i; + item_set(scroll + choice); + print_item(list, choice, TRUE); + wnoutrefresh(dialog); + wrefresh(list); + } + continue; /* wait for another key press */ + } + switch (key) { + case 'H': + case 'h': + button = 1; + /* fall-through */ + case 'S': + case 's': + case ' ': + case '\n': + item_foreach() + item_set_selected(0); + item_set(scroll + choice); + item_set_selected(1); + delwin(list); + delwin(dialog); + return button; + case TAB: + case KEY_LEFT: + case KEY_RIGHT: + if (button_names != NULL || button_count > 0) { + button = ((key == KEY_LEFT ? --button : ++button) < 0) + ? (button_count - 1) : (button > (button_count - 1) ? 0 : button); + } else { + button = ((key == KEY_LEFT ? --button : ++button) < 0) + ? 1 : (button > 1 ? 0 : button); + } + + print_buttons(dialog, height, width, button, button_names, button_count); + wrefresh(dialog); + break; + case 'X': + case 'x': + key = KEY_ESC; + break; + case KEY_ESC: + key = on_key_esc(dialog); + break; + case KEY_RESIZE: + delwin(list); + delwin(dialog); + on_key_resize(); + goto do_resize; + } + + /* Now, update everything... */ + doupdate(); + } + delwin(list); + delwin(dialog); + return key; /* ESC pressed */ +} diff --git a/examples/camera/camera_demo3_srcs/dialog.h b/examples/camera/camera_demo3_srcs/dialog.h new file mode 100644 index 0000000..e1a217f --- /dev/null +++ b/examples/camera/camera_demo3_srcs/dialog.h @@ -0,0 +1,276 @@ +/* + * dialog.h -- common declarations for all dialog modules + * + * AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __DIALOG_H__ +#define __DIALOG_H__ + +#include +#include +#include +#include +#include +#include +#include + +////////// lucz add begin +#define N_(text) (text) +////////// lucz add end + +#ifndef KBUILD_NO_NLS +# include +#else +# define gettext(Msgid) ((const char *) (Msgid)) +#endif + +#ifdef __sun__ +#define CURS_MACROS +#endif +#include + +/* + * Colors in ncurses 1.9.9e do not work properly since foreground and + * background colors are OR'd rather than separately masked. This version + * of dialog was hacked to work with ncurses 1.9.9e, making it incompatible + * with standard curses. The simplest fix (to make this work with standard + * curses) uses the wbkgdset() function, not used in the original hack. + * Turn it off if we're building with 1.9.9e, since it just confuses things. + */ +#if defined(NCURSES_VERSION) && defined(_NEED_WRAP) && !defined(GCC_PRINTFLIKE) +#define OLD_NCURSES 1 +#undef wbkgdset +#define wbkgdset(w,p) /*nothing */ +#else +#define OLD_NCURSES 0 +#endif + +#define TR(params) _tracef params + +#define KEY_ESC 27 +#define TAB 9 +#define MAX_LEN 2048 +#define BUF_SIZE (10*1024) +#define MIN(x,y) (x < y ? x : y) +#define MAX(x,y) (x > y ? x : y) + +#ifndef ACS_ULCORNER +#define ACS_ULCORNER '+' +#endif +#ifndef ACS_LLCORNER +#define ACS_LLCORNER '+' +#endif +#ifndef ACS_URCORNER +#define ACS_URCORNER '+' +#endif +#ifndef ACS_LRCORNER +#define ACS_LRCORNER '+' +#endif +#ifndef ACS_HLINE +#define ACS_HLINE '-' +#endif +#ifndef ACS_VLINE +#define ACS_VLINE '|' +#endif +#ifndef ACS_LTEE +#define ACS_LTEE '+' +#endif +#ifndef ACS_RTEE +#define ACS_RTEE '+' +#endif +#ifndef ACS_UARROW +#define ACS_UARROW '^' +#endif +#ifndef ACS_DARROW +#define ACS_DARROW 'v' +#endif + +/* error return codes */ +#define ERRDISPLAYTOOSMALL (KEY_MAX + 1) + +/* + * Color definitions + */ +struct dialog_color { + chtype atr; /* Color attribute */ + int fg; /* foreground */ + int bg; /* background */ + int hl; /* highlight this item */ +}; + +struct subtitle_list { + struct subtitle_list *next; + const char *text; +}; + +struct dialog_info { + const char *backtitle; + struct subtitle_list *subtitles; + struct dialog_color screen; + struct dialog_color shadow; + struct dialog_color dialog; + struct dialog_color title; + struct dialog_color border; + struct dialog_color button_active; + struct dialog_color button_inactive; + struct dialog_color button_key_active; + struct dialog_color button_key_inactive; + struct dialog_color button_label_active; + struct dialog_color button_label_inactive; + struct dialog_color inputbox; + struct dialog_color inputbox_border; + struct dialog_color searchbox; + struct dialog_color searchbox_title; + struct dialog_color searchbox_border; + struct dialog_color position_indicator; + struct dialog_color menubox; + struct dialog_color menubox_border; + struct dialog_color item; + struct dialog_color item_selected; + struct dialog_color tag; + struct dialog_color tag_selected; + struct dialog_color tag_key; + struct dialog_color tag_key_selected; + struct dialog_color check; + struct dialog_color check_selected; + struct dialog_color uarrow; + struct dialog_color darrow; +}; + +/* + * Global variables + */ +extern struct dialog_info dlg; +extern char dialog_input_result[]; +extern int saved_x, saved_y; /* Needed in signal handler in mconf.c */ + +/* + * Function prototypes + */ + +/* item list as used by checklist and menubox */ +void item_reset(void); +void item_make(const char *fmt, ...); +void item_add_str(const char *fmt, ...); +void item_set_tag(char tag); +void item_set_int(int int_value); +void item_set_data(void *p); +void item_set_selected(int val); +int item_activate_selected(void); +int item_activate_selected_pos(void); +void *item_data(void); +char item_tag(void); +int item_int(void); + +/* item list manipulation for lxdialog use */ +#define MAXITEMSTR 200 +struct dialog_item { + char str[MAXITEMSTR]; /* prompt displayed */ + char tag; + void *data; /* pointer to menu item - used by menubox+checklist */ + int int_value; /* the int value of the item */ + int selected; /* Set to 1 by dialog_*() function if selected. */ +}; + +/* list of lialog_items */ +struct dialog_list { + struct dialog_item node; + struct dialog_list *next; +}; + +extern struct dialog_list *item_cur; +extern struct dialog_list item_nil; +extern struct dialog_list *item_head; + +int item_count(void); +void item_set(int n); +int item_n(void); +const char *item_str(void); +int item_is_selected(void); +int item_is_tag(char tag); +#define item_foreach() \ + for (item_cur = item_head ? item_head: item_cur; \ + item_cur && (item_cur != &item_nil); item_cur = item_cur->next) + +/* generic key handlers */ +int on_key_esc(WINDOW *win); +int on_key_resize(void); + +/* minimum (re)size values */ +#define CHECKLIST_HEIGTH_MIN 6 /* For dialog_checklist() */ +#define CHECKLIST_WIDTH_MIN 6 +#define INPUTBOX_HEIGTH_MIN 2 /* For dialog_inputbox() */ +#define INPUTBOX_WIDTH_MIN 2 +#define MENUBOX_HEIGTH_MIN 15 /* For dialog_menu() */ +#define MENUBOX_WIDTH_MIN 65 +#define TEXTBOX_HEIGTH_MIN 8 /* For dialog_textbox() */ +#define TEXTBOX_WIDTH_MIN 8 +#define YESNO_HEIGTH_MIN 4 /* For dialog_yesno() */ +#define YESNO_WIDTH_MIN 4 +#define WINDOW_HEIGTH_MIN 19 /* For init_dialog() */ +#define WINDOW_WIDTH_MIN 80 + +int init_dialog(const char *backtitle); +void set_dialog_backtitle(const char *backtitle); +void set_dialog_subtitles(struct subtitle_list *subtitles); +void end_dialog(int x, int y); +void attr_clear(WINDOW * win, int height, int width, chtype attr); +void dialog_clear(void); +void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x); +void print_button(WINDOW * win, const char *label, int y, int x, int selected); +void print_title(WINDOW *dialog, const char *title, int width); +void draw_box(WINDOW * win, int y, int x, int height, int width, chtype box, + chtype border); +void draw_shadow(WINDOW * win, int y, int x, int height, int width); + +int first_alpha(const char *string, const char *exempt); +int dialog_yesno(const char *title, const char *prompt, int height, int width); +int dialog_msgbox(const char *title, const char *prompt, int height, + int width, int pause); + + +typedef void (*update_text_fn)(char *buf, size_t start, size_t end, void + *_data); +int dialog_textbox(const char *title, char *tbuf, int initial_height, + int initial_width, int *keys, int *_vscroll, int *_hscroll, + update_text_fn update_text, void *data); +int dialog_textbox_simple(const char *title, char *tbuf, int initial_height, int initial_width); + +int dialog_menu(const char *title, const char *prompt, int height, int width, + const void *selected, int selected_pos, int *s_scroll, + char *button_names[], int button_count); +int dialog_checklist(const char *title, const char *prompt, int height, + int width, int list_height, char *button_names[], int button_count); +int dialog_checkbox(const char *title, const char *prompt, int height, + int width, int list_height, int init_choice); +int dialog_inputbox(const char *title, const char *prompt, int height, + int width, const char *init); + +/* + * This is the base for fictitious keys, which activate + * the buttons. + * + * Mouse-generated keys are the following: + * -- the first 32 are used as numbers, in addition to '0'-'9' + * -- the lowercase are used to signal mouse-enter events (M_EVENT + 'o') + * -- uppercase chars are used to invoke the button (M_EVENT + 'O') + */ +#define M_EVENT (KEY_MAX+1) + +#endif /* __DIALOG_H__ */ + diff --git a/examples/camera/camera_demo3_srcs/dialog_camera_close.c b/examples/camera/camera_demo3_srcs/dialog_camera_close.c new file mode 100644 index 0000000..bb2ce6b --- /dev/null +++ b/examples/camera/camera_demo3_srcs/dialog_camera_close.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "dlg_close_camera" +#include +#include + +#include "param.h" +#include "app_dialogs.h" + +extern cams_t *cam_session; + +int dialog_camera_close(void) +{ + int ret_key = KEY_ESC; + int ret; + + if (cam_session == NULL) { + LOG_E("cam_session is NULL\n"); + return -1; + } + + if (cam_session->camera_handle == NULL) { + LOG_I("The camera has not been opened yet\n"); + dialog_textbox_simple("Infomation", "There's No camera opened yet", 10, 40); + return -1; + } + + + char str_buf[256]; + int cam_id = cam_session->camera_id; + snprintf(str_buf, sizeof(str_buf), + "The camera below is to be closed:\n" + "\t id=%d \n\t name='%s' \n\t device='%s' \n\t bus='%s'\n", + cam_id, + cam_session->camera_infos.info[cam_id].camera_name, + cam_session->camera_infos.info[cam_id].device_name, + cam_session->camera_infos.info[cam_id].bus_info); + + ret_key = dialog_yesno("Infomation", str_buf, 10, 50); + LOG_D("dialog_yesno() return %d\n", ret); + + if (ret_key != 0) { // 0 is the button position of 'yes' + LOG_D("close camera is canceled\n"); + return 0; + } + + ret = camera_close(cam_session); + if (ret != 0) { + LOG_E("camera_close() failed\n"); + ret = -1; + } + + snprintf(str_buf, sizeof(str_buf), + "The camera close %s: {%d, %s, %s, %s}\n", + (ret == 0) ? "OK" : "Failed", + cam_id, + cam_session->camera_infos.info[cam_id].camera_name, + cam_session->camera_infos.info[cam_id].device_name, + cam_session->camera_infos.info[cam_id].bus_info); + + message(str_buf, (ret == 0) ? 1 : 2); + return ret; +} + diff --git a/examples/camera/camera_demo3_srcs/dialog_camera_list.c b/examples/camera/camera_demo3_srcs/dialog_camera_list.c new file mode 100644 index 0000000..4bd751f --- /dev/null +++ b/examples/camera/camera_demo3_srcs/dialog_camera_list.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "dlg_camera_list" +#include +#include + +#include "param.h" +#include "app_dialogs.h" + +extern cams_t *cam_session; + +int dialog_camera_list(void) +{ + int ret_key = KEY_ESC; + int ret; + + if (cam_session == NULL) { + LOG_E("cam_session is NULL\n"); + return KEY_ESC; + } + + /* Get camera list */ + if (camera_query_list(cam_session) != 0) { + LOG_E("camera_query_list() failed\n"); + return KEY_ESC; + } + + char str_buf[1024] = ""; + char str_item[256]; + + /* Prepare for checklist nodes */ + csi_camera_infos_s *cam_infos = &cam_session->camera_infos; + for (int i = 0; i < cam_infos->count; i++) { + csi_camera_info_s *info = &(cam_infos->info[i]); + snprintf(str_item, sizeof(str_item), + "Camera[%d] info:\n" + "\t name = %s\n" + "\t dev = %s\n" + "\t bus = %s\n", + i, + info->camera_name, + info->device_name, + info->bus_info); + strcat(str_buf, str_item); + + + strcat(str_buf, "\t caps = "); + for (unsigned int j = 1; j < 0x80000000; j = j << 1) { + switch (info->capabilities & j) { + case CSI_CAMERA_CAP_VIDEO_CAPTURE: + strcat(str_buf,"VIDEO_CAPTURE | "); + break; + case CSI_CAMERA_CAP_META_CAPTURE: + strcat(str_buf, "META_CAPTURE | "); + break; + default: + if (info->capabilities & j) { + strcat(str_buf, "Unknown"); + } + break; + } + } + + strcat(str_buf, "\n\n"); + } + + dialog_textbox_simple("System Camera List", + str_buf, + 20, //int initial_height, + 80); //int initial_width + + return ret; +} + diff --git a/examples/camera/camera_demo3_srcs/dialog_camera_open.c b/examples/camera/camera_demo3_srcs/dialog_camera_open.c new file mode 100644 index 0000000..f7f70de --- /dev/null +++ b/examples/camera/camera_demo3_srcs/dialog_camera_open.c @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "dlg_open_camera" +#include +#include + +#include "param.h" +#include "app_dialogs.h" + +extern cams_t *cam_session; + +int dialog_camera_open(void) +{ + int ret_key = KEY_ESC; + int ret; + + if (cam_session == NULL) { + LOG_E("cam_session is NULL\n"); + return KEY_ESC; + } + + if (cam_session->camera_handle != NULL) { + csi_camera_info_s *info = + &(cam_session->camera_infos.info[cam_session->camera_id]); + char textbox_content[256]; + snprintf(textbox_content, sizeof(textbox_content), + "Please close camera below first:\n" + " camera: %s\n device:%s\n bus:%s\n", + info->camera_name, info->device_name, info->bus_info); + dialog_textbox("Error", + textbox_content, + 10, //int initial_height, + 40, //int initial_width, + (int []) {0}, //int *keys, + NULL, + NULL, + NULL, //update_text_fn update_text, + NULL /*void *data*/); + return 0; + } + +again: + item_reset(); + + if (camera_query_list(cam_session) != 0) { + LOG_E("camera_query_list() failed\n"); + return KEY_ESC; + } + + /* Prepare for checklist nodes */ + csi_camera_infos_s *cam_infos = &cam_session->camera_infos; + for (int i = 0; i < cam_infos->count; i++) { + csi_camera_info_s *info = &(cam_infos->info[i]); + item_make("%-16s", info->camera_name); + item_add_str("%-12s %s", + info->device_name, + info->bus_info); + + item_set_data(info); // store cam_info pointer + if (i == cam_session->camera_id) { + item_set_tag('X'); + item_set_selected(1); + } else { + item_set_tag(' '); + item_set_selected(0); + } + } + + ret = dialog_checklist( + "Select Camera to open", /* Title */ + "Select the camera to open. " + "Press 'h' to get capabilities details", + CHECKLIST_HEIGTH_MIN + 10, + WIN_COLS - CHECKLIST_WIDTH_MIN - 16, + 8, + NULL, 0); + + int cam_id; + int selected = item_activate_selected(); + LOG_D("ret=%d, selected=%s\n", ret, selected ? "true" : "false"); + char str_buf[256]; + switch (ret) { + case 0: // item is selected + if (!selected) + break; + + /* Show operation result */ + cam_id = item_activate_selected_pos(); + //char str_buf[256]; + bool opened = (camera_open(cam_session, cam_id) == 0); + snprintf(str_buf, sizeof(str_buf), + "Open the below camera %s:\n" + "\t id=%d \n\t name='%s' \n\t device='%s' \n\t bus='%s'\n", + opened ? "OK" : "Failed", + cam_id, + cam_session->camera_infos.info[cam_id].camera_name, + cam_session->camera_infos.info[cam_id].device_name, + cam_session->camera_infos.info[cam_id].bus_info); + + dialog_textbox_simple("Infomation", str_buf, 10, 40); + LOG_I("csi_camera_infos_s.info[%d] is opened %s\n", + cam_id, opened ? "OK" : "Failed"); + + /* Show message bar */ + snprintf(str_buf, sizeof(str_buf), + "The camera open %s: {%d, %s, %s, %s}\n", + opened ? "OK" : "Failed", + cam_id, + cam_session->camera_infos.info[cam_id].camera_name, + cam_session->camera_infos.info[cam_id].device_name, + cam_session->camera_infos.info[cam_id].bus_info); + + message(str_buf, opened ? 1 : 2); + + break; + case 1: // need help + cam_id = item_n(); + //char str_buf[256]; + snprintf(str_buf, sizeof(str_buf), + "Camera info:\n" + "\t id=%d \n\t name='%s' \n\t device='%s' \n\t bus='%s'\n", + cam_id, + cam_session->camera_infos.info[cam_id].camera_name, + cam_session->camera_infos.info[cam_id].device_name, + cam_session->camera_infos.info[cam_id].bus_info); + + dialog_textbox_simple("Infomation", str_buf, 10, 40); + goto again; + case KEY_ESC: // specific KEY + break; + case -ERRDISPLAYTOOSMALL: // error + default: + LOG_W("Oops? why return ret=%d\n", ret); + break; + } + + LOG_D("ret=%d\n", ret); + return 0; +} + diff --git a/examples/camera/camera_demo3_srcs/dialog_camera_property_bitmask.c b/examples/camera/camera_demo3_srcs/dialog_camera_property_bitmask.c new file mode 100644 index 0000000..53f2890 --- /dev/null +++ b/examples/camera/camera_demo3_srcs/dialog_camera_property_bitmask.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "dlg_prop_enum" +#include +#include +#include +#include + +#include "param.h" +#include "app_dialogs.h" + +extern cams_t *cam_session; + +int dialog_camera_property_bitmask(csi_camera_property_description_s *property) +{ + int ret_key = KEY_ESC; + + if (property == NULL) { + LOG_W("property is NULL\n"); + return KEY_ESC; + } + + char *title = property->name; + char *prompt = "Please set camera property"; + int item_pos = 0; + +again: + item_reset(); + + const camera_spec_bitmasks_t *bitmasks_array = camera_spec_get_bitmask_array(property->id); + if (bitmasks_array == NULL) + return KEY_ESC; + + for (int i = 0; i < bitmasks_array->count; i++) { + item_make("bit-%02d: %s", bitmasks_array->bitmask[i], + camera_string_bitmask_name(property->id, bitmasks_array->bitmask[i])); + item_add_str("%s", + (bitmasks_array->bitmask[i] & property->default_value.enum_value) ? " (default)" : ""); + + if (bitmasks_array->bitmask[i] & property->value.enum_value) { + item_set_selected(1); + item_set_tag('*'); + } + else { + item_set_selected(0); + item_set_tag(' '); + } + } + + int list_height = MIN(bitmasks_array->count + 1, 8); + ret_key = dialog_checkbox( + title, prompt, + CHECKLIST_HEIGTH_MIN + list_height + 2, + WIN_COLS - CHECKLIST_WIDTH_MIN - 32, + list_height, item_pos); + + LOG_D("dialog_checkbox() ret_key=%d\n", ret_key); + if (ret_key == KEY_ESC) { + return KEY_ESC; + } else if (ret_key == 0) {/* select */ + item_pos = item_n(); + int bitmask_value = bitmasks_array->bitmask[item_n()]; + if (item_is_selected()) { + property->value.bitmask_value |= bitmask_value; + } else { + property->value.bitmask_value &= ~bitmask_value; + } + + int enum_id = bitmasks_array->bitmask[item_n()]; + LOG_D("%s item: pos=%d, enum=%d-%s, bitmask_value=%08x\n", + item_is_selected() ? "Select" : "Unselect", + item_n(), enum_id, + camera_string_bitmask_name(property->id, enum_id), + property->value.bitmask_value); + goto again; + return ret_key; + } else if (ret_key == 1) {/* help */ + LOG_W("Help does not support yet\n"); + return ret_key; + } else { + LOG_E("Unknown return value: %d\n", ret_key); + return KEY_ESC; + } +} + diff --git a/examples/camera/camera_demo3_srcs/dialog_camera_property_boolean.c b/examples/camera/camera_demo3_srcs/dialog_camera_property_boolean.c new file mode 100644 index 0000000..eaa70ce --- /dev/null +++ b/examples/camera/camera_demo3_srcs/dialog_camera_property_boolean.c @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "dlg_prop_boolean" +#include + +#include "param.h" +#include "app_dialogs.h" +#include + +extern cams_t *cam_session; + +int dialog_camera_property_boolean(csi_camera_property_description_s *property) +{ + int ret_key = KEY_ESC; + + if (property == NULL) { + LOG_W("property is NULL\n"); + return KEY_ESC; + } + + item_reset(); + char *title = property->name; + char *prompt = "Please set camera property"; + + item_reset(); + + item_make("true"); + if (property->default_value.bool_value) + item_add_str("(default)"); + if (property->value.bool_value) { + item_set_tag('X'); + item_set_selected(1); + } else { + item_set_tag(' '); + item_set_selected(0); + } + + item_make("false"); + if (!property->default_value.bool_value) + item_add_str(" (default)"); + if (!property->value.bool_value) { + item_set_tag('X'); + item_set_selected(1); + } else { + item_set_tag(' '); + item_set_selected(0); + } + + ret_key = dialog_checklist( + title, prompt, + CHECKLIST_HEIGTH_MIN + 4, + WIN_COLS - CHECKLIST_WIDTH_MIN - 32, + 2, + NULL, 0); + + LOG_D("dialog_checklist() ret_key=%d\n", ret_key); + if (ret_key == KEY_ESC) { + return KEY_ESC; + } else if (ret_key == 0) {/* select */ + LOG_D("select item: %d, means '%s'\n", item_n(), + (item_n() == 0) ? "true" : "false"); + property->value.bool_value = (item_n() == 0) ? true : false; + return ret_key; + } else if (ret_key == 1) {/* help */ + LOG_W("Help does not support yet\n"); + return ret_key; + } else { + LOG_E("Unknown return value: %d\n", ret_key); + return KEY_ESC; + } +} + diff --git a/examples/camera/camera_demo3_srcs/dialog_camera_property_enum.c b/examples/camera/camera_demo3_srcs/dialog_camera_property_enum.c new file mode 100644 index 0000000..39a7195 --- /dev/null +++ b/examples/camera/camera_demo3_srcs/dialog_camera_property_enum.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "dlg_prop_enum" +#include + +#include +#include +#include +#include + +#include "param.h" +#include "app_dialogs.h" + +extern cams_t *cam_session; + +int dialog_camera_property_enum(csi_camera_property_description_s *property) +{ + int ret_key = KEY_ESC; + + if (property == NULL) { + LOG_W("property is NULL\n"); + return KEY_ESC; + } + + item_reset(); + char *title = property->name; + char *prompt = "Please set camera property"; + + item_reset(); + + const camera_spec_enums_s *enums_array = camera_spec_get_enum_array(property->id); + if (enums_array == NULL) + return KEY_ESC; + + for (int i = 0; i < enums_array->count; i++) { + item_make("%d-%s", + enums_array->enums[i], + camera_string_enum_name(property->id, enums_array->enums[i])); + item_add_str("%s", + (enums_array->enums[i] == property->default_value.enum_value) ? " (default)" : ""); + + if (enums_array->enums[i] == property->value.enum_value) { + item_set_selected(1); + item_set_tag('X'); + } + else { + item_set_selected(0); + item_set_tag(' '); + } + } + + int list_height = MIN(enums_array->count + 1, 8); + ret_key = dialog_checklist( + title, prompt, + CHECKLIST_HEIGTH_MIN + list_height + 2, + WIN_COLS - CHECKLIST_WIDTH_MIN - 32, + list_height, + NULL, 0); + + LOG_D("dialog_checklist() ret_key=%d\n", ret_key); + if (ret_key == KEY_ESC) { + return KEY_ESC; + } else if (ret_key == 0) {/* select */ + if (!item_is_selected()) + return KEY_ESC; + + int enum_id = enums_array->enums[item_n()]; + LOG_D("select item: pos=%d, enum=%d-%s\n", + item_n(), enum_id, + camera_string_enum_name(property->id, enum_id)); + property->value.enum_value = enum_id; + return ret_key; + } else if (ret_key == 1) {/* help */ + LOG_W("Help does not support yet\n"); + return ret_key; + } else { + LOG_E("Unknown return value: %d\n", ret_key); + return KEY_ESC; + } +} + diff --git a/examples/camera/camera_demo3_srcs/dialog_camera_property_integer.c b/examples/camera/camera_demo3_srcs/dialog_camera_property_integer.c new file mode 100644 index 0000000..818cfaf --- /dev/null +++ b/examples/camera/camera_demo3_srcs/dialog_camera_property_integer.c @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "dlg_prop_int" +#include +#include + +#include "param.h" +#include "app_dialogs.h" + +extern cams_t *cam_session; +extern char dialog_input_result[MAX_LEN + 1]; + +int dialog_camera_property_integer(csi_camera_property_description_s *property) +{ + int ret_key = KEY_ESC; + + if (property == NULL) { + LOG_W("property is NULL\n"); + return KEY_ESC; + } + + char title[64]; + snprintf(title, sizeof(title), "Camera %s setting", property->name); + + char prompt[64]; + snprintf(prompt, sizeof(prompt), "value range should be: [%d, %d], step %d", + property->minimum, property->maximum, property->step); + + char str_value[32]; + snprintf(str_value, sizeof(str_value), "%d", property->value.int_value); + +again: + curs_set(1); + ret_key = dialog_inputbox(title, prompt, 10, 50, str_value); + curs_set(0); + + if (ret_key == KEY_ESC || ret_key == 1) + return KEY_ESC; + + /* can't be empty */ + if (strlen(dialog_input_result) == 0) { + goto again; + } + + /* value check, reset if invalid */ + int value = atoi(dialog_input_result); + if (value < property->minimum || value > property->maximum || + ((value - property->minimum) % property->step != 0)) { + LOG_W("%s\n", prompt); + goto again; + } + + /* accept value, set into property */ + property->value.int_value = value; + return 0; +} + diff --git a/examples/camera/camera_demo3_srcs/dialog_camera_property_list.c b/examples/camera/camera_demo3_srcs/dialog_camera_property_list.c new file mode 100644 index 0000000..296bde3 --- /dev/null +++ b/examples/camera/camera_demo3_srcs/dialog_camera_property_list.c @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "dlg_prop_list" +#include + +#include +#include +#include +#include "param.h" +#include "app_dialogs.h" + +#include +#include + +extern cams_t *cam_session; + +//#define USE_DUMMY_DESC + +#ifdef USE_DUMMY_DESC +csi_camera_property_description_s property_hflip = { + CSI_CAMERA_PID_HFLIP, CSI_CAMERA_PROPERTY_TYPE_BOOLEAN, "horizon flip", + /*min, max , step, def, value, flag, res[2]*/ + 0, 1, 1, {.bool_value=true}, {.bool_value=false}, 0, {0, 0} +}; + +csi_camera_property_description_s property_rotate = { + CSI_CAMERA_PID_ROTATE, CSI_CAMERA_PROPERTY_TYPE_INTEGER, "rotate", + /*min, max, step, def, value, flag, res[2]*/ + 0, 270, 90, {.int_value=0}, {.int_value=90}, 0, {0, 0} +}; + +csi_camera_property_description_s property_exposure_mode = { + CSI_CAMERA_PID_EXPOSURE_MODE, CSI_CAMERA_PROPERTY_TYPE_ENUM, "exposure mode", + /*min, max, step, def, value, flag, res[2]*/ + 0, 0, 0, {.enum_value=CSI_CAMERA_EXPOSURE_MODE_AUTO}, {.enum_value=CSI_CAMERA_EXPOSURE_SHUTTER_PRIORITY}, 0, {0, 0} +}; + +csi_camera_property_description_s property_3a_lock = { + CSI_CAMERA_PID_3A_LOCK, CSI_CAMERA_PROPERTY_TYPE_BITMASK, "3A LOCK", + 1, 4, 2, {.bitmask_value = 3}, {.bitmask_value = 2}, 0, {0, 0} +}; +#endif + +struct desc_list { + csi_camera_property_description_s desc; + struct desc_list *next; +}; + +struct desc_list *desc_head = NULL; +struct desc_list *desc_cur = NULL; +struct desc_list desc_nil; + +static void desc_list_reset(void) +{ + struct desc_list *p, *next; + + for (p = desc_head; p && (p != &desc_nil); p = next) { + next = p->next; + free(p); + } + desc_head = NULL; + desc_cur = &desc_nil; +} + +static void desc_list_add(csi_camera_property_description_s *desc) +{ + struct desc_list *p = malloc(sizeof(*p)); + memcpy(p, desc, sizeof(*desc)); + + if (desc_head) + desc_cur->next = p; + else + desc_head = p; + desc_cur = p; + p->next = &desc_nil; +} + +static void init_camera_property_items(void) +{ +#ifdef USE_DUMMY_DESC + desc_list_add(&property_hflip); + desc_list_add(&property_rotate); + desc_list_add(&property_exposure_mode); + desc_list_add(&property_3a_lock); +#else + csi_camera_property_description_s desc; + + desc.id = CSI_CAMERA_PID_HFLIP; + while (!camera_query_property(cam_session, &desc)) { + //LOG_D("Get desc:%s\n", desc.name); + + desc_list_add(&desc); + + desc.id |= CSI_CAMERA_FLAG_NEXT_CTRL; + } +#endif +} + +static void uninit_camera_property_items(void) +{ + desc_list_reset(); +} + +static int fill_camera_property_items(void) +{ + struct desc_list *node; + for (node = desc_head; node && (node != &desc_nil); node = node->next) { + csi_camera_property_description_s *desc = &(node->desc); + char item_name[64]; + char item_value[64]; + + switch (desc->type) { + case (CSI_CAMERA_PROPERTY_TYPE_INTEGER): + snprintf(item_name, sizeof(item_name), "%-16s:%10s", desc->name, "INTEGER: "); + snprintf(item_value, sizeof(item_value), "(%d)", desc->value.int_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_BOOLEAN): + snprintf(item_name, sizeof(item_name), "%-16s:%10s", desc->name, "BOOLEAN: "); + snprintf(item_value, sizeof(item_value), "(%s)", desc->value.bool_value ? "true" : "false"); + break; + case (CSI_CAMERA_PROPERTY_TYPE_ENUM): + snprintf(item_name, sizeof(item_name), "%-16s:%10s", desc->name, "ENUM: "); + snprintf(item_value, sizeof(item_value), "(%s)", + camera_string_enum_name(desc->id, desc->value.enum_value)); + break; + case (CSI_CAMERA_PROPERTY_TYPE_STRING): + snprintf(item_name, sizeof(item_name), "%-16s:%10s", desc->name, "STRING: "); + snprintf(item_value, sizeof(item_value), "(%s)", desc->value.str_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_BITMASK): + snprintf(item_name, sizeof(item_name), "%-16s:%10s", desc->name, "BITMASK: "); + snprintf(item_value, sizeof(item_value), "(%08x)", desc->value.bitmask_value); + break; + default: + LOG_E("error type!\n"); + return -1; + } + + item_make("%s", item_name); + item_add_str("%s --->", item_value); + item_set_data(&(node->desc)); + //LOG_D("fill %s\n", desc->name); + } + + return 0; +} + +static int get_property_diff_count(void) +{ + int diff_count = 0; + struct desc_list *node; + for (node = desc_head; node && (node != &desc_nil); node = node->next) { + //LOG_D("comparing '%s'\n", node->desc.name); + csi_camera_property_description_s desc; + desc.id = node->desc.id; + if (camera_query_property(cam_session, &desc) != 0) { + LOG_E("camera_query_property(%d), %s failed\n", + node->desc.id, node->desc.name); + continue; + } + if (memcmp(&(node->desc.value), &(desc.value), sizeof(desc.value)) == 0) { + //LOG_D("property '%s' is same\n", desc.name); + continue; + } + diff_count ++; + } + return diff_count; +} + +/* return int: property count(rows) */ +static int get_diff_string(char *text, int length, int rows) +{ + int count = 0; + int total_len = 0; + int add_len; + text[0] = '\0'; + strcat(text,"-------------------------------------------\n"); + + for (struct desc_list *node = desc_head; + node && (node != &desc_nil); node = node->next) { + //LOG_D("comparing '%s'\n", node->desc.name); + csi_camera_property_description_s *desc = &(node->desc); + + csi_camera_property_description_s device_desc; + device_desc.id = node->desc.id; + if (camera_query_property(cam_session, &device_desc) != 0) { + LOG_E("camera_query_property(%d), %s failed\n", + node->desc.id, node->desc.name); + continue; + } + + if (memcmp(&(desc->value), &(device_desc.value), sizeof(desc->value)) == 0) { + continue; + } + + LOG_D("property '%s' is diff\n", desc->name); + + char item_name[64]; + char item_value[64]; + + switch (desc->type) { + case (CSI_CAMERA_PROPERTY_TYPE_INTEGER): + snprintf(item_name, sizeof(item_name), "%-16s:%10s", desc->name, "INTEGER: "); + snprintf(item_value, sizeof(item_value), "(%d)", desc->value.int_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_BOOLEAN): + snprintf(item_name, sizeof(item_name), "%-16s:%10s", desc->name, "BOOLEAN: "); + snprintf(item_value, sizeof(item_value), "(%s)", desc->value.bool_value ? "true" : "false"); + break; + case (CSI_CAMERA_PROPERTY_TYPE_ENUM): + snprintf(item_name, sizeof(item_name), "%-16s:%10s", desc->name, "ENUM: "); + snprintf(item_value, sizeof(item_value), "(%s)", + camera_string_enum_name(desc->id, desc->value.enum_value)); + break; + case (CSI_CAMERA_PROPERTY_TYPE_STRING): + snprintf(item_name, sizeof(item_name), "%-16s:%10s", desc->name, "STRING: "); + snprintf(item_value, sizeof(item_value), "(%s)", desc->value.str_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_BITMASK): + snprintf(item_name, sizeof(item_name), "%-16s:%10s", desc->name, "BITMASK: "); + snprintf(item_value, sizeof(item_value), "(%08x)", desc->value.bitmask_value); + break; + default: + LOG_E("error type!\n"); + return -1; + } + + if (count >= rows) + break; + + add_len = strlen(item_name) + strlen(item_value) + 1; + if ((total_len + add_len) >= length) { + LOG_W("text buffer overflow\n"); + break; + } + + strcat(text, item_name); + strcat(text, item_value); + strcat(text, "\n"); + + total_len += add_len; + count++; + } + return count; +} + +static int apply_property_setting(void) +{ + LOG_W("do apply camera property settings\n"); + int diff_count = get_property_diff_count(); + int ret; + + // assemble property struct + csi_camera_properties_s properties; + csi_camera_property_s *property = malloc(diff_count * sizeof(csi_camera_property_s)); + properties.count = diff_count; + properties.property = property; + int i = 0; + + for (struct desc_list *node = desc_head; + node && (node != &desc_nil); node = node->next) { + //LOG_D("comparing '%s'\n", node->desc.name); + csi_camera_property_description_s desc; + desc.id = node->desc.id; + if (camera_query_property(cam_session, &desc) != 0) { + LOG_E("camera_query_property(%d), %s failed\n", + node->desc.id, node->desc.name); + continue; + } + if (memcmp(&(node->desc.value), &(desc.value), sizeof(desc.value)) == 0) { + //LOG_D("property '%s' is same\n", desc.name); + continue; + } + + LOG_D("property '%s' is different\n", desc.name); + property[i].id = node->desc.id; + property[i].type = node->desc.type; + memcpy(&(property[i].value), &(node->desc.value), sizeof(node->desc.value)); + + i++; + } + + ret = camera_set_property(cam_session, &properties); + if (ret == 0) + LOG_O("camera_set_property() ok!\n"); + else + LOG_E("camera_set_property() failed!\n"); + + free(property); + return ret; +} + +int dialog_camera_property_list(void) +{ + int ret_key = KEY_ESC; + int ret = 0; + + csi_camera_property_description_s *init_item = NULL; + csi_camera_property_description_s *selected_item; + init_camera_property_items(); + +again: + item_reset(); + if (fill_camera_property_items() != 0) { + LOG_E("fill_camera_property_items() failed\n"); + ret = KEY_ESC; + goto exit; + } + + int s_scroll = 0; + ret_key = dialog_menu("Camera Property Settings", + "Select the property, press Enter to change the property setting", + WIN_ROWS-2, WIN_COLS, init_item, -1, &s_scroll, NULL, 0); + LOG_D("dialog_menu() ret_key=%d, s_scroll=%d\n", ret_key, s_scroll); + + if (ret_key == KEY_ESC) { + ret = KEY_ESC; + goto exit; + } else if (ret_key == 1) { /* Apply button */ + char str_buf[1024]; + int count = get_diff_string(str_buf, sizeof(str_buf), 32); + ret_key = dialog_yesno("Apply camera with new propertis below?", str_buf, 6 + count, 50); + if (ret_key != 0) { // 0 is the button position of 'yes' + LOG_D("Apply camera property is canceled\n"); + goto again; + } + ret = apply_property_setting(); + LOG_I("apply_property_setting() %s\n", (ret==0) ? "OK" : "Failed"); + + snprintf(str_buf, sizeof(str_buf), + "Apply camera properties below %s:\n", + (ret == 0) ? "OK" : "Failed"); + + dialog_textbox_simple("Infomation", str_buf, 10, 40); + + goto exit; + } else if (ret_key == 2) { /* Cancel button */ + // do nothing + ret = KEY_ESC; + goto exit; + } + + /* else, Select button */ + selected_item = (csi_camera_property_description_s *)item_data(); + + if (item_tag() == '-') { + init_item = NULL; + goto again; + } + + csi_camera_property_description_s *item_property = item_data(); + if (item_property == NULL) { + LOG_W("No item selected\n"); + ret = KEY_ESC; + goto exit; + } else { + LOG_D("item '%s' selected\n", item_property->name); + } + + switch (item_property->type) { + case CSI_CAMERA_PROPERTY_TYPE_INTEGER: + ret_key = dialog_camera_property_integer(selected_item); + if (ret != KEY_ESC) { + LOG_D("set property->value.int_value=%d\n", + selected_item->value.int_value); + } + break; + case CSI_CAMERA_PROPERTY_TYPE_BOOLEAN: + ret_key = dialog_camera_property_boolean(selected_item); + if (ret != KEY_ESC) { + LOG_D("set property.value.bool_value='%s'\n", + (selected_item->value.bool_value) ? "true" : "false"); + } + break; + case CSI_CAMERA_PROPERTY_TYPE_ENUM: + ret_key = dialog_camera_property_enum(selected_item); + if (ret != KEY_ESC) { + LOG_D("set property.value.enum_value='%s'(%d)\n", + camera_string_enum_name(selected_item->id, + selected_item->value.enum_value), + selected_item->value.enum_value); + } + break; + case CSI_CAMERA_PROPERTY_TYPE_STRING: + break; + case CSI_CAMERA_PROPERTY_TYPE_BITMASK: + ret_key = dialog_camera_property_bitmask(selected_item); + if (ret != KEY_ESC) { + LOG_D("set property.value.enum_value=(%08x)\n", + selected_item->value.enum_value); + } + break;; + default: + LOG_E("Not supported property_hflip type\n"); + } + + init_item = item_property; + goto again; + +exit: + uninit_camera_property_items(); + return ret; +} + diff --git a/examples/camera/camera_demo3_srcs/dialog_camera_set_mode.c b/examples/camera/camera_demo3_srcs/dialog_camera_set_mode.c new file mode 100644 index 0000000..526d322 --- /dev/null +++ b/examples/camera/camera_demo3_srcs/dialog_camera_set_mode.c @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "dlg_set_mode" +#include +#include + +#include "param.h" +#include "app_dialogs.h" + +extern cams_t *cam_session; + +int dialog_camera_set_mode(void) +{ + int ret_key = KEY_ESC; + int ret; + + LOG_D("Test dialog_checklist\n"); + if (cam_session == NULL) { + LOG_E("cam_session is NULL\n"); + return KEY_ESC; + } + + if (cam_session->camera_handle == NULL) { + LOG_I("The camera has not been opened yet\n"); + dialog_textbox_simple("Infomation", "There's No camera opened yet", 10, 40); + return -1; + } + +again: + item_reset(); + + if (camera_get_modes(cam_session) != 0) { + LOG_E("camera_query_list() failed\n"); + return KEY_ESC; + } + + /* Prepare for checklist nodes */ + csi_camera_modes_s *modes = &(cam_session->camera_modes); + for (int i = 0; i < modes->count; i++) { + char mode_desc[256]; + item_make("mode-%02d: ", modes->modes[i].mode_id); + strncpy(mode_desc, modes->modes[i].description, sizeof(mode_desc)); + mode_desc[60] = '\0'; + item_add_str(mode_desc); + item_set_data(&modes->modes[i]); // store cam_info pointer + + if (cam_session->camera_mode_id == modes->modes[i].mode_id) { + item_set_tag('X'); + item_set_selected(1); + } else { + item_set_tag(' '); + item_set_selected(0); + } + } + + ret = dialog_checklist( + "Set Camera mode", /* Title */ + "Select the camera working mode." + "Press 'h' to get details", + CHECKLIST_HEIGTH_MIN + 10, + WIN_COLS - CHECKLIST_WIDTH_MIN - 16, + 8, + NULL, 0); + + int item_id; + int cam_id; + int mode_id; + int selected = item_activate_selected(); + LOG_D("ret=%d, selected=%d\n", ret, selected); + char str_buf[256]; + switch (ret) { + case 0: // item is selected + if (!selected) + break; + + /* Set camera mode */ + item_id = item_n(); // selected item: modes->modes[item_id] + cam_id = cam_session->camera_id; + mode_id = modes->modes[item_id].mode_id; + + ret = camera_set_mode(cam_session, mode_id); + if (ret != 0) { + LOG_E("camera_set_mode() failed\n"); + } + + //char str_buf[256]; + snprintf(str_buf, sizeof(str_buf), + "Set the below camera:\n" + "\t id=%d \n\t name='%s' \n\t device='%s' \n\t bus='%s'\n" + "to be working mode:%d %s", + cam_id, + cam_session->camera_infos.info[cam_id].camera_name, + cam_session->camera_infos.info[cam_id].device_name, + cam_session->camera_infos.info[cam_id].bus_info, + mode_id, + (ret == 0) ? "OK" : "Failed"); + + dialog_textbox_simple("Infomation", str_buf, 10, 40); + LOG_I("csi_camera_infos_s.info[%d] is opened %s\n", + cam_id, (ret == 0) ? "OK" : "Failed"); + + cam_session->camera_mode_id = mode_id; + + /* Show message bar */ + snprintf(str_buf, sizeof(str_buf), + "Set camera(%d) to be mode: %d %s\n", + cam_id, mode_id, (ret == 0) ? "OK" : "Failed"); + + message(str_buf, (ret == 0) ? 1 : 2); + break; + case 1: // need help + cam_id = item_n(); + //char str_buf[256]; + snprintf(str_buf, sizeof(str_buf), + "Camera info:\n" + "\t id=%d \n\t name='%s' \n\t device='%s' \n\t bus='%s'\n", + cam_id, + cam_session->camera_infos.info[cam_id].camera_name, + cam_session->camera_infos.info[cam_id].device_name, + cam_session->camera_infos.info[cam_id].bus_info); + + dialog_textbox_simple("Infomation", str_buf, 10, 40); + goto again; + case KEY_ESC: // specific KEY + break; + case -ERRDISPLAYTOOSMALL: // error + default: + LOG_W("Oops? why return ret=%d\n", ret); + break; + } + + LOG_D("dialog_checklist() ret=%d\n", ret); + return 0; +} + diff --git a/examples/camera/camera_demo3_srcs/dialog_channel_list.c b/examples/camera/camera_demo3_srcs/dialog_channel_list.c new file mode 100644 index 0000000..798591f --- /dev/null +++ b/examples/camera/camera_demo3_srcs/dialog_channel_list.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "dlg_chn_list" +#include + +#include + +#include +#include +#include "app_dialogs.h" + +extern cams_t *cam_session; + +int dialog_channel_list(void) +{ + int ret_key = KEY_ESC; + int ret; + + if (cam_session == NULL) { + LOG_E("cam_session is NULL\n"); + return KEY_ESC; + } + + /* Get camera list */ + if (camera_channel_query_list(cam_session) != 0) { + LOG_E("camera_get_list() failed\n"); + return KEY_ESC; + } + + char title[64]; + snprintf(title, sizeof(title), "Camera[%d] Channels List", cam_session->camera_id); + + char text[256*CSI_CAMERA_CHANNEL_MAX_COUNT] = ""; + char item[256]; + char temp[128]; + + int valid_count = 0; + for (int i = CSI_CAMERA_CHANNEL_0; i < CSI_CAMERA_CHANNEL_MAX_COUNT; i++) { + csi_camera_channel_cfg_s *chn_cfg = &(cam_session->chn_cfg[i]); + if (chn_cfg->status == CSI_CAMERA_CHANNEL_INVALID) + continue; + + if (valid_count > 0) + strcat(text, "\n"); + + snprintf(item, sizeof(item), "Channel[%d] info:\n", chn_cfg->chn_id); + strcat(text, item); + + snprintf(item, sizeof(item), "\t Status : %s\n", + camera_string_chn_status(chn_cfg->status)); + strcat(text, item); + + snprintf(item, sizeof(item), "\t Capture: %s\n", + camera_string_chn_capture_types(chn_cfg->capture_type, temp)); + strcat(text, item); + + snprintf(item, sizeof(item), "\t Frame : count=%d, type=%s\n", + chn_cfg->frm_cnt, camera_string_img_type(chn_cfg->img_type)); + strcat(text, item); + + snprintf(item, sizeof(item), "\t Image : width=%d, height=%d, pix_fmt=%s\n", + chn_cfg->img_fmt.width, chn_cfg->img_fmt.height, + camera_string_pixel_format(chn_cfg->img_fmt.pix_fmt)); + strcat(text, item); + + snprintf(item, sizeof(item), "\t Meta : %s\n", + camera_string_chn_meta_fields(chn_cfg->meta_fields, temp)); + strcat(text, item); + + valid_count++; + } + + int height = MIN((valid_count * 7 + 4), (WIN_ROWS - 6)); + return dialog_textbox_simple(title, text, height, 80); +} + diff --git a/examples/camera/camera_demo3_srcs/dialog_channel_open.c b/examples/camera/camera_demo3_srcs/dialog_channel_open.c new file mode 100644 index 0000000..111bb60 --- /dev/null +++ b/examples/camera/camera_demo3_srcs/dialog_channel_open.c @@ -0,0 +1,573 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "dlg_prop_list" +#include +#include +#include +#include +#include + +#include "param.h" +#include "app_dialogs.h" + +extern cams_t *cam_session; + +static int fill_channel_config_items(csi_camera_channel_cfg_s *channel) +{ + char str_buf[128]; + const char item_format[] = "%-14s"; + + item_make(item_format, "capture_type"); + item_add_str(": (%s) --->", camera_string_chn_capture_types(channel->capture_type, str_buf)); + item_set_data(channel); + + item_make(item_format, "Capture fields"); + item_add_str(": (%s) --->", camera_string_chn_meta_fields(channel->meta_fields, str_buf)); + item_set_data(channel); + + item_make(item_format, "Buffer count"); + item_add_str(": (%d) --->", channel->frm_cnt); + item_set_data(channel); + + item_make(item_format, "Image width"); + item_add_str(": (%d) --->", channel->img_fmt.width); + item_set_data(channel); + + item_make(item_format, "Image height"); + item_add_str(": (%d) --->", channel->img_fmt.height); + item_set_data(channel); + + item_make(item_format, "Pixel format"); + item_add_str(": (%s) --->", camera_string_pixel_format(channel->img_fmt.pix_fmt)); + item_set_data(channel); + + item_make(item_format, "Buffer type"); + item_add_str(": (%s) --->", camera_string_img_type(channel->img_type)); + item_set_data(channel); + return 0; +} + +typedef enum channel_param { + CHANNEL_PARAM_CAPTURE_TYPE = 0, + CHANNEL_PARAM_META_FIELDS, + CHANNEL_PARAM_FRM_CNT, + CHANNEL_PARAM_IMG_WIDTH, + CHANNEL_PARAM_IMG_HEIGIT, + CHANNEL_PARAM_IMG_PIX_FMT, + CHANNEL_PARAM_IMG_TYPE, +} channel_param_e; + +extern char dialog_input_result[MAX_LEN + 1]; +static int channel_config_integer(csi_camera_channel_cfg_s *channel, channel_param_e param) +{ + int ret_key = KEY_ESC; + char title[64]; + char prompt[64] = "Please enter the appropriate number"; + char str_value[32]; + + if (channel == NULL) { + LOG_W("channel is NULL\n"); + return KEY_ESC; + } + +again: + switch (param) { + case CHANNEL_PARAM_FRM_CNT: + snprintf(title, sizeof(title), "Config Channel[%d] %s", + channel->chn_id, "Buffer Count"); + snprintf(str_value, sizeof(str_value), "%d", channel->frm_cnt); + break; + case CHANNEL_PARAM_IMG_WIDTH: + snprintf(title, sizeof(title), "Config Channel[%d] %s", + channel->chn_id, "Image Width"); + snprintf(str_value, sizeof(str_value), "%d", channel->img_fmt.width); + break; + case CHANNEL_PARAM_IMG_HEIGIT: + snprintf(title, sizeof(title), "Config Channel[%d] %s", + channel->chn_id, "Image Height"); + snprintf(str_value, sizeof(str_value), "%d", channel->img_fmt.height); + break; + default: + break; + } + + curs_set(1); + ret_key = dialog_inputbox(title, prompt, 9, 40, str_value); + curs_set(0); + + if (ret_key == KEY_ESC || ret_key == 1) + return KEY_ESC; + + /* can't be empty */ + if (strlen(dialog_input_result) == 0) { + goto again; + } + + /* value check, reset if invalid */ + int value = atoi(dialog_input_result); + if (value <= 0){ + goto again; + } + + /* accept value, set into property */ + switch (param) { + case CHANNEL_PARAM_FRM_CNT: + if (value > 100){ // filt unreasonable out + goto again; + } + channel->frm_cnt = value; + break; + case CHANNEL_PARAM_IMG_WIDTH: + if (value > (7680*2)){ // filt unreasonable out + goto again; + } + channel->img_fmt.width = value; + break; + case CHANNEL_PARAM_IMG_HEIGIT: + if (value > (4320*2)){ // filt unreasonable out + goto again; + } + channel->img_fmt.height = value; + break; + default: + break; + } + + return 0; +} + +static int channel_config_enum(csi_camera_channel_cfg_s *channel, channel_param_e param) +{ + int ret_key = KEY_ESC; + char title[64]; + char prompt[64] = "Please select the appropriate enum item"; + char str_value[32]; + const camera_spec_enums_s *enums_array; + + if (channel == NULL) { + LOG_W("channel is NULL\n"); + return KEY_ESC; + } + + item_reset(); + switch (param) { + case CHANNEL_PARAM_IMG_PIX_FMT: + snprintf(title, sizeof(title), "Config Channel[%d] %s", + channel->chn_id, "Image Pixel Format"); + + enums_array = camera_spec_get_enum_array(CAMERA_SPEC_ENUM_CHANNEL_PIX_FMT); + if (enums_array == NULL) { + LOG_E("No enum array for CAMERA_SPEC_ENUM_CHN_PIX_FMT(%d)\n", + CAMERA_SPEC_ENUM_CHANNEL_PIX_FMT); + return KEY_ESC; + } + + for (int i = 0; i < enums_array->count; i++) { + item_make("%d-%s", enums_array->enums[i], + camera_string_pixel_format(enums_array->enums[i])); + //item_add_str(""); + if (channel->img_fmt.pix_fmt == enums_array->enums[i]) { + item_set_selected(1); + item_set_tag('X'); + } else { + item_set_selected(0); + item_set_tag(' '); + } + } + + break; + case CHANNEL_PARAM_IMG_TYPE: + snprintf(title, sizeof(title), "Config Channel[%d] %s", + channel->chn_id, "Image Buffer Type"); + + enums_array = camera_spec_get_enum_array(CAMERA_SPEC_ENUM_CHANNEL_IMG_TYPE); + if (enums_array == NULL) { + LOG_E("No enum array for CAMERA_SPEC_ENUM_CHN_IMG_TYPE(%08x)\n", + CAMERA_SPEC_ENUM_CHANNEL_IMG_TYPE); + return KEY_ESC; + } + + for (int i = 0; i < enums_array->count; i++) { + item_make("%d-%s", i, camera_string_img_type(i)); + //item_add_str(""); + if (channel->img_type == enums_array->enums[i]) { + item_set_selected(1); + item_set_tag('X'); + } else { + item_set_selected(0); + item_set_tag(' '); + } + } + + break; + default: + return KEY_ESC; + } + + int list_height = MIN(enums_array->count + 1, 8); + ret_key = dialog_checklist( + title, prompt, + CHECKLIST_HEIGTH_MIN + list_height + 2, + WIN_COLS - CHECKLIST_WIDTH_MIN - 32, + list_height, + NULL, 0); + LOG_D("dialog_checklist() ret_key=%d\n", ret_key); + + if (ret_key == KEY_ESC) { + return KEY_ESC; + } else if (ret_key == 0) { /* select */ + if (!item_is_selected()) + return KEY_ESC; + + int enum_id = enums_array->enums[item_n()]; + + switch(param) { + case CHANNEL_PARAM_IMG_PIX_FMT: + LOG_D("select item: pos=%d, enum=%d:%s\n", + item_n(), enum_id, camera_string_pixel_format(enum_id)); + channel->img_fmt.pix_fmt = enum_id; + break; + case CHANNEL_PARAM_IMG_TYPE: + LOG_D("select item: pos=%d, enum=%d:%s\n", + item_n(), enum_id, camera_string_img_type(enum_id)); + channel->img_type = enum_id; + break; + default: + return KEY_ESC; + } + return ret_key; + } else if (ret_key == 1) { /* help */ + LOG_W("Help does not support yet\n"); + return ret_key; + } else { + LOG_E("Unknown return value: %d\n", ret_key); + return KEY_ESC; + } + + return 0; +} + +static int channel_config_bitmsk_capture_type(csi_camera_channel_cfg_s *channel) +{ + int ret_key = KEY_ESC; + if (channel == NULL) { + LOG_W("channel is NULL\n"); + return KEY_ESC; + } + + char title[64]; + char prompt[64] = "Please select the appropriate enum item"; + int item_pos = 0; + snprintf(title, sizeof(title), "Config Channel[%d] Capture Types", channel->chn_id); + const camera_spec_bitmasks_t *bitmasks_array = + camera_spec_get_bitmask_array(CAMERA_SPEC_BITMAKS_CHANNEL_CAPTURE_TYPE); + if (bitmasks_array == NULL) + return KEY_ESC; + +again: + item_reset(); + for (int i = 0; i < bitmasks_array->count; i++) { + item_make("bit-%02d: %s", bitmasks_array->bitmask[i], + camera_string_capture_type(bitmasks_array->bitmask[i], true)); + //item_add_str(""); + + if (bitmasks_array->bitmask[i] & channel->capture_type) { + item_set_selected(1); + item_set_tag('*'); + } + else { + item_set_selected(0); + item_set_tag(' '); + } + } + + int list_height = MIN(bitmasks_array->count + 1, 8); + ret_key = dialog_checkbox( + title, prompt, + CHECKLIST_HEIGTH_MIN + list_height + 2, + WIN_COLS - CHECKLIST_WIDTH_MIN - 32, + list_height, item_pos); + + LOG_D("dialog_checkbox() ret_key=%d\n", ret_key); + if (ret_key == KEY_ESC) { + return KEY_ESC; + } else if (ret_key == 0) {/* select */ + item_pos = item_n(); + int bitmask_value = bitmasks_array->bitmask[item_n()]; + if (item_is_selected()) { + channel->capture_type |= bitmask_value; + } else { + channel->capture_type &= ~bitmask_value; + } + + int enum_id = bitmasks_array->bitmask[item_n()]; + LOG_D("%s item: pos=%d, enum=%d-%s, bitmask_value=%08x\n", + item_is_selected() ? "Select" : "Unselect", + item_n(), enum_id, + camera_string_capture_type(enum_id, true), + channel->capture_type); + goto again; + return ret_key; + } else if (ret_key == 1) {/* help */ + LOG_W("Help does not support yet\n"); + return ret_key; + } else { + LOG_E("Unknown return value: %d\n", ret_key); + return KEY_ESC; + } +} + +static int channel_config_bitmsk_meta_type(csi_camera_channel_cfg_s *channel) +{ + int ret_key = KEY_ESC; + if (channel == NULL) { + LOG_W("channel is NULL\n"); + return KEY_ESC; + } + + char title[64]; + char prompt[64] = "Please select the appropriate enum item"; + int item_pos = 0; + snprintf(title, sizeof(title), "Config Channel[%d] Meta Types", channel->chn_id); + const camera_spec_bitmasks_t *bitmasks_array = + camera_spec_get_bitmask_array(CAMERA_SPEC_BITMAKS_CHANNEL_META_TYPE); + if (bitmasks_array == NULL) + return KEY_ESC; + +again: + item_reset(); + for (int i = 0; i < bitmasks_array->count; i++) { + item_make("bit-%02d: %s", bitmasks_array->bitmask[i], + camera_string_meta_field(bitmasks_array->bitmask[i], true)); + //item_add_str(""); + + if (bitmasks_array->bitmask[i] & channel->meta_fields) { + item_set_selected(1); + item_set_tag('*'); + } + else { + item_set_selected(0); + item_set_tag(' '); + } + } + + int list_height = MIN(bitmasks_array->count + 1, 8); + ret_key = dialog_checkbox( + title, prompt, + CHECKLIST_HEIGTH_MIN + list_height + 2, + WIN_COLS - CHECKLIST_WIDTH_MIN - 32, + list_height, item_pos); + + LOG_D("dialog_checkbox() ret_key=%d\n", ret_key); + if (ret_key == KEY_ESC) { + return KEY_ESC; + } else if (ret_key == 0) {/* select */ + item_pos = item_n(); + int bitmask_value = bitmasks_array->bitmask[item_n()]; + if (item_is_selected()) { + channel->meta_fields |= bitmask_value; + } else { + channel->meta_fields &= ~bitmask_value; + } + + int enum_id = bitmasks_array->bitmask[item_n()]; + LOG_D("%s item: pos=%d, enum=%d-%s, bitmask_value=%08x\n", + item_is_selected() ? "Select" : "Unselect", + item_n(), enum_id, + camera_string_meta_field(enum_id, true), + channel->meta_fields); + goto again; + return ret_key; + } else if (ret_key == 1) {/* help */ + LOG_W("Help does not support yet\n"); + return ret_key; + } else { + LOG_E("Unknown return value: %d\n", ret_key); + return KEY_ESC; + } +} + +/* return int: property rows */ +static int get_diff_string(char *text, csi_camera_channel_cfg_s *channel) +{ + int count = 0; + int total_len = 0; + int add_len; + text[0] = '\0'; + + strcat(text,"-------------------------------------------\n"); + + char item_name[64]; + char item_value[256]; + char str_buf1[128]; + char str_buf2[128]; + + if(cam_session->chn_cfg_tmp.capture_type != channel->capture_type) { + snprintf(item_name, sizeof(item_name), "%-14s", "capture type"); + snprintf(item_value, sizeof(item_value), ": (%s) => (%s)\n", + camera_string_chn_capture_types(channel->capture_type, str_buf1), + camera_string_chn_capture_types(cam_session->chn_cfg_tmp.capture_type, str_buf2)); + strcat(text, item_name); + strcat(text, item_value); + count++; + } + + if(cam_session->chn_cfg_tmp.meta_fields != channel->meta_fields) { + snprintf(item_name, sizeof(item_name), "%-14s", "Meta fields"); + snprintf(item_value, sizeof(item_value), ": (%s) =>\n\t\t (%s)\n", + camera_string_chn_meta_fields(channel->meta_fields, str_buf1), + camera_string_chn_meta_fields(cam_session->chn_cfg_tmp.meta_fields, str_buf2)); + strcat(text, item_name); + strcat(text, item_value); + count+=2; + } + + if(cam_session->chn_cfg_tmp.frm_cnt != channel->frm_cnt) { + snprintf(item_name, sizeof(item_name), "%-14s", "buffer count"); + snprintf(item_value, sizeof(item_value), ": (%d) => (%d)\n", + channel->frm_cnt, cam_session->chn_cfg_tmp.frm_cnt); + strcat(text, item_name); + strcat(text, item_value); + count++; + } + + if(cam_session->chn_cfg_tmp.img_fmt.width != channel->img_fmt.width) { + snprintf(item_name, sizeof(item_name), "%-14s", "Image width"); + snprintf(item_value, sizeof(item_value), ": (%d) => (%d)\n", + channel->img_fmt.width, cam_session->chn_cfg_tmp.img_fmt.width); + strcat(text, item_name); + strcat(text, item_value); + count++; + } + + if(cam_session->chn_cfg_tmp.img_fmt.height != channel->img_fmt.height) { + snprintf(item_name, sizeof(item_name), "%-14s", "Image height"); + snprintf(item_value, sizeof(item_value), ": (%d) => (%d)\n", + channel->img_fmt.height, cam_session->chn_cfg_tmp.img_fmt.height); + strcat(text, item_name); + strcat(text, item_value); + count++; + } + + if(cam_session->chn_cfg_tmp.img_fmt.pix_fmt != channel->img_fmt.pix_fmt) { + snprintf(item_name, sizeof(item_name), "%-14s", "Pixel format"); + snprintf(item_value, sizeof(item_value), ": (%s) => (%s)\n", + camera_string_pixel_format(channel->img_fmt.pix_fmt), + camera_string_pixel_format(cam_session->chn_cfg_tmp.img_fmt.pix_fmt)); + strcat(text, item_name); + strcat(text, item_value); + count++; + } + + if(cam_session->chn_cfg_tmp.img_type != channel->img_type) { + snprintf(item_name, sizeof(item_name), "%-14s", "Buffer type"); + snprintf(item_value, sizeof(item_value), ": (%s) => (%s)\n", + camera_string_img_type(channel->img_type), + camera_string_img_type(cam_session->chn_cfg_tmp.img_type)); + strcat(text, item_name); + strcat(text, item_value); + count++; + } + + return count; +} + +static int channel_open_internal(char *feedback) +{ + int ret = camera_channel_open(cam_session, &cam_session->chn_cfg_tmp); + if (ret != 0) { + LOG_E("camera_channel_open() failed, ret=%d\n", ret); + } + + if (feedback) { + if (ret) + sprintf(feedback, "camera_channel_open() failed, ret=%d\n", ret); + else + sprintf(feedback, "camera_channel_open() OK\n"); + } + return ret; +} + +int dialog_channel_open(csi_camera_channel_cfg_s *channel) +{ + int ret_key = KEY_ESC; + int ret = 0; + char str_buf[1024]; + int item_pos = 0; + csi_camera_channel_cfg_s *chn_tmp = &(cam_session->chn_cfg_tmp); + memcpy(chn_tmp, channel, sizeof(*chn_tmp)); + +again: + item_reset(); + if (fill_channel_config_items(chn_tmp) != 0) { + LOG_E("fill_channel_config_items() failed\n"); + ret = KEY_ESC; + goto exit; + } + + char *button_names[] = {"Config", " Open ", "Cancel"}; + + int s_scroll = 0; + snprintf(str_buf, sizeof(str_buf), "Config Camera[%d] - Channel[%d]", + cam_session->camera_id, chn_tmp->chn_id); + ret_key = dialog_menu(str_buf, "Select item, press Enter to config", + WIN_ROWS-2, WIN_COLS, NULL, item_pos, &s_scroll, button_names, 3); + item_pos = item_activate_selected_pos(); + LOG_D("dialog_menu() ret_key=%d, item_pos=%d\n", ret_key, item_pos); + + if (ret_key == 0) { /* Select button */ + if (item_pos == CHANNEL_PARAM_FRM_CNT || + item_pos == CHANNEL_PARAM_IMG_WIDTH || + item_pos == CHANNEL_PARAM_IMG_HEIGIT) { + channel_config_integer(chn_tmp, item_pos); + } else if (item_pos == CHANNEL_PARAM_IMG_PIX_FMT || + item_pos == CHANNEL_PARAM_IMG_TYPE) { + channel_config_enum(chn_tmp, item_pos); + } else if (item_pos == CHANNEL_PARAM_CAPTURE_TYPE) { + channel_config_bitmsk_capture_type(chn_tmp); + } else if (item_pos == CHANNEL_PARAM_META_FIELDS){ + channel_config_bitmsk_meta_type(chn_tmp); + } + goto again; + } else if (ret_key == 1) { /* Open button */ + ret = channel_open_internal(str_buf); + LOG_I("%s\n", str_buf); + dialog_textbox_simple("Infomation", str_buf, 10, 40); + + if (ret != 0) { + goto again; + } + + goto exit; + } else if (ret_key == 2) { /* Cancel button */ + // do nothing + ret = KEY_ESC; + goto exit; + } else { + ret = KEY_ESC; + goto exit; + } + + /* else, Select button */ + goto again; + +exit: + return ret; +} + diff --git a/examples/camera/camera_demo3_srcs/dialog_channel_run.c b/examples/camera/camera_demo3_srcs/dialog_channel_run.c new file mode 100644 index 0000000..82bb587 --- /dev/null +++ b/examples/camera/camera_demo3_srcs/dialog_channel_run.c @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "dlg_chn_run" +#include +#include +#include + +#include "param.h" +#include "platform_action.h" +#include "app_dialogs.h" + +extern cams_t *cam_session; + +#define _CHECK_SESSION_RETURN() \ + do { \ + if (session == NULL) { \ + LOG_E("session is NULL\n"); \ + return -EPERM; \ + } \ + } while (0) + +static int do_camera_event_action(cams_t *session, csi_camera_event_s *event) +{ + _CHECK_SESSION_RETURN(); + int ret = 0; + + for (uint32_t i = 0; i < CSI_CAMERA_EVENT_MAX_COUNT; i++) { + camera_event_action_t *event_action = + &(session->camera_event_action[i].camera); + if (event_action->event != event->id) + continue; + + LOG_D("Found camera action:%s\n", + camera_string_camera_action(event_action->action)); + + if(event_action->action == CAMERA_ACTION_NONE) { + LOG_D("CHANNEL CAMERA_ACTION_NONE!\n"); + continue; + } + if(event_action->action > (CAMERA_ACTION_LOG_PRINT|CAMERA_ACTION_CAPTURE_FRAME)) { + LOG_E("Unkonw camera event action: %d\n", event_action->action); + ret = -1; + } + for(uint32_t j = 0; j < CAMERA_ACTION_MAX_COUNT; j++) { + switch (event_action->action & 1 << j) { + case CAMERA_ACTION_LOG_PRINT: + LOG_D("CAMERA CAMERA_ACTION_LOG_PRINT\n"); + if((event_action->event & 1 << i) == CSI_CAMERA_EVENT_WARNING) { + LOG_D("LOG_PRINT CSI_CAMERA_EVENT_WARNING\n"); + } else if((event_action->event & 1 << i) == CSI_CAMERA_EVENT_ERROR) { + LOG_D("LOG PRINT CSI_CAMERA_EVENT_ERROR\n"); + } else if((event_action->event & 1 << i) == CSI_CAMERA_EVENT_SENSOR_FIRST_IMAGE_ARRIVE) { + LOG_D("LOG PRINT CSI_CAMERA_EVENT_SENSOR_FIRST_IMAGE_ARRIVE\n"); + } else if((event_action->event & 1 << i) == CSI_CAMERA_EVENT_ISP_3A_ADJUST_READY) { + LOG_D("LOG PRINT CSI_CAMERA_EVENT_ISP_3A_ADJUST_READY\n"); + } else { + ret = -1; + LOG_E("CAMERA CAMERA_ACTION_LOG_ERROR\n"); + } + break; + case CAMERA_ACTION_CAPTURE_FRAME: + LOG_D("CAMERA CAMERA_ACTION_CAPTURE_FRAME\n"); + if((event_action->event & 1 << i) == CSI_CAMERA_EVENT_WARNING) { + LOG_D("CAPTURE FRAME CSI_CAMERA_EVENT_WARNING\n"); + } else if((event_action->event & 1 << i) == CSI_CAMERA_EVENT_ERROR) { + LOG_D("CAPTURE FRAME CSI_CAMERA_EVENT_ERROR\n"); + } else if((event_action->event & 1 << i) == CSI_CAMERA_EVENT_SENSOR_FIRST_IMAGE_ARRIVE) { + LOG_D("CAPTURE FRAME CSI_CAMERA_EVENT_SENSOR_FIRST_IMAGE_ARRIVE\n"); + } else if((event_action->event & 1 << i) == CSI_CAMERA_EVENT_ISP_3A_ADJUST_READY) { + LOG_D("CAPTURE FRAME CSI_CAMERA_EVENT_ISP_3A_ADJUST_READY\n"); + } else { + ret = -1; + LOG_D("CAMERA CAMERA_ACTION_CAPTURE_FRAME_ERROR\n"); + } + break; + default: + break; + + } + } + } + return 0; +} + +static int do_channel_event_action(cams_t *session, csi_camera_event_s *event) +{ + _CHECK_SESSION_RETURN(); + csi_frame_s frame; + int ret = 0; + + camera_event_action_union_t *event_action_u = + session->channel_event_action[event->type - CSI_CAMERA_EVENT_TYPE_CHANNEL0]; + + LOG_D("do_channel_event_action()!\n"); + for (uint32_t i = 0; i < CSI_CAMERA_CHANNEL_EVENT_MAX_COUNT; i++) { + camera_channel_event_action_t *event_action = &(event_action_u[i].channel); + if (event_action->event != event->id) + continue; + if (event_action->action == 0) { + printf("event_action->action == 0\n"); + break; + } + + LOG_D("Found channel action:%s\n", + camera_string_channel_action(event_action->action)); + + if(event_action->action == CAMERA_CHANNEL_ACTION_NONE) { + LOG_D("CHANNEL CAMERA_ACTION_NONE!\n"); + continue; + } + if(event_action->action > (CAMERA_CHANNEL_ACTION_LOG_PRINT|CAMERA_CHANNEL_ACTION_CAPTURE_FRAME|CAMERA_CHANNEL_ACTION_DISPLAY_FRAME)) { + ret = -1; + LOG_E("Unkonw channel event action: %d\n", event_action->action); + } + + for(uint32_t j = 0; j < CAMERA_CHANNEL_ACTION_MAX_COUNT; j++) { + switch (event_action->action & 1 << j) { + case CAMERA_CHANNEL_ACTION_LOG_PRINT: + LOG_D("CHANNEL CAMERA_ACTION_LOG_PRINT\n"); + if((event_action->event & 1 << i) == CSI_CAMERA_CHANNEL_EVENT_FRAME_READY) { + LOG_D("LOG_PRINT CSI_CAMERA_CHANNEL_EVENT_FRAME_READY\n"); + } else if((event_action->event & 1 << i) == CSI_CAMERA_CHANNEL_EVENT_FRAME_PUT) { + LOG_D("LOG_PRINT CSI_CAMERA_CHANNEL_EVENT_FRAME_PUT\n"); + } else if((event_action->event & 1 << i) == CSI_CAMERA_CHANNEL_EVENT_OVERFLOW) { + LOG_D("LOG_PRINT CSI_CAMERA_CHANNEL_EVENT_OVERFLOW\n"); + } else { + ret = -1; + LOG_E("CHANNEL CAMERA_ACTION_LOG_ERROR\n"); + } + break; + case CAMERA_CHANNEL_ACTION_CAPTURE_FRAME: + LOG_D("CHANNEL CAMERA_ACTION_CAPTURE_FRAME\n"); + + /* To show the image can do below steps: + * 1. save capture img to be /tmp/capture.jpg + * 2. img2txt -W 80 -H 60 -f ansi /tmp/capture.jpg > /tmp/capture.txt + * 3. malloc a img_buf and store /tmp/capture.txt into it + * 4. printf(img_buf) to show on log console screen + */ + if((event_action->event & 1 << i) == CSI_CAMERA_CHANNEL_EVENT_FRAME_READY) { + int timeout = -1; + int read_frame_count = csi_camera_get_frame_count(NULL, event->type - CSI_CAMERA_EVENT_TYPE_CHANNEL0); + LOG_D("read_frame_count = %d\n",read_frame_count); + for (int i = 0; i < read_frame_count; i++) { + csi_camera_get_frame(NULL,event->type - CSI_CAMERA_EVENT_TYPE_CHANNEL0, &frame, timeout); + if(frame.img.usr_addr[0] != NULL) { + camera_action_image_save(&frame); + csi_camera_put_frame(&frame); + } + } + } else if((event_action->event & 1 << i) == CSI_CAMERA_CHANNEL_EVENT_FRAME_PUT) { + LOG_D("CSI_CAMERA_CHANNEL_EVENT_FRAME_PUT\n"); + } else if((event_action->event & 1 << i) == CSI_CAMERA_CHANNEL_EVENT_OVERFLOW) { + LOG_D("CSI_CAMERA_CHANNEL_EVENT_OVERFLOW\n"); + } else { + ret = -1; + LOG_E("CHANNEL CAMERA_ACTION_CAPTURE_FRAME_ERROR\n"); + } + break; + case CAMERA_CHANNEL_ACTION_DISPLAY_FRAME: + LOG_D("CHANNEL CAMERA_ACTION_DISPLAY_FRAME"); + if((event_action->event & 1 << i) == CSI_CAMERA_CHANNEL_EVENT_FRAME_READY) { + int timeout = -1; + int read_frame_count = csi_camera_get_frame_count(NULL, event->type - CSI_CAMERA_EVENT_TYPE_CHANNEL0); + LOG_D("read_frame_count = %d\n",read_frame_count); + //for (int i = 0; i < read_frame_count; i++) { + csi_camera_get_frame(NULL,event->type - CSI_CAMERA_EVENT_TYPE_CHANNEL0, &frame, timeout); + if(frame.img.usr_addr[0] != NULL) { + camera_action_image_display(&frame); + LOG_D("frame->usr_addr = %p\n",frame.img.usr_addr[0]); + csi_camera_put_frame(&frame); + } + //} + } else if((event_action->event & 1 << i) == CSI_CAMERA_CHANNEL_EVENT_FRAME_PUT) { + LOG_D("CSI_CAMERA_CHANNEL_EVENT_FRAME_PUT\n"); + } else if((event_action->event & 1 << i) == CSI_CAMERA_CHANNEL_EVENT_OVERFLOW) { + LOG_D("CSI_CAMERA_CHANNEL_EVENT_OVERFLOW\n"); + } else { + ret = -1; + LOG_E("CHANNEL CAMERA_ACTION_DISPLAY_FRAME_ERROR\n"); + } + break; + default: + break; + } + } + } + return ret; +} + +/* action: 0:close, 1:open, 2:start/stop */ +int dialog_channel_run(void) +{ + int ret_key = KEY_ESC; + int ret; + char str_buf[256]; + csi_camera_channel_cfg_s *selected_chn = NULL; + + if (cam_session == NULL) { + LOG_E("cam_session is NULL\n"); + return KEY_ESC; + } + + if (cam_session->camera_handle == NULL) { + csi_camera_info_s *info = + &(cam_session->camera_infos.info[cam_session->camera_id]); + snprintf(str_buf, sizeof(str_buf), + "Please open camera first\n"); + dialog_textbox_simple("Error", str_buf, 6, 30); + return KEY_ESC; + } + +again: + item_reset(); + + if (camera_channel_query_list(cam_session) != 0) { + LOG_E("camera_channel_query_list() failed\n"); + snprintf(str_buf, sizeof(str_buf), + "Failed to query channel list from Camera failed!\n"); + dialog_textbox_simple("Error", str_buf, 10, 40); + return KEY_ESC; + } + + /* Prepare for checklist nodes */ + for (int i = CSI_CAMERA_CHANNEL_0; i < CSI_CAMERA_CHANNEL_MAX_COUNT; i++) { + csi_camera_channel_cfg_s *channel = &(cam_session->chn_cfg[i]); + if (channel->status == CSI_CAMERA_CHANNEL_INVALID) { + continue; + } + + item_make("Channel[%d]", channel->chn_id); + item_add_str(" (%s)", camera_string_chn_status(channel->status)); + item_set_data(channel); + + if (channel->status == CSI_CAMERA_CHANNEL_RUNNING || channel->status == CSI_CAMERA_CHANNEL_OPENED) { + item_set_tag('X'); + item_set_selected(1); + } + } + + char *button_names[] = {"Start", "Stop", "Cancel"}; + ret = dialog_checklist( + "Start/Stop Channel", /* Title */ + "Select the channel to start or stop", + CHECKLIST_HEIGTH_MIN + 10, + WIN_COLS - CHECKLIST_WIDTH_MIN - 16, 8, + button_names, ARRAY_SIZE(button_names)); + + int selected = item_activate_selected(); + LOG_D("ret=%d, selected=%s\n", ret, selected ? "true" : "false"); + switch (ret) { + case 0: // button is: start + if (!selected || item_data() == NULL) + break; + + /* Show operation result */ + selected_chn = (csi_camera_channel_cfg_s *)item_data(); + if (selected_chn->status != CSI_CAMERA_CHANNEL_OPENED) { + /* Show message bar */ + snprintf(str_buf, sizeof(str_buf), + "The Camera[%d] channel[%d] can't start, current status is '%s'\n", + cam_session->camera_id, selected_chn->chn_id, + camera_string_chn_status(selected_chn->status)); + message(str_buf, 2); + + goto again; + } + camera_register_event_action(cam_session, + do_camera_event_action, + do_channel_event_action); + + LOG_D("camera_channel_start selected_chn->chn_id = %x\n",selected_chn->chn_id); + ret = camera_channel_start(cam_session, selected_chn->chn_id); + + /* Show message bar */ + snprintf(str_buf, sizeof(str_buf), + "Start Camera[%d] channel[%d] %s\n", + cam_session->camera_id, selected_chn->chn_id, + (ret == 0) ? "OK" : "failed"); + message(str_buf, (ret == 0) ? 1 : 2); + goto again; + break; + case 1: // button is: stop + if (!selected || item_data() == NULL) + break; + + /* Show operation result */ + selected_chn = (csi_camera_channel_cfg_s *)item_data(); + if (selected_chn->status != CSI_CAMERA_CHANNEL_RUNNING) { + /* Show message bar */ + snprintf(str_buf, sizeof(str_buf), + "The Camera[%d] channel[%d] can't stop, current status is '%s'\n", + cam_session->camera_id, selected_chn->chn_id, + camera_string_chn_status(selected_chn->status)); + message(str_buf, 2); + + goto again; + } + + ret = camera_channel_stop(cam_session, selected_chn->chn_id); + /* Show message bar */ + snprintf(str_buf, sizeof(str_buf), + "Stop Camera[%d] channel[%d] %s\n", + cam_session->camera_id, selected_chn->chn_id, + (ret == 0) ? "OK" : "failed"); + message(str_buf, (ret == 0) ? 1 : 2); + goto again; + break; + case 2: // button is: cancel + case KEY_ESC: // specific KEY + ret = KEY_ESC; + break; + default: + LOG_W("Oops? why return ret=%d\n", ret); + ret = KEY_ESC; + break; + } + + LOG_D("ret=%d\n", ret); + return ret; +} + diff --git a/examples/camera/camera_demo3_srcs/dialog_channel_select.c b/examples/camera/camera_demo3_srcs/dialog_channel_select.c new file mode 100644 index 0000000..16787bb --- /dev/null +++ b/examples/camera/camera_demo3_srcs/dialog_channel_select.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "dlg_chn_select" +#include +#include +#include + +#include "param.h" +#include "app_dialogs.h" + +extern cams_t *cam_session; + +/* action: 0:close, 1:open, 2:start/stop */ +int dialog_channel_select(csi_camera_channel_cfg_s **selected_chn, int action) +{ + int ret_key = KEY_ESC; + int ret; + char str_buf[256]; + *selected_chn = NULL; + + if (cam_session == NULL) { + LOG_E("cam_session is NULL\n"); + return KEY_ESC; + } + + if (cam_session->camera_handle == NULL) { + csi_camera_info_s *info = + &(cam_session->camera_infos.info[cam_session->camera_id]); + snprintf(str_buf, sizeof(str_buf), + "Please open camera first\n"); + dialog_textbox_simple("Error", str_buf, 6, 30); + return KEY_ESC; + } + +again: + item_reset(); + + if (camera_channel_query_list(cam_session) != 0) { + LOG_E("camera_channel_query_list() failed\n"); + snprintf(str_buf, sizeof(str_buf), + "Failed to query channel list from Camera failed!\n"); + dialog_textbox_simple("Error", str_buf, 10, 40); + return KEY_ESC; + } + /* Prepare for checklist nodes */ + for (int i = CSI_CAMERA_CHANNEL_0; i < CSI_CAMERA_CHANNEL_MAX_COUNT; i++) { + csi_camera_channel_cfg_s *chn_cfg = &(cam_session->chn_cfg[i]); + if (chn_cfg->status == CSI_CAMERA_CHANNEL_INVALID) { + continue; + } + + item_make("Channel[%d]", chn_cfg->chn_id); + item_add_str(" (%s) --->", camera_string_chn_status(chn_cfg->status)); + item_set_data(chn_cfg); + + if (chn_cfg->status == CSI_CAMERA_CHANNEL_OPENED || chn_cfg->status == CSI_CAMERA_CHANNEL_RUNNING) { + item_set_tag('X'); + item_set_selected(1); + } + } + + if (action == 0) { // action is close + char *button_names[] = {" Close ", " Help "}; + ret = dialog_checklist( + "Select Channel", /* Title */ + "Select the channel to close", + CHECKLIST_HEIGTH_MIN + 10, + WIN_COLS - CHECKLIST_WIDTH_MIN - 16, 8, + button_names, sizeof(button_names) / sizeof(button_names[0])); + } else if (action == 1) { // action is open + ret = dialog_checklist( + "Select Channel", /* Title */ + "Select the channel to config and open", + CHECKLIST_HEIGTH_MIN + 10, + WIN_COLS - CHECKLIST_WIDTH_MIN - 16, 8, + NULL, 0); + } else { // action is start/stop + ret = dialog_checklist( + "Select Channel", /* Title */ + "Select the channel to Start or Stop", + CHECKLIST_HEIGTH_MIN + 10, + WIN_COLS - CHECKLIST_WIDTH_MIN - 16, 8, + NULL, 0); + } + + int selected = item_activate_selected(); + LOG_D("ret=%d, selected=%s\n", ret, selected ? "true" : "false"); + switch (ret) { + case 0: // item is selected/close + if (!selected) + break; + + /* Show operation result */ + *selected_chn = (csi_camera_channel_cfg_s *)item_data(); + + if (action == 0) { // to close + if ((*selected_chn)->status == CSI_CAMERA_CHANNEL_RUNNING) { + dialog_textbox_simple("Infomation", + "The channel is Running, \nplease stop first", 6, 40); + goto again; + } + } else if (action == 1) { // to open + if ((*selected_chn)->status == CSI_CAMERA_CHANNEL_OPENED || + (*selected_chn)->status == CSI_CAMERA_CHANNEL_RUNNING) { + dialog_textbox_simple("Infomation", + "The channel is Opened or Running, \nplease close first", 6, 40); + goto again; + } + } + /* Show message bar */ + snprintf(str_buf, sizeof(str_buf), + "The Camera[%d] channel[%d] is selected\n", + cam_session->camera_id, + (*selected_chn)->chn_id); + message(str_buf, 0); + ret = 0; + break; + case 1: // need help + LOG_W("Help is not supported yet\n"); + goto again; + case KEY_ESC: // specific KEY + ret = KEY_ESC; + break; + case -ERRDISPLAYTOOSMALL: // error + default: + LOG_W("Oops? why return ret=%d\n", ret); + ret = KEY_ESC; + break; + } + + LOG_D("ret=%d\n", ret); + return ret; +} + diff --git a/examples/camera/camera_demo3_srcs/dialog_event_subscribe_action_camera.c b/examples/camera/camera_demo3_srcs/dialog_event_subscribe_action_camera.c new file mode 100644 index 0000000..5a83d85 --- /dev/null +++ b/examples/camera/camera_demo3_srcs/dialog_event_subscribe_action_camera.c @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "dlg_prop_enum" +#include +#include +#include +#include +#include + +#include "param.h" +#include "app_dialogs.h" + +extern cams_t *cam_session; + +static int add_event_actions(camera_event_action_union_t *event_action) +{ + int count = 0; + const camera_app_bitmasks_t *bitmasks = + camera_app_get_bitmask_array(CAMERA_APP_BITMAKS_CAMERA_EVENT_ACTION); + LOG_D("bitmasks->count=%d\n", bitmasks->count); + + if (bitmasks == NULL) { + LOG_E("Can't get camera's action support list\n"); + return count; + } + LOG_D("camera.action=0x%08x\n", event_action->camera.action); + + switch (event_action->camera.event) { + case CSI_CAMERA_EVENT_WARNING: + case CSI_CAMERA_EVENT_ERROR: + case CSI_CAMERA_EVENT_SENSOR_FIRST_IMAGE_ARRIVE: + case CSI_CAMERA_EVENT_ISP_3A_ADJUST_READY: + for (int i = 0; i < bitmasks->count; i++) { + csi_camera_event_id_e loop_action = bitmasks->bitmask[i]; + bool action_set = (event_action->camera.action & loop_action) != 0; + const char *item_name = camera_string_camera_action(loop_action); + + item_make("\t%-16s(0x%08x)", item_name, loop_action); + LOG_D("\t%-16s(0x%08x)\n", item_name, loop_action); + item_set_tag(action_set ? '*' : ' '); + item_set_int(loop_action); + } + break; + } +} + + +int dialog_event_subscribe_action_camera(camera_event_action_union_t *event_action) +{ + int ret_key = KEY_ESC; + + if (event_action == NULL) { + LOG_W("event_action is NULL\n"); + return KEY_ESC; + } + + char *title = "Set Camera Event Action(s)"; + char *prompt = "Select the Camera event then config the action(s)"; + int item_pos = 0; + int list_count = 0; + +again: + item_reset(); + + item_make("%-32s","Subscribe"); + if (event_action->camera.subscribed == false) { + item_set_tag(' '); + } else { + item_set_tag('*'); + list_count = add_event_actions(event_action); + } + + int list_height = MIN(list_count + 8, 24); + ret_key = dialog_checkbox( + title, prompt, + CHECKLIST_HEIGTH_MIN + list_height + 2, + WIN_COLS - CHECKLIST_WIDTH_MIN - 32, + list_height, item_pos); + + LOG_D("dialog_checkbox() ret_key=%d\n", ret_key); + if (ret_key == KEY_ESC) { + return KEY_ESC; + } else if (ret_key == 0) {/* select */ + item_pos = item_n(); + LOG_D("item_pos=%d\n", item_pos); + + if (item_pos == 0) { // 1st item, which indicate whether register actions + if (item_tag() == ' ') { + event_action->camera.subscribed = true; + goto again; + } else { + event_action->camera.subscribed = false; + goto again; + } + } else { // other item, which indicate whether register actions + camera_action_e cur_action = item_int(); + if (item_tag() == ' ') { + event_action->camera.action |= cur_action; + item_set_tag('*'); + } else { + event_action->camera.action &= ~cur_action; + item_set_tag(' '); + } + LOG_D("camera.action=0x%08x\n", event_action->camera.action); + } + + goto again; + return ret_key; + } else if (ret_key == 1) {/* Return */ + return ret_key; + } else if (ret_key == 2) {/* Help */ + LOG_W("Help does not support yet\n"); + return ret_key; + } else { + LOG_E("Unknown return value: %d\n", ret_key); + return KEY_ESC; + } +} + diff --git a/examples/camera/camera_demo3_srcs/dialog_event_subscribe_action_channel.c b/examples/camera/camera_demo3_srcs/dialog_event_subscribe_action_channel.c new file mode 100644 index 0000000..9a519a0 --- /dev/null +++ b/examples/camera/camera_demo3_srcs/dialog_event_subscribe_action_channel.c @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "dlg_prop_enum" +#include +#include +#include +#include +#include + +#include "param.h" +#include "app_dialogs.h" + +extern cams_t *cam_session; + +static int add_event_actions(camera_event_action_union_t *event_action) +{ + int count = 0; + const camera_app_bitmasks_t *bitmasks = + camera_app_get_bitmask_array(CAMERA_APP_BITMAKS_CHANNEL_EVENT_ACTION); + + if (bitmasks == NULL) { + LOG_E("Can't get channel's action support list\n"); + return count; + } + LOG_D("camera.action=0x%08x\n", event_action->channel.action); + + switch (event_action->channel.event) { + case CSI_CAMERA_CHANNEL_EVENT_FRAME_READY: + case CSI_CAMERA_CHANNEL_EVENT_FRAME_PUT: + case CSI_CAMERA_CHANNEL_EVENT_OVERFLOW: + for (int i = 0; i < bitmasks->count; i++) { + csi_camera_event_id_e loop_action = bitmasks->bitmask[i]; + bool action_set = (event_action->channel.action & loop_action) != 0; + const char *item_name = camera_string_channel_action(loop_action); + + item_make("\t%-16s(0x%08x)", item_name, loop_action); + item_set_tag(action_set ? '*' : ' '); + item_set_int(loop_action); + } + break; + } +} + + +int dialog_event_subscribe_action_channel(camera_event_action_union_t *event_action) +{ + int ret_key = KEY_ESC; + + if (event_action == NULL) { + LOG_W("event_action is NULL\n"); + return KEY_ESC; + } + + char *title = "Set Channel Event Action(s)"; + char *prompt = "Select the channel event then config the action(s)"; + int item_pos = 0; + int list_count = 0; + +again: + item_reset(); + + item_make("%-32s","Subscribe"); + if (event_action->channel.subscribed == false) { + item_set_tag(' '); + } else { + item_set_tag('*'); + list_count = add_event_actions(event_action); + } + + int list_height = MIN(list_count + 8, 24); + ret_key = dialog_checkbox( + title, prompt, + CHECKLIST_HEIGTH_MIN + list_height + 2, + WIN_COLS - CHECKLIST_WIDTH_MIN - 32, + list_height, item_pos); + + //LOG_D("dialog_checkbox() ret_key=%d\n", ret_key); + if (ret_key == KEY_ESC) { + return KEY_ESC; + } else if (ret_key == 0) {/* select */ + item_pos = item_n(); + LOG_D("item_pos=%d\n", item_pos); + + if (item_pos == 0) { // 1st item, which indicate whether register actions + if (item_tag() == ' ') { + event_action->channel.subscribed = true; + goto again; + } else { + event_action->channel.subscribed = false; + goto again; + } + } else { // other item, which indicate whether register actions + camera_action_e cur_action = item_int(); + if (item_tag() == ' ') { + event_action->channel.action |= cur_action; + item_set_tag('*'); + } else { + event_action->channel.action &= ~cur_action; + item_set_tag(' '); + } + LOG_D("camera.action=0x%08x\n", event_action->channel.action); + } + + goto again; + return ret_key; + } else if (ret_key == 1) {/* Return */ + return ret_key; + } else if (ret_key == 2) {/* Help */ + LOG_W("Help does not support yet\n"); + return ret_key; + } else { + LOG_E("Unknown return value: %d\n", ret_key); + return KEY_ESC; + } +} + diff --git a/examples/camera/camera_demo3_srcs/dialog_event_subscribe_action_list.c b/examples/camera/camera_demo3_srcs/dialog_event_subscribe_action_list.c new file mode 100644 index 0000000..06caa16 --- /dev/null +++ b/examples/camera/camera_demo3_srcs/dialog_event_subscribe_action_list.c @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "dlg_prop_list" +#include + +#include +#include +#include +#include + +#include "param.h" +#include "app_dialogs.h" + +#include +#include + +extern cams_t *cam_session; + +static int fill_items(void) +{ + char item_name[64]; + char item_value[64]; + camera_event_action_union_t *event_action; + + /* Fill camera event and action */ + item_make("*** Camera[%d] Event/Action ***", cam_session->camera_id); + item_set_tag(' '); + item_set_data(NULL); + + for (int i = 0; i < CSI_CAMERA_EVENT_MAX_COUNT; i++) { + event_action = &(cam_session->camera_event_action[i]); + if (!event_action->camera.supported) + continue; + + item_make(" %-18s", camera_string_camera_event_type(event_action->camera.event)); + item_add_str("(%s) --->", event_action->camera.subscribed + ? "Subscribed" : "Un-subscribed"); + item_set_tag('M'); + item_set_data(event_action); + //cam_mng_dump_event_action_union(event_action); + } + for(int i = 0;i < CSI_CAMERA_CHANNEL_MAX_COUNT;i++){ + if((cam_session->chn_cfg[i].status == CSI_CAMERA_CHANNEL_OPENED) || + (cam_session->chn_cfg[i].status == CSI_CAMERA_CHANNEL_RUNNING)){ + item_make(" *** Channel[%d] Event/Action ***", + cam_session->chn_cfg[i].chn_id); + item_set_tag(' '); + item_set_data(NULL); + + for (int j = 0; j < CSI_CAMERA_CHANNEL_EVENT_MAX_COUNT; j++) { + event_action = &(cam_session->channel_event_action[i][j]); + if (!event_action->channel.supported) + continue; + + item_make(" %-18s", camera_string_channel_event_type(event_action->channel.event)); + item_add_str("(%s) --->", event_action->channel.subscribed + ? "Subscribed" : "Un-subscribed"); + item_set_tag('N'); // Type: Channel + item_set_data(event_action); + //cam_mng_dump_event_action_union(event_action); + + } + } + } + return 0; +} + +int dialog_event_subscribe_action_list(camera_event_action_union_t **event_action) +{ + int ret_key = KEY_ESC; + int ret = 0; + + char selected_tag; + camera_event_action_union_t *selected_data; + *event_action = NULL; + char str_buf[128]; + +again: + item_reset(); + if (fill_items() != 0) { + LOG_E("fill_items() failed\n"); + ret = KEY_ESC; + goto exit; + } + + int s_scroll = 0; + ret_key = dialog_menu("Camera Event Subscribe & Action Setting", + "Select the Camera/Channel, press Enter to set the event subscribe and event action", + WIN_ROWS-2, WIN_COLS, NULL, -1, &s_scroll, NULL, 0); + //LOG_D("dialog_menu() ret_key=%d, s_scroll=%d\n", ret_key, s_scroll); + + switch (ret_key) { + case 0: /* Select button */ + selected_tag = item_tag(); + selected_data = (camera_event_action_union_t *)item_data(); + if (selected_data != NULL) { + *event_action = selected_data; + ret = 0; + break; + } else { + goto again; + } + break; + case 1: /* Apply button */ + ret = camera_subscribe_event(cam_session); + snprintf(str_buf, sizeof(str_buf), + "Apply Camera[%d] and Channels event register %s.", + cam_session->camera_id, (ret == 0) ? "OK" : "failed"); + dialog_textbox_simple("Infomation", str_buf, 6, 56); + goto again; + case 2: /* Cancel button */ + case KEY_ESC: /* Escape */ + default: + ret = KEY_ESC; + goto exit; + } + +exit: + return ret; +} + diff --git a/examples/camera/camera_demo3_srcs/inputbox.c b/examples/camera/camera_demo3_srcs/inputbox.c new file mode 100644 index 0000000..fc55a09 --- /dev/null +++ b/examples/camera/camera_demo3_srcs/inputbox.c @@ -0,0 +1,302 @@ +/* + * inputbox.c -- implements the input box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "dialog.h" +#include "app_dialogs.h" + +char dialog_input_result[MAX_LEN + 1]; + +/* + * Print the termination buttons + */ +static void print_buttons(WINDOW * dialog, int height, int width, int selected) +{ + int x = width / 2 - 11; + int y = height - 2; + + print_button(dialog, gettext(" Ok "), y, x, selected == 0); + print_button(dialog, gettext(" Help "), y, x + 14, selected == 1); + + wmove(dialog, y, x + 1 + 14 * selected); + wrefresh(dialog); +} + +/* + * Display a dialog box for inputing a string + */ +int dialog_inputbox(const char *title, const char *prompt, int height, int width, + const char *init) +{ + int i, x, y, box_y, box_x, box_width; + int input_x = 0, key = 0, button = -1; + int show_x, len, pos; + char *instr = dialog_input_result; + WINDOW *dialog; + + if (!init) + instr[0] = '\0'; + else + strcpy(instr, init); + +do_resize: + if (WIN_ROWS <= (height - INPUTBOX_HEIGTH_MIN)) + return -ERRDISPLAYTOOSMALL; + if (WIN_COLS <= (width - INPUTBOX_WIDTH_MIN)) + return -ERRDISPLAYTOOSMALL; + + /* center dialog box on screen */ + x = (WIN_COLS - width) / 2; + y = (WIN_ROWS - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, + dlg.dialog.atr, dlg.border.atr); + wattrset(dialog, dlg.border.atr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dlg.dialog.atr); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dlg.dialog.atr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + /* Draw the input field box */ + box_width = width - 6; + getyx(dialog, y, x); + box_y = y + 2; + box_x = (width - box_width) / 2; + draw_box(dialog, y + 1, box_x - 1, 3, box_width + 2, + dlg.dialog.atr, dlg.border.atr); + + print_buttons(dialog, height, width, 0); + + /* Set up the initial value */ + wmove(dialog, box_y, box_x); + wattrset(dialog, dlg.inputbox.atr); + + len = strlen(instr); + pos = len; + + if (len >= box_width) { + show_x = len - box_width + 1; + input_x = box_width - 1; + for (i = 0; i < box_width - 1; i++) + waddch(dialog, instr[show_x + i]); + } else { + show_x = 0; + input_x = len; + waddstr(dialog, instr); + } + + wmove(dialog, box_y, box_x + input_x); + + wrefresh(dialog); + + while (key != KEY_ESC) { + key = wgetch(dialog); + + if (button == -1) { /* Input box selected */ + switch (key) { + case TAB: + case KEY_UP: + case KEY_DOWN: + break; + case KEY_BACKSPACE: + case 127: + if (pos) { + wattrset(dialog, dlg.inputbox.atr); + if (input_x == 0) { + show_x--; + } else + input_x--; + + if (pos < len) { + for (i = pos - 1; i < len; i++) { + instr[i] = instr[i+1]; + } + } + + pos--; + len--; + instr[len] = '\0'; + wmove(dialog, box_y, box_x); + for (i = 0; i < box_width; i++) { + if (!instr[show_x + i]) { + waddch(dialog, ' '); + break; + } + waddch(dialog, instr[show_x + i]); + } + wmove(dialog, box_y, input_x + box_x); + wrefresh(dialog); + } + continue; + case KEY_LEFT: + if (pos > 0) { + if (input_x > 0) { + wmove(dialog, box_y, --input_x + box_x); + } else if (input_x == 0) { + show_x--; + wmove(dialog, box_y, box_x); + for (i = 0; i < box_width; i++) { + if (!instr[show_x + i]) { + waddch(dialog, ' '); + break; + } + waddch(dialog, instr[show_x + i]); + } + wmove(dialog, box_y, box_x); + } + pos--; + } + continue; + case KEY_RIGHT: + if (pos < len) { + if (input_x < box_width - 1) { + wmove(dialog, box_y, ++input_x + box_x); + } else if (input_x == box_width - 1) { + show_x++; + wmove(dialog, box_y, box_x); + for (i = 0; i < box_width; i++) { + if (!instr[show_x + i]) { + waddch(dialog, ' '); + break; + } + waddch(dialog, instr[show_x + i]); + } + wmove(dialog, box_y, input_x + box_x); + } + pos++; + } + continue; + default: + if (key < 0x100 && isprint(key)) { + if (len < MAX_LEN) { + wattrset(dialog, dlg.inputbox.atr); + if (pos < len) { + for (i = len; i > pos; i--) + instr[i] = instr[i-1]; + instr[pos] = key; + } else { + instr[len] = key; + } + pos++; + len++; + instr[len] = '\0'; + + if (input_x == box_width - 1) { + show_x++; + } else { + input_x++; + } + + wmove(dialog, box_y, box_x); + for (i = 0; i < box_width; i++) { + if (!instr[show_x + i]) { + waddch(dialog, ' '); + break; + } + waddch(dialog, instr[show_x + i]); + } + wmove(dialog, box_y, input_x + box_x); + wrefresh(dialog); + } else + flash(); /* Alarm user about overflow */ + continue; + } + } + } + switch (key) { + case 'O': + case 'o': + delwin(dialog); + return 0; + case 'H': + case 'h': + delwin(dialog); + return 1; + case KEY_UP: + case KEY_LEFT: + switch (button) { + case -1: + button = 1; /* Indicates "Help" button is selected */ + print_buttons(dialog, height, width, 1); + break; + case 0: + button = -1; /* Indicates input box is selected */ + print_buttons(dialog, height, width, 0); + wmove(dialog, box_y, box_x + input_x); + wrefresh(dialog); + break; + case 1: + button = 0; /* Indicates "OK" button is selected */ + print_buttons(dialog, height, width, 0); + break; + } + break; + case TAB: + case KEY_DOWN: + case KEY_RIGHT: + switch (button) { + case -1: + button = 0; /* Indicates "OK" button is selected */ + print_buttons(dialog, height, width, 0); + break; + case 0: + button = 1; /* Indicates "Help" button is selected */ + print_buttons(dialog, height, width, 1); + break; + case 1: + button = -1; /* Indicates input box is selected */ + print_buttons(dialog, height, width, 0); + wmove(dialog, box_y, box_x + input_x); + wrefresh(dialog); + break; + } + break; + case ' ': + case '\n': + delwin(dialog); + return (button == -1 ? 0 : button); + case 'X': + case 'x': + key = KEY_ESC; + break; + case KEY_ESC: + key = on_key_esc(dialog); + break; + case KEY_RESIZE: + delwin(dialog); + on_key_resize(); + goto do_resize; + } + } + + delwin(dialog); + return KEY_ESC; /* ESC pressed */ +} diff --git a/examples/camera/camera_demo3_srcs/menu_process.c b/examples/camera/camera_demo3_srcs/menu_process.c new file mode 100644 index 0000000..2ee8ee3 --- /dev/null +++ b/examples/camera/camera_demo3_srcs/menu_process.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "camera_demo3" +#include + +#include +#include +#include + +#include "param.h" +#include "app_dialogs.h" +#include "menu_process.h" + +void menu_camera_process(menu_camera_item_e item) +{ + int ret; + switch (item) { + case MENU_CAMERA_LIST: + LOG_D("dialog_camera_list()\n"); + ret = dialog_camera_list(); + break; + case MENU_CAMERA_OPEN: + LOG_D("dialog_camera_open()\n"); + ret = dialog_camera_open(); + break; + case MENU_CAMERA_SET_MODE: + LOG_D("dialog_camera_set_mode()\n"); + ret = dialog_camera_set_mode(); + break; + case MENU_CAMERA_SET_PROPERTY: + LOG_D("dialog_camera_property_list()\n"); + ret = dialog_camera_property_list(); + break; + case MENU_CAMERA_CLOSE: + LOG_D("dialog_camera_close()\n"); + ret = dialog_camera_close(); + break; + case -1: + ret = -1; + break; + default: + LOG_E("Unknown menu item:%d\n", item); + ret = -1; + } + return; +} + +void menu_channel_process(menu_camera_item_e item) +{ + int ret; + csi_camera_channel_cfg_s *selected_chn; + char str_buf[128]; + + switch (item) { + case MENU_CHANNEL_LIST: + LOG_D("channel_list()\n"); + dialog_channel_list(); + break; + case MENU_CHANNEL_OPEN: +again_channel_open: + LOG_D("channel_open()\n"); + ret = dialog_channel_select(&selected_chn, 1); + if (ret == KEY_ESC || ret != 0 || /* Not "Select" button */ + selected_chn == NULL) { /* No Channel be selected */ + return; + } + ret = dialog_channel_open(selected_chn); + touchwin(stdscr); + refresh(); + goto again_channel_open; + break; + case MENU_CHANNEL_CLOSE: +again_channel_close: + LOG_D("channel_close()\n"); + ret = dialog_channel_select(&selected_chn, 0); + if (ret == KEY_ESC || ret != 0 || /* Not "Select" button */ + selected_chn == NULL) { /* No Channel be selected */ + return; + } + ret = camera_channel_close(cam_session, selected_chn->chn_id); + snprintf(str_buf, sizeof(str_buf), + "Close Camera[%d]:Channel[%d] %s", + cam_session->camera_id, selected_chn->chn_id, + (ret == 0) ? "OK" : "failed"); + dialog_textbox_simple("Infomation", str_buf, 10, 40); + touchwin(stdscr); + refresh(); + goto again_channel_close; + break; + case -1: + break; + default: + LOG_E("Unknown menu item:%d\n", item); + break; + } + + return; +} + +void menu_event_run_process(menu_camera_item_e item) +{ + int ret; + char str_buf[128]; + camera_event_action_union_t *event_action = NULL; + csi_camera_channel_cfg_s *selected_chn; + + switch (item) { + case MENU_EVENT_SUBSCRIBE_ACTION: +again_subscribe_action: + LOG_D("dialog_event_subscribe_action_list()\n"); + ret = dialog_event_subscribe_action_list(&event_action); + if (ret == KEY_ESC || ret != 0 || event_action == NULL) + return; + + cam_mng_dump_event_action_union(event_action); + if (event_action->target == MANAGE_TARGET_CAMERA) { + ret = dialog_event_subscribe_action_camera(event_action); + LOG_D("dialog_event_subscribe_action_camera() ret=%d\n", ret); + goto again_subscribe_action; + } else if (event_action->target == MANAGE_TARGET_CHANNEL) { + ret = dialog_event_subscribe_action_channel(event_action); + LOG_D("dialog_event_subscribe_action_channel() ret=%d\n", ret); + goto again_subscribe_action; + } + break; + case MENU_EVENT_START_STOP: + ret = dialog_channel_run(); + break; + case -1: + break; + default: + LOG_E("Unknown menu item:%d\n", item); + break; + } + + return; +} + diff --git a/examples/camera/camera_demo3_srcs/menu_process.h b/examples/camera/camera_demo3_srcs/menu_process.h new file mode 100644 index 0000000..acc74d0 --- /dev/null +++ b/examples/camera/camera_demo3_srcs/menu_process.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "camera_demo3" +#include + +#include "camera_manager.h" +#include "param.h" +#include "app_dialogs.h" + +extern cams_t *cam_session; + +void menu_camera_process(menu_camera_item_e item); +void menu_channel_process(menu_camera_item_e item); +void menu_event_run_process(menu_camera_item_e item); + diff --git a/examples/camera/camera_demo3_srcs/menubox.c b/examples/camera/camera_demo3_srcs/menubox.c new file mode 100644 index 0000000..da63156 --- /dev/null +++ b/examples/camera/camera_demo3_srcs/menubox.c @@ -0,0 +1,442 @@ +/* + * menubox.c -- implements the menu box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcapw@cfw.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Changes by Clifford Wolf (god@clifford.at) + * + * [ 1998-06-13 ] + * + * *) A bugfix for the Page-Down problem + * + * *) Formerly when I used Page Down and Page Up, the cursor would be set + * to the first position in the menu box. Now lxdialog is a bit + * smarter and works more like other menu systems (just have a look at + * it). + * + * *) Formerly if I selected something my scrolling would be broken because + * lxdialog is re-invoked by the Menuconfig shell script, can't + * remember the last scrolling position, and just sets it so that the + * cursor is at the bottom of the box. Now it writes the temporary file + * lxdialog.scrltmp which contains this information. The file is + * deleted by lxdialog if the user leaves a submenu or enters a new + * one, but it would be nice if Menuconfig could make another "rm -f" + * just to be sure. Just try it out - you will recognise a difference! + * + * [ 1998-06-14 ] + * + * *) Now lxdialog is crash-safe against broken "lxdialog.scrltmp" files + * and menus change their size on the fly. + * + * *) If for some reason the last scrolling position is not saved by + * lxdialog, it sets the scrolling so that the selected item is in the + * middle of the menu box, not at the bottom. + * + * 02 January 1999, Michael Elizabeth Chastain (mec@shout.net) + * Reset 'scroll' to 0 if the value from lxdialog.scrltmp is bogus. + * This fixes a bug in Menuconfig where using ' ' to descend into menus + * would leave mis-synchronized lxdialog.scrltmp files lying around, + * fscanf would read in 'scroll', and eventually that value would get used. + */ + +#include "dialog.h" +#include "app_dialogs.h" + +static int menu_width, item_x; + +/* + * Print menu item + */ +static void do_print_item(WINDOW * win, const char *item, int line_y, + int selected, int hotkey) +{ + int j; + char *menu_item = malloc(menu_width + 1); + + strncpy(menu_item, item, menu_width - item_x); + menu_item[menu_width - item_x] = '\0'; + j = first_alpha(menu_item, "YyNnMmHh"); + + /* Clear 'residue' of last item */ + wattrset(win, dlg.menubox.atr); + wmove(win, line_y, 0); +#if OLD_NCURSES + { + int i; + for (i = 0; i < menu_width; i++) + waddch(win, ' '); + } +#else + wclrtoeol(win); +#endif + wattrset(win, selected ? dlg.item_selected.atr : dlg.item.atr); + mvwaddstr(win, line_y, item_x, menu_item); + if (hotkey) { + wattrset(win, selected ? dlg.tag_key_selected.atr + : dlg.tag_key.atr); + mvwaddch(win, line_y, item_x + j, menu_item[j]); + } + if (selected) { + wmove(win, line_y, item_x + 1); + } + free(menu_item); + wrefresh(win); +} + +#define print_item(index, choice, selected) \ +do { \ + item_set(index); \ + do_print_item(menu, item_str(), choice, selected, !item_is_tag(':')); \ +} while (0) + +/* + * Print the scroll indicators. + */ +static void print_arrows(WINDOW * win, int item_no, int scroll, int y, int x, + int height) +{ + int cur_y, cur_x; + + getyx(win, cur_y, cur_x); + + wmove(win, y, x); + + if (scroll > 0) { + wattrset(win, dlg.uarrow.atr); + waddch(win, ACS_UARROW); + waddstr(win, "(-)"); + } else { + wattrset(win, dlg.menubox.atr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } + + y = y + height + 1; + wmove(win, y, x); + wrefresh(win); + + if ((height < item_no) && (scroll + height < item_no)) { + wattrset(win, dlg.darrow.atr); + waddch(win, ACS_DARROW); + waddstr(win, "(+)"); + } else { + wattrset(win, dlg.menubox_border.atr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } + + wmove(win, cur_y, cur_x); + wrefresh(win); +} + +/* + * Display the termination buttons. + */ +static void print_buttons(WINDOW * win, int height, int width, int selected, + char *button_names[], int button_count) +{ + int x = width / 2 - 28; + int y = height - 2; + + if (button_names == NULL) { + print_button(win, gettext("Select"), y, x + 12, selected == 0); + print_button(win, gettext("Apply"), y, x + 24, selected == 1); + print_button(win, gettext("Cancel"), y, x + 36, selected == 2); + } else { + for (int i = 0; i < button_count; i++) { + print_button(win, gettext(button_names[i]), y, x + 12 * (i + 1), selected == i); + } + } + + wmove(win, y, x + 1 + 12 + 12 * selected); + wrefresh(win); +} + +/* scroll up n lines (n may be negative) */ +static void do_scroll(WINDOW *win, int *scroll, int n) +{ + /* Scroll menu up */ + scrollok(win, TRUE); + wscrl(win, n); + scrollok(win, FALSE); + *scroll = *scroll + n; + wrefresh(win); +} + +/* + * Display a menu for choosing among a number of options + Indicate selected item: set 'selected' or 'selected_pos' + Indicate buttons: char *button_name[], int button_count + */ +int dialog_menu(const char *title, const char *prompt, + int height, int width, + const void *selected, int selected_pos, int *s_scroll, + char *button_names[], int button_count) +{ + int i, j, x, y, box_x, box_y; + int menu_height; + int key = 0, button = 0, scroll = 0, choice = 0; + int first_item = 0, max_choice; + WINDOW *dialog, *menu; + +do_resize: + //height = getmaxy(stdscr); + //width = getmaxx(stdscr); + if (height < MENUBOX_HEIGTH_MIN || width < MENUBOX_WIDTH_MIN) + return -ERRDISPLAYTOOSMALL; + + height -= 4; + width -= 5; + menu_height = height - 8; + + max_choice = MIN(menu_height, item_count()); + + /* center dialog box on screen */ + x = (WIN_COLS - width) / 2; + y = (WIN_ROWS - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, + dlg.dialog.atr, dlg.border.atr); + wattrset(dialog, dlg.border.atr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dlg.dialog.atr); + wbkgdset(dialog, dlg.dialog.atr & A_COLOR); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dlg.dialog.atr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + menu_width = width - 6; + box_y = height - menu_height - 5; + box_x = (width - menu_width) / 2 - 1; + + /* create new window for the menu */ + menu = subwin(dialog, menu_height, menu_width, + y + box_y + 1, x + box_x + 1); + keypad(menu, TRUE); + + /* draw a box around the menu items */ + draw_box(dialog, box_y, box_x, menu_height + 2, menu_width + 2, + dlg.menubox_border.atr, dlg.menubox.atr); + + if (menu_width >= 60) + item_x = (menu_width - 50) / 3; + else + item_x = 4; + + /* Set choice to default item */ + item_foreach() { + if (selected && (selected == item_data())) + choice = item_n(); + } + if (selected_pos >= 0) + choice = selected_pos; + + /* get the saved scroll info */ + scroll = *s_scroll; + if ((scroll <= choice) && (scroll + max_choice > choice) && + (scroll >= 0) && (scroll + max_choice <= item_count())) { + first_item = scroll; + choice = choice - scroll; + } else { + scroll = 0; + } + if ((choice >= max_choice)) { + if (choice >= item_count() - max_choice / 2) + scroll = first_item = item_count() - max_choice; + else + scroll = first_item = choice - max_choice / 2; + choice = choice - scroll; + } + + /* Print the menu */ + for (i = 0; i < max_choice; i++) { + print_item(first_item + i, i, i == choice); + } + + wnoutrefresh(menu); + + print_arrows(dialog, item_count(), scroll, + box_y, box_x + item_x + 1, menu_height); + + print_buttons(dialog, height, width, 0, button_names, button_count); + wmove(menu, choice, item_x + 1); + wrefresh(menu); + + while (key != KEY_ESC) { + key = wgetch(menu); + + if (key < 256 && isalpha(key)) + key = tolower(key); + + if (strchr("ynmh ", key)) + i = max_choice; + else { + for (i = choice + 1; i < max_choice; i++) { + item_set(scroll + i); + j = first_alpha(item_str(), "YyNnMmHh"); + if (key == tolower(item_str()[j])) + break; + } + if (i == max_choice) + for (i = 0; i < max_choice; i++) { + item_set(scroll + i); + j = first_alpha(item_str(), "YyNnMmHh"); + if (key == tolower(item_str()[j])) + break; + } + } + + if (item_count() != 0 && + (i < max_choice || + key == KEY_UP || key == KEY_DOWN || + key == '-' || key == '+' || + key == KEY_PPAGE || key == KEY_NPAGE)) { + /* Remove highligt of current item */ + print_item(scroll + choice, choice, FALSE); + + if (key == KEY_UP || key == '-') { + if (choice < 2 && scroll) { + /* Scroll menu down */ + do_scroll(menu, &scroll, -1); + + print_item(scroll, 0, FALSE); + } else + choice = MAX(choice - 1, 0); + + } else if (key == KEY_DOWN || key == '+') { + print_item(scroll+choice, choice, FALSE); + + if ((choice > max_choice - 3) && + (scroll + max_choice < item_count())) { + /* Scroll menu up */ + do_scroll(menu, &scroll, 1); + + print_item(scroll+max_choice - 1, + max_choice - 1, FALSE); + } else + choice = MIN(choice + 1, max_choice - 1); + + } else if (key == KEY_PPAGE) { + scrollok(menu, TRUE); + for (i = 0; (i < max_choice); i++) { + if (scroll > 0) { + do_scroll(menu, &scroll, -1); + print_item(scroll, 0, FALSE); + } else { + if (choice > 0) + choice--; + } + } + + } else if (key == KEY_NPAGE) { + for (i = 0; (i < max_choice); i++) { + if (scroll + max_choice < item_count()) { + do_scroll(menu, &scroll, 1); + print_item(scroll+max_choice-1, + max_choice - 1, FALSE); + } else { + if (choice + 1 < max_choice) + choice++; + } + } + } else + choice = i; + + print_item(scroll + choice, choice, TRUE); + + print_arrows(dialog, item_count(), scroll, + box_y, box_x + item_x + 1, menu_height); + + wnoutrefresh(dialog); + wrefresh(menu); + + continue; /* wait for another key press */ + } + + switch (key) { + case KEY_LEFT: + case TAB: + case KEY_RIGHT: + button = ((key == KEY_LEFT ? --button : ++button) < 0) ? + 2 : (button > 2 ? 0 : button); + + print_buttons(dialog, height, width, button, button_names, button_count); + wrefresh(menu); + break; + case ' ': + case 'S': + case 's': + case 'A': + case 'a': + case 'C': + case 'c': + case '\n': + /* save scroll info */ + *s_scroll = scroll; + delwin(menu); + delwin(dialog); + item_set(scroll + choice); + item_set_selected(1); + switch (key) { + case 'S': + case 's': + return 0; + case 'A': + case 'a': + return 1; + case 'C': + case 'c': + return 2; + case '\n': + return button; + } + return 0; + case 'e': + case 'x': + key = KEY_ESC; + break; + case KEY_ESC: + key = on_key_esc(menu); + break; + case KEY_RESIZE: + on_key_resize(); + delwin(menu); + delwin(dialog); + goto do_resize; + } + } + delwin(menu); + delwin(dialog); + return key; /* ESC pressed */ +} diff --git a/examples/camera/camera_demo3_srcs/param.c b/examples/camera/camera_demo3_srcs/param.c new file mode 100644 index 0000000..e729f65 --- /dev/null +++ b/examples/camera/camera_demo3_srcs/param.c @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include + +#include "param.h" + +char param[10][10][MENU_ITEM_MAX_LEN+1]; + +static void GetSubStr(char *des, char *src, char ch, int n) +{ + int i, len; + char *p1, *p, tmp[300]; + strcpy(tmp, src); + *des = 0; + p1 = tmp; + + for (int i = 0; i < n; i++) { + p = (char *)strchr(p1, ch); + if (p != NULL) { + *p++ = 0; + p1 = p; + } + } + + p = (char *)strchr(p1, ch); + if (p != NULL) { + *p = 0; + strcpy(des, p1); + } +} + +int get_param(char *name) +{ + FILE *fp; + char ss[201], xm[3], gs[3]; + int i, j; + sprintf(ss, "%s.conf", name); + if ((fp = fopen(ss, "r")) == NULL) return (-1); + for (j = 0; j < 10; j++) + for (i = 0; i < 10; i++) + memset(param[j][i], 0, 13); + while (1) { + memset(ss, 0, 201); + fgets(ss, 200, fp); + if (feof(fp)) break; + if (ss[0] == '#') continue; + GetSubStr(xm, ss, '|', 0); + GetSubStr(gs, ss, '|', 1); + j = atoi(xm); + for (i = 1; i <= atoi(gs); i++) { + sprintf(param[j][0], "%s", gs); + GetSubStr(param[j][i], ss, '|', i + 1); + } + } + fclose(fp); + return (0); +} + diff --git a/examples/camera/camera_demo3_srcs/param.h b/examples/camera/camera_demo3_srcs/param.h new file mode 100644 index 0000000..3c93507 --- /dev/null +++ b/examples/camera/camera_demo3_srcs/param.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __PARAM_H__ +#define __PARAM_H__ + +#include + +#define MENU_ITEM_MAX_LEN 16 +extern char param[10][10][MENU_ITEM_MAX_LEN+1]; +int get_param(char *name); + +#define MENU_CAMERA 1 +typedef enum { + MENU_CAMERA_LIST = 0, + MENU_CAMERA_OPEN, + MENU_CAMERA_SET_MODE, + MENU_CAMERA_SET_PROPERTY, + MENU_CAMERA_CLOSE, +} menu_camera_item_e; + +#define MENU_CHANNEL 2 +typedef enum { + MENU_CHANNEL_LIST = 0, + MENU_CHANNEL_OPEN, + MENU_CHANNEL_CLOSE, +} menu_channel_item_e; + +#define MENU_EVENT_RUN 3 +typedef enum { + MENU_EVENT_SUBSCRIBE_ACTION = 0, + MENU_EVENT_START_STOP, +} menu_event_run_item_e; + + +#endif /* __PARAM_H__ */ diff --git a/examples/camera/camera_demo3_srcs/textbox.c b/examples/camera/camera_demo3_srcs/textbox.c new file mode 100644 index 0000000..903242c --- /dev/null +++ b/examples/camera/camera_demo3_srcs/textbox.c @@ -0,0 +1,414 @@ +/* + * textbox.c -- implements the text box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "dialog.h" +#include "app_dialogs.h" + +static void back_lines(int n); +static void print_page(WINDOW *win, int height, int width, update_text_fn + update_text, void *data); +static void print_line(WINDOW *win, int row, int width); +static char *get_line(void); +static void print_position(WINDOW * win); + +static int hscroll; +static int begin_reached, end_reached, page_length; +static char *buf; +static char *page; + +/* + * refresh window content + */ +static void refresh_text_box(WINDOW *dialog, WINDOW *box, int boxh, int boxw, + int cur_y, int cur_x, update_text_fn update_text, + void *data) +{ + print_page(box, boxh, boxw, update_text, data); + print_position(dialog); + wmove(dialog, cur_y, cur_x); /* Restore cursor position */ + wrefresh(dialog); +} + +/* + * Display text from a file in a dialog box. + * + * keys is a null-terminated array + * update_text() may not add or remove any '\n' or '\0' in tbuf + */ +int dialog_textbox(const char *title, char *tbuf, int initial_height, + int initial_width, int *keys, int *_vscroll, int *_hscroll, + update_text_fn update_text, void *data) +{ + int i, x, y, cur_x, cur_y, key = 0; + int height, width, boxh, boxw; + WINDOW *dialog, *box; + bool done = false; + + begin_reached = 1; + end_reached = 0; + page_length = 0; + hscroll = 0; + buf = tbuf; + page = buf; /* page is pointer to start of page to be displayed */ + + if (_vscroll && *_vscroll) { + begin_reached = 0; + + for (i = 0; i < *_vscroll; i++) + get_line(); + } + if (_hscroll) + hscroll = *_hscroll; + +do_resize: + getmaxyx(stdscr, height, width); + if (height < TEXTBOX_HEIGTH_MIN || width < TEXTBOX_WIDTH_MIN) + return -ERRDISPLAYTOOSMALL; + if (initial_height != 0) + height = initial_height; + else + if (height > 4) + height -= 4; + else + height = 0; + if (initial_width != 0) + width = initial_width; + else + if (width > 5) + width -= 5; + else + width = 0; + + /* center dialog box on screen */ + x = (WIN_COLS - width) / 2; + y = (WIN_ROWS - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + /* Create window for box region, used for scrolling text */ + boxh = height - 4; + boxw = width - 2; + box = subwin(dialog, boxh, boxw, y + 1, x + 1); + wattrset(box, dlg.dialog.atr); + wbkgdset(box, dlg.dialog.atr & A_COLOR); + + keypad(box, TRUE); + + /* register the new window, along with its borders */ + draw_box(dialog, 0, 0, height, width, + dlg.dialog.atr, dlg.border.atr); + + wattrset(dialog, dlg.border.atr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dlg.dialog.atr); + wbkgdset(dialog, dlg.dialog.atr & A_COLOR); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + print_button(dialog, gettext(" Exit "), height - 2, width / 2 - 4, TRUE); + wnoutrefresh(dialog); + getyx(dialog, cur_y, cur_x); /* Save cursor position */ + + /* Print first page of text */ + attr_clear(box, boxh, boxw, dlg.dialog.atr); + refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x, update_text, + data); + + while (!done) { + key = wgetch(dialog); + switch (key) { + case 'E': /* Exit */ + case 'e': + case 'X': + case 'x': + case 'q': + case '\n': + done = true; + break; + case 'g': /* First page */ + case KEY_HOME: + if (!begin_reached) { + begin_reached = 1; + page = buf; + refresh_text_box(dialog, box, boxh, boxw, + cur_y, cur_x, update_text, + data); + } + break; + case 'G': /* Last page */ + case KEY_END: + + end_reached = 1; + /* point to last char in buf */ + page = buf + strlen(buf); + back_lines(boxh); + refresh_text_box(dialog, box, boxh, boxw, cur_y, + cur_x, update_text, data); + break; + case 'K': /* Previous line */ + case 'k': + case KEY_UP: + if (begin_reached) + break; + + back_lines(page_length + 1); + refresh_text_box(dialog, box, boxh, boxw, cur_y, + cur_x, update_text, data); + break; + case 'B': /* Previous page */ + case 'b': + case 'u': + case KEY_PPAGE: + if (begin_reached) + break; + back_lines(page_length + boxh); + refresh_text_box(dialog, box, boxh, boxw, cur_y, + cur_x, update_text, data); + break; + case 'J': /* Next line */ + case 'j': + case KEY_DOWN: + if (end_reached) + break; + + back_lines(page_length - 1); + refresh_text_box(dialog, box, boxh, boxw, cur_y, + cur_x, update_text, data); + break; + case KEY_NPAGE: /* Next page */ + case ' ': + case 'd': + if (end_reached) + break; + + begin_reached = 0; + refresh_text_box(dialog, box, boxh, boxw, cur_y, + cur_x, update_text, data); + break; + case '0': /* Beginning of line */ + case 'H': /* Scroll left */ + case 'h': + case KEY_LEFT: + if (hscroll <= 0) + break; + + if (key == '0') + hscroll = 0; + else + hscroll--; + /* Reprint current page to scroll horizontally */ + back_lines(page_length); + refresh_text_box(dialog, box, boxh, boxw, cur_y, + cur_x, update_text, data); + break; + case 'L': /* Scroll right */ + case 'l': + case KEY_RIGHT: + if (hscroll >= MAX_LEN) + break; + hscroll++; + /* Reprint current page to scroll horizontally */ + back_lines(page_length); + refresh_text_box(dialog, box, boxh, boxw, cur_y, + cur_x, update_text, data); + break; + case KEY_ESC: + if (on_key_esc(dialog) == KEY_ESC) + done = true; + break; + case KEY_RESIZE: + back_lines(height); + delwin(box); + delwin(dialog); + on_key_resize(); + goto do_resize; + default: + for (i = 0; keys[i]; i++) { + if (key == keys[i]) { + done = true; + break; + } + } + } + } + delwin(box); + delwin(dialog); + if (_vscroll) { + const char *s; + + s = buf; + *_vscroll = 0; + back_lines(page_length); + while (s < page && (s = strchr(s, '\n'))) { + (*_vscroll)++; + s++; + } + } + if (_hscroll) + *_hscroll = hscroll; + return key; +} + +int dialog_textbox_simple(const char *title, char *tbuf, int initial_height, int initial_width) +{ + return dialog_textbox(title, tbuf,initial_height, initial_width, + (int []) {0}, NULL, NULL, NULL, NULL); +} + +/* + * Go back 'n' lines in text. Called by dialog_textbox(). + * 'page' will be updated to point to the desired line in 'buf'. + */ +static void back_lines(int n) +{ + int i; + + begin_reached = 0; + /* Go back 'n' lines */ + for (i = 0; i < n; i++) { + if (*page == '\0') { + if (end_reached) { + end_reached = 0; + continue; + } + } + if (page == buf) { + begin_reached = 1; + return; + } + page--; + do { + if (page == buf) { + begin_reached = 1; + return; + } + page--; + } while (*page != '\n'); + page++; + } +} + +/* + * Print a new page of text. + */ +static void print_page(WINDOW *win, int height, int width, update_text_fn + update_text, void *data) +{ + int i, passed_end = 0; + + if (update_text) { + char *end; + + for (i = 0; i < height; i++) + get_line(); + end = page; + back_lines(height); + update_text(buf, page - buf, end - buf, data); + } + + page_length = 0; + for (i = 0; i < height; i++) { + print_line(win, i, width); + if (!passed_end) + page_length++; + if (end_reached && !passed_end) + passed_end = 1; + } + wnoutrefresh(win); +} + +/* + * Print a new line of text. + */ +static void print_line(WINDOW * win, int row, int width) +{ + char *line; + + line = get_line(); + line += MIN(strlen(line), hscroll); /* Scroll horizontally */ + wmove(win, row, 0); /* move cursor to correct line */ + waddch(win, ' '); + waddnstr(win, line, MIN(strlen(line), width - 2)); + + /* Clear 'residue' of previous line */ +#if OLD_NCURSES + { + int x = getcurx(win); + int i; + for (i = 0; i < width - x; i++) + waddch(win, ' '); + } +#else + wclrtoeol(win); +#endif +} + +/* + * Return current line of text. Called by dialog_textbox() and print_line(). + * 'page' should point to start of current line before calling, and will be + * updated to point to start of next line. + */ +static char *get_line(void) +{ + int i = 0; + static char line[MAX_LEN + 1]; + + end_reached = 0; + while (*page != '\n') { + if (*page == '\0') { + end_reached = 1; + break; + } else if (i < MAX_LEN) + line[i++] = *(page++); + else { + /* Truncate lines longer than MAX_LEN characters */ + if (i == MAX_LEN) + line[i++] = '\0'; + page++; + } + } + if (i <= MAX_LEN) + line[i] = '\0'; + if (!end_reached) + page++; /* move past '\n' */ + + return line; +} + +/* + * Print current position + */ +static void print_position(WINDOW * win) +{ + int percent; + + wattrset(win, dlg.position_indicator.atr); + wbkgdset(win, dlg.position_indicator.atr & A_COLOR); + percent = (page - buf) * 100 / strlen(buf); + wmove(win, getmaxy(win) - 3, getmaxx(win) - 9); + wprintw(win, "(%3d%%)", percent); +} diff --git a/examples/camera/camera_demo3_srcs/util.c b/examples/camera/camera_demo3_srcs/util.c new file mode 100644 index 0000000..3bca4e4 --- /dev/null +++ b/examples/camera/camera_demo3_srcs/util.c @@ -0,0 +1,735 @@ +/* + * util.c + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#include "dialog.h" + +/* Needed in signal handler in mconf.c */ +int saved_x, saved_y; + +struct dialog_info dlg; + +static void set_mono_theme(void) +{ + dlg.screen.atr = A_NORMAL; + dlg.shadow.atr = A_NORMAL; + dlg.dialog.atr = A_NORMAL; + dlg.title.atr = A_BOLD; + dlg.border.atr = A_NORMAL; + dlg.button_active.atr = A_REVERSE; + dlg.button_inactive.atr = A_DIM; + dlg.button_key_active.atr = A_REVERSE; + dlg.button_key_inactive.atr = A_BOLD; + dlg.button_label_active.atr = A_REVERSE; + dlg.button_label_inactive.atr = A_NORMAL; + dlg.inputbox.atr = A_NORMAL; + dlg.inputbox_border.atr = A_NORMAL; + dlg.searchbox.atr = A_NORMAL; + dlg.searchbox_title.atr = A_BOLD; + dlg.searchbox_border.atr = A_NORMAL; + dlg.position_indicator.atr = A_BOLD; + dlg.menubox.atr = A_NORMAL; + dlg.menubox_border.atr = A_NORMAL; + dlg.item.atr = A_NORMAL; + dlg.item_selected.atr = A_REVERSE; + dlg.tag.atr = A_BOLD; + dlg.tag_selected.atr = A_REVERSE; + dlg.tag_key.atr = A_BOLD; + dlg.tag_key_selected.atr = A_REVERSE; + dlg.check.atr = A_BOLD; + dlg.check_selected.atr = A_REVERSE; + dlg.uarrow.atr = A_BOLD; + dlg.darrow.atr = A_BOLD; +} + +#define DLG_COLOR(dialog, f, b, h) \ +do { \ + dlg.dialog.fg = (f); \ + dlg.dialog.bg = (b); \ + dlg.dialog.hl = (h); \ +} while (0) + +static void set_classic_theme(void) +{ + DLG_COLOR(screen, COLOR_CYAN, COLOR_BLUE, true); + DLG_COLOR(shadow, COLOR_BLACK, COLOR_BLACK, true); + DLG_COLOR(dialog, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(title, COLOR_YELLOW, COLOR_WHITE, true); + DLG_COLOR(border, COLOR_WHITE, COLOR_WHITE, true); + DLG_COLOR(button_active, COLOR_WHITE, COLOR_BLUE, true); + DLG_COLOR(button_inactive, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(button_key_active, COLOR_WHITE, COLOR_BLUE, true); + DLG_COLOR(button_key_inactive, COLOR_RED, COLOR_WHITE, false); + DLG_COLOR(button_label_active, COLOR_YELLOW, COLOR_BLUE, true); + DLG_COLOR(button_label_inactive, COLOR_BLACK, COLOR_WHITE, true); + DLG_COLOR(inputbox, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(inputbox_border, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(searchbox, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(searchbox_title, COLOR_YELLOW, COLOR_WHITE, true); + DLG_COLOR(searchbox_border, COLOR_WHITE, COLOR_WHITE, true); + DLG_COLOR(position_indicator, COLOR_YELLOW, COLOR_WHITE, true); + DLG_COLOR(menubox, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(menubox_border, COLOR_WHITE, COLOR_WHITE, true); + DLG_COLOR(item, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(item_selected, COLOR_WHITE, COLOR_BLUE, true); + DLG_COLOR(tag, COLOR_YELLOW, COLOR_WHITE, true); + DLG_COLOR(tag_selected, COLOR_YELLOW, COLOR_BLUE, true); + DLG_COLOR(tag_key, COLOR_YELLOW, COLOR_WHITE, true); + DLG_COLOR(tag_key_selected, COLOR_YELLOW, COLOR_BLUE, true); + DLG_COLOR(check, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(check_selected, COLOR_WHITE, COLOR_BLUE, true); + DLG_COLOR(uarrow, COLOR_GREEN, COLOR_WHITE, true); + DLG_COLOR(darrow, COLOR_GREEN, COLOR_WHITE, true); +} + +static void set_blackbg_theme(void) +{ + DLG_COLOR(screen, COLOR_RED, COLOR_BLACK, true); + DLG_COLOR(shadow, COLOR_BLACK, COLOR_BLACK, false); + DLG_COLOR(dialog, COLOR_WHITE, COLOR_BLACK, false); + DLG_COLOR(title, COLOR_RED, COLOR_BLACK, false); + DLG_COLOR(border, COLOR_BLACK, COLOR_BLACK, true); + + DLG_COLOR(button_active, COLOR_YELLOW, COLOR_RED, false); + DLG_COLOR(button_inactive, COLOR_YELLOW, COLOR_BLACK, false); + DLG_COLOR(button_key_active, COLOR_YELLOW, COLOR_RED, true); + DLG_COLOR(button_key_inactive, COLOR_RED, COLOR_BLACK, false); + DLG_COLOR(button_label_active, COLOR_WHITE, COLOR_RED, false); + DLG_COLOR(button_label_inactive, COLOR_BLACK, COLOR_BLACK, true); + + DLG_COLOR(inputbox, COLOR_YELLOW, COLOR_BLACK, false); + DLG_COLOR(inputbox_border, COLOR_YELLOW, COLOR_BLACK, false); + + DLG_COLOR(searchbox, COLOR_YELLOW, COLOR_BLACK, false); + DLG_COLOR(searchbox_title, COLOR_YELLOW, COLOR_BLACK, true); + DLG_COLOR(searchbox_border, COLOR_BLACK, COLOR_BLACK, true); + + DLG_COLOR(position_indicator, COLOR_RED, COLOR_BLACK, false); + + DLG_COLOR(menubox, COLOR_YELLOW, COLOR_BLACK, false); + DLG_COLOR(menubox_border, COLOR_BLACK, COLOR_BLACK, true); + + DLG_COLOR(item, COLOR_WHITE, COLOR_BLACK, false); + DLG_COLOR(item_selected, COLOR_WHITE, COLOR_RED, false); + + DLG_COLOR(tag, COLOR_RED, COLOR_BLACK, false); + DLG_COLOR(tag_selected, COLOR_YELLOW, COLOR_RED, true); + DLG_COLOR(tag_key, COLOR_RED, COLOR_BLACK, false); + DLG_COLOR(tag_key_selected, COLOR_YELLOW, COLOR_RED, true); + + DLG_COLOR(check, COLOR_YELLOW, COLOR_BLACK, false); + DLG_COLOR(check_selected, COLOR_YELLOW, COLOR_RED, true); + + DLG_COLOR(uarrow, COLOR_RED, COLOR_BLACK, false); + DLG_COLOR(darrow, COLOR_RED, COLOR_BLACK, false); +} + +static void set_bluetitle_theme(void) +{ + set_classic_theme(); + DLG_COLOR(title, COLOR_BLUE, COLOR_WHITE, true); + DLG_COLOR(button_key_active, COLOR_YELLOW, COLOR_BLUE, true); + DLG_COLOR(button_label_active, COLOR_WHITE, COLOR_BLUE, true); + DLG_COLOR(searchbox_title, COLOR_BLUE, COLOR_WHITE, true); + DLG_COLOR(position_indicator, COLOR_BLUE, COLOR_WHITE, true); + DLG_COLOR(tag, COLOR_BLUE, COLOR_WHITE, true); + DLG_COLOR(tag_key, COLOR_BLUE, COLOR_WHITE, true); + +} + +/* + * Select color theme + */ +static int set_theme(const char *theme) +{ + int use_color = 1; + if (!theme) + set_bluetitle_theme(); + else if (strcmp(theme, "classic") == 0) + set_classic_theme(); + else if (strcmp(theme, "bluetitle") == 0) + set_bluetitle_theme(); + else if (strcmp(theme, "blackbg") == 0) + set_blackbg_theme(); + else if (strcmp(theme, "mono") == 0) + use_color = 0; + + return use_color; +} + +static void init_one_color(struct dialog_color *color) +{ + static int pair = 10; + + pair++; + init_pair(pair, color->fg, color->bg); + if (color->hl) + color->atr = A_BOLD | COLOR_PAIR(pair); + else + color->atr = COLOR_PAIR(pair); +} + +static void init_dialog_colors(void) +{ + init_one_color(&dlg.screen); + init_one_color(&dlg.shadow); + init_one_color(&dlg.dialog); + init_one_color(&dlg.title); + init_one_color(&dlg.border); + init_one_color(&dlg.button_active); + init_one_color(&dlg.button_inactive); + init_one_color(&dlg.button_key_active); + init_one_color(&dlg.button_key_inactive); + init_one_color(&dlg.button_label_active); + init_one_color(&dlg.button_label_inactive); + init_one_color(&dlg.inputbox); + init_one_color(&dlg.inputbox_border); + init_one_color(&dlg.searchbox); + init_one_color(&dlg.searchbox_title); + init_one_color(&dlg.searchbox_border); + init_one_color(&dlg.position_indicator); + init_one_color(&dlg.menubox); + init_one_color(&dlg.menubox_border); + init_one_color(&dlg.item); + init_one_color(&dlg.item_selected); + init_one_color(&dlg.tag); + init_one_color(&dlg.tag_selected); + init_one_color(&dlg.tag_key); + init_one_color(&dlg.tag_key_selected); + init_one_color(&dlg.check); + init_one_color(&dlg.check_selected); + init_one_color(&dlg.uarrow); + init_one_color(&dlg.darrow); +} + +/* + * Setup for color display + */ +static void color_setup(const char *theme) +{ + int use_color; + + use_color = set_theme(theme); + if (use_color && has_colors()) { + start_color(); + init_dialog_colors(); + } else + set_mono_theme(); +} + +/* + * Set window to attribute 'attr' + */ +void attr_clear(WINDOW * win, int height, int width, chtype attr) +{ + int i, j; + + wattrset(win, attr); + for (i = 0; i < height; i++) { + wmove(win, i, 0); + for (j = 0; j < width; j++) + waddch(win, ' '); + } + touchwin(win); +} + +void dialog_clear(void) +{ + int lines, columns; + + lines = getmaxy(stdscr); + columns = getmaxx(stdscr); + + attr_clear(stdscr, lines, columns, dlg.screen.atr); + /* Display background title if it exists ... - SLH */ + if (dlg.backtitle != NULL) { + int i, len = 0, skip = 0; + struct subtitle_list *pos; + + wattrset(stdscr, dlg.screen.atr); + mvwaddstr(stdscr, 0, 1, (char *)dlg.backtitle); + + for (pos = dlg.subtitles; pos != NULL; pos = pos->next) { + /* 3 is for the arrow and spaces */ + len += strlen(pos->text) + 3; + } + + wmove(stdscr, 1, 1); + if (len > columns - 2) { + const char *ellipsis = "[...] "; + waddstr(stdscr, ellipsis); + skip = len - (columns - 2 - strlen(ellipsis)); + } + + for (pos = dlg.subtitles; pos != NULL; pos = pos->next) { + if (skip == 0) + waddch(stdscr, ACS_RARROW); + else + skip--; + + if (skip == 0) + waddch(stdscr, ' '); + else + skip--; + + if (skip < strlen(pos->text)) { + waddstr(stdscr, pos->text + skip); + skip = 0; + } else + skip -= strlen(pos->text); + + if (skip == 0) + waddch(stdscr, ' '); + else + skip--; + } + + for (i = len + 1; i < columns - 1; i++) + waddch(stdscr, ACS_HLINE); + } + wnoutrefresh(stdscr); +} + +/* + * Do some initialization for dialog + */ +int init_dialog(const char *backtitle) +{ + int height, width; + + //initscr(); /* Init curses */ + + /* Get current cursor position for signal handler in mconf.c */ + getyx(stdscr, saved_y, saved_x); + + getmaxyx(stdscr, height, width); + if (height < WINDOW_HEIGTH_MIN || width < WINDOW_WIDTH_MIN) { + endwin(); + return -ERRDISPLAYTOOSMALL; + } + + dlg.backtitle = backtitle; + color_setup("classic"); + + keypad(stdscr, TRUE); + cbreak(); + noecho(); + dialog_clear(); + + return 0; +} + +void set_dialog_backtitle(const char *backtitle) +{ + dlg.backtitle = backtitle; +} + +void set_dialog_subtitles(struct subtitle_list *subtitles) +{ + dlg.subtitles = subtitles; +} + +/* + * End using dialog functions. + */ +void end_dialog(int x, int y) +{ + /* move cursor back to original position */ + move(y, x); + refresh(); + endwin(); +} + +/* Print the title of the dialog. Center the title and truncate + * tile if wider than dialog (- 2 chars). + **/ +void print_title(WINDOW *dialog, const char *title, int width) +{ + if (title) { + int tlen = MIN(width - 2, strlen(title)); + wattrset(dialog, dlg.title.atr); + mvwaddch(dialog, 0, (width - tlen) / 2 - 1, ' '); + mvwaddnstr(dialog, 0, (width - tlen)/2, title, tlen); + waddch(dialog, ' '); + } +} + +/* + * Print a string of text in a window, automatically wrap around to the + * next line if the string is too long to fit on one line. Newline + * characters '\n' are propperly processed. We start on a new line + * if there is no room for at least 4 nonblanks following a double-space. + */ +void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x) +{ + int newl, cur_x, cur_y; + int prompt_len, room, wlen; + char tempstr[MAX_LEN + 1], *word, *sp, *sp2, *newline_separator = 0; + + strcpy(tempstr, prompt); + + prompt_len = strlen(tempstr); + + if (prompt_len <= width - x * 2) { /* If prompt is short */ + wmove(win, y, (width - prompt_len) / 2); + waddstr(win, tempstr); + } else { + cur_x = x; + cur_y = y; + newl = 1; + word = tempstr; + while (word && *word) { + sp = strpbrk(word, "\n "); + if (sp && *sp == '\n') + newline_separator = sp; + + if (sp) + *sp++ = 0; + + /* Wrap to next line if either the word does not fit, + or it is the first word of a new sentence, and it is + short, and the next word does not fit. */ + room = width - cur_x; + wlen = strlen(word); + if (wlen > room || + (newl && wlen < 4 && sp + && wlen + 1 + strlen(sp) > room + && (!(sp2 = strpbrk(sp, "\n ")) + || wlen + 1 + (sp2 - sp) > room))) { + cur_y++; + cur_x = x; + } + wmove(win, cur_y, cur_x); + waddstr(win, word); + getyx(win, cur_y, cur_x); + + /* Move to the next line if the word separator was a newline */ + if (newline_separator) { + cur_y++; + cur_x = x; + newline_separator = 0; + } else + cur_x++; + + if (sp && *sp == ' ') { + cur_x++; /* double space */ + while (*++sp == ' ') ; + newl = 1; + } else + newl = 0; + word = sp; + } + } +} + +/* + * Print a button + */ +void print_button(WINDOW * win, const char *label, int y, int x, int selected) +{ + int i, temp; + + wmove(win, y, x); + wattrset(win, selected ? dlg.button_active.atr + : dlg.button_inactive.atr); + waddstr(win, "<"); + temp = strspn(label, " "); + label += temp; + wattrset(win, selected ? dlg.button_label_active.atr + : dlg.button_label_inactive.atr); + for (i = 0; i < temp; i++) + waddch(win, ' '); + wattrset(win, selected ? dlg.button_key_active.atr + : dlg.button_key_inactive.atr); + waddch(win, label[0]); + wattrset(win, selected ? dlg.button_label_active.atr + : dlg.button_label_inactive.atr); + waddstr(win, (char *)label + 1); + wattrset(win, selected ? dlg.button_active.atr + : dlg.button_inactive.atr); + waddstr(win, ">"); + wmove(win, y, x + temp + 1); +} + +/* + * Draw a rectangular box with line drawing characters + */ +void +draw_box(WINDOW * win, int y, int x, int height, int width, + chtype box, chtype border) +{ + int i, j; + + wattrset(win, 0); + for (i = 0; i < height; i++) { + wmove(win, y + i, x); + for (j = 0; j < width; j++) + if (!i && !j) + waddch(win, border | ACS_ULCORNER); + else if (i == height - 1 && !j) + waddch(win, border | ACS_LLCORNER); + else if (!i && j == width - 1) + waddch(win, box | ACS_URCORNER); + else if (i == height - 1 && j == width - 1) + waddch(win, box | ACS_LRCORNER); + else if (!i) + waddch(win, border | ACS_HLINE); + else if (i == height - 1) + waddch(win, box | ACS_HLINE); + else if (!j) + waddch(win, border | ACS_VLINE); + else if (j == width - 1) + waddch(win, box | ACS_VLINE); + else + waddch(win, box | ' '); + } +} + +/* + * Draw shadows along the right and bottom edge to give a more 3D look + * to the boxes + */ +void draw_shadow(WINDOW * win, int y, int x, int height, int width) +{ + int i; + + if (has_colors()) { /* Whether terminal supports color? */ + wattrset(win, dlg.shadow.atr); + wmove(win, y + height, x + 2); + for (i = 0; i < width; i++) + waddch(win, winch(win) & A_CHARTEXT); + for (i = y + 1; i < y + height + 1; i++) { + wmove(win, i, x + width); + waddch(win, winch(win) & A_CHARTEXT); + waddch(win, winch(win) & A_CHARTEXT); + } + wnoutrefresh(win); + } +} + +/* + * Return the position of the first alphabetic character in a string. + */ +int first_alpha(const char *string, const char *exempt) +{ + int i, in_paren = 0, c; + + for (i = 0; i < strlen(string); i++) { + c = tolower(string[i]); + + if (strchr("<[(", c)) + ++in_paren; + if (strchr(">])", c) && in_paren > 0) + --in_paren; + + if ((!in_paren) && isalpha(c) && strchr(exempt, c) == 0) + return i; + } + + return 0; +} + +/* + * ncurses uses ESC to detect escaped char sequences. This resutl in + * a small timeout before ESC is actually delivered to the application. + * lxdialog suggest which is correctly translated to two + * times esc. But then we need to ignore the second esc to avoid stepping + * out one menu too much. Filter away all escaped key sequences since + * keypad(FALSE) turn off ncurses support for escape sequences - and thats + * needed to make notimeout() do as expected. + */ +int on_key_esc(WINDOW *win) +{ + int key; + int key2; + int key3; + + nodelay(win, TRUE); + keypad(win, FALSE); + key = wgetch(win); + key2 = wgetch(win); + do { + key3 = wgetch(win); + } while (key3 != ERR); + nodelay(win, FALSE); + keypad(win, TRUE); + if (key == KEY_ESC && key2 == ERR) + return KEY_ESC; + else if (key != ERR && key != KEY_ESC && key2 == ERR) + ungetch(key); + + return -1; +} + +/* redraw screen in new size */ +int on_key_resize(void) +{ + dialog_clear(); + return KEY_RESIZE; +} + +struct dialog_list *item_cur; +struct dialog_list item_nil; +struct dialog_list *item_head; + +void item_reset(void) +{ + struct dialog_list *p, *next; + + for (p = item_head; p; p = next) { + next = p->next; + free(p); + } + item_head = NULL; + item_cur = &item_nil; +} + +void item_make(const char *fmt, ...) +{ + va_list ap; + struct dialog_list *p = malloc(sizeof(*p)); + + if (item_head) + item_cur->next = p; + else + item_head = p; + item_cur = p; + memset(p, 0, sizeof(*p)); + + va_start(ap, fmt); + vsnprintf(item_cur->node.str, sizeof(item_cur->node.str), fmt, ap); + va_end(ap); +} + +void item_add_str(const char *fmt, ...) +{ + va_list ap; + size_t avail; + + avail = sizeof(item_cur->node.str) - strlen(item_cur->node.str); + + va_start(ap, fmt); + vsnprintf(item_cur->node.str + strlen(item_cur->node.str), + avail, fmt, ap); + item_cur->node.str[sizeof(item_cur->node.str) - 1] = '\0'; + va_end(ap); +} + +void item_set_tag(char tag) +{ + item_cur->node.tag = tag; +} + +void item_set_int(int int_value) +{ + item_cur->node.int_value = int_value; +} + +void item_set_data(void *ptr) +{ + item_cur->node.data = ptr; +} + +void item_set_selected(int val) +{ + item_cur->node.selected = val; +} + +int item_activate_selected(void) +{ + item_foreach() + if (item_is_selected()) + return 1; + return 0; +} + +int item_activate_selected_pos(void) +{ + int i = 0; + item_foreach() { + if (item_is_selected()) + return i; + i++; + } + return -1; +} + +void *item_data(void) +{ + return item_cur->node.data; +} + +char item_tag(void) +{ + return item_cur->node.tag; +} + +int item_int(void) +{ + return item_cur->node.int_value; +} + +int item_count(void) +{ + int n = 0; + struct dialog_list *p; + + for (p = item_head; p; p = p->next) + n++; + return n; +} + +void item_set(int n) +{ + int i = 0; + item_foreach() + if (i++ == n) + return; +} + +int item_n(void) +{ + int n = 0; + struct dialog_list *p; + + for (p = item_head; p; p = p->next) { + if (p == item_cur) + return n; + n++; + } + return 0; +} + +const char *item_str(void) +{ + return item_cur->node.str; +} + +int item_is_selected(void) +{ + return (item_cur->node.selected != 0); +} + +int item_is_tag(char tag) +{ + return (item_cur->node.tag == tag); +} diff --git a/examples/camera/camera_demo3_srcs/yesno.c b/examples/camera/camera_demo3_srcs/yesno.c new file mode 100644 index 0000000..193d6ba --- /dev/null +++ b/examples/camera/camera_demo3_srcs/yesno.c @@ -0,0 +1,123 @@ +/* + * yesno.c -- implements the yes/no box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define LOG_LEVEL 2 +//#define LOG_PREFIX "" +#include + +#include "dialog.h" +#include "app_dialogs.h" + +/* + * Display termination buttons + */ +static void print_buttons(WINDOW * dialog, int height, int width, int selected) +{ + int x = width / 2 - 10; + int y = height - 2; + + print_button(dialog, gettext(" Yes "), y, x, selected == 0); + print_button(dialog, gettext(" No "), y, x + 13, selected == 1); + + wmove(dialog, y, x + 1 + 13 * selected); + wrefresh(dialog); +} + +/* + * Display a dialog box with two buttons - Yes and No + */ +int dialog_yesno(const char *title, const char *prompt, int height, int width) +{ + int i, x, y, key = 0, button = 0; + WINDOW *dialog; + +do_resize: + if (WIN_ROWS < (height + YESNO_HEIGTH_MIN)) + return -ERRDISPLAYTOOSMALL; + if (WIN_COLS < (width + YESNO_WIDTH_MIN)) + return -ERRDISPLAYTOOSMALL; + + /* center dialog box on screen */ + x = (WIN_COLS - width) / 2; + y = (WIN_ROWS - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + //wbkgd(dialog, COLOR_PAIR(0)); + draw_box(dialog, 0, 0, height, width, + dlg.dialog.atr, dlg.border.atr); + + wattrset(dialog, dlg.border.atr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dlg.dialog.atr); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dlg.dialog.atr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + print_buttons(dialog, height, width, 0); + + while (key != KEY_ESC) { + key = wgetch(dialog); + switch (key) { + case 'Y': + case 'y': + delwin(dialog); + return 0; + case 'N': + case 'n': + delwin(dialog); + return 1; + + case TAB: + case KEY_LEFT: + case KEY_RIGHT: + button = ((key == KEY_LEFT ? --button : ++button) < 0) ? 1 : (button > 1 ? 0 : button); + + print_buttons(dialog, height, width, button); + wrefresh(dialog); + break; + case ' ': + case '\n': + delwin(dialog); + return button; + case KEY_ESC: + key = on_key_esc(dialog); + break; + case KEY_RESIZE: + //delwin(dialog); + //on_key_resize(); + //goto do_resize; + ; + } + } + + delwin(dialog); + return key; /* ESC pressed */ +} diff --git a/examples/camera/camera_demo4.c b/examples/camera/camera_demo4.c new file mode 100644 index 0000000..05ef418 --- /dev/null +++ b/examples/camera/camera_demo4.c @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "camera_demo4" +#include + +#include +#include + +#define TEST_DEVICE_NAME "/dev/video0" + +typedef struct cam_channel_control { + pthread_t thread_id; + bool run; + csi_cam_handle_t cam_handle; + csi_camera_channel_cfg_s chn_cfg; +} cam_channel_control_t; + +static void dump_camera_meta(csi_frame_s *frame); +static void *channel_thread(void *arg); + +int main(int argc, char *argv[]) +{ + bool running = true; + csi_camera_info_s camera_info; + + // 获取设备中,所有的Camera + struct csi_camera_infos camera_infos; + csi_camera_query_list(&camera_infos); + LOG_O("csi_camera_query_list() OK\n"); + + // 打印所有设备所支持的Camera + for (uint32_t i = 0; i < camera_infos.count; i++) { + printf("Camera[%d]: camera_name='%s', device_name='%s', bus_info='%s', capabilities=0x%08x\n", + i, camera_infos.info[i].camera_name, camera_infos.info[i].device_name, + camera_infos.info[i].bus_info, camera_infos.info[i].capabilities); + } + /* Camera[0]: camera_name='RGB_Camera', device_name='/dev/video0', bus_info='CSI-MIPI', capabilities=0x00800001 + * Camera[1]: camera_name:'Mono_Camera', device_name:'/dev/video8', bus_info='USB', capabilities=0x00000001 + */ + + bool found_camera = false; + for (uint32_t i = 0; i < camera_infos.count; i++) { + if (strcmp(camera_infos.info[i].device_name, TEST_DEVICE_NAME) == 0) { + camera_info = camera_infos.info[i]; + printf("found device_name:'%s'\n", camera_info.device_name); + found_camera = true; + break; + } + } + if (!found_camera) { + LOG_E("Can't find camera_name:'%s'\n", TEST_DEVICE_NAME); + exit(-1); + } + + printf("The caps are:\n"); + for (uint32_t i = 1; i < 0x80000000; i = i << 1) { + switch (camera_info.capabilities & i) { + case CSI_CAMERA_CAP_VIDEO_CAPTURE: + printf("\t Video capture\n"); + break; + case CSI_CAMERA_CAP_META_CAPTURE: + printf("\t metadata capture\n"); + break; + default: + if (camera_info.capabilities & i) { + printf("\t unknown capabilities(0x%08x)\n", camera_info.capabilities & i); + } + break; + } + } + /* The caps are: Video capture, metadata capture */ + + // 打开Camera设备获取句柄,作为后续操对象 + csi_cam_handle_t cam_handle; + csi_camera_open(&cam_handle, camera_info.device_name); + LOG_O("csi_camera_open() OK\n"); + + // 获取Camera支持的工作模式 + struct csi_camera_modes camera_modes; + camera_modes.count = 0; + csi_camera_get_modes(cam_handle, &camera_modes); + LOG_O("csi_camera_get_modes() OK\n"); + + // 打印camera所支持的所有工作模式 + printf(" Camera:'%s' modes are:{\n", TEST_DEVICE_NAME); + for (uint32_t i = 0; i < camera_modes.count; i++) { + printf("\t mode_id=%d: description:'%s'\n", + camera_modes.modes[i].mode_id, camera_modes.modes[i].description); + } + printf("}\n"); + + // 设置camera的工作模式及其配置 + csi_camera_mode_cfg_s camera_cfg; + camera_cfg.mode_id = 1; + camera_cfg.calibriation = NULL; // 采用系统默认配置 + camera_cfg.lib3a = NULL; // 采用系统默认配置 + csi_camera_set_mode(cam_handle, &camera_cfg); + + // 获取单个可控单元的属性 + csi_camera_property_description_s description; + description.id = CSI_CAMERA_PID_HFLIP; + csi_camera_query_property(cam_handle, &description); + printf("properity id=0x%08x type=%d default=%d value=%d\n", + description.id, description.type, + description.default_value.bool_value, description.value.bool_value); + /* id=0x0098090x, type=2 default=0 value=1 */ + /* Other example: + * id=0x0098090y, type=3 min=0 max=255 step=1 default=127 value=116 + * id=0x0098090z, type=4 min=0 max=3 default=0 value=2 + * 0: IDLE + * 1: BUSY + * 2: REACHED + * 3: FAILED + */ + + // 轮询获取所有可控制的单元 + description.id = CSI_CAMERA_PID_HFLIP; + while (!csi_camera_query_property(cam_handle, &description)) { + //printf(...); // 打印属性 + description.id |= CSI_CAMERA_FLAG_NEXT_CTRL; + } + LOG_O("csi_camera_query_property() OK\n"); + + // 同时配置多个参数 + csi_camera_properties_s properties; + csi_camera_property_s property[2]; + property[0].id = CSI_CAMERA_PID_HFLIP; + property[0].type = CSI_CAMERA_PROPERTY_TYPE_BOOLEAN; + property[0].value.bool_value = true; + property[0].id = CSI_CAMERA_PID_VFLIP; + property[0].type = CSI_CAMERA_PROPERTY_TYPE_BOOLEAN; + property[0].value.bool_value = true; + properties.count = 2; + properties.property = property; + csi_camera_get_property(cam_handle, &properties); + LOG_O("csi_camera_get_property() OK\n"); + + cam_channel_control_t channel_control[2]; + + for (int i = 0; i < 2; i++) { + cam_channel_control_t *chn_ctrl = &channel_control[i]; + pthread_t *thread_id = &(chn_ctrl->thread_id); + csi_camera_channel_cfg_s *chn_cfg = &(chn_ctrl->chn_cfg); + chn_ctrl->run = true; + chn_ctrl->cam_handle = cam_handle; + + chn_cfg->frm_cnt = 4; + if (i = 0) { + chn_cfg->chn_id = CSI_CAMERA_CHANNEL_0; + chn_cfg->img_fmt.width = 640; + chn_cfg->img_fmt.height = 480; + chn_cfg->img_fmt.pix_fmt = CSI_PIX_FMT_NV12; + } else { + chn_cfg->chn_id = CSI_CAMERA_CHANNEL_1; + chn_cfg->img_fmt.width = 1280; + chn_cfg->img_fmt.height = 720; + chn_cfg->img_fmt.pix_fmt = CSI_PIX_FMT_I420; + } + chn_cfg->meta_fields = CSI_CAMERA_META_DEFAULT_FIELDS; + chn_cfg->capture_type = CSI_CAMERA_CHANNEL_CAPTURE_VIDEO | + CSI_CAMERA_CHANNEL_CAPTURE_META; + + pthread_create(thread_id, NULL, channel_thread, chn_ctrl); + sleep(1); // channel[1] start after 1 second + } + sleep(3); + channel_control[0].run = false; + sleep(2); + channel_control[1].run = false; + + for (int i = 0; i < 2; i++) { + cam_channel_control_t *chn_ctrl = &channel_control[i]; + int chn_id = chn_ctrl->chn_cfg.chn_id; + if (pthread_join(chn_ctrl->thread_id, NULL)) { + LOG_E("[chn-%d] pthread_join() failed, %s\n", chn_id, strerror(errno)); + } + } + + csi_camera_close(cam_handle); + return 0; +} + +static void *channel_thread(void *arg) +{ + int ret; + cam_channel_control_t *chn_ctrl = (cam_channel_control_t *)arg; + csi_cam_handle_t cam_handle = chn_ctrl->cam_handle; + int chn_id = chn_ctrl->chn_cfg.chn_id; + + ret = csi_camera_channel_open(cam_handle, &(chn_ctrl->chn_cfg)); + if (ret != 0) { + LOG_E("[chn_%d] csi_camera_channel_open() failed, ret=%d\n", chn_id, ret); + return NULL; + } + LOG_O("[chn_%d] csi_camera_channel_open() OK\n", chn_id); + + // 订阅Event + csi_cam_event_handle_t event_handle; + csi_camera_create_event(&event_handle, cam_handle); + struct csi_camera_event_subscription subscribe_cam; + struct csi_camera_event_subscription subscribe_chn; + subscribe_cam.type = CSI_CAMERA_EVENT_TYPE_CAMERA; // 订阅Camera的ERROR事件 + subscribe_cam.id = CSI_CAMERA_EVENT_WARNING | CSI_CAMERA_EVENT_ERROR; + csi_camera_subscribe_event(event_handle, &subscribe_cam); + + subscribe_chn.type = chn_id; // 订阅Channel0的FRAME_READY事件 + subscribe_chn.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY | + CSI_CAMERA_CHANNEL_EVENT_OVERFLOW; + csi_camera_subscribe_event(event_handle, &subscribe_chn); + LOG_O("Event subscript OK\n"); + + // 处理订阅的Event + LOG_O("[chn_%d] Starting get events...\n", chn_id); + csi_frame_s frame; + struct csi_camera_event event; + + while (chn_ctrl->run) { + int timeout = 100; // unit: ms, -1 means wait forever, or until error occurs + LOG_D("[chn_%d] before csi_camera_get_event\n", chn_id); + csi_camera_get_event(event_handle, &event, timeout); + LOG_D("[chn_%d] after csi_camera_get_event\n", chn_id); + + switch (event.type) { + case CSI_CAMERA_EVENT_TYPE_CAMERA: + switch (event.id) { + case CSI_CAMERA_EVENT_ERROR: + // do sth. + break; + default: + break; + } + case CSI_CAMERA_EVENT_TYPE_CHANNEL0: + case CSI_CAMERA_EVENT_TYPE_CHANNEL1: + if (event.type != chn_id) { + LOG_E("[chn_%d] Recv type(%d) is not expected\n", + chn_id, event.type); + break; + } + switch (event.id) { + case CSI_CAMERA_CHANNEL_EVENT_FRAME_READY: { + LOG_O("[chn_%d] Get ready frame\n", chn_id); + int read_frame_count = csi_camera_get_frame_count(cam_handle, chn_id); + for (int i = 0; i < read_frame_count; i++) { + csi_camera_get_frame(cam_handle, chn_id, &frame, timeout); + dump_camera_meta(&frame); + csi_camera_put_frame(&frame); + } + break; + } + default: + LOG_W("[chn_%d] Get unknown event type\n", event.type); + break; + } + default: + break; + } + } + + csi_camera_unsubscribe_event(event_handle, &subscribe_cam); + csi_camera_unsubscribe_event(event_handle, &subscribe_chn); + csi_camera_destory_event(event_handle); + csi_camera_channel_stop(cam_handle, chn_id); + pthread_exit(NULL); +} + +static void dump_camera_meta(csi_frame_s *frame) +{ + int i; + if (frame->meta.type != CSI_META_TYPE_CAMERA) + return; + + csi_camera_meta_s *meta_data = (csi_camera_meta_s *)frame->meta.data; + int meta_count = meta_data->count; + csi_camrea_meta_unit_s meta_unit; + + for (i = 0; i < meta_count; i++) { + csi_camera_frame_get_meta_unit( + &meta_unit, meta_data, CSI_CAMERA_META_ID_FRAME_ID); + printf("meta_id=%d, meta_type=%d, meta_value=%d", + meta_unit.id, meta_unit.type, meta_unit.int_value); + } +} + diff --git a/examples/camera/camera_frame_display.c b/examples/camera/camera_frame_display.c new file mode 100644 index 0000000..b7b4461 --- /dev/null +++ b/examples/camera/camera_frame_display.c @@ -0,0 +1,438 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: ShenWuYi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include "process_linker_types.h" +#include "process_linker.h" +#include "video_mem.h" + + +#define NUM_OF_BUFFERS 5 + +typedef struct _CsiPictureBuffer +{ + unsigned int bus_address; + void *virtual_address; + unsigned int size; + int fd; +} CsiPictureBuffer; +typedef struct _CsiPlinkContext +{ + int useplink; + int exitplink; + CsiPictureBuffer sendbuffer[NUM_OF_BUFFERS]; + int sendid; + int available_bufs; + void *vmem; + PlinkHandle plink; + PlinkChannelID chnid; +} CsiPlinkContext; +static int get_buffer_count(PlinkPacket *pkt) +{ + int ret = 0; + for (int i = 0; i < pkt->num; i++) + { + PlinkDescHdr *hdr = (PlinkDescHdr *)(pkt->list[i]); + if (hdr->type == PLINK_TYPE_MESSAGE) + { + int *data = (int *)(pkt->list[i] + DATA_HEADER_SIZE); + if (*data == PLINK_EXIT_CODE) + { + ret |= 0x80000000; // set bit 31 to 1 to indicate 'exit' + } + else if (*data >= 0) + ret++; + } + } + + return ret; +} + +static void allocate_sendbuffers(CsiPictureBuffer picbuffers[NUM_OF_BUFFERS], unsigned int size, void *vmem) +{ + unsigned int buffer_size = (size + 0xFFF) & ~0xFFF; + VmemParams params; + params.size = buffer_size; + params.flags = VMEM_FLAG_CONTIGUOUS | VMEM_FLAG_4GB_ADDR; + for (int i = 0; i < NUM_OF_BUFFERS; i++) + { + VMEM_allocate(vmem, ¶ms); + VMEM_mmap(vmem, ¶ms); + VMEM_export(vmem, ¶ms); + LOG_O("[SERVER] mmap %p from %x with size %d, dma-buf fd %d\n", + params.vir_address, params.phy_address, params.size, params.fd); + picbuffers[i].virtual_address = params.vir_address; + picbuffers[i].bus_address = params.phy_address; + picbuffers[i].size = buffer_size; + picbuffers[i].fd = params.fd; + } +} + +void *vi_plink_create(csi_camera_channel_cfg_s *chn_cfg) +{ + int ret = 0; + CsiPlinkContext *plink_ctx = NULL; + char *env=NULL; + if (chn_cfg == NULL) { + LOG_E("%s failt to get chn_cfg\n", __func__); + return NULL; + } + + plink_ctx = malloc(sizeof(CsiPlinkContext)); + if(!plink_ctx) + { + LOG_E("malloc faile\n"); + return NULL; + } + + char *plinknamebase0 = "/tmp/plink.test"; + char *plinknamebase1 = "/tmp/plink_npu_rgb.test"; + char *plinknamebase2 = "/tmp/plink.test2"; + env = getenv("ISP_PLINK_NAME0"); + + if (env != NULL) { + plinknamebase0 = env; + } + + env = getenv("ISP_PLINK_NAME1"); + if (env != NULL) { + plinknamebase1 = env; + } + + env = getenv("ISP_PLINK_NAME2"); + if (env != NULL) { + plinknamebase2 = env; + } + char plinkname[32]; + if (chn_cfg->chn_id == CSI_CAMERA_CHANNEL_0) { + sprintf(plinkname, "%s", plinknamebase0); + } else if (chn_cfg->chn_id == CSI_CAMERA_CHANNEL_1) { + sprintf(plinkname, "%s", plinknamebase1); + } else { + sprintf(plinkname, "%s", plinknamebase2); + } + fprintf(stderr, "%s, %d: ISP_PLINK_NAME = %s\n", __func__, __LINE__, plinkname); + + + plink_ctx->exitplink = 0; + fprintf(stderr, "%s, %d: Launching plink server...\n", __func__, __LINE__); + memset(&plink_ctx->sendbuffer[0], 0, sizeof(plink_ctx->sendbuffer)); + if (VMEM_create(&plink_ctx->vmem) != VMEM_STATUS_OK) + { + fprintf(stderr, "Failed to create VMEM."); + return NULL; + } + else { + int framesize = 0; + int stride = (chn_cfg->img_fmt.width + 127) & (~127); // align stride to 128 + switch (chn_cfg->img_fmt.pix_fmt) { + case CSI_PIX_FMT_BGR: + { + framesize = stride * 304 * 3; //stride * chn_cfg->img_fmt.height * 3; + allocate_sendbuffers(plink_ctx->sendbuffer, framesize, plink_ctx->vmem); + // reset to black picture + uint32_t lumasize = stride * chn_cfg->img_fmt.height; + for (int i = 0; i < NUM_OF_BUFFERS; i++) + memset(plink_ctx->sendbuffer[i].virtual_address, 0, framesize); + break; + } + + case CSI_PIX_FMT_NV12: + default: + { + framesize = stride * chn_cfg->img_fmt.height * 3 / 2; + allocate_sendbuffers(plink_ctx->sendbuffer, framesize, plink_ctx->vmem); + // reset to black picture + uint32_t lumasize = stride * chn_cfg->img_fmt.height; + for (int i = 0; i < NUM_OF_BUFFERS; i++) { + CsiPictureBuffer *buf = &plink_ctx->sendbuffer[i]; + memset(buf->virtual_address, 0, lumasize); + memset(buf->virtual_address + lumasize, 0x80, lumasize/2); + } + break; + } + } + } + + PLINK_create(&plink_ctx->plink, plinkname, PLINK_MODE_SERVER); + if (plink_ctx->plink) { + PLINK_connect(plink_ctx->plink, &plink_ctx->chnid); + } + + plink_ctx->sendid = 0; + plink_ctx->available_bufs = NUM_OF_BUFFERS; + + return plink_ctx; +} + +void vi_plink_release(void * plink) +{ + CsiPlinkContext * plink_ctx = (CsiPlinkContext *)plink; + if(plink_ctx) + { + PLINK_close(plink_ctx->plink,plink_ctx->chnid); + VMEM_destroy(plink_ctx->vmem); + } +} + + +void display_camera_frame(void *plink, csi_frame_s *frame) +{ + + CsiPlinkContext * plink_ctx = (CsiPlinkContext *)plink; + if ((plink_ctx == NULL) || (frame == NULL)) { + LOG_E("%s check param fail\n", __func__); + return; + } + + + LOG_O("%sfmt=%d img.strides[0] = %d\n", __func__,frame->img.pix_format, frame->img.strides[0]); + + if (!plink_ctx->exitplink) { + struct timeval tv_start, tv_end, tv_duration; + gettimeofday(&tv_start, 0); + // retrieve buffers + PlinkPacket pkt = {0}; + if (plink_ctx->plink != NULL) { + while (PLINK_wait(plink_ctx->plink, plink_ctx->chnid, 0) == PLINK_STATUS_OK) + { + if (PLINK_recv(plink_ctx->plink, plink_ctx->chnid, &pkt) != PLINK_STATUS_OK) { + plink_ctx->exitplink = 1; + break; + } + int count = get_buffer_count(&pkt); + if (count >= 0) + plink_ctx->available_bufs += count; + else { + fprintf(stderr, "[SERVER] Exit!\n"); + plink_ctx->exitplink = 1; + break; + } + } + } + + /* + void *phy_address = NULL; + int dmabuf_fd = -1; + if (vi_mem_dmabuf_isenable()) { + phy_address = vi_mem_unmap(frame->img.usr_addr[0]); + dmabuf_fd = vi_mem_export(phy_address); + } + */ + + // send one buffer if there is available slot + if (frame->img.type == CSI_IMG_TYPE_DMA_BUF && !plink_ctx->exitplink + && plink_ctx->available_bufs > 0 && plink_ctx->plink != NULL && ((frame->img.pix_format == CSI_PIX_FMT_RAW_8BIT)|| (frame->img.pix_format == CSI_PIX_FMT_RAW_12BIT))) { + char str[200]; + static int i = 0; + uint32_t dstw = frame->img.width;//800; + uint32_t dsth = frame->img.height;//1280; + uint32_t dsts = frame->img.strides[0];//896; + i++; + sprintf(str, "echo frame is %d > ~/frame", i); + system(str); + + PlinkRawInfo info = {0}; + + info.header.type = PLINK_TYPE_2D_RAW; + info.header.size = DATA_SIZE(info); + info.header.id = plink_ctx->sendid + 1; + + info.format = PLINK_COLOR_FormatRawBayer10bit; + // info.bus_address = vi_mem_import(frame->img.dmabuf[0].fds) + frame->img.dmabuf[0].offset; + // vi_mem_release(info.bus_address); + + info.img_width = dstw; + info.img_height = dsth; + info.stride = frame->img.strides[0]; + info.offset = 0; + pkt.list[0] = &info; + pkt.num = 1; + pkt.fd = frame->img.dmabuf[0].fds; + if (PLINK_send(plink_ctx->plink, plink_ctx->chnid, &pkt) != PLINK_STATUS_OK) + plink_ctx->exitplink = 1; + gettimeofday(&tv_end, 0); + timersub(&tv_end, &tv_start, &tv_duration); + plink_ctx->sendid = (plink_ctx->sendid + 1) % NUM_OF_BUFFERS; + plink_ctx->available_bufs -= 1; + } + else if (frame->img.type == CSI_IMG_TYPE_DMA_BUF && !plink_ctx->exitplink + && plink_ctx->available_bufs > 0 && plink_ctx->plink != NULL && ((frame->img.pix_format == CSI_PIX_FMT_YUV_SEMIPLANAR_420)|| (frame->img.pix_format == CSI_PIX_FMT_NV12))) { + char str[200]; + static int i = 0; + uint32_t dstw = frame->img.width;//800; + uint32_t dsth = frame->img.height;//1280; + uint32_t dsts = frame->img.strides[0];//896; + i++; + sprintf(str, "echo frame is %d > ~/frame", i); + system(str); + + PlinkYuvInfo info = {0}; + + info.header.type = PLINK_TYPE_2D_YUV; + info.header.size = DATA_SIZE(info); + info.header.id = plink_ctx->sendid + 1; + + info.format = PLINK_COLOR_FormatYUV420SemiPlanar; + // info.bus_address_y = vi_mem_import(frame->img.dmabuf[0].fds) + frame->img.dmabuf[0].offset; + // info.bus_address_u = info.bus_address_y + frame->img.dmabuf[1].offset; + // vi_mem_release(info.bus_address_y); + + info.pic_width = dstw; + info.pic_height = dsth; + info.stride_y = dsts; + info.stride_u = dsts; + info.stride_v = dsts; + info.offset_y = 0; + info.offset_u = frame->img.dmabuf[1].offset; + info.offset_v = frame->img.dmabuf[2].offset; + pkt.list[0] = &info; + pkt.num = 1; + pkt.fd = frame->img.dmabuf[0].fds; + if (PLINK_send(plink_ctx->plink, plink_ctx->chnid, &pkt) != PLINK_STATUS_OK) + plink_ctx->exitplink = 1; + gettimeofday(&tv_end, 0); + timersub(&tv_end, &tv_start, &tv_duration); + plink_ctx->sendid = (plink_ctx->sendid + 1) % NUM_OF_BUFFERS; + plink_ctx->available_bufs -= 1; + } else if (!plink_ctx->exitplink && plink_ctx->available_bufs > 0 && plink_ctx->plink != NULL && (frame->img.pix_format == CSI_PIX_FMT_BGR)) { + CsiPictureBuffer *buf = &plink_ctx->sendbuffer[plink_ctx->sendid]; + int y = 0; + + uint8_t *src = frame->img.usr_addr[0]; + uint8_t *dst = buf->virtual_address; + uint32_t srcw = frame->img.width; + uint32_t srch = frame->img.height; + uint32_t dstw = frame->img.width; + uint32_t dsth = frame->img.height; + uint32_t dsts = frame->img.width; + switch (frame->img.pix_format ) { + case CSI_PIX_FMT_BGR: + { + dstw = 300; + dsth = 304; + uint32_t w = dstw > srcw ? srcw : dstw; + uint32_t h = dsth > srch ? srch : dsth; + if (w != 300 || h != 176 || dsts != 304) { + fprintf(stderr, "ERROR: expect 300x300 (stride 304) image while recieved %dx%d (stride %d) image\n", w, h, dsts); + break; + } + for (y = 0; y < h; y++) + { + memcpy(dst, src, w); + src += srcw; + dst += dsts; + } + src = frame->img.usr_addr[0] + srcw*srch; + dst = buf->virtual_address + dsts*dsth; + for (y = 0; y < h; y++) + { + memcpy(dst, src, w); + src += srcw; + dst += dsts; + } + src = frame->img.usr_addr[0] + srcw*srch*2; + dst = buf->virtual_address + dsts*dsth*2; + for (y = 0; y < h; y++) + { + memcpy(dst, src, w); + src += srcw; + dst += dsts; + } + + PlinkRGBInfo info = {0}; + + info.header.type = PLINK_TYPE_2D_RGB; + info.header.size = DATA_SIZE(info); + info.header.id = plink_ctx->sendid + 1; + + info.format = PLINK_COLOR_Format24BitBGR888Planar; + info.bus_address_b = buf->bus_address; + info.bus_address_g = info.bus_address_b + dsts*dsth; + info.bus_address_r = info.bus_address_g + dsts*dsth; + info.img_width = dstw; + info.img_height = dsth; + info.stride_b = dsts; + info.stride_g = dsts; + info.stride_r = dsts; + + pkt.list[0] = &info; + pkt.num = 1; + pkt.fd = buf->fd; + + if (PLINK_send(plink_ctx->plink, plink_ctx->chnid, &pkt) != PLINK_STATUS_OK) + plink_ctx->exitplink = 1; + break; + } + + case CSI_PIX_FMT_NV12: + default: + { + uint32_t w = dstw > srcw ? srcw : dstw; + uint32_t h = dsth > srch ? srch : dsth; + for (y = 0; y < h; y++) + { + memcpy(dst, src, w); + src += srcw; + dst += dsts; + } + src = frame->img.usr_addr[0] + frame->img.strides[0] * frame->img.height; + dst = buf->virtual_address + (dsts * dsth); + for (y = 0; y < h/2; y++) + { + memcpy(dst, src, w); + src += srcw; + dst += dsts; + } + + PlinkYuvInfo info = {0}; + + info.header.type = PLINK_TYPE_2D_YUV; + info.header.size = DATA_SIZE(info); + info.header.id = plink_ctx->sendid + 1; + + info.format = PLINK_COLOR_FormatYUV420SemiPlanar; + info.bus_address_y = buf->bus_address; + info.bus_address_u = buf->bus_address + dsts*dsth; + info.bus_address_v = info.bus_address_u; + info.pic_width = dstw; + info.pic_height = dsth; + info.stride_y = dsts; + info.stride_u = dsts; + info.stride_v = dsts; + + pkt.list[0] = &info; + pkt.num = 1; + pkt.fd = buf->fd; + if (PLINK_send(plink_ctx->plink, plink_ctx->chnid, &pkt) != PLINK_STATUS_OK) + plink_ctx->exitplink = 1; + break; + } + } + + gettimeofday(&tv_end, 0); + timersub(&tv_end, &tv_start, &tv_duration); + + plink_ctx->sendid = (plink_ctx->sendid + 1) % NUM_OF_BUFFERS; + plink_ctx->available_bufs -= 1; + } + } + LOG_O("%s exit \n", __func__); +} diff --git a/examples/camera/camera_test1.c b/examples/camera/camera_test1.c new file mode 100755 index 0000000..a2bc1d0 --- /dev/null +++ b/examples/camera/camera_test1.c @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#define LOG_LEVEL 2 +#define LOG_PREFIX "camera_demo1" +#include + +#include +#include + +#ifdef PLATFORM_SIMULATOR +#include "apputilities.h" +#endif + +static void dump_camera_meta(csi_frame_s *frame); +static int save_camera_img(csi_frame_s *frame); +static int save_camera_stream(csi_frame_s *frame); + +#define TEST_DEVICE_NAME "/dev/video0" +#define CSI_CAMERA_TRUE 1 +#define CSI_CAMERA_FALSE 0 + +char *prog_name; + +void usage() +{ + fprintf (stderr, + "usage: %s [options]\n" + "Options:\n" + "\t-D dev target video device like /dev/video0\n" + "\t-R resolution format is like 640x480\n" + "\t-F format like NV12\n" + "\t-C channel_index channel index defined in dts, index from 0\n" + "\t-M output mode 0: save as image; 1: save as stream file\n" + "\t-N number for frames 0: record forever; others: the recorded frame numbers" + , prog_name + ); +} + +int main(int argc, char *argv[]) +{ + bool running = false; + csi_camera_info_s camera_info; + + int opt; + char device[CSI_CAMERA_NAME_MAX_LEN]; /* target video device like /dev/video0 */ + char *resolution; /* resolution information like 640x480 */ + int hres, vres; /* resolution information like 640x480 */ + char *resDelim = "xX"; + char *format; /* format information like NV12 */ + enum csi_pixel_fmt fenum; + int channelIdx; /* channel information like 8 */ + int outMode = 0; + int fNum = 0; + + prog_name = argv[0]; + + while ((opt = getopt(argc, argv, "D:R:F:C:M:hN:")) != EOF) { + switch(opt) { + case 'D': + //device = optarg; + strcpy(device, optarg); + continue; + case 'R': + resolution = optarg; + hres = atoi(strtok(resolution, resDelim)); + vres = atoi(strtok(NULL, resDelim)); + continue; + case 'M': + outMode = atoi(optarg); + continue; + case 'N': + fNum = atoi(optarg); + continue; + case 'F': + format = optarg; + if(strstr(format, "NV12")){ + fenum = CSI_PIX_FMT_NV12; + } + else if(strstr(format, "I420")){ + fenum = CSI_PIX_FMT_I420; + } + else{ + fenum = CSI_PIX_FMT_I420; + } + continue; + case 'C': + channelIdx = atoi(optarg); + continue; + case '?': + case 'h': + default: + usage(); + return 1; + } + } + + const csi_camera_channel_id_e CAMERA_CHANNEL_ID = CSI_CAMERA_CHANNEL_0 + channelIdx; + const csi_camera_event_type_e CAMERA_CHANNEL_EVENT_TYPE = CSI_CAMERA_EVENT_TYPE_CHANNEL0 + channelIdx; + if (fNum == 0) + running == true; + + + // 打印HAL接口版本号 + csi_api_version_u version; + csi_camera_get_version(&version); + printf("Camera HAL version: %d.%d\n", version.major, version.minor); + + // 获取设备中,所有的Camera + struct csi_camera_infos camera_infos; + memset(&camera_infos, 0, sizeof(camera_infos)); + csi_camera_query_list(&camera_infos); + + // 打印所有设备所支持的Camera + for (int i = 0; i < camera_infos.count; i++) { + camera_info = camera_infos.info[i]; + printf("Camera[%d]: camera_name='%s', device_name='%s', bus_info='%s', capabilities=0x%08x\n", + i, + camera_info.camera_name, camera_info.device_name, camera_info.bus_info, + camera_info.capabilities); + printf("Camera[%d] caps are:\n", + i); /* The caps are: Video capture, metadata capture */ + for (int j = 1; j <= 0x08000000; j = j << 1) { + switch (camera_info.capabilities & j) { + case CSI_CAMERA_CAP_VIDEO_CAPTURE: + printf("\t camera_infos.info[%d]:Video capture,\n", i); + break; + case CSI_CAMERA_CAP_META_CAPTURE: + printf("\t camera_infos.info[%d] metadata capture,\n", i); + break; + default: + if (camera_info.capabilities & j) { + printf("\t camera_infos.info[%d] unknown capabilities(0x%08x)\n", i, + camera_info.capabilities & j); + } + break; + } + } + } + + + // 打开Camera设备获取句柄,作为后续操对象 + csi_cam_handle_t cam_handle; + //csi_camera_open(&cam_handle, camera_info.device_name); + strcpy(camera_info.device_name, device); + //camera_info.device_name = device; + csi_camera_open(&cam_handle, camera_info.device_name); + //csi_camera_open(&cam_handle, device); + // 获取Camera支持的工作模式 + struct csi_camera_modes camera_modes; + csi_camera_get_modes(cam_handle, &camera_modes); + + // 打印camera所支持的所有工作模式 + printf("Camera:'%s' modes are:\n", device); + printf("{\n"); + for (int i = 0; i < camera_modes.count; i++) { + printf("\t mode_id=%d: description:'%s'\n", + camera_modes.modes[i].mode_id, camera_modes.modes[i].description); + } + printf("}\n"); + + // 设置camera的工作模式及其配置 + csi_camera_mode_cfg_s camera_cfg; + camera_cfg.mode_id = 1; + camera_cfg.calibriation = NULL; // 采用系统默认配置 + camera_cfg.lib3a = NULL; // 采用系统默认配置 + csi_camera_set_mode(cam_handle, &camera_cfg); + + // 获取单个可控单元的属性 + csi_camera_property_description_s description; + /* id=0x0098090x, type=2 default=0 value=1 */ + /* Other example: + * id=0x0098090y, type=3 min=0 max=255 step=1 default=127 value=116 + * id=0x0098090z, type=4 min=0 max=3 default=0 value=2 + * 0: IDLE + * 1: BUSY + * 2: REACHED + * 3: FAILED + */ + + // 轮询获取所有可控制的单元 + printf("all properties are:\n"); + description.id = CSI_CAMERA_PID_HFLIP; + while (!csi_camera_query_property(cam_handle, &description)) { + switch (description.type) { + case (CSI_CAMERA_PROPERTY_TYPE_INTEGER): + printf("id=0x%08x type=%d default=%d value=%d\n", + description.id, description.type, + description.default_value.int_value, description.value.int_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_BOOLEAN): + printf("id=0x%08x type=%d default=%d value=%d\n", + description.id, description.type, + description.default_value.bool_value, description.value.bool_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_ENUM): + printf("id=0x%08x type=%d default=%d value=%d\n", + description.id, description.type, + description.default_value.enum_value, description.value.enum_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_STRING): + printf("id=0x%08x type=%d default=%s value=%s\n", + description.id, description.type, + description.default_value.str_value, description.value.str_value); + break; + case (CSI_CAMERA_PROPERTY_TYPE_BITMASK): + printf("id=0x%08x type=%d default=%x value=%x\n", + description.id, description.type, + description.default_value.bitmask_value, description.value.bitmask_value); + break; + default: + LOG_E("error type!\n"); + break; + } + description.id |= CSI_CAMERA_FLAG_NEXT_CTRL; + } + + // 同时配置多个参数 + csi_camera_properties_s properties; + csi_camera_property_s property[3]; + property[0].id = CSI_CAMERA_PID_HFLIP; + property[0].type = CSI_CAMERA_PROPERTY_TYPE_BOOLEAN; + property[0].value.bool_value = false; + property[1].id = CSI_CAMERA_PID_VFLIP; + property[1].type = CSI_CAMERA_PROPERTY_TYPE_BOOLEAN; + property[1].value.bool_value = false; + property[2].id = CSI_CAMERA_PID_ROTATE; + property[2].type = CSI_CAMERA_PROPERTY_TYPE_INTEGER; + property[2].value.int_value = 0; + properties.count = 3; + properties.property = property; + if (csi_camera_set_property(cam_handle, &properties) < 0) { + LOG_O("set_property fail!\n"); + } + LOG_O("set_property ok!\n"); + + // 查询输出channel + csi_camera_channel_cfg_s chn_cfg; + chn_cfg.chn_id = CAMERA_CHANNEL_ID; + csi_camera_channel_query(cam_handle, &chn_cfg); + if (chn_cfg.status != CSI_CAMERA_CHANNEL_CLOSED) { + printf("Can't open CSI_CAMERA_CHANNEL_0\n"); + exit(-1); + } + + // 打开输出channel + chn_cfg.chn_id = channelIdx; + chn_cfg.frm_cnt = 4; + chn_cfg.img_fmt.width = hres; + chn_cfg.img_fmt.height = vres; + chn_cfg.img_fmt.pix_fmt = fenum; + chn_cfg.img_type = CSI_IMG_TYPE_DMA_BUF; + chn_cfg.meta_fields = CSI_CAMERA_META_DEFAULT_FIELDS; + chn_cfg.capture_type = CSI_CAMERA_CHANNEL_CAPTURE_VIDEO | + CSI_CAMERA_CHANNEL_CAPTURE_META; + csi_camera_channel_open(cam_handle, &chn_cfg); + + // 订阅Event + csi_cam_event_handle_t event_handle; + csi_camera_create_event(&event_handle, cam_handle); + csi_camera_event_subscription_s subscribe; + subscribe.type = + CSI_CAMERA_EVENT_TYPE_CAMERA; // 订阅Camera的ERROR事件 + subscribe.id = CSI_CAMERA_EVENT_WARNING | CSI_CAMERA_EVENT_ERROR; + csi_camera_subscribe_event(event_handle, &subscribe); + subscribe.type = + CAMERA_CHANNEL_EVENT_TYPE; // 订阅Channel0的FRAME_READY事件 + subscribe.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY | + CSI_CAMERA_CHANNEL_EVENT_OVERFLOW; + csi_camera_subscribe_event(event_handle, &subscribe); + + // 开始从channel中取出准备好的frame + csi_camera_channel_start(cam_handle, CAMERA_CHANNEL_ID); + + // 处理订阅的Event + csi_frame_s frame; + struct csi_camera_event event; + + while (running || fNum >= 0 ) { + int timeout = -1; // unit: ms, -1 means wait forever, or until error occurs + csi_camera_get_event(event_handle, &event, timeout); + if(event.type == CSI_CAMERA_EVENT_TYPE_CAMERA){ + switch (event.id) { + case CSI_CAMERA_EVENT_ERROR: + // do sth. + LOG_D("get CAMERA EVENT CSI_CAMERA_EVENT_ERROR!\n"); + break; + default: + break; + } + } + if(event.type == CAMERA_CHANNEL_EVENT_TYPE){ + switch (event.id) { + case CSI_CAMERA_CHANNEL_EVENT_FRAME_READY: { + int read_frame_count = csi_camera_get_frame_count(cam_handle, + CAMERA_CHANNEL_ID); + for (int i = 0; i < read_frame_count; i++) { + csi_camera_get_frame(cam_handle, CAMERA_CHANNEL_ID, &frame, timeout); + fNum--; + +#ifdef PLATFORM_SIMULATOR + show_frame_image(frame.img.usr_addr[0], frame.img.height, frame.img.width); +#endif + + if (outMode == 0) + save_camera_img(&frame); + else + save_camera_stream(&frame); + + csi_frame_release(&frame); + } + break; + } + default: + break; + } + } + } + csi_camera_channel_stop(cam_handle, CAMERA_CHANNEL_ID); + usleep (1000000); + // 取消订阅某一个event, 也可以直接调用csi_camera_destory_event,结束所有的订阅 + subscribe.type = CAMERA_CHANNEL_EVENT_TYPE; + subscribe.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY; + csi_camera_unsubscribe_event(event_handle, &subscribe); + + csi_camera_destory_event(event_handle); + + csi_camera_channel_close(cam_handle, CAMERA_CHANNEL_ID); + csi_camera_close(cam_handle); +} + +static void dump_camera_meta(csi_frame_s *frame) +{ + int i; + //printf("%s\n", __func__); + if (frame->meta.type != CSI_META_TYPE_CAMERA) + return; + + csi_camera_meta_s *meta_data = (csi_camera_meta_s *)frame->meta.data; + int meta_count = meta_data->count; + csi_camrea_meta_unit_s meta_unit; + + for (i = 0; i < meta_count; i++) { + csi_camera_frame_get_meta_unit( + &meta_unit, meta_data, CSI_CAMERA_META_ID_FRAME_ID); + //printf("meta_id=%d, meta_type=%d, meta_value=%d", + // meta_unit.id, meta_unit.type, meta_unit.int_value); + } +} + +static int save_camera_img(csi_frame_s *frame) +{ + static int fcount = 0; + char fname[20]; + FILE *fp; + fcount = fcount%4; + sprintf(fname, "%s%d%s", "hal_image", fcount, ".yuv"); + fcount++; + + if((fp = fopen(fname, "wb")) == NULL){ + printf("Error: Can't open file\n"); + return -1; + } + fwrite(frame->img.usr_addr[0], sizeof(char), frame->img.size, fp); + fclose(fp); + return 0; +} + +static int save_camera_stream(csi_frame_s *frame) +{ + FILE *fp; + char *fname = "hal_stream.yuv"; + if((fp = fopen(fname, "ab+")) == NULL){ + printf("Error: Can't open file\n"); + return -1; + } + fwrite(frame->img.usr_addr[0], sizeof(char), frame->img.size, fp); + fclose(fp); + return 0; +} + diff --git a/examples/camera/opencv/Makefile b/examples/camera/opencv/Makefile new file mode 100644 index 0000000..fb267f4 --- /dev/null +++ b/examples/camera/opencv/Makefile @@ -0,0 +1,41 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../../.. + +include $(DIR_TO_ROOT)/build.param +LIBOPENCV_INC = $(shell pkg-config --cflags opencv) -I./ +LIBOPENCV_LIBS = $(shell pkg-config --libs opencv) + +TARGET := libapp_utilities.a +OUTPUT_DIR := $(DIR_TO_ROOT)/output/hal/ + +#CFLAGS = -Wall -g -O0 + +SRCS = $(wildcard *.cpp) +OBJS = $(SRCS:.cpp=.o) + +all: $(TARGET) + +$(TARGET): $(OBJS) + @mkdir -p $(OUTPUT_DIR) + @echo "Linking" $@ "..." + $(AR) -r -o $(OUTPUT_DIR)/$@ .obj/*.o + +$(OBJS): %.o:%.cpp + @echo $(BUILD_LOG_START) + @mkdir -p .obj + @echo "Compiling" $< "..." + $(CXX) $(CFLAGS) $(INCLUDE) $(LIBOPENCV_INC) $(LIBOPENCV_LIBS) -c -o .obj/$(notdir $@) $< + @echo $(BUILD_LOG_END) + +clean: + rm -rf .obj + rm -f $(OUTPUT_DIR)/$(TARGET) + +.PHONY: clean all diff --git a/examples/camera/opencv/apputilities.cpp b/examples/camera/opencv/apputilities.cpp new file mode 100644 index 0000000..7ec805e --- /dev/null +++ b/examples/camera/opencv/apputilities.cpp @@ -0,0 +1,24 @@ +#include +#include +#include +#include +#include +#include +using namespace cv; +using namespace std; + +#include "apputilities.h" + +int show_frame_image(void *mat, int height, int width) +{ + Mat dis_mat; + dis_mat.create(height * 3 / 2, width, CV_8UC1); + dis_mat.data = (uchar *)mat; + cvtColor(dis_mat, dis_mat, CV_YUV2BGRA_I420); + namedWindow("camera local i420"); + imshow("camera local i420", dis_mat); + waitKey(1); + dis_mat.release(); + return 0; +} + diff --git a/examples/camera/opencv/apputilities.h b/examples/camera/opencv/apputilities.h new file mode 100644 index 0000000..1af53d1 --- /dev/null +++ b/examples/camera/opencv/apputilities.h @@ -0,0 +1,16 @@ +#ifndef __APPUTILITIES_H__ +#define __APPUTILITIES_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +//wait for complete +int show_frame_image(void *mat, int height, int width); + + +#ifdef __cplusplus +}; +#endif + +#endif diff --git a/examples/camera/run_camera_demo3.sh b/examples/camera/run_camera_demo3.sh new file mode 100755 index 0000000..fd4874c --- /dev/null +++ b/examples/camera/run_camera_demo3.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# To watch log output, use: tail -f /tmp/debug_camera_demo3.log +stdbuf -oL ./camera_demo3 >> /tmp/debug_camera_demo3.log 2>&1 diff --git a/examples/common/.gitignore b/examples/common/.gitignore new file mode 100644 index 0000000..29735fe --- /dev/null +++ b/examples/common/.gitignore @@ -0,0 +1 @@ +mtrace_demo diff --git a/examples/common/Makefile b/examples/common/Makefile new file mode 100644 index 0000000..73a0174 --- /dev/null +++ b/examples/common/Makefile @@ -0,0 +1,27 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../.. +include $(DIR_TO_ROOT)/build.param + +#CFLAGS += +LIBS += -lhal_common +OUTPUT_DIR := $(DIR_TO_ROOT)/output/examples/common + +TARGET_1 := mtrace_demo +SRCS_1 = mtrace_demo.c + +all: $(TARGET_1) + +clean: + rm -rf .obj + rm -f $(TARGET_1) $(OUTPUT_DIR)/$(TARGET_1) + +include $(DIR_TO_ROOT)/common_target.mk + +.PHONY: clean all diff --git a/examples/common/mtrace_demo.c b/examples/common/mtrace_demo.c new file mode 100644 index 0000000..4af6618 --- /dev/null +++ b/examples/common/mtrace_demo.c @@ -0,0 +1,25 @@ +#define DEBUG_MTRACE +#include // for malloc +#include // for mtrace() / muntrace(); + +#define LOG_LEVEL 2 +#include + +int main(int argc, char **argv) +{ + LOG_I("Enter\n"); + int ret; + +#ifdef DEBUG_MTRACE + mtrace(); +#endif + + int *tmp = malloc(0x1234); + LOG_I("malloc 0x1234 bytes(%p) without free.\n", tmp); + +#ifdef DEBUG_MTRACE + muntrace(); +#endif + LOG_I("Exit\n"); +} + diff --git a/examples/common/run_mtrace_demo.sh b/examples/common/run_mtrace_demo.sh new file mode 100755 index 0000000..04cffde --- /dev/null +++ b/examples/common/run_mtrace_demo.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +export MALLOC_TRACE=/tmp/mtrace_demo_mtrace.log +rm -f $MALLOC_TRACE +# set '/proc/sys/kernel/randomize_va_space' to be 0 +# to disable Address Space Layout Randomization(ASLR) +#sudo sysctl -w kernel.randomize_va_space=0 + +echo "\n**** Run mtrace demo ****" +./mtrace_demo + +echo "\n**** mtrace analysis result ****" +mtrace ./mtrace_demo $MALLOC_TRACE | grep -E "\.c|\.cpp" diff --git a/examples/frame/.gitignore b/examples/frame/.gitignore new file mode 100644 index 0000000..22b5782 --- /dev/null +++ b/examples/frame/.gitignore @@ -0,0 +1,3 @@ +frame_demo1 +frame_demo2 +consumer_dump_file.yuv diff --git a/examples/frame/Makefile b/examples/frame/Makefile new file mode 100644 index 0000000..99e9470 --- /dev/null +++ b/examples/frame/Makefile @@ -0,0 +1,33 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../.. +include $(DIR_TO_ROOT)/build.param + +CFLAGS += -I$(DIR_TO_ROOT)/src/platform/simulator/drivers/ion/ +LIBS += -lhal_common -lhal_platform +OUTPUT_DIR := $(DIR_TO_ROOT)/output/examples/frame + +TARGET_1 := frame_demo1 +SRCS_1 = frame_demo1.c frame_demo_common.c + +TARGET_2 := frame_demo2 +SRCS_2 = frame_demo2.c frame_demo2_producer.c frame_demo2_consumer.c frame_demo_common.c + +all: $(TARGET_1) $(TARGET_2) + @mkdir -p $(OUTPUT_DIR)/../resource + cp -f $(DIR_TO_ROOT)/examples/resource/yuv420_1280x720_csky2016.yuv $(OUTPUT_DIR)/../resource + +clean: + rm -rf .obj + rm -f $(TARGET_1) $(OUTPUT_DIR)/$(TARGET_1) + rm -f $(TARGET_2) $(OUTPUT_DIR)/$(TARGET_2) + +include $(DIR_TO_ROOT)/common_target.mk + +.PHONY: clean all diff --git a/examples/frame/frame_demo1.c b/examples/frame/frame_demo1.c new file mode 100644 index 0000000..72922a4 --- /dev/null +++ b/examples/frame/frame_demo1.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define LOG_LEVEL 2 +#define LOG_PREFIX "camera_demo1" +#include + +#include +#include +#include +#include "frame_demo_common.h" + +static const char *img_name = "../resource/yuv420_1280x720_csky2016.yuv"; +static csi_img_format_t img_format = { + .width = 1280, + .height = 720, + .pix_fmt = CSI_PIX_FMT_I420, +}; + +int main(int argc, char *argv[]) +{ + int ret = -1; + int i; + + int img_fd; + long img_size; + int ion_fd; + int dmabuf_fd; + void *dmabuf_addr; + csi_frame_s camera_frame; + csi_camera_meta_s *camera_meta; + + // get image file size + if (fdc_get_img_file_size(&img_size, img_name) != 0) + goto LAB_ERR; + + // open ion device + if (fdc_get_ion_device_fd(&ion_fd) != 0) + goto LAB_ERR; + + + // alloc dma-buf from ion device + struct ion_allocation_data alloc_data; + if (fdc_alloc_ion_buf(&alloc_data, ion_fd, img_size) != 0) + goto LAB_ERR_ION_OPENED; + dmabuf_fd = alloc_data.fd; + + // map dma-buf to user addr + if (fdc_mmap_dmabuf_addr(&dmabuf_addr, img_size, dmabuf_fd) != 0) + goto LAB_ERR_BUF_ALLOCED; + + // open image file + img_fd = open(img_name, O_RDONLY); + if (img_fd < 0) { + LOG_E("Open image file '%s' failed, %s\n", + img_name, strerror(errno)); + goto LAB_ERR_MMAPPED; + } + + // store image into dma-buf + if (fdc_store_img_to_dmabuf(dmabuf_addr, img_fd, img_size) != 0) + goto LAB_ERR_IMG_OPENED; + + // create csi_frame + if (fdc_create_csi_frame(&camera_frame, &img_format, &camera_meta, img_size, dmabuf_fd, + dmabuf_addr) != 0) + goto LAB_ERR_IMG_OPENED; + + // dump camera meta from csi frame + fdc_dump_camera_meta((csi_camera_meta_s *)camera_frame.meta.data); + + // destory csi_frame + fdc_destory_csi_frame(&camera_frame); + ret = 0; + +LAB_ERR_IMG_OPENED: + close(img_fd); + +LAB_ERR_MMAPPED: + munmap(dmabuf_addr, img_size); + +LAB_ERR_BUF_ALLOCED: + close(dmabuf_fd); + +LAB_ERR_ION_OPENED: + close(ion_fd); + +LAB_ERR: + return ret; +} + diff --git a/examples/frame/frame_demo2.c b/examples/frame/frame_demo2.c new file mode 100644 index 0000000..dda6848 --- /dev/null +++ b/examples/frame/frame_demo2.c @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define LOG_LEVEL 2 +#define LOG_PREFIX "master" +#include + +#include +#include +#include +#include "frame_demo_common.h" + +const char *img_name = "../resource/yuv420_1280x720_csky2016.yuv"; +csi_img_format_t img_format = { + .width = 1280, + .height = 720, + .pix_fmt = CSI_PIX_FMT_I420, +}; + +extern int producer_process(int socket_fd); +extern int consumer_process(int socket_fd); + +int main(int argc, char *argv[]) +{ + int ret; + int socket_pair[2]; /* sockpair() */ + pid_t producer_pid; + pid_t consumer_pid; + int process_status; /* the status of child process */ + + char str_sockfd[10]; /* store the string format */ + + /* Create socket pare */ + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, + socket_pair) == -1) { /* must be AF_LOCAL or AF_UNIX !!!!*/ + LOG_E("create socketpair error(%d), %s\n", errno, strerror(errno)); + exit(1); + } + LOG_I("Create socketpair OK:{%d, %d}\n", socket_pair[0], socket_pair[1]); + + /* run producer process, using socket_pair[0] */ + if ((producer_pid = fork()) == 0) { + /* child process2: producer_process */ + LOG_D("producer process starting ...\n"); + close(socket_pair[1]); // producer only use socket_pair[0] + + producer_process(socket_pair[0]); + LOG_D("producer process exiting ...\n"); + exit(0); + } + + /* run consumer process, using socket_pair[1] */ + if ((consumer_pid = fork()) == 0) { + /* child process1: consumer_process */ + LOG_D("consumer process starting...\n"); + close(socket_pair[0]); // producer only use socket_pair[1] + + consumer_process(socket_pair[1]); + LOG_D("consumer process exiting...\n"); + exit(0); + } + + /* wait the children process end */ + waitpid(producer_pid, &process_status, 0); + if (WIFEXITED(process_status) == 0) { + LOG_E("producer exit abnormal\n"); + } + + waitpid(consumer_pid, &process_status, 0); + if (WIFEXITED(process_status) == 0) { + LOG_E("consumer eixt abnormal\n"); + } + + /* close the socket_pair */ + ret = close(socket_pair[0]); + LOG_D("close(socket_pair[0]) ret=%d, %s\n", errno, strerror(errno)); + + ret = close(socket_pair[1]); + LOG_D("close(socket_pair[1]) ret=%d, %s\n", errno, strerror(errno)); + + LOG_D("Exit master\n"); + return 0; +} + diff --git a/examples/frame/frame_demo2_consumer.c b/examples/frame/frame_demo2_consumer.c new file mode 100644 index 0000000..fe5291f --- /dev/null +++ b/examples/frame/frame_demo2_consumer.c @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "consumer" +#include +#include + +#include +#include +#include +#include "frame_demo_common.h" + +extern const char *img_name; +extern csi_img_format_t img_format; + +int consumer_process(int socket_fd) +{ + int ret = -1; + + LOG_D("Starting consumer\n"); + sleep(1); // waiting for producer exit to watch log easily + if (socket_fd <= 0) { + LOG_E("socket_fd(%d) is invalid\n", socket_fd); + goto LAB_ERR; + } + LOG_D("The input socket_fd=%d\n", socket_fd); + + struct msghdr msghdr_recv = { // init data + .msg_name = NULL, // the socket address + .msg_namelen = 0, // len of socket name + }; + + char ctrls[CMSG_SPACE(sizeof(int)*3)] = {}; + memset(ctrls, 0, sizeof(ctrls)); + msghdr_recv.msg_control = ctrls; + msghdr_recv.msg_controllen = sizeof(ctrls); + + csi_frame_s camera_frame; + csi_camera_meta_s camera_meta; + char meta_data[CSI_CAMERA_META_MAX_LEN]; //CSI_CAMERA_META_MAX_LEN + + struct iovec iov[3]; + iov[0].iov_base = &camera_frame.img; // iov[0] stores csi_frame.img info + iov[0].iov_len = sizeof(camera_frame.img); + iov[1].iov_base = &camera_frame.meta; // iov[1] stores csi_frame.meta info + iov[1].iov_len = sizeof(camera_frame.meta); + iov[2].iov_base = meta_data; // iov[2] stores csi_frame.meta data + iov[2].iov_len = sizeof(meta_data); + + msghdr_recv.msg_iov = iov; // the io/vector info + msghdr_recv.msg_iovlen = CSI_IMAGE_MAX_PLANES; // the num of iov + + ssize_t recv_len; + if ((recv_len = recvmsg(socket_fd, &msghdr_recv, 0)) < 0) { + // the msg is recv by msghdr_recv + LOG_E("recv error:%d, %s\n", errno, strerror(errno)); + goto LAB_ERR; + } + LOG_D("recvmsg() length=%zd\n", recv_len); + fdc_dump_msghdr(&msghdr_recv); + + int msghdr_count = 0; + struct cmsghdr *pCmsghdr = NULL; // the pointer of control + for (pCmsghdr = CMSG_FIRSTHDR(&msghdr_recv); pCmsghdr != NULL; + pCmsghdr = CMSG_NXTHDR(&msghdr_recv,pCmsghdr)) { + msghdr_count++; + } + LOG_D("msghdr_count=%d\n", msghdr_count); + + // now, only 1 plane even in I420 format (CSI_IMAGE_I420_PLANES) + if ((pCmsghdr = CMSG_FIRSTHDR(&msghdr_recv)) != NULL && + pCmsghdr->cmsg_len == CMSG_LEN(sizeof(int) * 1)) { + if (pCmsghdr->cmsg_level != SOL_SOCKET) { + LOG_E("Ctl level should be SOL_SOCKET\n"); + goto LAB_ERR; + } + + if (pCmsghdr->cmsg_type != SCM_RIGHTS) { + LOG_E("Ctl type should be SCM_RIGHTS\n"); + goto LAB_ERR; + } + + int fd_count = (pCmsghdr->cmsg_len - CMSG_SPACE(0)) / sizeof(int); + LOG_D("pCmsghdr->cmsg_len=%zd, fd_count=%d\n", pCmsghdr->cmsg_len, fd_count); + int *fdptr = (int *) CMSG_DATA(pCmsghdr); + + for (int i = 0; i < CSI_IMAGE_MAX_PLANES; i ++) { + if (i < fd_count) + camera_frame.img.fds[i] = fdptr[i]; + else + camera_frame.img.fds[i] = -1; + } + } else { + if (pCmsghdr == NULL) + LOG_E("pCmsghdr=%p\n", pCmsghdr); + else + LOG_E("pCmsghdr->cmsg_len=%zu, should=%zu\n", + pCmsghdr->cmsg_len, + CMSG_LEN(sizeof(int) * CSI_IMAGE_MAX_PLANES)); + + goto LAB_ERR; + } + int dma_buf_fd = camera_frame.img.fds[0]; + LOG_D("recvmsg() with dmabuf_fds={%d, %d, %d}\n", + camera_frame.img.fds[0], camera_frame.img.fds[1], camera_frame.img.fds[2]); + + // fill camera frame img fds + camera_frame.meta.data = meta_data; + csi_dump_hex((char *)(&camera_frame.meta), sizeof(camera_frame.meta), "meta"); + csi_dump_hex(camera_frame.meta.data, camera_frame.meta.size, "consumer: meta.data"); + csi_dump_img_info(&camera_frame.img); + + csi_camera_meta_s *camera_meta_data = (csi_camera_meta_s *)camera_frame.meta.data; + camera_meta_data->units = (csi_camrea_meta_unit_s *)((char *)camera_meta_data + sizeof(csi_camera_meta_s)); + fdc_dump_camera_meta(camera_meta_data); + + LOG_D("csi frame meta = {type=%d, size=%zd, data=%p}\n", + camera_frame.meta.type, camera_frame.meta.size, camera_frame.meta.data); + LOG_D("csi camera meta = {.count=%d, .size=%zd, .units=%p}\n", + camera_meta_data->count, camera_meta_data->size, camera_meta_data->units); + + // save image file + const char *save_file_name = "consumer_dump_file.yuv"; + remove(save_file_name); + FILE *dump_file = fopen(save_file_name, "wb"); + if (dump_file) { + void *addr = NULL; + int length = 0; + + length = camera_frame.img.size; //(img_format.height * img_format.width); + + ret = fdc_mmap_dmabuf_addr(&addr, length, camera_frame.img.fds[0]); + LOG_D("ret=%d, addr=%p, lenght=%d\n", ret, addr, length); + if (ret == 0 || addr != NULL) { + size_t write_len = fwrite(addr, length, 1, dump_file); + LOG_O("dump dma-buf into '%s', write_len=%zd\n", + save_file_name, write_len); + } + + fclose(dump_file); + } else { + LOG_E("fopen failed, %s\n", strerror(errno)); + } + //fclose(dump_file); + + + for (int i = 0; i < CSI_IMAGE_MAX_PLANES; i++) { + if (camera_frame.img.fds[i] > 0) { + ret = close(camera_frame.img.fds[i]); + LOG_D("close(camera_frame.img.fds[%d]) ret=%d, %s\n", + i, errno, strerror(errno)); + } + } + + ret = close(socket_fd); + LOG_D("close(socket_fd) ret=%d, %s\n", errno, strerror(errno)); + ret = 0; + +LAB_ERR: + if (ret) + LOG_E("Exiting consumer\n"); + else + LOG_D("Exiting consumer\n"); + return ret; +} + diff --git a/examples/frame/frame_demo2_producer.c b/examples/frame/frame_demo2_producer.c new file mode 100644 index 0000000..1278bdf --- /dev/null +++ b/examples/frame/frame_demo2_producer.c @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "producer" +#include +#include + +#include +#include +#include +#include "frame_demo_common.h" + +extern const char *img_name; +extern csi_img_format_t img_format; + +int producer_process(int socket_fd) +{ + int ret = -1; + + int img_fd; + long img_size; + int ion_fd; + int dmabuf_fd; + void *dmabuf_addr; + csi_frame_s camera_frame; + csi_camera_meta_s *camera_meta; + + LOG_D("Starting producer\n"); + if (socket_fd <= 0) { + LOG_E("socket_fd(%d) is invalid\n", socket_fd); + goto LAB_EXIT; + } + LOG_D("The input socket_fd=%d\n", socket_fd); + + // get image file size + if (fdc_get_img_file_size(&img_size, img_name) != 0) + goto LAB_EXIT; + + // open ion device + if (fdc_get_ion_device_fd(&ion_fd) != 0) + goto LAB_EXIT; + + // alloc dma-buf from ion device + struct ion_allocation_data alloc_data; + if (fdc_alloc_ion_buf(&alloc_data, ion_fd, img_size) != 0) + goto LAB_ERR_ION_OPENED; + dmabuf_fd = alloc_data.fd; + + // map dma-buf to user addr + if (fdc_mmap_dmabuf_addr(&dmabuf_addr, img_size, dmabuf_fd) != 0) // should do unmap!!!!!!!!!!!!!!!!! + goto LAB_ERR_BUF_ALLOCED; + + // open image file + img_fd = open(img_name, O_RDONLY); + if (img_fd < 0) { + LOG_E("Open image file '%s' failed, %s\n", + img_name, strerror(errno)); + goto LAB_ERR_MMAPPED; + } + + // store image into dma-buf + if (fdc_store_img_to_dmabuf(dmabuf_addr, img_fd, img_size) != 0) + goto LAB_ERR_IMG_OPENED; + + // create csi_frame + if (fdc_create_csi_frame(&camera_frame, &img_format, &camera_meta, + img_size, dmabuf_fd, dmabuf_addr) != 0) + goto LAB_ERR_IMG_OPENED; + + // dump camera meta from csi frame + + fdc_dump_camera_meta((csi_camera_meta_s *)camera_frame.meta.data); + csi_dump_hex((char*)camera_meta, camera_frame.meta.size, "producer: meta.data"); + csi_dump_img_info(&camera_frame.img); + + //////////////////////////////////////////////////////////////////////// + // store csi_frame into socket msg and send begin + struct msghdr msghdr_send = { // init data + .msg_name = NULL, // the socket address + .msg_namelen = 0, // len of socket name + }; + + int transfer_fd_count = 1; // it's YUV420P, has only 2 planes + char ctrls[CMSG_SPACE(sizeof(int)) * CSI_IMAGE_MAX_PLANES] = {}; // store dma-buf fd + msghdr_send.msg_control = ctrls; + msghdr_send.msg_controllen = CMSG_SPACE(sizeof(int)) * transfer_fd_count; + LOG_D("msg_controllen = %zd\n", msghdr_send.msg_controllen); + + struct cmsghdr *pCmsghdr; // the pointer of control + int *fdptr = (int *) CMSG_DATA(pCmsghdr); + + // the 1st info stores dma-buf fd[0] + pCmsghdr = CMSG_FIRSTHDR(&msghdr_send); // the info of head + pCmsghdr->cmsg_len = CMSG_LEN(sizeof(int)); // the msg len + pCmsghdr->cmsg_level = SOL_SOCKET; // stream mode + pCmsghdr->cmsg_type = SCM_RIGHTS; // file descriptor + fdptr = (int *) CMSG_DATA(pCmsghdr); + *fdptr = camera_frame.img.fds[0]; + + struct iovec iov[3]; // io vector strores csi_frame + iov[0].iov_base = &camera_frame.img; // iov[0] stores csi_frame.img + iov[0].iov_len = sizeof(camera_frame.img); + iov[1].iov_base = &camera_frame.meta; // iov[1] stores csi_frame.meta info + iov[1].iov_len = sizeof(camera_frame.meta); + iov[2].iov_base = camera_frame.meta.data; // iov[2] stores csi_frame.meta data + iov[2].iov_len = sizeof(camera_frame.meta) + camera_frame.meta.size; + + msghdr_send.msg_iov = iov; // the io/vector info + msghdr_send.msg_iovlen = 3; // the num of iov + + ssize_t size = sendmsg(socket_fd, &msghdr_send, 0);// send msg now + if (size < 0) { + LOG_E("sendmsg() failed, errno=%d, %s\n", errno, strerror(errno)); + } else if (size == 0) { + LOG_W("sendmsg() failed, %zd bytes\n", size); + } else { + LOG_O("sendmsg() OK, %zd bytes\n", size); + } + + // destory csi_frame + fdc_destory_csi_frame(&camera_frame); + ret = 0; + +LAB_ERR_IMG_OPENED: + ret = close(img_fd); + LOG_D("close(img_fd) ret=%d, %s\n", ret, strerror(errno)); + +LAB_ERR_MMAPPED: + ret = munmap(dmabuf_addr, img_size); + LOG_D("munmap() ret=%d, %s\n", ret, strerror(errno)); + +LAB_ERR_BUF_ALLOCED: + ret = close(dmabuf_fd); + LOG_D("close(dmabuf_fd) ret=%d, %s\n", ret, strerror(errno)); + +LAB_ERR_ION_OPENED: + close(ion_fd); + LOG_D("close(ion_fd) ret=%d, %s\n", ret, strerror(errno)); + +LAB_EXIT: + ret = close(socket_fd); + LOG_D("close(socket_fd) ret=%d, %s\n", ret, strerror(errno)); + + if (ret) + LOG_E("Exiting producer\n"); + else + LOG_D("Exiting producer\n"); + return ret; +} + diff --git a/examples/frame/frame_demo_common.c b/examples/frame/frame_demo_common.c new file mode 100644 index 0000000..35d23f4 --- /dev/null +++ b/examples/frame/frame_demo_common.c @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_LEVEL 2 +//#define LOG_PREFIX "demo_common" +#include + +#include +#include +#include +#include "frame_demo_common.h" + +#define PAGE_SIZE 4096 +//#define IMG_YUV420_FILENAME "../resource/yuv420_1280x720_csky2016.yuv" +#define IMG_RESOLUTION_WIDTH 1280 +#define IMG_RESOLUTION_HEIGHT 720 + +/* fdc = frame demo common */ +int fdc_get_img_file_size(long *img_size, const char *filename) +{ + struct stat img_stat; + if (stat(filename, &img_stat) != 0) { + LOG_E("stat file '%s' failed, %s\n", filename, strerror(errno)); + return -1; + } + *img_size = img_stat.st_size; + LOG_O("Get image '%s' size=%ld\n", filename, *img_size); + return 0; +} + +int fdc_get_ion_device_fd(int *ion_fd) +{ + *ion_fd = open(ION_DEVICE_NAME, O_RDWR); + if (*ion_fd < 0) { + LOG_E("Open ion device:'%s' failed, %s\n", ION_DEVICE_NAME, strerror(errno)); + return -1; + } + LOG_O("Open ion device OK, ion_fd=%d\n", *ion_fd); + return 0; +} + +int fdc_alloc_ion_buf(struct ion_allocation_data *alloc_data, + int ion_fd, unsigned long img_size) +{ + alloc_data->len = img_size; + if (ioctl(ion_fd, ION_IOC_ALLOC, alloc_data) || + alloc_data->fd < 0) { + LOG_E("ion ioctl failed, %s\n", strerror(errno)); + return -1; + } + int dmabuf_fd = alloc_data->fd; + LOG_O("Alloc %ld from ion device OK, dmabuf_fd=%d\n", img_size, dmabuf_fd); + return 0; +} + +int fdc_mmap_dmabuf_addr(void **addr, unsigned long length, int dmabuf_fd) +{ + LOG_D("Enter, length=%zd, dmabuf_fd=%d\n", length, dmabuf_fd); + *addr = mmap(NULL, length, PROT_READ | PROT_WRITE, + MAP_SHARED, dmabuf_fd, 0); + if (*addr == NULL || *addr == MAP_FAILED) { + LOG_E("Map dmabuf failed\n"); + return -1; + } + LOG_O("mmap dmabuf to user addr: %p OK\n", *addr); + return 0; +} + +int fdc_store_img_to_dmabuf(void *dmabuf_addr, int img_fd, long img_size) +{ + long totle_len=0, read_len = 0; + char *img_ptr = (char*)dmabuf_addr; + while ((read_len = read(img_fd, (img_ptr + totle_len), PAGE_SIZE)) > 0) { + totle_len += read_len; + } + if (read_len < 0) { + LOG_E("Read from image file failed, %s\n", strerror(errno)); + return -1; + } + if (totle_len != img_size) { + LOG_E("Read total len(%ld) != stat size(%ld)\n", totle_len, img_size); + return -1; + } + LOG_O("Store image into dma-buf OK\n"); + return 0; +} + +int fdc_create_csi_frame(csi_frame_s *camera_frame, + csi_img_format_t *img_format, csi_camera_meta_s **camera_meta, + long img_size, int dmabuf_fd, void *dmabuf_addr) +{ + // fill csi_frame's img info + camera_frame->img.type = CSI_IMG_TYPE_DMA_BUF; + camera_frame->img.size = img_size; + camera_frame->img.width = img_format->width; + camera_frame->img.height = img_format->height; + camera_frame->img.pix_format = img_format->pix_fmt; + camera_frame->img.num_planes = 1; // Now use only 1 plane data + camera_frame->img.fds[0] = dmabuf_fd; + camera_frame->img.strides[0] = img_format->width; + camera_frame->img.offsets[0] = 0; + + camera_frame->img.fds[1] = -1; + camera_frame->img.strides[1] = 0; + camera_frame->img.offsets[1] = 0; + + camera_frame->img.fds[2] = -1; + camera_frame->img.strides[2] = 0; + camera_frame->img.offsets[2] = 0; + camera_frame->img.modifier = 0; + + // prepare csi_camera_frame meta info + int meta_unit_count = 5; + size_t meta_data_size; + csi_camera_frame_alloc_meta(camera_meta, meta_unit_count, &meta_data_size); + + (*camera_meta)->units[0].id = CSI_CAMERA_META_ID_CAMERA_NAME; + (*camera_meta)->units[0].type = CSI_META_VALUE_TYPE_STR; + strncpy((*camera_meta)->units[0].str_value, "/dev/video0", + sizeof((*camera_meta)->units[0].str_value)); + + (*camera_meta)->units[1].id = CSI_CAMERA_META_ID_CHANNEL_ID; + (*camera_meta)->units[1].type = CSI_META_VALUE_TYPE_UINT; + (*camera_meta)->units[1].uint_value = 1; + + (*camera_meta)->units[2].id = CSI_CAMERA_META_ID_FRAME_ID; + (*camera_meta)->units[2].type = CSI_META_VALUE_TYPE_INT; + (*camera_meta)->units[2].uint_value = 2; + + (*camera_meta)->units[3].id = CSI_CAMERA_META_ID_TIMESTAMP; + (*camera_meta)->units[3].type = CSI_META_VALUE_TYPE_TIMEVAL; + gettimeofday(&(*camera_meta)->units[3].time_value, NULL); + + (*camera_meta)->units[4].id = CSI_CAMERA_META_ID_HDR; + (*camera_meta)->units[4].type = CSI_META_VALUE_TYPE_TIMEVAL; + (*camera_meta)->units[4].bool_value = false; + + camera_frame->meta.type = CSI_META_TYPE_CAMERA; + camera_frame->meta.size = meta_data_size; + camera_frame->meta.data = *camera_meta; + + LOG_D("*camera_meta=camera_frame->meta.data=%p\n", camera_frame->meta.data); + + return 0; +} + +void fdc_dump_camera_meta(csi_camera_meta_s *camera_meta) +{ + int ret; + csi_camrea_meta_unit_s meta_unit; + ret = csi_camera_frame_get_meta_unit(&meta_unit, camera_meta, CSI_CAMERA_META_ID_TIMESTAMP); + if (ret == 0) { + struct timeval tv = meta_unit.time_value; + struct tm *t = localtime(&tv.tv_sec); + if (t != NULL) { + LOG_O("Get timestamp from csi frame:%d-%d-%d %d:%d:%d.%ld\n", + 1900+t->tm_year, 1+t->tm_mon, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec, tv.tv_usec); + return; + } else { + LOG_E("timestamp is invalid\n"); + return; + } + } + return; +} + +void fdc_destory_csi_frame(csi_frame_s *camera_frame) +{ + if (camera_frame->meta.data != NULL) + csi_camera_frame_free_meta((csi_camera_meta_s*)camera_frame->meta.data); +} + +void fdc_dump_msghdr(struct msghdr *msg) +{ + int i; + if (msg == NULL) { + LOG_E("mgs = NULL\n"); + return; + } + + LOG_D("struct msghdr *msg(%p) = {\n", msg); + LOG_D("\t .msg_iov=%p\n", msg->msg_iov); + LOG_D("\t .msg_iovlen=%zd\n", msg->msg_iovlen); + for(i = 0; i < msg->msg_iovlen; i++) { + struct iovec *iovc = &(msg->msg_iov[i]); + LOG_D("\t .msg_iov[%d]={.iov_base=%p, .iov_len=%zd}\n", + i, msg->msg_iov[i].iov_base, msg->msg_iov[i].iov_len); + } + LOG_D("\t .msg_control=%p\n", msg->msg_control); + LOG_D("\t .msg_control=%zd\n", msg->msg_controllen); + LOG_D("\t .name=%p\n", msg->msg_name); + LOG_D("\t .msg_namelen=%d\n", msg->msg_namelen); + LOG_D("\t .msg_flags=%d\n", msg->msg_flags); + LOG_D("}\n"); +} + diff --git a/examples/frame/frame_demo_common.h b/examples/frame/frame_demo_common.h new file mode 100644 index 0000000..0f255ed --- /dev/null +++ b/examples/frame/frame_demo_common.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __FRAME_DEMO_COMMON_H__ +#define __FRAME_DEMO_COMMON_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#define ION_DEVICE_NAME "/dev/ion" + +extern const int PAGE_SIZE; +extern const char *IMG_YUV420_FILENAME; +extern const int IMG_RESOLUTION_WIDTH; +extern const int IMG_RESOLUTION_HEIGHT;; + +int fdc_get_img_file_size(long *img_size, const char *filename); +int fdc_get_ion_device_fd(int *ion_fd); +int fdc_alloc_ion_buf(struct ion_allocation_data *alloc_data, + int ion_fd, unsigned long img_size); +int fdc_mmap_dmabuf_addr(void **addr, unsigned long length, int dmabuf_fd); +int fdc_store_img_to_dmabuf(void *dmabuf_addr, int img_fd, long img_size); +int fdc_create_csi_frame(csi_frame_s *camera_frame, + csi_img_format_t *img_format, csi_camera_meta_s **camera_meta, + long img_size, int dmabuf_fd, void *dmabuf_addr); +void fdc_dump_camera_meta(csi_camera_meta_s *camera_meta); +void fdc_destory_csi_frame(csi_frame_s *camera_frame); +void fdc_dump_msghdr(struct msghdr *msg); + +#endif /* __FRAME_DEMO_COMMON_H__ */ diff --git a/examples/g2d/g2d_demo.c b/examples/g2d/g2d_demo.c new file mode 100644 index 0000000..c1bf3b1 --- /dev/null +++ b/examples/g2d/g2d_demo.c @@ -0,0 +1,1070 @@ +#include +#include +#include +#include +#include + +typedef struct _bitmap_file { + const char *name; + unsigned int width; + unsigned int height; + unsigned int format; +} bitmap_file; + +const static bitmap_file sbitmaps[] = { + { + .name = "resource/image1_640x480_nv12.yuv", + .width = 640, + .height = 480, + .format = CSI_G2D_FMT_NV12, + }, + { + .name = "resource/image2_640x480_argb.rgb", + .width = 640, + .height = 480, + .format = CSI_G2D_FMT_BGRA8888, + }, + { + .name = "resource/image3_640x480_nv12.yuv", + .width = 640, + .height = 480, + .format = CSI_G2D_FMT_NV12, + }, + { + .name = "resource/horse_640x480_nv12.yuv", + .width = 640, + .height = 480, + .format = CSI_G2D_FMT_NV12, + }, +}; + +static int g2d_load_bitmap(csi_g2d_surface *surface, const char *path) +{ + int i, j, bytes, lsize; + int width[3], height[3]; + FILE *file; + void *ptr; + + if (!surface || !surface->nplanes || surface->nplanes > 3) + return -1; + + file = fopen(path, "r"); + if (!file) + return -1; + + width[0] = surface->width; + height[0] = surface->height; + switch (surface->format) { + case CSI_G2D_FMT_NV12: + case CSI_G2D_FMT_NV21: + width[1] = surface->width / 2; + height[1] = surface->height / 2; + break; + case CSI_G2D_FMT_ARGB8888: + case CSI_G2D_FMT_BGRA8888: + break; + /* TODO: add more formats support here */ + default: + return -1; + } + + for (i = 0; i < surface->nplanes; i++) { + for (j = 0; j < height[i]; j++) { + ptr = surface->lgcaddr[i] + j * surface->stride[i]; + lsize = width[i] * surface->cpp[i]; + + bytes = fread(ptr, 1, lsize, file); + if (bytes != lsize) + return -1; + } + } + + return 0; +} + +static int g2d_save_bitmap(csi_g2d_surface *surface, const char *path) +{ + int i, j, bytes, lsize; + int width[3], height[3]; + FILE *file; + void *ptr; + + if (!surface || !surface->nplanes || surface->nplanes > 3) + return -1; + + file = fopen(path, "w"); + if (!file) + return -1; + + width[0] = surface->width; + height[0] = surface->height; + switch (surface->format) { + case CSI_G2D_FMT_NV12: + case CSI_G2D_FMT_NV21: + width[1] = surface->width / 2; + height[1] = surface->height / 2; + break; + /* TODO: add more formats support here */ + case CSI_G2D_FMT_ARGB8888: + break; + default: + return -1; + } + + for (i = 0; i < surface->nplanes; i++) { + for (j = 0; j < height[i]; j++) { + ptr = surface->lgcaddr[i] + j * surface->stride[i]; + lsize = width[i] * surface->cpp[i]; + + bytes = fwrite(ptr, 1, lsize, file); + if (bytes != lsize) + return -1; + } + } + + return 0; +} + +static int g2d_test_filterblit_with_rotation(void) +{ + int ret; + const bitmap_file *bitmap = &sbitmaps[0]; + csi_g2d_surface *source, *target; + csi_g2d_region dstRect; + + printf("start to do filterblit operation with 90 rotation: "); + + source = calloc(1, sizeof(*source)); + if (!source) { + printf("allocate source surface failed\n"); + exit(-1); + } + + target = calloc(1, sizeof(*target)); + if (!target) { + printf("allocate target surface failed\n"); + exit(-1); + } + + source->width = bitmap->width; + source->height = bitmap->height; + source->format = bitmap->format; + ret = csi_g2d_surface_create(source); + if (ret) { + printf("create source surface failed\n"); + exit(-1); + } + + target->width = bitmap->width * 2; + target->height = bitmap->height * 2; + target->format = CSI_G2D_FMT_ARGB8888; + ret = csi_g2d_surface_create(target); + if (ret) { + printf("create target surface failed\n"); + exit(-1); + } + + printf("%ux%u NV12 -> %ux%u ARGB8888\n", source->width, source->height, target->width, target->height); + + ret = g2d_load_bitmap(source, bitmap->name); + if (ret) { + printf("load bitmap to source failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_set_source(source); + if (ret) { + printf("set source surface failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_set_target(target); + if (ret) { + printf("set target surface failed\n"); + exit(-1); + } + + ret = csi_g2d_blit_set_rotation(CSI_G2D_ROTATION_0_DEGREE); + if (ret) { + printf("set rotation to 0 failed\n"); + exit(-1); + } + + dstRect.left = dstRect.top = 0; + dstRect.right = target->width; + dstRect.bottom = target->height; + + ret = csi_g2d_blit_set_filter_tap(CSI_G2D_FILTER_TAP_5, + CSI_G2D_FILTER_TAP_5); + if (ret) { + printf("set filter tap failed\n"); + exit(-1); + } + + ret = csi_g2d_blit_filterblit(&dstRect, 1); + if (ret) { + printf("filterblit nv12 to argb8888 failed\n"); + exit(-1); + } + + ret = csi_g2d_flush(); + if (ret) { + printf("flush g2d failed\n"); + exit(-1); + } + + ret = g2d_save_bitmap(target, "result/image1_result_1280x960_argb8888.rgb"); + if (ret) { + printf("save result failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_destroy(source); + if (ret) { + printf("destroy source surface failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_destroy(target); + if (ret) { + printf("destroy target surface failed\n"); + exit(-1); + } + + printf("filterblit operation finished\n"); + return 0; + +} + +static int g2d_test_stretchblit_with_rotation(void) +{ + int ret; + const bitmap_file *bitmap = &sbitmaps[0]; + csi_g2d_surface *source, *target; + csi_g2d_region dstRect; + + printf("start to do stretchblit operation with 90 rotation: "); + + source = calloc(1, sizeof(*source)); + if (!source) { + printf("allocate source surface failed\n"); + exit(-1); + } + + target = calloc(1, sizeof(*target)); + if (!target) { + printf("allocate target surface failed\n"); + exit(-1); + } + + source->width = bitmap->width; + source->height = bitmap->height; + source->format = bitmap->format; + ret = csi_g2d_surface_create(source); + if (ret) { + printf("create source surface failed\n"); + exit(-1); + } + + target->width = bitmap->height * 2; + target->height = bitmap->width * 2; + target->format = CSI_G2D_FMT_ARGB8888; + ret = csi_g2d_surface_create(target); + if (ret) { + printf("create target surface failed\n"); + exit(-1); + } + + printf("%ux%u NV12 -> %ux%u ARGB8888\n", source->width, source->height, target->width, target->height); + + ret = g2d_load_bitmap(source, bitmap->name); + if (ret) { + printf("load bitmap to source failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_set_source(source); + if (ret) { + printf("set source surface failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_set_target(target); + if (ret) { + printf("set target surface failed\n"); + exit(-1); + } + + dstRect.left = dstRect.top = 0; + dstRect.right = target->width; + dstRect.bottom = target->height; + + ret = csi_g2d_blit_set_rotation(CSI_G2D_ROTATION_90_DEGREE); + if (ret) { + printf("set rotation to 90 failed\n"); + exit(-1); + } + + ret = csi_g2d_blit_stretchblit(&dstRect, 1); + if (ret) { + printf("stretchblit nv12 to argb8888 failed\n"); + exit(-1); + } + + ret = csi_g2d_flush(); + if (ret) { + printf("flush g2d failed\n"); + exit(-1); + } + + ret = g2d_save_bitmap(target, "result/image1_result_960x1280_argb8888.rgb"); + if (ret) { + printf("save result failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_destroy(source); + if (ret) { + printf("destroy source surface failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_destroy(target); + if (ret) { + printf("destroy target surface failed\n"); + exit(-1); + } + + printf("stretchblit operation with 90 rotation finished\n"); + return 0; +} + +static int g2d_test_bitblit_with_alpha_blending(void) +{ + int ret; + const bitmap_file *bitmap = &sbitmaps[0]; + csi_g2d_surface *source, *target; + csi_g2d_region srcRect, dstRect; + csi_g2d_blend_mode blend_mode; + + printf("start to do bitblit operation with alpha blending\n"); + + source = calloc(1, sizeof(*source)); + if (!source) { + printf("allocate source surface failed\n"); + exit(-1); + } + + target = calloc(1, sizeof(*target)); + if (!target) { + printf("allocate target surface failed\n"); + exit(-1); + } + + source->width = bitmap->width; + source->height = bitmap->height; + source->format = CSI_G2D_FMT_ARGB8888; + + ret = csi_g2d_surface_create(source); + if (ret) { + printf("create source surface failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_set_target(source); + if (ret) { + printf("set source surface failed\n"); + exit(-1); + } + + srcRect.left = srcRect.top = 0; + srcRect.right = source->width; + srcRect.bottom = source->height; + + ret = csi_g2d_fill(&srcRect, 1, 0x0); + if (ret) { + printf("fill source surface failed\n"); + exit(-1); + } + + srcRect.left = 50; + srcRect.top = 60; + srcRect.right = 250; + srcRect.bottom = 260; + + ret = csi_g2d_fill(&srcRect, 1, 0xFF0000FF); + if (ret) { + printf("fill source surface failed\n"); + exit(-1); + } + + srcRect.left = srcRect.top = 0; + srcRect.right = source->width; + srcRect.bottom = source->height; + + target->width = bitmap->width; + target->height = bitmap->height; + target->format = CSI_G2D_FMT_ARGB8888; + ret = csi_g2d_surface_create(target); + if (ret) { + printf("create target surface failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_set_target(target); + if (ret) { + printf("set target surface failed\n"); + exit(-1); + } + + dstRect.left = dstRect.top = 0; + dstRect.right = target->width; + dstRect.bottom = target->height; + + ret = csi_g2d_fill(&dstRect, 1, 0x0); + if (ret) { + printf("fill source surface failed\n"); + exit(-1); + } + + dstRect.left = 20; + dstRect.top = 30; + dstRect.right = 120; + dstRect.bottom = 130; + + ret = csi_g2d_fill(&dstRect, 1, 0xFFFF0000); + if (ret) { + printf("fill source surface failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_set_source(source); + if (ret) { + printf("set source surface failed\n"); + exit(-1); + } + + /* set alpha modes */ + blend_mode = CSI_G2D_BLEND_MODE_XOR; + + ret = csi_g2d_surface_set_source_alpha_mode( + CSI_G2D_ALPHA_MODE_STRAIGHT, + blend_mode + ); + if (ret) { + printf("set source alpha mode failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_set_source_global_alpha_mode( + CSI_G2D_GLOBAL_ALPHA_MODE_OFF, + 0xFFFFFFFF + ); + if (ret) { + printf("set source global alpha mode failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_set_target_alpha_mode( + CSI_G2D_ALPHA_MODE_STRAIGHT, + blend_mode + ); + if (ret) { + printf("set target alpha mode failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_set_target_global_alpha_mode( + CSI_G2D_GLOBAL_ALPHA_MODE_OFF, + 0xFFFFFFFF + ); + if (ret) { + printf("set target global alpha mode failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_enable_disable_alpha_blend(true); + if (ret) { + printf("enable alpha blend failed\n"); + exit(-1); + } + + dstRect.left = dstRect.top = 0; + dstRect.right = target->width; + dstRect.bottom = target->height; + + ret = csi_g2d_blit_bitblit(&dstRect, 1); + if (ret) { + printf("bitblit argb8888 to argb8888 with alpha blend failed\n"); + exit(-1); + } + + ret = csi_g2d_flush(); + if (ret) { + printf("flush g2d failed\n"); + exit(-1); + } + + ret = g2d_save_bitmap(target, "result/alpha_blend_result_640x480_argb8888.rgb"); + if (ret) { + printf("save result failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_destroy(source); + if (ret) { + printf("destroy source surface failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_destroy(target); + if (ret) { + printf("destroy target surface failed\n"); + exit(-1); + } + + printf("bitblit operation with alpha blending finished\n"); + return 0; +} + +static int g2d_test_bitblit_with_rotation(void) +{ + int ret; + const bitmap_file *bitmap = &sbitmaps[0]; + csi_g2d_surface *source, *target; + csi_g2d_region dstRect; + + printf("start to do bitblit operation with 90 rotation: "); + + source = calloc(1, sizeof(*source)); + if (!source) { + printf("allocate source surface failed\n"); + exit(-1); + } + + target = calloc(1, sizeof(*target)); + if (!target) { + printf("allocate target surface failed\n"); + exit(-1); + } + + source->width = bitmap->width; + source->height = bitmap->height; + source->format = bitmap->format; + ret = csi_g2d_surface_create(source); + if (ret) { + printf("create source surface failed\n"); + exit(-1); + } + + target->width = bitmap->height; + target->height = bitmap->width; + target->format = CSI_G2D_FMT_ARGB8888; + ret = csi_g2d_surface_create(target); + if (ret) { + printf("create target surface failed\n"); + exit(-1); + } + + printf("%ux%u NV12 -> %ux%u ARGB8888\n", source->width, source->height, target->width, target->height); + + ret = g2d_load_bitmap(source, bitmap->name); + if (ret) { + printf("load bitmap to source failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_set_source(source); + if (ret) { + printf("set source surface failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_set_target(target); + if (ret) { + printf("set target surface failed\n"); + exit(-1); + } + + dstRect.left = dstRect.top = 0; + dstRect.right = target->width; + dstRect.bottom = target->height; + + ret = csi_g2d_blit_set_rotation(CSI_G2D_ROTATION_90_DEGREE); + if (ret) { + printf("set rotation to 90 failed\n"); + exit(-1); + } + + ret = csi_g2d_blit_bitblit(&dstRect, 1); + if (ret) { + printf("bitblit nv12 to nv12 failed\n"); + exit(-1); + } + + ret = csi_g2d_flush(); + if (ret) { + printf("flush g2d failed\n"); + exit(-1); + } + + ret = g2d_save_bitmap(target, "result/image1_result_480x640_argb8888.rgb"); + if (ret) { + printf("save result failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_destroy(source); + if (ret) { + printf("destroy source surface failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_destroy(target); + if (ret) { + printf("destroy target surface failed\n"); + exit(-1); + } + + printf("bitblit operation with 90 rotation finished\n"); + return 0; + +} + +static int g2d_test_fill_rectangle(void) +{ + int ret; + csi_g2d_surface *target; + csi_g2d_region dstRect; + + printf("start to do rectangle fill operation: "); + + target = calloc(1, sizeof(*target)); + if (!target) { + printf("allocate target surface failed\n"); + exit(-1); + } + + target->width = 640; + target->height = 480; + target->format = CSI_G2D_FMT_ARGB8888; + + ret = csi_g2d_surface_create(target); + if (ret) { + printf("create target surface failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_set_target(target); + if (ret) { + printf("set target surface failed\n"); + exit(-1); + } + + printf("fill red to %ux%u rectangle\n", target->width, target->height); + + dstRect.left = dstRect.top = 0; + dstRect.right = target->width; + dstRect.bottom = target->height; + + ret = csi_g2d_fill(&dstRect, 1, 0xffff0000); + if (ret) { + printf("g2d fill operation failed\n"); + exit(-1); + } + + ret = csi_g2d_flush(); + if (ret) { + printf("flush g2d failed\n"); + exit(-1); + } + + ret = g2d_save_bitmap(target, "result/image1_result_640x480_argb8888_fill.rgb"); + if (ret) { + printf("save result failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_destroy(target); + if (ret) { + printf("destroy target surface failed\n"); + exit(-1); + } + + printf("fill rectangle operation finished\n"); + return 0; +} + +static int g2d_test_draw_rectangle(void) +{ + int ret; + const bitmap_file *bitmap = &sbitmaps[1]; + csi_g2d_surface *target; + csi_g2d_region dstRect; + csi_g2d_rectangle rectangle; + + printf("start to do rectangle draw operation\n"); + + target = calloc(1, sizeof(*target)); + if (!target) { + printf("allocate target surface failed\n"); + exit(-1); + } + + target->width = bitmap->width; + target->height = bitmap->height; + target->format = CSI_G2D_FMT_ARGB8888; + ret = csi_g2d_surface_create(target); + if (ret) { + printf("create target surface failed\n"); + exit(-1); + } + + ret = g2d_load_bitmap(target, bitmap->name); + if (ret) { + printf("load bitmap to target failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_set_target(target); + if (ret) { + printf("set target surface failed\n"); + exit(-1); + } + + dstRect.left = dstRect.top = 0; + dstRect.right = target->width; + dstRect.bottom = target->height; + + ret = csi_g2d_brush_create(0xffff0000, false); + if (ret) { + printf("create brush failed\n"); + exit(-1); + } + + rectangle.line[0].start.x = dstRect.left + 30; + rectangle.line[0].start.y = dstRect.top + 30; + rectangle.line[0].end.x = dstRect.right - 30; + rectangle.line[0].end.y = dstRect.top + 30; + + rectangle.line[1].start.x = dstRect.left + 30; + rectangle.line[1].start.y = dstRect.top + 30; + rectangle.line[1].end.x = dstRect.left + 30; + rectangle.line[1].end.y = dstRect.bottom - 30; + + rectangle.line[2].start.x = dstRect.left + 30; + rectangle.line[2].start.y = dstRect.bottom - 30; + rectangle.line[2].end.x = dstRect.right - 30; + rectangle.line[2].end.y = dstRect.bottom - 30; + + rectangle.line[3].start.x = dstRect.right - 30; + rectangle.line[3].start.y = dstRect.top + 30; + rectangle.line[3].end.x = dstRect.right - 30; + rectangle.line[3].end.y = dstRect.bottom - 30; + + ret = csi_g2d_line_draw_rectangle(&rectangle, 1); + if (ret) { + printf("draw one rectangle failed\n"); + exit(-1); + } + + ret = csi_g2d_flush(); + if (ret) { + printf("flush g2d failed\n"); + exit(-1); + } + + ret = g2d_save_bitmap(target, "result/image1_result_640x480_argb8888_rectangle.rgb"); + if (ret) { + printf("save result failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_destroy(target); + if (ret) { + printf("destroy target surface failed\n"); + exit(-1); + } + + printf("draw rectangle operation finished\n"); + return 0; +} + +static int g2d_test_multisrc_blit(void) +{ + int i, ret; + csi_g2d_surface *source[4], *target; + csi_g2d_region srcRect, dstRect; + + printf("start to do multisrc blit operation\n"); + + for (i = 0; i < 4; i++) { + source[i] = calloc(1, sizeof(csi_g2d_surface)); + if (!source[i]) { + printf("allocate source surface %d failed\n", i); + exit(-1); + } + } + + target = calloc(1, sizeof(*target)); + if (!target) { + printf("allocate target surface failed\n"); + exit(-1); + } + + for (i = 0; i < 4; i++) { + source[i]->width = sbitmaps[i].width; + source[i]->height = sbitmaps[i].height; + source[i]->format = sbitmaps[i].format; + ret = csi_g2d_surface_create(source[i]); + if (ret) { + printf("create source surface failed\n"); + exit(-1); + } + + ret = g2d_load_bitmap(source[i], sbitmaps[i].name); + if (ret) { + printf("load bitmap %d to source failed\n", i); + exit(-1); + } + } + + target->width = 640; + target->height = 480; + target->format = CSI_G2D_FMT_ARGB8888; + ret = csi_g2d_surface_create(target); + if (ret) { + printf("create target surface failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_set_target(target); + if (ret) { + printf("set target surface failed\n"); + exit(-1); + } + + dstRect.left = dstRect.top = 0; + dstRect.right = target->width; + dstRect.bottom = target->height; + + ret = csi_g2d_fill(&dstRect, 1, 0x0); + if (ret) { + printf("fill target surface to 0x0 failed\n"); + exit(-1); + } + + for (i = 0; i < 4; i++) { + ret = csi_g2d_surface_select_source(i); + if (ret) { + printf("select source %d\n", i); + exit(-1); + } + + ret = csi_g2d_surface_set_source(source[i]); + if (ret) { + printf("set source %d surface failed\n", i); + exit(-1); + } + + switch (i % 4) { + case 0: + srcRect.left = 320; + srcRect.top = 240; + srcRect.right = srcRect.left + 320; + srcRect.bottom = srcRect.top + 240; + break; + case 1: + srcRect.left = 0; + srcRect.top = 240; + srcRect.right = srcRect.left + 320; + srcRect.bottom = srcRect.top + 240; + break; + case 2: + srcRect.left = 0; + srcRect.top = 0; + srcRect.right = srcRect.left + 320; + srcRect.bottom = srcRect.top + 240; + break; + case 3: + srcRect.left = 320; + srcRect.top = 0; + srcRect.right = srcRect.left + 320; + srcRect.bottom = srcRect.top + 240; + break; + } + + ret = csi_g2d_surface_set_source_clipping(&srcRect); + if (ret) { + printf("set source %d clipping failed\n", i); + return ret; + } + } + + ret = csi_g2d_blit_multisrc_blit(0xF, &dstRect, 1); + if (ret) { + printf("multisrc blit failed\n"); + exit(-1); + } + + ret = csi_g2d_flush(); + if (ret) { + printf("flush g2d failed\n"); + exit(-1); + } + + ret = g2d_save_bitmap(target, "result/multisrc_result_640x480_argb.rgb"); + if (ret) { + printf("save result failed\n"); + exit(-1); + } + + for (i = 0; i < 4; i++) { + ret = csi_g2d_surface_destroy(source[i]); + if (ret) { + printf("destroy source surface failed\n"); + exit(-1); + } + } + + ret = csi_g2d_surface_destroy(target); + if (ret) { + printf("destroy target surface failed\n"); + exit(-1); + } + + printf("multisrc blit operation with 4 sources finished\n"); + return 0; +} + +static int g2d_test_bitblit(void) +{ + int ret; + const bitmap_file *bitmap = &sbitmaps[0]; + csi_g2d_surface *source, *target; + csi_g2d_region dstRect; + + printf("start to do bitblit operation: "); + + source = calloc(1, sizeof(*source)); + if (!source) { + printf("allocate source surface failed\n"); + exit(-1); + } + + target = calloc(1, sizeof(*target)); + if (!target) { + printf("allocate target surface failed\n"); + exit(-1); + } + + source->width = bitmap->width; + source->height = bitmap->height; + source->format = bitmap->format; + ret = csi_g2d_surface_create(source); + if (ret) { + printf("create source surface failed\n"); + exit(-1); + } + + target->width = bitmap->width; + target->height = bitmap->height; + target->format = CSI_G2D_FMT_ARGB8888; + ret = csi_g2d_surface_create(target); + if (ret) { + printf("create target surface failed\n"); + exit(-1); + } + + printf("%ux%u NV12 -> %ux%u ARGB8888\n", source->width, source->height, target->width, target->height); + + ret = g2d_load_bitmap(source, bitmap->name); + if (ret) { + printf("load bitmap to source failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_set_source(source); + if (ret) { + printf("set source surface failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_set_target(target); + if (ret) { + printf("set target surface failed\n"); + exit(-1); + } + + dstRect.left = dstRect.top = 0; + dstRect.right = target->width; + dstRect.bottom = target->height; + + ret = csi_g2d_blit_bitblit(&dstRect, 1); + if (ret) { + printf("bitblit nv12 to nv12 failed\n"); + exit(-1); + } + + ret = csi_g2d_flush(); + if (ret) { + printf("flush g2d failed\n"); + exit(-1); + } + + ret = g2d_save_bitmap(target, "result/image1_result_640x480_argb8888.rgb"); + if (ret) { + printf("save result failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_destroy(source); + if (ret) { + printf("destroy source surface failed\n"); + exit(-1); + } + + ret = csi_g2d_surface_destroy(target); + if (ret) { + printf("destroy target surface failed\n"); + exit(-1); + } + + printf("bitblit operation with 90 rotation finished\n"); + return 0; +} + +int main(void) +{ + int ret; + + ret = csi_g2d_open(); + if (ret) { + printf("open csi g2d failed\n"); + exit(-1); + } + + g2d_test_bitblit(); + + g2d_test_draw_rectangle(); + + g2d_test_fill_rectangle(); + + g2d_test_bitblit_with_rotation(); + + g2d_test_stretchblit_with_rotation(); + + g2d_test_filterblit_with_rotation(); + + g2d_test_bitblit_with_alpha_blending(); + + g2d_test_multisrc_blit(); + + ret = csi_g2d_close(); + if (ret) { + printf("close g2d failed\n"); + exit(-1); + } + + exit(0); +} \ No newline at end of file diff --git a/examples/resource/yuv420_1280x720_csky2016.yuv b/examples/resource/yuv420_1280x720_csky2016.yuv new file mode 100644 index 0000000..edc51c1 --- /dev/null +++ b/examples/resource/yuv420_1280x720_csky2016.yuv @@ -0,0 +1,118 @@ +                +  +   + + + + + + +   + + +  + + + + + +  + + + + + +  + + + + + + + + + +   + + + +  + +   + + + + +  + + + + + + + + + + + +   + + + + + +    +  + +  + + +  + + + + +   + +  + +     +                + + + + + +        + + + + + +   + +  + + + + +  + + + +   + + + + + + +  + + + + + + + +      zywutrponmmnpruvyyyywtspjllptw|~~yungaUTRQRSTTUUTTTTSSNNMMMLLLLLLLLLLLJJIJKKKKKKKKJJJJIIIIIIIIJKMMMMKJHGGFFEEDGHJLNPRS\\\\\\\\]]^^__``aaaaaaaaddddddbb]]^^][ZXWWWWWWWWWWWWXXYZ_`abcdddggfeedccaaabbccdeeeffgghggfeedcccccdeeffgghhhhiihiijjkkljjiihhggiiiiiiiikmmoooookkkkiiiihhhggffeddddcccca`aabcddggghhhhigghhijjjmmmmnnnonnnnnnnnrrrrrrrropqqqponppqqqqrrpoonnoopqrrsstttqqrrrrssuvwxxwvutuuvvvwwuuuuttstvwxz{|}~~|}hWWb^_`_\]chd[NGFKRV\^bfgijjihgda_`dlllllllmnnnnnnnppqqrstuuvvvvvvvvxxxxxxxxxxxxz|||||||}}}}~~}}~~}}~t^I95@Thvwunkjifcd`_^cltzpjYP^vp^KBA@??><<<=<=30,+)&$#! !&).37:;;9964101049;;<:861*'$"!!"'/26:<<;9644568;>A@?<82.*&&&&''''*+-03689@@ABEGHIJIHFEDCBACFINU[eknoomhf`]UPJECBCGGFGFDC@>974**$$!!""$$$$#"!!$'&),//.../13799861+" !!""""#"  (/6>DGHGFEEHKMMKEB??ADHMRVTPKC;.(%%)++*)*)'&%&0AYmvz{yvuttttsrqqrqqqqpppqqqqqqqqssssssssppppppommmmmmmkiiihggfeeeeeeeeec``__^^^]ZZYYXXXVVXZZYYYYWVUTTNIIFDEDDDEEFEEEABBACCCBA@@?>=;98:=;=;98:=;:97754432/0256789>=::<<<;64.+++++,,+)'&&%¼}{xxyz{{{{{zzyyyrrqppnmnnnnnnnnnijkkllmnmmlllkkkkkkkkkkkmnopponmhggfeeddgilpuy|}~~~~~~~~~~qefp|Ŀm_jtɯwd^]]ZXTTUUUWIG=;852/-*))))))+27>FMQRROPJFCBCDJRSUURNKC:60-,-/4>CJPSUTQKHGIKNSY]\ZUOF=9444555669;?DINRTZ\]adfjjkjhfdbaa_bfks~vme`abhhhhedc^\TOL<<43//.11110/.,+0337>@BAABCEJORTROG<4,%  $(*,-.//001.+(&% "&-7ANZciihdcdhnpqmfa\[_ekt{|un`VA8227<<;;:8306Gc||~|{zuooihhgggghiifffeeedccb`^\ZXWVTQRTQWURPPRONJFEDB?>=8:=?@BEGONNORRQPEB><==<<>>;75443vsqprsttwwwvvuuunomlkjiiiiiiiiiideffhijjhhggfffeffffffffghjkkjhgbbaa`_^^fhjnrvyz~{yyyyyyyyyzyz{|~۹yzrklu}~~pƲ}e][\YWVUTSRRGD<:630-+)'''''')0464...0/10/-,*).227;>??>?AEIMPRROG<;1(" #((*,-.///0-*(%$ "&+6AMYbeec^^`ejnola]YX[bhouzwpj]S@6/.5;<896409Qp~}||{uux{|{{||{zyxwtppkjhgffghhhijbca`dcb`^\[ZVUTSQNKJSQPNKHFF>;:9642102224568<=@CEDCB:99999999:743321wroppqsrqsuvvvuronljhggghhhhhgecacehjkllkjhgfeeeeeeeefffklmlkjgf_`^^\]`bikmqux{|~}{xxxxxxxx{zz{|}Ú{v|{~|s̽q__a\WXVTRPNIA<:630.+)((((((*18@HNPQPOKEB@ABEJORRROLF=72,**,/5;BINPPNJFDDFINSWXXVQJB<5112223346:?CHKOTXYY\^`cdddb_\ZYZ\`bfmwzrjedklmllkihe_XPJ@963212221/.-*)+/24;>@@AADGMSWXXVQG;1)#!! !!!!!!%*+,./001120-*'&"#%'.8GT^dihe^`flqrqme_Y[ahlwxzytlcXG6115:<<;44Da~}}yustw{}|z|}}{yxvusqrpnkllhilkgfihefcaaa_^ZYXVTRPMJHFD?><;65332210-.//01333568:<;;<;===<;<95433211vqoppqsssuwxxxxvpnligfggghhhgfcaabfikkkliigfedddeeefffffijkkjhge__^\\^adjlnrvy}~|yxxxyyyz{|zz{|}œ|wvt{ysµ~hab^[[YVSQMFA;952/,*(((((((*06>FLOPONID@??ACGMPPOMJC;61+))+.49@HMOOMIECCEHNSWXWUPIA;40011222349>BGJNSWXXZ\^abba_]ZXWXZ]`dkv~unhfjlmnmmlkie^WOG?954224320/-+**-03:=@@@ADHOTXYYVPG<4*%"!! !!!!%)*+-/01111/,)'%! #&(.;HT]cfec\^cjprsnfa]]bilrzyyundXG9227<;77>Rs~xursux|||}|{zxxxxvvtssnmklnliimkljiged`]ZWROLI@=;;:87632210..-.//01122222333577999:::964332100sonopqstuwy{{|{ytqnligggghggfebaabgkmmklihgfedeeddddeeeehiijhfed`^^\\^celnptx{~{yxxxxyyz{z{{||}Ǡ~vtlggjjtzxwȼqccc_^[YUQLF?:851-+)(''''''*/5BFIMQTVVX[]``__][XWUVX[^cju|tnkkmpqqqpomid]UME<865444421.-+),.2887664433200/.--,,---.....--./0124555668743321100~pmlnoquvwx{}~~~{xuspmkihhhgfedba`chlmmkjgfedccccccddeeffgfggfebb`_]\\`dgmorvz}~zyxzyzz{{|||}}~ɣvqaWPMLKYks{{gdfc`]ZWQLE>963/+)((&&%%%'*06=CIMNLID?<;;>AFKMMKHC=74/*()*-15=DJKKIEB??AEJPTVUQKD=70-../112338=AFHLPRSSVY[^^]][YUSRTWZ]`fq||tnmoqsssrpolgaYRJA;98666531/,)(*.17;?@BDGMTY\^^[UKB90($###"""""$&),-/1234420-*(%#!$%-2BMZbfhed`bhpvzzxrnklpsux}zxtmdYL=2--4D]{~}zwusrrsvy{zz{{{{{z}~{ywtuwwuodSOKFDA?=><;;999755443222100/.-,,,,,,,,,,++++,-./1110012322211111{mjjmnquxyz}}zxuroljjigfdcaa`cilmljgddcbaabbbbcddeefeffgedba_^]]`cfhnpsw{~}yyyzzzz{{|||}Ψu~{yyoYHB@>=>Kbu}Ʒneedb_[WSLE>963/+)((&%%$%'+17>DILMLHC>;:;=BFLMLJEA;62/+**+-04;BHIIGD@==?CHNSVTOIB<6/+,-/122348=AFHLPRSRUXZ]][\ZWSRQSWZ\_dmzyroprtuvurqnic[TMD=<:988752/,*()-06:>@BDHMUZ^``^XNE<3*%$$##"""#$'*-0234432110.+(%!#(,07FU`ddef`\bmu|~|ywuuvz|~|ytlcYK<79DXt~}{yxwtrsuvzyzzyzz{|}}~|{z}zhSC820012454666666663443322210/.--,+++++++++****+,,-//..../000011111xjhilmqvz|~~zvspmljgedbaabdhlljgfcbaa``aaaabbcdeeefggedba____cfijoqsw{|yyyzyyyzz{{}~ҭqztccr~gRA?>;;;Jatʾzicdda]YRLE>963/+)((&&&%&(,29@EJMNKGC><;<>BFKLKID@:52/+**+-03:AEGGEB><<=AFKPSQLF@:3.*+,.//0126;?DFJNPQPRUX[ZXYWTPONPUXZ]ajw~vpqsuwxwtspke^WPF?=;9887540.+)*-059=?BDHNU[_bb`ZQH>4,&$###"""#$'*.0244443221/+(&" #(,29KXcfgfga]cox}{z|}|ytlbULAI[t~}}||yurpqtvxyyyyz{{}~sY?-%&%&'+/36743444556233322210//.-,+++++++++*))()()*+,-,,,,-...///011thghkmqw||xtqolhfdbabceijjhec`__^^^__^_``bbcdgfgggdbb``_afiklprtx|~|yzz{zyzzz||~~Աt{{_[jnWECC@<963/+)(('&&&').4;AGKNMKGC?<<=?CGKLKHD?942/+++,-029?DEED@<:9;?DIMPNIC=71,)*+,--./049=ACGLNNNPSUXWUVTQMLKMQTVY^gsyrrsuwxxwurmhaZRHA?<:998651/,*+-/47963/+)((''''(+06>DIMOOLHD@==>@EIMNKHD?953/,,,-.039?CDDC?:879=BFJMKGA<60+)**++,,-.27:?AEILLKMPRUTRSQNJIHIMPQTYbn||trruwxyxwtpjc\UKC@=;9986630-++./46;>BDHMTZ_cec^VMB8/(%##$###$%(+.02444433320,)'# !#(,4>O[chgeca]fs}}vmecn}k^Zbt~~ysqoprsvy{zzwvxw{pR=-&%'% #"#&+.121112222232212121/.--,+**+++++++)(('&&&&&'())))((**+,--..xldadgmsw|ysrmkfd_befifggb_`]_]\]__`___cegfighikgecacfjklllqrrz|}}|||{zzzzz}԰ojtVJGDA@I\oĵn^c_\YTKC>963/+)(('''(*,19@EKOQQOJD@?>?CHLOPOJE@;63/.--./149>ACDC@9768;@DHKIE@:4.+)))***+,,/37;>AEHIJKNPPOPPNKHGFFIKMPU]iwwtsuwxyzzwsmg`XOFC@=<;:8631.,+++/38=@BGMU[`ege`YPF;1*'&&%$$$%&),.02444433320,)'# #*-5BQ]dhihe_`iu}voks}_B524;CRg~|wqmlmotxyyyywwvtwzvgP93+)((&$$! !$'+.0111111221110//../.-,+**)********)('&'''('''''''())**++,,uhaaccenv{wuolgeddegige``_]^]___]_aaabdeffghhiifffdfhjkmllqrvy}~~~|{zzz|~ƦycQIFDBI\mɻya^[ZUQIA=963/+))(''()+.4ACDC@96569=BFIGC>82-)'''(((()*-159;>BEFFGJLLKLKIGDBBCEGIKPXdsytsuwxyz{xtoib[QIEB?=<:7642/,+++.179852/+))('')*,06>EKPSUTQLGCAADGKORRQMGA<840///1345:>BCDC@:6569=ADGEA<61+'$$%%&&&'(+/379=?BCDEJR_m~zustvxz{{yuqkd\TKGC@><:76420-+*+-16:<@DIQW\bdeb[QG=3,)'''&&&&')+-/133332221/,)'$" "(-8FV`fijiecfp|yy}ziQNUcnrroje\SRXi~|ywrqnmlljiruy|~yj[KEKQTTSPLGCBCEHMPSSQMHA<8631112467BDFEA;60*&####$$%&&),0358;>>>?AAAAA?=:8779>>?@DLXhz{urrtvxyzyvrkd]TKFB@=;977640.,**,/48;>BHOUZ`bc`ZPG=3,)('''&&&')*,/123321110.,)&# "',:IXbhkkjfdfp|}xl^_gu~||yri_UQYp}{ytqonnmlllmpruvri[OA6.+))(&((('''&%""#$%'(*/11111111110/.--+*******++++++++**)('&&'&&&&&%%&'(((()))vh_\_^__anx}}yurnlmiedb^_^^[Z_^`abehjighhgjlmljhhnnnopqrrsux~~~~~~}zyyz|~DZtVHEBKTboaTNJB<731.+)'&&%%&'(+06=DKQTSROKEBBBDHLORRPLGA<7644334567=@CDDB@;867:>BDFD@;50*&##""##$%&(+-0259;;;<==<==:6433359:;<@FSdw|uqqruwxzywrld]TJEA?<:877751.,+),/37:=AFMSY^aa`ZOF<3,)('''&%%&(*+-/1222111/-+)&""%*7J[cilkjefkuvmly~{sgZQNf~|zywtpopsrqrspng^TJ@80/0/.-+*()))('&&%#"#$&'(*.0001111000/.-,,+****************))('&&'''&&&%%&'''((())l^[WY\_^blrv~|{xvsophfbswf^_^^]^d`baccccbddeilojihlmoopqqqs~~~~~|yxyy{~}ZIFDITbpƹjUJE>731.+(&$#$$#$&').5BDFD@;5/*&##""##$%&(+-0258:::;<<;:9631///26789=CPbu~wrpqsuwyyvrle]TJDA?<:876641/,*),.269;?EKQW\^_]WNE;3,)('''&%%&(*,.02222100/-+(%"!%+8L[cilkjefku|{~ztogTQ]v~|zxwusqsrquzxpbWMD<854455431/-,,,+*('&%##$%&'(*.///0001//..-,++*****************)(('&&'''&&&%%%&''''(((Ŀvgbmpa[\]`bl{{xookXۄ^__]Z^`xmkhknnpqqpppuȂ}z娃{zؕĦДvֵ~~~~~~zxxxyz~Է~[IFGIWht˾uWGA;4/.+(%#"!!!!"$%',29@GMPONKFA>=>@CFKNNKGB<7542345679:?CFFFDB<866:>BDFD@:5/)&##""##$%&(+-0257999:;;:8630-,,,.4566:AM`sysppqtvxxvrme]TIC@>:8764542/,+*-.148;>CHNUX\\ZUKB91+('&''&%%&(*,.022210//-+)&$" $*:M\djmkiefkv}yqjZPUn~~}{xwvtsrvuwzviUD;889::987787421100.,*('&$$%&'()*--..//00//..-,++****************))((''''(('''&&&%&&&''''{rǂY\ZqŸ̱ҝ~}zsniJxZ[WX]]Vinhnnopqqpnl{yf|c突s驀ٗqy_~~~}}zwvxx{}ϰsZJHIR`q|´~[E=91-,(&#!  !"%*06=DJLLJGB=:8::77:>BEGE@:4.)&##""##$%&(+-0257889:::9752/-+++.4556:@M_q{tonoqtwwurme]THB?<975435431-,+/.046:=BFLQVZ[WRH?7/)&&&&&%%%&()+-/1110/.-,*(%# ")BCB?;72.,,-02468<>CHIKKIE@<879=BFGE@:4.(%""!!""#$'),.13688889::9742/,+*+-13359@M_q~}uolmoruvtqle]TMF@=;84544441/.+.-/37:<@DIOSVVSME=5.(%%%&&%$$%')*,./0//.--,*(%#!!*AR_fkmkggfmv~yvndYT`}|{zywvwyyx}~rV9($&''(*+,-..01223343210-+((''())***++,,-..---,,+*)))))))))))))))))))((((''(()(('&%%&&%%%%%%ǼoPck@:Q^mQ}嶢klfcLڇɵhmlmmnpnqn_݃vrΰ޵{w䊍`~~~}zvvuvz}Ī_TZ]^bnzǺgE74-'$# !%+29?DFDA=9521247;=>=:62.+)*,/2469>AEJMNONID?;9;?CGIGB<5/*%""!!""#$'),.13677789998642/,+*+-02359@M_o|vokknqtutrmg`VNF@<:7343356420-/./269;=AEIJHC=71+&""!!""#$'),.13677789997531.+*)*,.1248>J[jxxojjmpsttrnhaXPHB=;855467885300./258;87889::9621./158//+$$  $)/48<<:840-*)+-02342/,($""$'*.26:@FMSX\\[YSMIFFHLOQPKE>6.(#"!!"###%(+.1357778::86431.+)((*-./26;EUfs{rlklnsuvvsoi`VPHB?;989:==>=:51..1375-'%$$%%%%'),03578889::9641/+(&%%'*+,/38BRanzvnjjmruwwvtog^XPID@>=?ABEFDA=730027GOW_cefe^XUSTX\_`^YRJB90*('''(((*,/258:;::;<<;9731-*('&(*+,/38BQ`mzxoiilqtvwwuqib\TMHDA?ACEHIHF@;63127<@EKOSVVTNF=4,&"!"$%&&'(,2469<=<:8541.+'%""5IZagjjihjjnqrtwy~|z{xupmfXNYy}xk[MA:50-+*+))(&%%&$  !!"$$$$####$$$$$$$&'''''''''''''''&" թm_USQmyyO@ꮖytЦߡrloxk_h~f᪞fllppqtquwdgwn\o~gdʜƝ{unk撃qވ~~|xvxy{}~ɫ{joyN530*%$ #(-269;:642/,+*,/24342-)$ !#'+/37=CLV]acec_[XXY\`bba^XPG?5-+***+++.0369;=>>=>@@><:751.+*)*+,-04:DScp|~slikpsuwxwtne_WPJEB@?BHLPNLGA92/16?=<8641.,(%"%9K\cgjhhfijrw{~}{zxvqokaVP[t~}p^J;6753/+((++**)'&" !! !! !#######%&&&'''()'''&&&&%##!  թ{gYWLVf>iŎxxޮ笍~m浛fkportsstuiYԂwo\禆܂h׼ꯇ塄mlꖂsx}~~~zyvwxy{}~Ʈtq~ƻrK740)%% #).49;=;752/-++-035442.)$ "%)-15;AJT[_acb^ZXXZ^bddd`[SKB91/-----/247;>@ABBABDDC@>:841.-,,-./26:8740-)&# );M]eghgfcfksz}|zyvurpi^SQ]w~{xywn]I<513543/*''))(''&$!!!!!!!!$%&&&'''('&&&%%%###"  !!!!!ګ}oZUQJI[H:d|}rs궕pjmotqsqtuurZfؽk|fȉÄp֜mv܋y}}||yywxyz{|}~êv|ǾjH850)%$  $*06;<><8530.,+-045553.*%! #'+/39?HRY]_a`\XVWZ^beeeb]UME;5310000357:>ACEEEDEGHFDB?<8521013458=DO[huuoosvxz{{ytnh`XRLHEEHNTWXWTKB8105;AFLRWYXUPJB8.'#!""#$$%&+17:?BDBA><:730,)% ->P\dgihfdnw~}|{yvtpnkg`ULSv{yrh`WK?50//2576.(%%&&&$#"" !#$$$$%%%'('&&&&&#$$#""!! !!"#####ᮙvbQTSOC674337;PVPKGThry}ljqs|XZZZZUR]ikorqpqswvulQ{̝mv|m`x}upqemxzŗv~|zzzzyyzzzz{}}yxcG951+&%!"&,28<=><9630.--0367652.)%!"&*.27=FPW\^`^ZWUVX\aeffc]VNE<743343369<951-*%!!2BS\dghjkjz}|{zzwtrolic\SMZw|wrldVMB:321/-.04660(""###!  "#""###$$%&&%%%%$#%%$$$###########帛|nXPSPNLKLMLMKMQW]cjwpuydcpsnnlge_POamqsrtqrrwtvwgS`z{e^e\pnehdfqwx`d{x_dr}{zzzz{zzzyz{|}|r{\F:51+(%! #'-49=>><963/../2578863.*%""'+.16>@FQ_myywy{~ztme]VQNNQU\abbbYNB614:AGOVZ]]ZUPG<1)$"#"###$',39>CFIIIGFC@=940)%$4EU_gikpvx~}|{zyxtrqnjgd]PFUyxdYQNKFB<851/.0/../1440)#""""  !#!!"""$$%%%%%%$$$&&&&%%%%%$######Ÿn`TTQMOOSPPOSSSTYbpy~}{wopqrrqsrpqrtvwwwps|{|~xxyzzz{{|{zyxyz{|ttuvÿsVE<61+(%!!$)/5:>??=:740//14689973.*&""&*-1694/)"(9IXajou|~~}|{zyywusqokhgd[JEYwcO?:6444310--,-,,+,,,/230*$"!! !!"""#$$&&&&&&&&&'''''&%%&%#""!! ҟyy]RPMNQMNNPOQQRUZft}}zvvtwrurrprsyzx}}{xzz{|~|zywwyz|~~~}}}|}{{{{{z|||}}~~~{{{zzyyyy|}~zkgizlRE<71+)'""&+17;75211368:;:84/*&""&*,06N[grz~}}|{zyxxwvsqnlifdaXKJa~{jVC60/123442...,+*((()**,.10,&"  !""$$%&&&&'''('''(('&%$$#"! ⱟjkQKMPQNNNOPPQTU]gw}z}x{xvvtssyw||}yxx{|~|zxvvxz{~}}|{ztttsssrrqqrqqqpppppqqrrrrqqpoonnnppprsuwwxyyyzzx{|||{{}}|}pgfy{eOE=60-*(" $(-39>ABA?<8643358:<<;95/+&" "&),/5;DOV[^`_[WSRTWZ^`_\WOG@953234459=ADGJMOPPPRTVUSQPMJGEDEFGHIKQ\ivxohdeflsz|}~ul^M?78BJU^dilkhaXL@6/+*)***+.4;BGNRVXXVWVUSNG@;3+*3CRdq~~}}|{zyxwwvvrpnlkhecaYKH`}}}~|gQ>1.021221120..-,+*)))(*)),/0,'#  !"#$'&&''((((&&&%$#! ápjJLMNMLLMNNRRQSZhu|}|xywuttvy{{|zyzz|~}wuttvz|~}}|{yutsqoonlkkkkkkkklllllllllkjjihhhhhhiijkljjjjkkjjiiiijjjkjkkkklllllmnnnnnooppppppssttuuuussuymjq`QF>72.+'#  $(-4:?ABB?<865568;=>=<:50+&" #&)+/5:DNV[`aa]YUTVXZ]`_[UMF=731012237;?CFIKMNNNPSTTRPNLHFDDEFHHIJO[iuyrmilszvhUD:;IP[djorqkf^SG=510001125:@IOTY]]]^]][XQLG?:119K]t~~}}|zyxwvvussqpoliffda]SFKe{||}o\D821100010110.-,+)))*++**)+--,)$!#%'$%%$$$%$%$$#! ׫uehJLMNMLLMMMRRPUamx~{zxyzywvyy{~z{|{x|{vtsvvw~~~}{zyyvutrponmiiiiiiiijjjjjihhgffeedddceeeeeeedcdeeedddcddddddeeefffeefgffgffffgggggffhiijjjkkkmnov}uu~ruyqjjrut{syi\MD<72/,'$!!%*/5<@CCCA=97678:=???=;60,'" !#&(+.4:DNV[`bb^ZVUWZ[^`_\UNF=61//011258<@CFHJKKKMPQQOMKHECBBCDFFGHMZgszuqszq_J<;HR]gnuwvoldZNC:53334458=DMSX]_`bbaa`^YULF?68G`v~}}|||zxwvvuussqponjgggb_ZOEMn}yxyz|ydJ742220/121110/..-+******))(),-,)& !$&'%&&$#""!"! 亡{bfJLMMMLLLMPPRQXer}{|zyxxxyz~|y|x{~~}yxuswxx|}{}{yxxutsqonmlhihhhhiiiiiiihfeeddccbbbbbaaabbaaaa```aabbcdcccdeeeffffeegfeedccdeddddddeddddefghhkqw~vpf`jreI;MijYEBUinU@789:?HIPVZblv{|obXLC;72/,'#  #',17=BDEDB>:7689<71,'"!!#&(*-3:DNV\abc_[WVX[\^`_\VNF=50//0002369=@CFHIHHILNOMJGDA???@ACCDFKXeq~~y|zgP@ENTY^acdefedc]UNKEHYo~}||{{{yxwvuuussqppoliggb_]WMGSw~{{xvwy|~}kS<0.2331//00///..---++**))(()((*,,*'!!%&'&%%"! ̨pmcHKLMMLKLMMMST^px~|{|{|{{}}x}}}zzusuuy|z}~}~|zyxvtrpnmmlhihhhhiiighhhhgffedcba``_`___`a``aa```aabacdcddfghijjjjjijhgfdbbbcbbbbccdabbbbdfginu}uodUG>6>XdY=-C`dN47PcfK6/2227?>?>>BHLPdlzxqqpppqqtz|~zsh^UJA951.+&"  #(-38>CFFDB>:768:=@BCB@<70+&# !"$&'),28BLTZ_ab`]YXZ]^`b`]VNF=61//0011248@@BCIUamz~oWD=IWcmv{~~{woeYMB<:88888:>EOUZ_bdfeffec^XUY\k|~}||{{zyyxwvuuttrrqoonljgda`^XUKE`zyxz|}|zzkV@2//1/00//0/.----,,,+)(((('''('((**)'" #%&&&$# v`aFJKLKJJKLMPRT|{zxx}~~}|}tuwwxz|z||}|}}~{yxvtrpnmmlhjiiiijjjjklmmmllihfdb`_^_]]]^^^^__^^^_```bcdeghklmnoooonmkhfda``a`__`abcabbbcegikqz|kUMB:3.,-+=U]P92APTF5:MZ[KFBDFFHKPMJFEECD@T||vpjeddcddefikmmoqstturokcZRG>730,)$! #(-38?DGGEA>;878:=ACDC@=70+'# "#$&')+06?IQW\_a_]ZY[^`bdb^WOG>73100111137;>@CEGGFGHIIGEB?;9889;=<>@FQ]ivu^G=FWdnx|~xpeYMB<999999;?FPVZ_bdgghgeb`_ft|}|{{zzyxxxvvuuttsqqpnnmlkhdaa]ZZQCLj}{yvqhbK=311110--------,,,,+++*('''&&&&&&&'(((($!!#%%%%"x`cGJKKKJJJKONQVΎܷ߯Әѱ{~|}|{u{tzxzz{y}}{zЎ⢍|zxvtrpnmmlhkjkklmmnpqsttttspnkifc`^][ZZZ[[[]\\\\]^_`bdfhjmprrsttttsqolifc`__]\\]^`aaabbceilovs]K>110/--0205AE@85?EGFGMSY]\[\]^^]_][YWWVTOF[tlfa^]]\]]^^^_aabegiijjie^VOD<52.*'# #(-28>DGFDA>;879;>ADDDA=70+'$! "#$&')*/4>FOUZ]_]\YY[^`bec_YQH@84211111137;>@CEGGFGHIHFCA=:76788:9;=BNYfs}{dK>DVcnx|xpfZMB<8998889>ENTZ^adedcccejq}~~}{{zzyyxwwvuuttsrqqonnmlkgec`^]ZTNDTsxl`VMC=5/.0110/------,,,+++***)'&&&%%%%%%%&''(($"!#$$$$$"wjwbEJJKKJIJKLOVZ͌ˮЧިҥⶍែ窅փӦؕ߱|{xw{z|}|ẅҞ}zyvtrpnmmlillmnoqrtwxy{{{{zvsqnkfb_]ZXWWXWWXXXYZ\]^behkmpsvwwxyxxxwvsolieb_][ZYZ\]___`bcdinu{ubSB41216:7657698>DHLOUabgloqsx}~~}{vrnic[a|ne`]ZYYXYYZZYZZ[\^acdegfb[SJB:41.*'"#(-28>CFFDA=:879;>BDDDA=70+'# "#$&')*-2;DLRWZ\ZYWWY]`ced`ZRJA953111112489743321000/.,,,,,,,+****)))(&%%%$$$$$%%&&'''%# "%%%$$#"  pgaEIJKKJIJKIKV[ʉθم研ȏtz]zЅ|~;wyzz|||~{{xȃ{˟~{zwtrpnmmlknoprtvw{}~|yvtpkfa]ZWUUTTTUUVXZ]^`filpsvy}~~~~}}||yvsolhd_]ZXWWY[]\\^`acip|xU?4/+.26557:=@FKTV\`gnps{~ww{~mv}h_\[WVVUVVWWVVVVXZ]`bcdc`XPG?830-)&!"'-27=BEDC@<9668:>ACDC@<70*&" "#$&')*,08@HNSVXVVTTW[^adb_YQI@9531122246:>ACFHIHGGIIHFB>:744455668:?KVanw|~nV@AN]ku{yqh\OC;76554358?HOSWZ]^`bjw~}||{zyyxxwwvuutssrrqpoonmllkigebab`[ZYTIRlr_KC;74235443320.,,-++++++**))))((('&%$$$###$$$%&&''&$ !#%&%%$"!  megDILJJaֳhkjijdlahififc~uݡpZ]W]TRRMtgQQRk繎繛~ҳືWnx{vwwwxxypȃfT{ף~{yvtrpnmmmloqruwz|}zwqle_ZUSQQQRTTVX[^`chlptx|}zwsplf`[XUSTVXZZ\_bbdlvyrrd?1-,/6;>ADIOV]bdmqtvy}wfci|cXUTSSRRRSSSSSSSUX[^aba_]WOG>72/,(%!"',17=ADDB?;8668:>ADEC@;6/)%! !#$'()*+07>DJNPQRPPORW[`a`\VNG?943223224:>BDFILMJIIJJIGC?;7434576568>IU`ku}||~z|u^FBJYiu{{sj]OC951121237:7568:=ACDC?:5.($  !#%'))*+0683,'# !#%')*+,05:?DHIIIHGFIOSWXXTNF?8410113336<@DFHKNONMMONLIEA=96678:::<>DO[epx{{~~iQEEScox}vk^PB9241//0147>FMWfx~~}|{{zyxxwwvvuutsrpopopppnljjkjigfgda^YQJB=:731233421212/..-,++*))))))))(((''&&&%%%%$$$$##""##$%%%('$  #%%&$"  thZAKFJKKE@0;@Ah\cggӧ⻩nڟu}؊݋z\RSTZRPUQQLKNJKLQ쭑Ǿ۰ÿи|uqppqrsuvx{~~wlcYQKGGHLPUY^djmrv|{uoh`YRMMNPTXW[_cit{dVW^a\TLILX]dkquy|wcduNELLHHHHHHHGHIIJKPTXZ\\YWQI@72,(%" &+05:=?><9643369=@AA@;50*&"!$')*++059>BEFEECBBDIMQRQMGA:51-../1126;7788::<=@GR]ir{{|sYIEPanw}wm`RE;432211137?L_q~~}||{zyxxwwwvvutsrqpoonnnnmlomjihggec^WQKC>94443211110.///-,--,+++*)))))))((('&&%%$$$$$####"""##$$%%('%!"%%$$"  uqQCJIHIJKFUVbkq͑Ҩ͚soϞ⤖ږwvvx䯏vjoy֨eQQONOON[qOWTVUUTT浼몶̋{urqqrstvy{~}si^SJDDEJNTZ`ekovz~xrkcZRLKLNQWX[^bhuzW?5DIIECGSboqtw{}uzzN?GHDDDDDDDCBCDDGKQUXZYWTOG>72,(%" %+049<=<:753225893.($! #&))*+059=ACDCA?>?AEHLKIE@:40-**+-./15;>ADGKMNLLMNMKIEB>;7788:;<=@HS`js|{|}cQFK]lvzpdVG<73454215?@ADIOSUWWUSME<61+'$! %+049;<;86321158;>??=82-'#  "%((*+059=ACDCA?>>?BEIHFB<61-*''(*+,.28;?BEHJKJJKLKIGC?;865679;<:74233232200/.+,,,,+*))))())))))))(''&&%%$$#####"""!!!!"##$$''&$ !"$&%"  "#yqAEHHHKHILHEz}Ҧѫ|ikjhhgfӃfjhegikvƉfڏӘ{}sntoq|~~|~Éc[KfePOLMLLLNNPObiUPb|vsstuwy{|vj\NDAAGMV]dimsz~yqh\QJGHIJPSWX_npU<159>J[juy{z[;9>;;;;;;;::;<=AGMQSUVTQLD;61+'$! &+059;;974200147:<==;61+&"!$'(*+059=ACDCA>>==@BEDB>93-)&#$%&'(*.37BJU`lt|||ycLIVhuyl_OD=;757@Pf~}}|{{zzyyyxwwvuttsrrqqqqonnmmnopnljhfb\TLC<98775311/00100...-+**,,*)))(((((((((((''&&%%$######"""!! !!""###&'&$  !!$%%%$! !$&'V@DIFIIIIHJHFҨ⭂Ρroqppkn{ȍyxwsՂvxxsކruspzտTMKKIny~ޓ}}}~z}㜄|wtsuvx{}}|pbTGB@GNX`fjnt}|uk^QIFFGHLPSV_rvZWRI<:AJ[hsy{|~|d;28666666666779=DKOQTUSPJC:4/)%"!&,159::8631//047:;<;94/*$!#&')+059=ACDCA>=<<>@CB@<71+'$ !"##$&).37;>@ABBBBCB@>;953112378:<@HS_ks{}z{~nQHSgu|qbRF?9:@Ncy~}||{zzyyxxwwwvvutssrrrqpppnmmlkllnkgbaZQHA=:977432001/01210-,.,**)+++)(((('(((((((('&&%%$##""##"""!! !""##%&&$! "#$$$$$$&'&%#   #%'))sDAHGGGGIIIHJLFѪܱݴ͝Т稏rrqmoqrqnzkkjkgxyu߄nbTRTVUUOgߌy{~~~}݄ɇꛄ|yuvvwy}vgXJA?JQ[chlpvypdTIBCEHLTSSbyqG29DIGIWenuz|~|xy|r;,12222222233448@GLQSSSOJB;3-(%""'-26:;97520.-/25899972,'#"%(*-059=ACDC?==<<=<<=?AB@<71+'"  "%)-0368876677530.,*)*,--/037@MXckrxz{zxsrsu{|wx{~gQO^q}ugWMPcu~}||{zyyxxxwvvuussssrqqqponlkkjjkhaZSLF@>;743322122211220/..--,,+**)))(((('''&&'(((((((&&%$##"""""""!!!! !!!"#$%#  "#&''&&&&''('&#  !#&&()***|H;@CDEDEFGGHHIIIMSSVR^empq̉zr{ԅϱhΜkz⯸Ê殏ۖjkڻauli[Om܊z~~}ssi䱆rXSTQSwORy†PQSMPj{vuuvy|rbRECLT^ejnpv}qfVF;=AFLRQTik?'!$4GXfqwy{~_JOg~?'+)))))))(((),3=<<=?ACA=71,'" !#&)+/4553333320,)'%%&'))*+.2;GR^gmstutqlklnt{{uqrux}nUNZly~tkgky~~}|{{zyyxxwwwvuutrqrrrqooooomlkig`[ULC=:88643321001110010/..--,,+*))))((((''''&&''''''''&%$$#""!!"""!!!!  !!!"$%$! "$%''&%$$%&'('%# !#%'(())))ŏlD??BCCDEDEFGGGGHILNYYUa}Шܑӷɜگ٪ޕj~ebhzcbpߚ݌z|߈bbx|xw݅Zt|ܥyrrwx|wuwxz|~vgWJEMV_fjmqx}qfVF;=AGMRQXnd>&#'3FWit{~|yWESt}="($$$$$$$###%(/9BIPSSSRMF>71,(%"#(.37:;86420.-.1368763.)$ "$',/49>ADEDA?>==>@BCA=71,'" #&()-13200000.,)'%"!"#$%&').7BLYcinpqolhghiowyrliknqu{tYNUfuzz~}}|{zyyxxxwvuuttstttspooppopoolhaZUNLIC>::853222210///////.-,++**))((((((((''''&&&&'''''''%$##""!!!!"!!!!  !"$%$"  !#%'(('%#!"#%&'&%"!!"$&())*))(DzeJC?@BBBDCABEFGGFEGHIMVchsϨܑsvǴɜ輗̆׫~ݕxx٭}d][\ZZ[Skx{}}ۉ^sc֭ۆU清~|ywwx{}~zk[NHNW`gknry}qfVF;>=>@BCB=81+'# #'(),011/../.,*'&#! !""##%*3=GT^chiheb^]]`gnt}}wohb`aehlqxx]NRbo{~~}|{zyxxwwwwvvvtsrqqrusrpppqnojd_UKB@CED@=:8853222210/.......-,++*)))((((((((&&&&%%%%&&&&&&&$#"""!! !!!  "#$%$!!"#%')*%#!  "%&''%" "##$%')****)(kYQH@AC@BBBEEDEEFGFEEHJLR]jrzΨΉruμɮ⛘ݾ{ԁӬ{ۋvງ̰݉wҁ]{n^ѹ|ٵ}zxx{|n_SMPXbhlptz~rgWF;<@GNRUbxnD-((0@Rdoy~}}~];Ipy8#,7AIPTWXVSNF<60+'$!!%*059<<96410/./2479862-'#!#&+/4:?DFGGDB?>>?ABDB>92,'# #'(),011/../.,*'%#!  "'0:COX]bcb_[VTTX^elt{~}ztng_YUW[]afnx|bONZht~~}}|{{yxxwwvvvvvutsrqqqqrqpqomi`XOG?958?CC@<8754321110/.-------,++**))(''''''''&%%%$$$$$%%%%%$##"!!!  !"$%$!!$&'('&$  $'('%" %%%&&&''(((((((ul]RMDACDAAAAABCEDFFEEGKOT\dq{ϧݱqswy¦ɸ纂rۋmƲuyߗۍճ|UWWz὇܍v}ݴf^~lRzɈ廀}~~qcXRT[cjnqu|~rgXG<=AHPTXg~H*"+3AWjty}~~}p@7[~y6#,8BKRVXYXUPH@93.*&# #',27;=<9741/../3579852,'"!#&*.39?DGHIGDA@@@BCEC?93-'# #'(),011/../.,*'%#! %.7@KRW\]\XUOMMQX^emtwvrlf_WPKMQTX]doy~eNFQcv~}|{{zyxxwwvuuvutssrqqrroponke]VG?:531257=@?;9764211110/.-------,+**)(((''''''''&%%%$$$$$$$$$$$##"!!! !!#$$! "%&&&%#!#&('%" !%%%&&&'')))))))vmWOGCABC@DAACCDDBCEEEEHMR[ckvΥhjń||ݓsМآeuؖvweėqX^uv`Vpvpigdgdcap܇{~|ɁWe}pVTjjz~xqlgbYY|sg]WW\dkosx~~qgXH>@EKRW\md0$%1?Wmy{||}}O/Jly5!*7AJSWZ[ZWSKC<60,(%"!%*/5:=?=:7420..04689751,'"!#&*.39?DGJKIFCBABCDFD@:4.(# #'(),011/../.,*'%"  $,6?IOTYYXUQLIILSZ`hnqplf_XQKEGKNRW^hqy~{|jRJVm~||{zyxxwwvvuuuutsrrqppqspmic[QF=4211221329=>;9854211110.-,,,,,,,+**)((''''''''''&%%%$$$$###"####""!!!  !"##" #%'('&# "&'&%" !"%%%&&&'')))))))xj[HECBA@EBBAGABADGGDCBAIITbjs~tkv|jj}rut|k}sgpmspsY^k~{aVZ]k~tifkgedfa\Z\ckx~l]X_p~tdYSb{{\RRRSVZ^abgge`r\w}ske^X[clqux|pgZKCDHNVXasX5&'-9Ply{~~~^/:X|6",8DMSWZ\[XSLE?72.*%"!  #(-49>ACA=:74211368:;963-'" "%),18?DHLNMJFEDEEFGFB<6/)$  #'(),011/../.,*(&# $,4=FMSVXWTOIFFJPV]djnlgaZSLFCEGJNRZcmu}yvu{obdu~}zyzyyyzywwwvutsqqqrrpoppc[SKE>9653222112159<>=82421000/.,+++++++*))(('''''''''''&%%%$$$#""!!!"""""!!!  !"#" !$')*('$!!%'&%"!#$%%%&&&''(((((((yiZTCDBC@?@CBCAAACECHCECIKR`hpyux~{q|lnff|~{o_\m}|pd{|wtqxtr~||~~o`l~tmhc]^cmquxzofZNIILQWZcwzW5!%&3Kcv}z}k.,Gq8#-9DNUY\]\YTMHA:51.)%#"!!!!#',18>BEGD@>:75336:<==;73.(# "%(,18?EIMONLIGGGGHJHD?82+% !#&(*,/11/../.,*(&# $+3DD@>=@CCDB@>DEEIM\hqx|yww{~~~|}wokf``elrvzxof[RONPTZ]ex~}T,%$'&:\qy|x}}8$9b<$.9EOV[]^]YTNJC<841,'%$####&+06=CGJKHDA>:8669=?@?=94/)$ "%(+07>EJNONMJIIJIJLKFA:4-&! "#&(*,/110//0/-+(%" #+2;EKQTUTQLGDDGNTZbgigb\TMFA?ABEILT^fowzson{~}}|{{zzyxwwwvuuuvutrqqrsrplcYME?<65433211110/...-16;=:6231100/.,+****)))))((''&&&&&&&&&&&%%%$$$#!  !"""!!!  !##!!"$&)('''(*,-+*'#"%&&%""%&'%%%&&&''(((((((~GA?J{DAEGCD@TiBEFpemy{z{~~~}}||~~~~ztmgbbelqw{xpg^VTSUY__hz~p>&'()+Giw{{{}~G-S{?%/:ENV[^^]ZTNJD=962-)''&%%&*/5;CIMOQNHEA=<;;>CEFDA=71+%  "$(+07=EJNOOMKJKLKLNMIC<6/("! !"$&)+,.00/////-+(%"$+2:DLRUUTQLGCCFMSYaegfa[SKD?=>@DHJQ[enw~woms}}||{{zzyxvvuuutsstrqpqqqoi^VKA:51-+,.---,../.---,+-38<;722000/-,+*)))(((''&&&&&&&&&&&&&&&&%%%$$$"  !!!!!  !!!!$&()-/.-,,,-.-,*'"#&&&%"!#%''%%%&&&''(((((((n`XRN@B?UA?s@CCAGJDWǫlv}z~~~~{~~}|{upjfdfmsx}~wpia[YZ[_cdl|[A:60,1Utzz||}~S'ElB'2:63.+))(''(,28@HMQSUSMIDA@@ADILLJFB;4-'!!"$(+/6=DJNOONMLLMNOPOLF?81*%#"!!!"#%'*,-.00////.,*(%" $+2:CJORTSOJFDEFLSZ`ced_YQIB=<=?CGIOYcmv~xrooz~~}|{{{zyyxwuutttsrrqqqrqmh_SF>731.+())+***),,,+++*)),.59:721000.-+**)))((''&&&&&&&&&&&&&&&&%%%%$$#"  !%'+-/01210.--...,)&!#%%%%# !#%''%%%&&&'''''''''羻{RKE?>AC=T>z@CCCC=r}C|q~~~}}}}}}}~}}||}wrnjgjoty|~}vpke```bdghp}`?HNG:3;az{{{}}}~i##=_E!*5?HPV[^^]YSLHC>:63.+**)((*.4;CKPTVXVQMHEDDEHMOOMID>7/)# "$%(+/5AEHNXalu}{uop|~}}|{z{zzyxxxvutsssrqrrrqng[OE:30-+*(%$$%'(()*******)()*,26973100/.,+*)))(('&&%%%%%%%%%%%%%%%%%%%%$##!  !&)-0234332110//-+*'$  "$%%%$! "$&''&&%&'(''&&&&&&&⹹tnF??;>??9Tֽt==DEAACEb̥l~|}}~~|{}~}{|~~}|{|~xpokmpuz~}wqmiffghilms}m?-BPSL:Bj{z{{~}}~n*6[F$.8AJRW\^^\XRLHC>:63/,***))+06>ENSWZ\YTPKHGGILQSSQMHA:3,&" !#%&')+/5FMSX\^^\WRLGB=:52.+**)))+07?GOUY\^[VRMJJJLPUWWTQLE=5/)%"!!!""#%')*+,/6ACIR\gpy|~}|{zzyxwwxxwwvvutsrssrpmf^TG=60,+*'#!!#$#"#"""#$$$%%%&&&&'%%(/4651/.-,+**))('&%$$################%%%%$$# #'(+/356563210/.--+*('#!!$%&&%#  "#%&&%$$%&'&%%%%%$$$޶nbWIGwvfeʼA?9Vf;@BAADIKv}~|{~~{}~~~}{wywwuvww}~~}zx}|~~yvvx}~{ywussstttwz}~~rP@6-+8JK7Lu|x|{{}}}~~C5mN)0....----,,2:CKPSZ^__\XRLE@<941.++++**-29@HOUY\][YTOMLMNRWYYVSNG?70*&$##$%%&(*+-./27AEMWdow~{{zzyxwwvwwvvuutrrssqmg\NC:3/+)(''%$##"!!! !!"#####$$$$$##$'+.010/-,+***))('&%$########"!!!!!!!"#$$$$#"!#(-2567765320.-,+**(&#  "$%&&$"!"$%&%$$%&&&%%$$$##"ᶰҹcQLE;899/[6/*'&%&''')+-.01248;:MW\WVE?:UӾE;RPFINXce꧆˸Ⱦò̬ƫõ̳ŪǸλȧŧɷ}|zoy}wx}~{vzy}}}~{y~aACdtswwwzxzxxyxyxxy~x1.FtdHPOOOONNNNOQTXZ\^^]^_]ZUPJE@;73/,*((((),18?GNTXZZWSOKJJLOTXYXUPKC:2-*(''()*+-0246789;>ADFHIHGGHHJKMNKHB:4.*'&%&'(),/23578778898752.*'$#""""#$'-5;CJNPPNJFCCEHLRWZ\[WQJB;65567:=?@GT^gpy~}|zyywvuvvutttsssrpojaQB70-*)&%$$#"! ! "#########""!! !%)-/.,***))(('&$#""##"""!!  $(+-/13456531.-+*(''%$"!#$%%$"  !#$$###$%%$$##"!! ĽxnaUNG<;;?Zmsa:@=J{nA?@VTFPZbisª¼ɱʰ״ЯŨ­Ƽɦ׺¯Ȩ­}{wsyrïzwv|y~~}}~}yz~~nWHRn|}C$GykRZ[[[[ZZZ[[\__`aa`^^^\YUPJE@<84/,+*)((*-29@GNTWYXTPMJHIKOSWXWSMH@70+)(''()*-03689::;=?BDFGGFFFFGHJKLJGA;4-)'&%&()*-0247::989::9752.*'%####$$%(.5=DKPQQOKGDDFIOTX[]\XRKC;65457:?@><;>BA=BHMZbjr|°˳¯ξijĨǰ̷²z~~|}xtwyvkr}w~xwx{}~}||}~zwz}~}}~yx}\"M}s`efffeeeeeffgggfda^^][WSOJEA<851-+**))+.3:AHOTXXWSOKHGHJNRVWVQLF?6.*('&&'(*-03689;;;=?ACEFFEEEEFGIJKIF@:4-)'&%&()*-0258::99:;;:863/+'%$#$$%%&)/7>EMQSRPMHEEGKQVZ]^^ZTLC;65567:9;:8<;=;;=;;?>??>@BADBGPYbjvxûĪǵ©ģħά}~~{wƣ—Ř~|{{|}yy~u%Hrzkonnnnmmmmnnnnligd_][XUQMID@<951.+))))+.4;BIPUXYWRNJGFGIMQUVTOJD=4-('%%%&'),/368:<<<=?ABCDDDDDDDFHIJHE?93,)'&%&()+-0258::99:;;:863/+(%$$$%&''*18?GNRTSQNJGHJNTY]```\VNE=75479<>@@Iat~}|{zyxwwvuusqpqqpopplgbZPH><3+&#!  !!"##$$%%%%%%%%$$##""!!!$'+,+*)('''&&%$#""##"""!! !$&())+-./000/-,+*)'&&$#" !""#$$#! !"#$$########""!! ypj]MD><;:;<:;:::<;A@A?=>A@@DEGOW^is{Dzˬ³ɲĹ©çŷðȷſƺ~|xy~~~}{z{|}~1:Yp|qxuuuuttttttuspmje_\YVROKHC?;851.,****-06=DKQVYZXRMJFFGIMQTUSNHB;3+'%%$$%')+/368:===>?@BCCCCCCCCEFHIFC>71+(&%%&')*-/2479:99:;;:863/+(&$$%&''(+18?HOSUTROKHILPV\`bcb]WNE=744677;FQf~}|{{zyxwvuuttsqqqrqommhc[SLF?:92,(&# !!##$$%%%%%%%%%%%%%$$##""  #%)+*)('&&&&%$##""##"""!!  "$&'(()+,-...-,+*))'&&%#" !!"###"!! "#$#""""""""!!! vl`UKB=88889999:;<===<<=>?>=?BDNUblu|zxwwwxwutuuvwxxwuvwyzz{zzz{{|}~~~~~~~~~~~}}}|}~{yyxz}~B-Jjx{{{{zzzz{{yvroke_ZUPMJHDB>;852.-++,-/39@GMSX\\ZTMIHGHKORUUSNGA:2*&%$$$%&'*.157:<==>?@BCCCBBBBCDEGGEA<60+'%$$%&'),/13699878998642/+(&%$%&&%'*08?IPTUUSPLIHKOU\begeaZQF=88336?Qj~}||{zyxxvutststrsrqolkhZTMHC?<850,&# !#$&&'''('&&&&&&&&&&%%$###!  !#(**(&&%&%%$$##""##"""!! """"""""!!!  !!"""###$%&'&&&()*+++,++))('''&$#! !"###"!!  !"##"""""""! }si^QFA=;88889999:;<====<=>?@?AGKT\gr{}}|||{yxxwxyyywvvxxzz|z{{{||~~~}}|||}}}}}~}|{{z{}|zyxyz~P%Ip}xsme_XRMJGEB@<9741.-,,,.04;CIPVZ^^[UNKJIJMQTVVSNGA:1*&%$$$%%&*-1579<==>?ABCCCBBBBCCDFFC?:5/*&$#"#%&(+.025887689986410,(&%$%%&%'*08?IPUVUSPLIILPW]bded`XOG@8416Fc~}|{{zyxvutttssrsqooolf`VMICA=;8430,'#! !!#&'(())))(''''''''&&&%%$$#"!  !&))(&%$%%$$$##""##"""!!  !#()))))))'''&$##"#&'))*)()(&&&&%%%%%&'())))*))'''('&$" !""#""""!!"""!!!!!!!! yrf[RG?<;:88889999::;<=====?AAAENU`fqz~|yywvwxvuswwyz{{{|{||}~~}|{z{{||}||}|{zyxy|~}{zxxy{~]"Lw}wog^VOIFDB?<9641/-,,,-/15>=?@BDKW_ipz}{yywwvusqvvxzz{{z{{{|}}~}{zyzz{|}|||{yyxxy|~~}zyyxy{~~~~~gIoyqi^ULEB@>;8531/-+**++-05;DLRX\__\WQNMMNQUWXXUOHB:1+'&%%%$$&),0469;<=>?ABCCCCCCCCCDEED@:3,'$!  !#&(*-/24555666532.,)&$##$%%%'*09@HOTVUSOKHIKPU\bfhfb[N@=E\z~}|{{zzywvttstrsrqpnnjc\TJEA=<:9876320-*(%#" "$&((),./.-,+*))((('''''(''&%%%#""! "$'(&$#####"""""!!! "$(+-/3333333311000/../00111002.*)'%#####$%&&&&&'(((()))('$"!!!""###"! !!! |si^UHA=:888888889999:::;<=>?<>AEKU_gsx~|zyxwvtvxxyzyzzzz{z{}||}|{zyzz{|||||zyxxxy{}~|{yyyy{}~~t$:Tq{sj^TJB?=;8520.,+)(())*.3:CLRX\_^[WQONNPSWYZYUOID;2,)&&&&$$&),/369;=>>?@BCCCCCCCCDEEED@92,'# !$'(*-/2343454320-*'%#""##$$'*09@IOTVUSOKIHJOU\bega\SKL]v~~}|{zzyxwutrssrqoppnjg]RLHE@<8988776442/--+(&#$&)*+,.00/--+*))((((''''(''&&%%##""!! !"%'&$#""""""""!! "$'+/135555555532333444433333332.*)'%$$$"""#####$&&''())))'&#! !"##"!  yodZOF?;98788888889999:::;<=?@>@DLU_jp{}{zzxwxyy{{zzyyzzzz{z{~|{zzyzz{{{|||{yyyyy{}~}|zyxxy{}~0*8W}uk^SI@=;9620.,*(''''')-29BKQW[^]ZUPMMMORWZ[YVPJD;2-)''''%%'*.147:<=>>?@BCCCCCCDDDFFFE@92,'# !$&(*,/123233321/,)&$"!!""##&)/8?HNSVUROKIHJPV[`bc\YZew~~}||{zyxwvutsqtspnnnmh_SMFCB@>=77676655542110-*&'(*+,-.//.-,**))((('''''''&&%%$#""!! !$'&$#!!!!!!!!!  '*.02334444344433445677643322111-*)'%$$$"!!! !$%&'())))('%#! "#"!! {tk`UKC<:888778888889999::::<=?@?EMV^irz~}|{|}}~~|{zzzyzz{z|~~~}{zzzyzzzzz{{|{zzzzz{|}~~~~{zyyxy{|~@%0L~uk^RG?;973/,*(&%$$%%%',17@IPUY[ZWSNLLLNRVZ[YVPJD;2-)('''&&(,/258;<>>??@BCCCCCDEEEFGGE@92,'# !#&')+.01111221/.*(%#! !!""$'-6=FNSUTRNJHFJPUZ_baeo~~}}|{zyywvuttsrrsqpomjc[PDB@>=:9:455555577665541.*++,-./.--,,++*))(('''&&&'&&%$$$""!!  #%&$#!! !$*-023322110001223445667754321000-*)'$$##"!!  "$%'()))))(&$" !"""" yog]RG@;88777777788889999::::<=?ABJT^eny}|{zz{{{z{{}}}|{zzzyyzzzzzz{zzz{{zz{||}}~~}}{yxxwxy{}P /Lvl^QF>:752-*(&$#""###%).5>FLRVXVSPLJJKMQUXYXTNHB:1,)'''''(*-0369<=>???@BCCCDDEFGGHGGE@92,'# !#%&(*-.00//00/-,)'$! !!#&,4;ELQSSQMIGGHLRV\bd~}}}|{{zyxwvussrrqqponlg_ULB@=<;:9552334455899899850,,++,-.-,,++**))(('''&&&&&%%$$##!!! !$%$#!  '*.023310/..-./0124456677743220///-)('$###"  "#&'))))(('%#  !"##" |vlcZNC<8876666667788889999;;;;=>@BLU`jrz}|}|||{zyy}}}||{{{yyyzzz{{yyy{||zyzzz{{|}|{yxwwwwx{}['-I~uj]QF>:740-*(&$#!!!!!#',2:BHMQRPNKHEDEHLPSTSQLE>:/)&%$%&&'*.258;>@?@??@BCDCDEFGHHIHHGB;4-(#  "$%')+-....//.,+($!!%+17?GLONLJHIEDEL[jz~}||zyyxwvvuussqpponkfaXNF@=::99863234434446:<<;:972.-,+++,,-,*))'''''&'&&&%$##"""!!"$$# !%*-023210.,+**+,.013555666642100//-*)'%$##"  "#$&''())(''%" !"###"!{slaVLA966777777778876677778;<<>@BGOXajrz~|{zzzz||||||{{zzzzz{{zxyy{|}}zyyxyyz{{{yxwwwwxz|~wRBBQ~uh[PF>;840,*(&$"! $)/6=CHLMKIGB@@ADHLOONLF?95-'$##$$%'*.258;?A@@@@ACDEEFGIJKKKJKHC=6/(#  "$%&(*+,,,---+*(&$" "&.7=CHLLKFB?:@Mc{~}|{{zyxxwvutusqpommnf[RJB?<;987654223444677:>AA?<:71..-,++++-,*)((''&%%$%$%$#""!! !#$# !#'+/224320.,****+,.0135555554310//..,*('%#""!  !#%''()((((&#! "#$$#" xncZMA>:755677777777676678888;<>@CGNXemt{~~~~~~}}}}||{zzxwvvwx{|||zzyxxyyz{{yxwvvvwy{}{{viZOE?;95/,)'%#! #(-3:@DGHFDB?<<=AEILLKHB;51+&#!!"#$'*.258;?AAA@@ACEFGIJLMNOOMLJD>81)#  "#$&()+++++,+*(&$ !&-3:BFKLE<9AQc{~}|{zxxwvuutrqpnnmmh_TLE@==<;97543332345778;@DEEB>:71,,+++**),+*)('''%%$$#$##"!  !##  !%&(+0353320.,*(()*+,.11344433321//..--+)('%#""! !$&'()(((('%#!  !#$$$#!|ulbVLB<:8667887777777677789888;=?DJPXcnu{~{yxuttuvy{||z{zyxxxyyywutssuxz|~~}~~~~thYME?<:61,)'%$"  $(-39>ACDB@>:89:@@@@@ACDFHJLMOQSSRQNIB<4,%! !#$&()+++++++)'%#" "&,3;CFFB@DQg~}|{zyxwwutsrqoonmlkbUJC>;;==;76433344578:<=@FIHGDA=:3-,,++**)*)))''&'%$""""""!  "# !#&&'''(&),-/1457421.,*((())*,.0023332110/..--,,*)('%$##"  #$%'(((()('%#! !####" |vph_TH?9766666777777777677899889<>CJRZcnw~}{ywuuvwz|||z{zyxxxyyxwutssuxz|}~~|{z{~~}||||}~}sfWLD>;:72-*'%#! !$(-49=@AA?=;8667:<@CA@>92,)! "&).158<>>?@@ABCFIKMORUWWWVSMG?6/("!$%&()++,,,,+*'%##!"'+18=?AIXm~}|{zyywvvtrqpommmjaXLB@@><:987553334569;=@BCEHJIGFC?=7//.-,++*((()'%&%%#""!!!! "#  !!!!!"##$%'*---../-0235678831/-+)((()))+-/.022110/.---,,++*))'&%$$#!  "#$%''(())'&$" !"#$#!{tldZPG?8566666666777777777789889;?CKS]fnw~{ywvvwz|||z{zyxxxyyxvutssuwy{|}}}~~|zyxy|}|{{{{|}~~~{qdTIC>;:84.*'$"  !$(-39=?@@?<853447:=@@>;60*'!#&+.259<<=>>?@BEILNQTWYZZXVRJB91*$  "$%')*+,,,,,,*(%#"" #&).6@Obx~~}}{zyxxuttsqpooomhaWME>==;977654322357:=?BEHIIHGGFEC@?:4321/.-,++++('&%&$###"""!!  ! !""#$%%&()))))**,..25444454666676772/,*(''()))*++,-.000.-,++++**))))('&&%$#! !"#$&&''))''&%#! !#$$"!|vph^TJA<8557777655677777777778889<@FMW_iqx}{xxy{|||z{zyxxxyyxvutsrtvxz{{{{{|}~~~~~{yxwx{~}|{zyyz{}}~~~}~~ynbSIB><<94.*'$! !%).39=?@@>;7322358;><;73-&# #(+/2699:;;<=@DIMPSW[^_^\ZVOG>3,&"! !"%&')+,,,,,,,*'%$#!&3BUi~}}|{zyxwvtrqponmmng]SID>;:9865544333346:?CEHJKKKGCCCCB@@=:98754330//.,*)'(&&&%%$$#$"    "#$$%&'),-./01/123233448;977676786644431.,)'''()))*++,-.//.-,+****)))(((('&&%$#"!!!!!!  !#$$%&'(())((&$"!"###"}xrjbXNF=86666666777777777777777888:?EMWbjsyzzz||{{z{zyxxxxxxvtsrrsuwxyzzzyz{||||{zxwwx{}~|{yyxxz{}}~~}}}~~~~~wl`RHB?==:5/+'$"  "%).49=?@@>;6422368<>;:62+%"!&),/3667778:>CHMPTY]acb`^ZSLA7/)%#"!"#&'(*,--,,,,,*'%$" #0F_v~}||{zyxwvusqonmmljh\NE><;:8876554334568;>CHIIJIIGC?@@@@@@><;:9877644310.-***)**(((''&$"!  "#$$$&)++,,-.156677766899:9889:87766667653222/-+)'''()))*++,-.//.-,+****)))(((('&&%$$#""""""!  "###%&'(**))(&#  !"###!~ysmf\SJB=866776555667777777777788:;>BHQZdnu|~}|zyxxxyyywutsrrstuvwwwwwxzz{{zzzxwwwz|~~~|zyyyz{}~~}}}~~~~~~}~~~}|zz|}|||{|}sg]LHHE@<;81-)'%#!  "%).4:=?@@>;7533469<>:940*$!"$'+.01123345:?EJOTY^bdedb_YRH=3,(&$$$%&'(*,--+-,++,*'$"!+;:9644544533359<@DHKKIHEB@>;><;<>?>;::888887988756523210//....,+*)(&&&%$"""""!""!"#%')*+++,/01112359:;;;;:::;;;:978988765555443210.,*(&&'()))*++,-.//.-,+****)))(((('&&%$$$$$$$$$"!  !"$%)+****'%#  "##" xphaYPG?96556655655555677777777789<@FLT]env{}yxwwwxxxvtsrqqqsuuvvvvvwyyzzyyzywwwz|~~}|zyyxyz|}~~}{{|~~}}~~~~~~~~~~~}}}~yusppqqrrrqxyzzz{{{~}}}}~~~~~~~~~wqhZOHDCA@=82.*(&$"! !"&*/5;>@@@=:7544569<>;84.(# !$&),.../00127=CINTZ_cfgfda\UKA60+)'&&'()+,./.-,,-..,($%#*Bb}~}{{zxwvutsrqonnnnicYLC>;:964344445556:=?ADFFA@=;97646766678778799::999998887776554544332211//.-+*(('(%#"! %%$$$$%')*+-/0110122333346:;<<<;:9889998778876554443210/-,*(&%%&()))*++,-...-,+*))**)))(((('&&%$$$%%%%%%$"! !#%(***++(&%"!###"icYOG@=9666666666666666678987777:=BHOV^gow}|zyxxwxxvtsrqpqrtuuuuuvwxyyyxxyywwwy{}~}|zzyxxz{{|}~}{yz|~~}|}}}}}~~~~}}}|||~~~~~~~~~{ywuwxxxxyyy{zzxxxwvvuttssrqtttuvx{}~~}|zzzzyxxz~slcVJFECA?;830,)'%#! !"$(,17;83-(#  #%(+,,,-../04:AGMSZ_dfggeb]VMC92-+)((*+,.012200.../-)&'" "-Gk~}}|zyxxwvvtsrqpmmmmldUF?=;9886456666778>ABA@><:7643211001000013799:<<==<;;;::;;;;:99898888889866755320./,+*(''')*)*++-/1245555432333333469;<<<;:87677887787765443321/.-+*('%%%&()))*++,--..-,+*))**)))(((('&&%$$%%%%%%%%$"  "#'))*++)'&$! !"##"!\ULD=866666666666666666677876679?ELRZbiqw~~|zyxwwxvronopqrtttsrsuwwwwvvxwvvvxz{}||{zyxxxyyz{|~~|zxy|~}|{||||}~~~~}}|{||||{{zz|}}~~~}|zwtttsttvz|zvwtstwyz{}|}~~}~~~~~xgZRLGEDA?<741-*(&$#"#$&*/49>ABB@=9664469;<>;83-'"!$&(*+*++,,-17>EKQX^ceggeb]VNE;40.-,-.0146788763122.*)("(@e~~}|{zyxwvvusrqppnlnoj_PC;:976555688999:;=DCA>:73133210/..-0010024::;<=>?@??>=<<;;>==<;:;;;;;<===<<<;;::87654210.-0001224799:97654334444445679:;;:976666677887765432210.-+*('&$$$&'())*++,--..-,+*))**)))(((''&&%$%&&&&&&&%%$! !"%'()**)('%$#"!  "###"NH@954445555555566666666787779;?GMU\dlsy~}{zxwwurpopqrssttsrstuuutssvuuuuwyz{{zzyxxwxxxxy{~~|zxy|~~|{{{||}~~~~~}|{{{{zyxxxxyyz{||~uljijloqtvwxy{{yyzzz{{{|||}~u`KFDFEA?=:52.*(&%$$$%',16;?BCCA=9764479;<>;72,&!!#%'())))**+-3;AGNU\adfgfb\UNE<6210012357:;<;::88861,**:[~~}|{zzyxwvusqpoonmmmoi[H:4334454457::;<==@B?=;7521002220/.-035668:;;<==??@BA?><;::9==<;;<=====>>>>>=>>???>==;988764666788:<===<97542245544455568::987666556777665433210/-,*)(&%$#$&'())*++,,---,+*)))**)))('''&&&%%%'((((((&%$#! !$&&())(('&%$$#"!  "###" C>84224555555555666666678888:>AHPW_fnu{}yvvtrqqrsssssssrstuuutsrtssstvyzzzzzxxwwvvwxy|~~|zxy{}~}|{{|||~~~}|{zzyxwvvvvvwxxyy|~xtnjiihnnosvxzzxxxyyzzzyzz{|||}||||||||`?;72,&!!#&'()())))),18?EKSZ`cfgeb\UNE;521112357:<>>>=<=>=:500Fm~}|{zyxwvutsronoommnngVA3..01234468:;<<;;;<<764321001111110068:<=>@?;;<=???B>=;:9877;::9:;<<<==>=>>><=>?@@A@?><;:::9;9:::;<<==<:87654468:;;9:77899998877766556766543320/.,+*)('&%$$$$&(())*+,,++**))))***))('''&&&%%%&(((((((&%$"  "$%&'('''&&&&%$#"! !"####!96422234555555556666666678:A@:740-*(&&%%&).38>BDEEC?;88668;=>>;72,% !$&())((((((*/6@??=6799:;;<98765656:987889:::;;;;<<99;<=???>==<<;;:<;:98::;;;;98766679;<<:8987777677776665446877654310/-+**))('&%$$$%'(()*++++*)((())****)(''&&&&%%%%'(((((((&%$#" !#"#$%%%''''''&%$#""""""""####!4443344455555555666666668:=AGNT[gntz|zyxwwwwusrqppqrrsssrqqnnnopruwvvwvvuusssuwy{}}|{zyx{||{zz{|}~~||{zxxx|~}|{xwvvtsrrrrrrrrruxz|~}sljjssrrstttttttuvvuvuutttsttxz|mL;8<><862.*(''&&'*/5:?DFGGEA=:988:=???<82,%  "%'())(('''')-4:@FOV]`ced`[UMD:52112358;>ACEFFGFD?=BVy~}|zyxwvuttrqponlkkkjf_M:,%#$%&.0369;<<:98532/-**+,-.--211358:<<<>??><:566666574432222497655567788888885679:;<=;:;:::99;:98799:::99888989:<==;9:98876667766554446766543210/-+**))('&%$$%%'(()*+++*)((((()****)(''&&&%%%$%'(((((()'&%$#"  "!""##$&'((((('%$############! 334555544444444566666668;@EKS[cinu}~}{zxupnnnoopqttsqqokiijlnprtttttsssstwz|}~}|{zzyz{{zyz{{{}|~~}||}~||{zyyy}~~~}|ywwvtsrqqqrqppoonqstwy{}skmlnppoorrsutttturpqpqpppprvy}`B;:84330+'$!$%').5;AFJJJHE?<98:=?@A@=81*$  #%'))*(('''''+07=CJSZ`bcb_ZTMC<6321126:>=;:98766677655443333332211100/-+*)))('&%%%%''()**+*)((''((*****)'((&&&%$$$$&((((()''&%$#"  !""$&'(((''(&$$$$##$#####"! 34445544444444565566679ABCA>92+%!!$&()**)(('&&&).5:@HQX^bdc`ZSLD<620/027;=?BEGHJLIELj~~|{zywvutsrsrpnmlnkhgcWG3&$#" &46689875320.--+++*+,-/02479:;<;;979:8520./02344322220...012222221121000012223345555666777788888788888889:;;<>??>=;:9877777655443333222211100/-+*)))('&%%&&''(()*+))(''''())**)('''%%%%$#$$&((((()(''%$#"! !#$%&''''('%%%%$$%$###"""!233344444444334555678;?DKRX_gmry~}yuqmkjklnpsrrppnkfffgiklnoopqqqqrvx{}}}||{yxxxyzyzzzyyyzz||}~~~}zxwvwxz|}~}|{zzz{{}~~~}|zyxvusrqqpqqonnmkklnprtuwzyqmkjllmopplppomklmmmnpuy~}W;0+)--)('%%(-26=EHLMLJGA>=<=?BDDC@;3,&" "%')*++*)('&%%',28>FOV]acca[UNF>73101257:>BEHJKKDNp~}||{zyxvutssrrqomlijgeaT?/(&#" %08888853200.-,,+,+,/134446788888875775300../123321111/--,./000000../.-,,-./000122334445555778887777777789;;;<>???><;:988877666544443222211100/-+*)))('&&&&&&''()**)('&&&''(())('&&&%%%%##$$&((((()(('&%$#! !#$%&&&&((&&&&%%%$###"""" 2233344444443335678;>BGMU\bipvz|xtplkjkmoooonnlieddegikmmnoppqqqux{}}|{yxvuuvwxxxxwwwwxy{{{||{zwutstvxz{|}}~~~{|||||}|}~~~}|zxxwutsrpppponnmllmmopqruw}~xrmkkjhjlmljggjklnqtx|kS;/*$%(&&&).38=EJNONLIC@??ACEFGEA<4-(#!#%()*+,+*)'&%%%*/5;CMT\`ddb^WOH?94111236=@BDHJGFRs~|{zzwvutsrqqpponljjeee_P>.&%##!!$).5<:98630...-,+,,--046875555543455534520..--.02221000/.++++,,-----++,+*))*+,..//01233333334677877676666679:;;<=???>=<;:::987776654543222111100.-+*))))'&&&&&&&'()**(('&&&&'''(('&%%%$%%$##$#%((((()(('&%$#!!"#$$%%''%%%%$$%$###""""! 223334455544444679=AFLRY`flrx}}xtomkklmnnnnnmieddegikmnnoopqqptw{||zyvusrrsuvvuuuuuuvxzyyzzyxusrqrsuwxy{{{|~|{{|}~~~~~~}}}|zxwwutsrqoppoonnmmnnoopqtv{}vlfb`aegghghiloqsvyX7'##&'').58?GKNONLJEAABDFGHIHC=5.)$ "%')++,,+*)'&%%$'+18@JRZ_cec^XQH?941122258?>>=<<<<;:98887665553221100000.,+****)(&&%%%&&'())*(('&%&&&&'''&%$$$#$$$##$#%'''''('''&%$#! !""##&&$$$$##$##""""""! 2233344444444568CMd~~}{zyxvvutssrponnlkihggfc]J6*%&&#"%+27;;;975420/./-,-.035579;:7300//./0344110-+))(,-/010/..--+))(*+*+++++*))(''&&'')**+,-.-------.00123445544556789;<<===<<<<<<;;::98876554332211000/.,+**+**(&%%%%%&'()))''&%$%%%&&&%%$####$$$##$$%&&''''&''&%$#! !!"$%$$$$###"!!!!!!!! 22333433334567:=DHNTZahmsx|}ytpljiklllllifddfhijlmnoppqqqtvwwusrponnnnpqrqqqqqrsuutsttssqonnoppqstwwy{}~~}|{{{{||||}}~~~~~~}|}~~}}~~{{z{~}|zxwwutsrqpqqppoonnnoppqrtvz|~k]ZYZZ^cgkmpt|~{yz}xL&# &,4;@HKOPOMKFBBCFHJJKHC=5.)$ "$'*+,---,+)('&%#%',3;DKU[`aa^XRKC<6212245899?Wy~}|{yxwvttsqqponmlljhfghgbYK8+'('&'*05:<<;:85321/.-.-./24687777530.-----/13410.,+**),-/1110/.,,+*)()*))))))())('&&&'')**++,,+++++++,../01223333345678:;;<<;;;;;;;;;;;:9876543333221100/-,+*+++*('&%%%&&'))))'&%$$$%%&%%$$########""##$&&'''%&''&%$#! #$####"""!!!!!!!! 223334323468;=AFNSY`ejpuz~ztpkjjkkjjigeddfhijklmnoppqqrtutrpnkkklmnoppopppppqssrqqqqqponnnnoopqtuwy{||{zzzzzz{{|}}~~}}~}}~~}}~}|}~~~~{yyxy|~}|zxwwutsrrqrrppppnnopqqqsuwz|~~~~~}}~r^VUX]cgjmpquxtrstua6%$*4;@DKOPOMKFCBCFIKLKHC=5.)$ !#%(+,-...-,+)('&#$&*17@GSX]__]XRKC=742223416Lm}|{zyvutsrqqoonmlkjjifdefc\I:1--*),17:=>=;864421/...//124678645420..--.--/12321/.,+++-./1110/.,-,**(**))))))((('&&%%&&)))**+*)))))))*++,-.//111123455689::::999999999:::87542234433221/.-,+*,,++)'&&&&&'())))'&%$$$%%&%%$$####"###""##$%&&&&$%&&%$#"!!"!!!! ! 2233344457:>BEJPX\cimquzzuqnllljihgeefgiijjjlmnoopopqqpnljggikmnpqpoooooopqqqponnnnnmmmmmmnorsuwxyzyyxxxxyyz{|}~~{{|~|{|~}|}~~}{{|~}{~|||}{xwwxyz|~}}{ywwutsrrqrqqqpppppqqqrsuvy{}~}}||{{zzzz{|||}}~~~na[WWZ`dfjoqqoqqowwD%)-8ADILOONKGDBCFIKLLHC=60*$! "$&),.///0//.,*)(#"$'.4??=<;87865310///00234555431/-..,,,-,,++,./010.-++++./0110./.,,,,+*)(((((((''%%$###$%((('''&%%%%%%%%&&&'()*+,--../02334565433333444677753210024332211.,,+*+,-,+)(&&''''()))('&%$$$%%&%%$$##!!!"""""###$$%%%#%&&%$#"!3444569<@DIOUY^djntx{}xspmjhfeeeghiihggijklmlhhhhgfcbbcegijkmnnnmmnnopnnmllllkkkkkkkjkmoqstttstuttttuvwxy{|}~}{|~}|{{{|~~|}~~|{{|||{zyw{|~}zy{}~xrqsstuvxz|}}}|{yxxvutssssrrqqqqqqqqqrsuvy{|}~}~~}}}|xxwwwvwxvvutsttuttuuuvxyyzzz{{{{{|}~~rj]\XZ]`clooqv|S+-?DFJKLJHCAABFIKLKGB<5/)#  !#%'*-/01221210.+*%#$&+/6615BXz~|{zxwutsponmlkkkkihgfddcb^UG<5127=CEC>;;864454420../02455555421.,+,-,--/-,++,./00.--,,,,10110/-.-,--,+*)(((((((&'$$#"""#$'''&&%$########$$%&'(()*++,,-.0112332222122334456542100023322110.,+++,,-,+*('&'''(())(('&%$$$%%&%%$$##!!!!!!!!""##$$$$#%&&%$#"! 46668;?CHNTY_dhlqu{xsokheddegiigfeeghiiiifeeefebaabdfghikmmnnoooponnmlllkjjjjjjkkklnopqqpopqqrrrssuwxz{||||zyz{}~~~}|{zyyzz|}}}~}{z{|~}}}}~~}{{{{zywvuxz{||zwxzz|}wqnoqrrsuvxyz{|~~~~}|zxxxvuuttsssrrrqqqqrrrrsuvyz|}~~{xwxxyyyxwutssrrrrrrrssrrrsssttuvvwxxxxyyxxyz{|~{qd][foqrsw|O.3ADHJKIGC@ACGIKKJF@;4.(#! "$&(+.012332321.,*%%&'*/49CFKOQQNLH<69Ga~~|zyxvusrqonmlkjiihedeeea\[SF8020;DKMID>9:75323330...12466444420/-***+,.00/.--./000/...///43420/-.-,---,*)(''''''&&$#"!!!##'&&%%$#""""""""##$%&''(())*+,-.//011000000012233443100001221100/-++++,-.,+*(''''(()))(('&%$$$%%&%%$$##!!!!!!!!"""##$$$#%&&%$#"!  689;>BGLQW\bglosy}~ysnjgffeghhfeddfghgggedccdca__`dfgghjkmnppppponnmlllkjiijjjkklmmmnnnmmmnopqqqrsuwyyzyyxvvwyz|{z{{|~|~}{zyyxxxyz{{{}}}}}~~~|{yxxxyzyz{{||}}|{{zzywutsvwxwvurvzz{|~~~~~~~yokkmopqrstuwxyz{{|||}}||}}}}}}~~~~}zxxxwvvuuttssrrrrrrrrrstuvxz{|~{xuqssttsqrppnnmmmmlklmnoonnpqqrssrsttuuuvvuvvwxz{|}}}}}}~xgcinrpruuxH)=CGHHFDCABDGIJJIE?93-&#" !#%'),/023443432/,*'''')-39@FLOPMF?<>Ok~}|zxwvtsqpomlkjihgigeddca^ZP@5138JRSPKD>:664311321/../2466532220.-+))*+-.12110/022221101122876420./.-...-+*)''''''&&##"!!!"#'&&%$#"!!!!!!!!!""#$%&&'(()*++,--.//../.../01122332100//0000///.,+++,-//.-+)(())))***)''&%$$$%%&%%$$##! !""##$$$$$$%%%$"!  9EGE@@EN_{}|{zxvutrponmkjihgfedccba^[WL?22;EJTRPJD>::753200110../1356631000.-,,)))*-/333322234444434576;:9731.///000/-+*((((((''(&$""""!%%%$##"  !"#$%%&'(((()***++,,,----.001111100/////...---,++,-./010.,+))*+++++)('&%%$$$%%&&%$#""  !""#$$$$$$$%%%$#!  >BFKPUZ_dinsw{~ztpligedca`abdefeeedcbbaa`_``ddeefhkmnoppponmllkkkkjihijjklmmmmllllllmnopppppqrstttssrstuuvwwxxxz|~}{zz{{yyyywwwwxxxxyyxxxxyyywvvwxyxxyyyyz{|}}{zxvtrqrvxvsrrrwxz{}~~}}~~~}}~}sjedegikmnnpqrttvwwxxxyyyyzz{{{{|}}}}~~~~~}{yxyyxwwwvuuuttttssssssstuvwxz|}~tib]YXY^\ZWXY[\]a^adgijiiijkklnnnooooopppqqqqstvvuuuuuuvxyy{|~vttuuwwwh&7?CFFB?>=?AEFGHGC=71+%!  !#%'),/1235454320.++('()+17==BGO_s~}|zyxvsrqpnlkkhhgedccbaa`^[UI?@CEFFEA<6/)# !#%'),/1234333332/,,(&&)+/5CCDD@93,'" $%*-1455353-,+*,4?Rex{yxvutsqponlkihfedcba`_^^[YVTRNKMNKHFFED?=;842/.*))((+/120,)')**)+-,,,-,0345567889::;:98777889886777766655555321.,,,,,,,-,)'%$$$##""!!  !"#########$%%&&''''(()******************))*+,./0112222100/-,+****(('(((())(('&%%$%&&&&%%%$$$$$$$##$%%&&%#"!  ehlorux~wsmhdbbcccdeeeddeecbbcegeeddeghiiiiihhggddeghijjiijjkkmmlkkjjjkmnmllkjiiihgfggiknnnnmlkllmoruwxyz}}zxxxvttsrqqqqqqqqqqqponnoqrsssstuvwwwvvuuttsqponnnnossrrstvxxwxyzzy{{||{zzz{}|sg]XYZ[]_``accdeghijknnnooopprrstuvwyzz{{{|||}}}|{zyzyyyyyzzxxxxwwwwvutttuuvvvvwxyz{{|}zsphd^YVROOPPQRRTTUTX[^abfihhiijgghgijjkklmlnoprsssttuuuuvvwwxxyyyzxwwwwwvuttuuvwy{jP?7649:=?ABCB<60+$ #$'*-/1-.0.*+29Qb{|yxvutsqponmkjhgedcba`_^\ZWROMKIGHFDCBB?=<<9631.,*)'')-132-(''*,,+,//./01367889:;;;;;;:98667887776787777765554322/........,)'%%%$###""!!  !""""""""""##$$%%&&&''())((((((()))*****)))*+,-./00122210/.,+****+*********))(('&&''''&&&%%%%%%%%$$$%&&&%$"!  jmpsvy|~ztojgeddccdeddbcdccdeffddddeggggggfffeebcdfghjjjkkjjkklllkkkkkkkjihggfffedddegkmlkkjiggjloruvwxxz}}{xwwvtrrqpppoonoppqqqponnooprrrrsttutssttsrrpnmlkkkmnqrrrsuvwxvwvwxxzz{{zyxyz|~}vk`XVVXY[]]]_abcdeghijmmmnnnoopqrsstuwxyzz{{|||}}||{{{{{{||||yzyyxwwwvvuuuuuuuvvwxyzz{||~zrogeedbaab]_]XX]`^fcffffdcdfeffghhhiklmnnopqqqqrrrssttuuuvuttttssrqqrrstuw||}nO=259=?>BB?<5/)#! %')()'%&+19DYp|ywvtsqponnmkhfdba`___^[YUTQMIGFECCA??>>;:99752/-+*)((+021/)&&'+-/011222457:;;<<===<;;:9877777776557777666655432210////////-)'%%%$####""!!  !!!!!!!!!!""###$$%%&''((''&&&''((()****)))*++,-.//02211/-,+**)*+,--,,,+******)((((((''&&&&&&&&&&%%%&&&&&$"!  qtwy||wrmifecccdccabbbbdeffcccdeffffeeedddcabdfgikkkkjhhijklllllkjjihfedddddcbbccglmkihgfddhlpstuuwwy||zxvuusrqpppponmnoooppoonmmnooppqqqrrrqpqrsrqpmkjjihhkmopqrstuvwwutstwy{zzxwwwy{{xpcYTSSVWXYZ[\]^_`abdefghijlmnooopqrstuvwxz{||||||||||||}~~~}}{{zyyxwwwwvvuutttttuvvwxy{{}~|m]SQR\`a`_aaa`abbccdeeefghhijkllmmmnnnoopppqqqppppoonmmmnpqrsvwxz~}{}jJ755;=AC@;4/)#!"$%,49N[m~|zwutrponmllkigeca`_^\[ZXVROLHECBB@A?==<<:987642/-+,*)+.///+('&'*-04565678:;>>>>>>>>=<:9877666665544565554455432111000000000,)(&&&%%$####"!  ! !!"""###$$%%%&%%%%%&&'''()))))(())**+,-,.000/.,+****+,../...-+*******))*))))(((''&&&&&%%%&&&&%$#!  xz}|wqmjecbbbba````bbdfebbcceeedddccccbb`acegikklkhfffghjjjjjjiiigedcccccccccdhkljhgfddeimqsttuwxz|}~|yxvtsrrrpppooonmmmmmnnmmmllmmmmmooopppppqrsrqpmkjjhghjlmopqrsttvvtsrsvx{yyxvvwxz{tgZSRRSTUVVWXYZ[\]_`bcdffgijklmnnopqrstuvxyz{|||||||||}}~~~~||{zyxxxwwwvuuuttttuvvwxxz{|}o[ORTTZ\Z\^[a]^_``aaabbccdefggghhiiijjkkllmllllkkkkiiijlmopprtvx|{xuyyzdA48?@A?;61,& !%-7FYl~|zxurqpnlkjjihgecba_^[YWUSQMJGDBA?@>?><<;;98777520-+-,,.//-+((()+.048:;;:;<>?@@@??>>=;:987777655442233332221332110//0000000/.+)(('&&&$#$$#"!  !!!"""""###$$$$$$%%%%&''''''''((())***,...-,+***+,-..//00/.-+*****+++++****))(''&&&%%%%%%%%$$$#!  !! }|wrogdbaa`_`_`_``cdbabbcdddcbbbbaaa`_`beghjkjigdddfhiiiiiihhhgedcbbccccddegjkigfeddfjnqsstuwxz{|||{yvusrqppqpoooonmlllkkkkjkkkkkkkllmmmnnnnorstsrqnljihggijlmnopqrsttsrqrtwyxxwvvvwy{seXTTSSSSTTUUVXXZ[]^_`acdefgijjlmnpqrrstuwxyz{||||||}}~}~}}|{zyyxxxwvvuuuutuuvvwwxzz{|}~mPMPSQRVYVXXYZ[\]\\\]^^__`aaabccddddeeffgggggggggedefgijknoqssstvqrrruxz{rK8>CA=;7/(!&2BQiz~|zxvsponljihggfecb``^]ZVTRPNIGDCAA@?=>=;;::87556420.-////-*)))*+-/358=>???>?@@@@@@@?><:9887766664422233332221100//..-.0///...-+)((''&&&%%%%$#"  !!!"""######$$$$%%&&&&&&&'(((((((*,,,-,+****+,--/0000/-+*)**+,,,+++***))(''&&&%%%%$$$$$$$#"!  !!! |wtlgcb`_^```____a`aabccccbaaaa```_^_adfgiihgecccfhiiiiihhggfedccbccddeeefghgffedehloqrssuwxyzzyxxvsqpoonoooonnnnljkjihhghiiiiiiijjlllmmmmmpqrrqpnkihggghiklmnopqrrqqpqrruvwwvuvvwyytke`]XTWVUTTSTVVXY[\]^^aabcefghklmopqqrrsuwxyz{{|||}}}~~~}|{zzyyxxwvvvuuuuuvvwwwyyzz{|}~}~sTIGIKNRQPRRSUVWUUVVWXXYZZZ[\\]^]^^^__``aabbbbbba`abcefgkklnoonlpnllpsssx|}~q;5=;750,'',5AUs~|zxvtqnmljhgfeedcb`_^][XTRPNLHECBAA@>>><;:9976556430/.3430+'&)*/013679>???>>>?@@?>??>=;988776666543111322111110//..--,-/...---,+))(('&&&%%%%$#"! !!!"""""""####$&&&&&&&'''''''')*++++**)))*++,./00//-+***+,--,++++***)(''&&&%%%%$$$$$$$#"!  ! }yqkfca_^_a`^^^___`aabbbbaa```____^^acdfggedcbcegiiihhhgggfeeedcdeeeffffffhffeeffjloqqqrtvyyxwusqpnmlkkklmoonnnnljjihfedefhihgghhhjjjkkkkklnoonmkiigggijjkklmnopqpponnopsuvvvvwxy{zzzyupje\YTQPQRTVWWWYZ\]^^_abdeehikmnooprsuwxy{|||}}~~~}||{zyyxxwwvvvttuuuuvvxxxyyzz||}~kN;:CEDNMLLKMMMNPPQRSTSSSTUWXXWWWXXYYZZZ[\]^^_]]^_abdeeghhgghhgklmprtporty{E-889=HO]l~~~|zxvtrpmljhgfeddcb`_]\ZXUSQNMKGDBA???>>=<;:9877665422127751,*)+.3678:;<=>?>>>>?@>=<==<;98776655333322112210///////..-,,-..----,,+))((''&%%%%%$#"!  ! !!!""#$%%%%%%&&%%&''&'))**)((((())**,,-..-,*))+,-...***)))(((''&&&%%%%$$$$$$$#"!  vpjfca_`__^^]]]]_`aaabba```____`__abcdfedcbbcefghhgggggggfffeeefffgffeeffffffggknpqppqtwyywtqomllkjjjjlmnnnmmmkhhgfdcbcdghgfeeeehhhiiihhjklmlljggffgijkjklmnopqonmllnprssuuuvxz}~|wpib\XTRQRSTUVXXXY[\_abcefhiklmnpqsuwxy{||}}~~}}{zyyxxwwvvuuuuuuuuwwwwxxxzz{|}bD53=E@BDDDHGGHHIJJILMNOOOPOQQQRRRSSTUVVWXXYZZZ[\^`aaaa```aaacefkqwtnoostu{icry~|{|zxvtrpmkjhgedcba`_][YYVQPNMKIGDDBA??>>==<;:9877765433347741../279;;<==>>>>==<==?=;;;;:97666554421111111210/....//..--,,,---,,,,++*))((''&%&&%$##"!!  !#$$$$$$$##$%%&%&'(()(''''''(((*+,,,,+*))*,-./.))))(((((''&&&%%%%$$$$$$$#"!  }wqlgdaba_]\\\\]^`aaabba```____a``abbcdcaaaabdefggffffggggggfffffgggedefffffffhlprqppruxxxvsoljiiihhhhkmnmmmlljggfdcbbbcfgfeddddfffgggffhjkkkjifeddgjllkklmnppqnmlllmoqqrstuwy{~~xpi`ZSUTSRSTUTUVX[\^_acefhiklnprtuwxz{|}}~~~|{zyyxwwwvuuuuuuuuvuuuuuuvxyz{}~yR:143<:9====<;;:87767665555565433579==>>>>>>>==<<;;<=;::::986555433310//01111/.-,,,,..--,,+++,,,++++****)((''&&&&&%$##""!  """"""""""##$$%%&''('&%%&&&'''()**++*)((*+-..-))))(((''''&&&%%%%$$$$$$$#"!  }wrlgccb`^\\]^]^_```aa```__^^_a``aaaacb````abbcedcccceeffggfffffggffeffefefggiloqqqqrtvwvrnjhfeedddffiklllkjjgdaaaaabccddcccdddeeefffefghiiihgdbbdgikmmlmnnoppmlkjjkmoooqsuwy|~{un`]XTRRSTSSTVWXYZ]^`abdfhjlnprsuwxz{|}~|{zzyxxwwuuuuuuutuuuutttuuvxy{|}~~}}}}~~~~~lC6,0233758:99:<==>?BDDDEEEFGHIIHIJJIJKMPSRSSTTVWWWWVUUUUSUY\aflsyxrlgists~}}}}|{yxvtrpomjggfeca``^\ZYWTQNLJIIHGFFDBBA>=<<<;::988778777778887779;=>???>>>>====<;::;;:999987544432220.-./0122/.,+++,---,++**+++++***)***))(('&&''&%$####"!  !"##$$%&&''&%$%%%&&&&&'()**)('')+-..,(((('''&'''&&&%%%%$$$$$$$#"!  |wqkheda__^__]^____``____^^^_a__````bb`````aaabba```bcdeffffffffffffeedeefggkmopqrsssttrmiedbbaaabdehjkkjjhgdaaaaaabccddcccdddeeeeeeegggggggecabdghjlnmmmnnnnlkjihikmnnpruwy{|}}|}~{qmg`ZUSQRRQQRSUUXYZ[]]_bdfhjlnortuwz{}}}||{zyyxwvuuttssrsssssssstuvxyz{}~~~~}|{{zz|||~~}|}~}}}~]D3.-.2666444676579::<>>>?@ABBACDDDEFHKNNNNOPQRQPPPPOOOOSUX[_dkow~~{{sjn~|{{zxwvusqonljgeedcba`_\ZXWUROLJIHHGFEECBBA><;;<;;:998779989::;;<<;<>?@?>?>>===<<<;;:999:988888644332211/../013330.-+*+,---,++**+++***)))))))((''&&&&&&&%###"!  !"#"$%&&&%%%%%%%%%%%%&'((''&&)*,--+'&&&%%%%%%%%%$$$$$$$$$$$$#"!  }wqmhgc````a_^____``________`__````bb```_``__``___`bcdeeffeeeeeeeeeeeeeddegjnpqqrrrrrpmhecbaaa``acdgijjiigfca__```accccbbbccceeeeeeegggfffedcbcdfgiikkkkllllkjihhijmooqsuvxy{{~~}|zzzyzz|}{yuqld_ZWRQPOPPQRSTUVWXY\_acefhimopsvxzz}}~~}}|{zyxwvvuttssrrrrrrrrrttuwxyz{||}~}}|zyxwvvxxyz{|}}}}}}||}||||||{{z|}~|}~iL?98=;<==>==:9777666:;::;<;:<==?ACFIKJJJKKLLJJJJKJJKKMPRUX^dhls|{nl}|zyywtrqqonmkgdb__^]]][XVTTROLKIHGGFEDDCBBA><;;<;;;:9988;:9:;<<>?@??@AA??>>=<<<<<=<<;:999877776543322100//00123442/.,**+-..-,+********)(&((((('''&'''(((()('%#!  !!"$$$$$%%$######$$%%%%%%%&()*++*$$$$$$$$#################"!! ~xtkidb``__`^____``____`````__````bb``___`_^__^^__abcdeeeeeeeeeeeeddedddefjnpqrqqqpnkhdaaa``___`bceghhhgfeb_]]]^^`abbbaaabbbddddddegffedddcbbcdeffghiiijjjjiggffgimoprstuuwywy{{{zxxyyyzzzzw{xwtojfdXVSPNMNMPPQQRSTVYZ\^_`begilortuyyz|~~~}|{zyxvvuutttrrrrrrrrrttuvvwxz{{{|}}~|zzxwvutttvuvwwxyyyyyzzzyyyyyyyyzyy||||}{}~t^JC?BEGHHIJIGFD@=:=;9887657779;=AEFEFFGFFEDEDEEEFGHKKMORX[\agknopqkp~|zyxwuspnoligea]ZVVVVVVUSRQQPNLKJHHGFEDDCBBA><;;<<;;;:::9==>>>?@@@AAAAAA?>>==<<<<=;;::987766555544332210/0001123432/-+*)*-.-,*)))))**+*)(('('''&&&')))****+)(&$"!!!!   !"""####"""""""##$$$$$$$&'()*)$$$$$$$#"""""""""""""""""!! ~zplfb`_^]`]^^^^__^___``a``__````bb`______^__^___`abcdddccccccccccdedcbcfinoqqponlhfda`__^^^]^_`bcdffffeda^]]]^^`abbbaaabbbccccccdffedccbbbbbcdddeefggghhhfedccfhloprstsstvvwyyyxwvttuuvvwwwwvtsrppd`\WSONMONNOPQQRTUVXYZ\^`behjmnttuwy|}~~~~}|{zyywwvuuttssssssssstuuvvwxyzzz{{{}~}|ywvutsrqssttuvvwxxxxxxxxwwwwwwwwywxy{{{|{~cPC@ADHMLORTUTRQC>;9754454577:<<>?@AA@@??A@?@@ABDGIIJMQTTVZ[^^acbr~|zwuttsplijea^[WTPJJKKKLLLMNONMKLKIHHGEEECBBA><;;<=<<<;;;;@@AAABAA@@@AAA@?>>=<;::;;::9887665444444433221001001223432/.,***+++)((''(**+,+*)(('''&&&%&))***+++*)'%#"!!!!!  !!!""!!!!!!!!""""""""$%&'('#######"!!!!!!!!!!!!!!!!! xoid`^[YYZZZ[[[[\^^^_``aa```````a`_____`__````````abcdcbbbbbbbbbcaabbcehlnopnmkhc``__^]\]\\\]_`accccbb`^^^^^^^_``aaaaabccbbbbbbbcccbbaaaaabbbbccdddeeefdbaaaadgkpppqppppqtvxxxxurrrstuvvvvvutsrqnojd]WTMIMMNNOOONQPQRRSSTWZ\_bdfhmnqsvy{}z{|}}}}|~~}|{zyyxxwvvvutttuuuuvvvvwwxyzzzzzz|~~~~~~~~~~~~~~~~}}}|{zywutssrqpqpqqqrstuvuvvvvvvvvvvvuvvwxyz{{{}|}}cI@??CFEJNRWYTTMIB98:9646242.47;>?><9;8:::?A@?BBCEFGHJMNOSVUVWU}|{yxvsrqomi`YRLHGED@>?AABCDEFIIJJKJGGGGFFFDCCB?=;;<@AA@?>??ABBBBA@@@@@@A@?>>=;:988888887654433344444332211110012234320/-+,,+*)(''''(*+,,,+*''&&&%%%&(***+++++*)'%#"!!"!! !   !!!!!!!!"#$$$$#""""!!!! ~vpid_[XWXXXYYYYZ[[\]^__________``____`````````__`abbcbaaaaaaaaababaabdgklnnmkie``___^\\\[[[\^^`bbbbaa_^^^^^^^^__``````bbaaaaaabcbbbaaaaaaaaaaabcbcccddcbbbbceinqqponnnnoqrtuutsqpppqrrstuuuttutrtrnib^WQPNMLLLLMOOOPPPQQRTVXZ\^_dfhkoqsuvxyz{{{{|}||{{zzyyyxxwwvuuuuvvvvxwwwwwxz{{{{{{|~~}}}||}}}}~~~}||{{{{{|zz{zzzyxvtsrrqqppooopppqsstustttttuuuuuuttuvwxzzzy{{zy|ePB;BBEHIJLQNID>92+2304766775579::9==>?@BCEGJLMNOKJh~|{zxvtqooke\OGDA?>=;:8888777?ACEEGHGFEFFDDDDCCB?>=>?BCBA@?>?ACCBA@??>>?@@?>><<:9766666765443322233444332222221112233311/.---,+*)(('()+,---,+('&&&%%%&(*++++,,,+)'%$#""#""!"!  !""""!!!!! ! }vnhb]XUWXXXXXXXYYZ\]^^^^^^^^^^______``````_____`abbba````````abaaaabcfknpqpmjfbba`_^[ZZ[[[]^^`aaaa``_^^^^^^^^__``````bbaaaaaaaaaaaaaaaaaa`````aaabbbcccdcegjnqqonmmlmmmooppponnnnnoopqsttttuuuuwwuqlhb[UPMJJKLNNNONOOOPPPQSTVWW[\^bfikmpstuvvvvxzzzzzzzzyyyxwwwvuuuvvvvyxxxxxy{|||||||~~}|||{||||||||||{yyyyyyxxyxxwvusqpoppoooopooooqqqqrpqrrsssssssssstuvwyyyxyx{{{~hJA><;ABDIID:6:CJNSTMPitVD:2..../1423477677899:;=?=AGJGAH~|zywwsomkf\PIC@?=<<9876655458;>?ACDECDEDBBBBCBA@@?BDDDCB@?>?ABBA@?>===>>>>=<::986554456543322222223333332222221122232100/.....-,*)))*+,-..,+*)'''&&&&)+++,,,-,+*(&%$##$##"""!  !  wphb[WVVUUVVWVVWWXXYYZ\\\\\\]^^^^^^__`________`aabba`________````bceimqsttrpmihfcb_\[Z^]]\\]^_````__^_______````__^^\_^^_____```````a_`````_`aaabbbbcdefimpqrokkkkkkkkllllkkkkkkklmopprrsstvwwyywvsplf^ZSOLKLMNNMLMNNNNNOPQRRSUUW[^befjmoprrrruxxxyyyyzzyyxxwwwvvvwwwwyyyyyyy{|||||||~~~|||{{{{{||{{{{{zxxxxxxvvvvutsrqonnnoooooononopppppopqqqqrrrrrrrrrstvwwwwxxy{|}nbSG@=?BC=45BNSWXXTNawjTA76.-+./35557878789999<<87Jm}zyyvsqmhc[PFA>=;::9754321000257:;>@BBBCBA@@ABBB@?ADGGFDBA?>>?@?>=<<;;<<==<<;998754433454322122222222233333333212222221000/0000/.,+**+,-...-+*)((('''')+,,,,---,+)'%%##$$####"  zri`[YVTTUVVVVVVVVVVWZZZZZZZ\\\\\\]]^______```aaaba`___________bdfikouwxxxwurpnjgdb`^^^]]\\]_```__^]^^^^^^^____^^]]\\[\\]]]^________^_______``bbaaabdfinpqppnkkkkkkkklkkkjjjjjjjklnopqrrstvxyyxwwwusnkf^VPMLJMMLLLMNMLLLLLLLLNPRSUWYY]`begjmoqrstuvwwwxxxwwvvvvvwxyyyyyyyyyz{{{{{|||~~~~~~~}~~~~}|{{zzzzz{{zzzyyxxwwvutttsrrqpponmmmnnnooonnoppnnopnopppppppqqqqqqqrstttuvvvxz{|}~~ziWI@7.1?NW^_[\ZLMvhUB63./00/222112232532<;;:8752210..-,-/257:=???@A@@@ABBCBABEHGGECA@?>>==<;;::::;<<;:97766643344443210122221111233333332222222100011223320/.---.///..-+**))(''(*,--------+*(&%$$%%$$$$$# xphb]YVUUVWVWWWWWWWWXXXXXXXZ[[[[[[[]]]^^^_``aaabbbaa```____`abdfikmrvxyyyxwvvtpnkjfcaaa``_^_aa`_^^]]]]]]]]^^^^]\\\[\[[[\\\]^^^^^^^_^^^^^^^^__aa``abfimrsqonljjjjjjjjkjjjiiiiiiijklnopqrrtvwywxxwwvtrsmgaYTQILKJKLMNMLLLLLLLLMNOQQSTUXY\_acfhkmnopqsstuuuuutttuuvxyzzyyyyyyzz{{{{||}~~~~~~~~~~~~}}}}}~}}~~~}||||}}}}}}}}~~~}}}}}{{zyyyyyzzyyyxxwvvutsrqpqonmmmlllllmnnnooonnoooooonnnnooooopppoonooppqrsrtuvwyyz{|||{~|kM79MY_cb_`^RGk~kXE:52..--,-//028>Ro~|yvurpkdZOFCB>>><:9753320.-,++,.047:;>>??AABBDDCCABDFEDCB@?>=<;::::9999::998755566533443321000121110000112233432222321/011245654210//00000///--,,*)(')+-.///.---,+)(&%%&&&%&&&%"!  yqkc^YVVWWVVVVVVVVVWWWWWWWXYYYYYYZZ[\\]]^_``aaabaabaa``__`abcehknpsuvxxxxxxywvtsrnjgggffedceca_^]]]]]\\\\]]]]\\[[[[ZZZ[[[\]]]]]]]^]^^^^^^^^^____achmpsspnmljjjjjjjiiiiihhhhgghhiklmopqqsuwxxyyxxxwuvsokda\QOKIIKKKKKKKKKKKKLLMNNOPPRRUWY[]_cefhiklmnqqrrrrssstuwxyyyyyyyyyzzz{{{||}~~~~~~~~~~}~}}}}|||||||||}}~~~~~~}}||||||||}}}}}}}}|||||||{||{{zyzz{{{{{{{{|||{{{{{zyyxwwwwxxwwwvvutsrqppomnmkjjjjjjjkllmnooonoonnnoonnnonnoooonnnmmmmnnopqprstuvxyy{{{z|~~eVWXY]\__]WJQkwyyxy}}pbXVSRSTVVWfp}yurnkd[QHBA@>?><;:8655310/.+)++.047:>>?ADDDFEEDBAABCBA@@@?=;:::::::::98876555445543344432100/01000000///0123421123330/01235766532100111100////..,*))*,-./0/.---,+*('%%&&&&''%%""! "!!!!!! zsjc]YWWWVWWWWWWWWVVVVVVVWWWWWWWXXYZZ[\]^____```abaa```_`acdfilorsstuvvvwxyyxwvvsqmnnmmlkjigeb`^]]]\\[[[]]]]\\[[[[ZZZZZZ[\\\\\\\]]]]]]]]]^]^^^`bfkpqrqommkiiiiiiihhhhhggggfffghijkmooqsuwxyzzzzzyxuttspnj`XSMJJIJJJJJJJJJJLLMMMNMMOPQSUVWY\^_`bdefgkllmnopqrrtvwxyyyyyyyyxxxyyyzz{||}}}~}}|||||||{|{zzzzzzzzzzzz{{|||||{{|{{{{zzzz||||{{{{zzzzzzzzzzzzxxyyyyyyyyyzzzzzyyyywwvuuuttuuuttttsrqponnlklkihhhhiihijjklmnnnmnmnnnonnnnnnmmmmlllkkkllmmnoopqrstuwwwyzyzz{{~{dTIKQY_^WQNVgtvwxzzx|}}xsoid\RJECB?>>>>=;976653221.*+,-/37;?ABDGGGGGFDB@??A@@???><;:;;;;;;;:87655554445544444432100/////0000/./0112211123430/012367665432112222100/0/..,*))*+-./0/..--,+*)(&%&&&%&&%$#""!!!!"!!!!!!!yrjd_[YXWVVVVVVVVVVVVVVVVVVVVVVVWWXXYYZ\]]]]^^_``aaaaa`abcdfhkmoopqrssttuvvuuttsrrrrqqqrrnligea^]]\\[ZZ\[]]]]]\[ZZZZZZZZ\\\[ZZ[]^^^^^^^]]^^^`cfkorqpomlkjiiiiiiihggggfffedddefgijlmnprtwxxyz{{||{zyxwwwxsh\PKLJILKKKKKKKKLKKKKLLLLLNPQQRTUVWYZ\^_adefhiklnpprsuvwwwwwwwvvvvwwwxxyzzz{{{{zzzzzzzyxyxxxxxxxxxxxxxyyyyyyyyzzzzzzzyyyyyyyyxxxxxxxxxxyzzyywwwwvvvvvvvwwwwvvvuttssrrqqqrrrqqqqpnnnnmmlkkihgffffffghhhikmnnnmlmnmnmmlllllkkkkkkkjjjkkllmmoopqrsssttuvwwxyyz{}~~xfVILQXXTPTalssvxwwx|{~|xslhbZTLFDCB@B?=<<=<;7766531-00./16=CEFEFHHGEGEB@>=<<=>>>>=<:89:::::::7533334444444444433210/.--.//000/.//0111001234310123455665432223332100//.--+)()*,./010/.-,,+*))('''&&%%$#####$#"#""""""!zrke`]ZXWWWWWWWVWWWWWWVVVVVVVVVVVVWWWXYZZ[[[\\]^__````abcdegjlmnoopqrrrsttttttsstttuuuvvusqoniedbaa_^^\\^_^^]]]ZZZYYZ[][\[ZZZZ\_^^^^^^^]]^_bfkorsqnmlkiiiiiiiiihfffeeeddcccdeghikmnoqtvwxyz{|||||}||}|}|yreWLJLJKKKJIIJLLLLLLLMMLKMMMMNPPQRTUWXY[^_abdfgjmnoqstuuuuuuuttuuuvvvwwxxxxxxwwwwwwwwvvvvuuuuuvvvvvvvwwxxxxxxxxxxxxxxxxxxxxwwvvvvvvvwwwyywwvvuutttttttttttssssrqqppoonopooonnnnllllkkjihgecbabbcdeeeefgjklkjikkklkkjjjjihihihhhgghhiijkllmmnopqqrrstuuuvwxyzz{{y{}}|}ufUUXWQNQZfmopruwvxx{xsoia[VSNIEDDCA@?>==>>=;;:9753113368???>=>>=;:86657;<@ACFGIIHHHHFDBAA?<:988:;;;;:97788888885311234431112332110/.---,,-////.........-/02443//01123333322112333210/.----+*)*,-/01110/-+++++++*((''&%$#%%%&&%$$######" yrkfb^[YXXXXYWWWVVVVVVVVVVVVVVUUUUUUVVVWWWWWXZZ[[\]^_`aabceghjkklmnonmooppqqqqqrstttuvwwxxxwutsrponnigfb_\ZZ\\\ZYXYZ^_`````aa_______^]^_bflqtqmmlkkkjjkkkjjjjhffffffffeeeeeeefikmnprsuuwxz{||~xaPELIHHJJIHJJJKKLLLLLMMMLLMLKLMNOPPUVWY[]^_cfgijlnoppppppppqrrssssssrrqqqpqqqqpppppppqpppqqqqrrrsssttttttttttttttttttttttsssssssttsuutsrrrrqqqqqqponnmmllmmlllkkkkknmllkkkjiihhggdca_\YXXY[\]^^_`abcddeddfefffgffggeeedcba`aabcddeggjjjkllllmnpqrrqpqrstuvvtuuvvxxy}xh^[dltvssrrtwz|~yq{yaXQNOOOPPLIFC@?ABACDDB@@AB@><::;:ACFFHIHJIGFFEEDA@@@><:98899:::98667777777420134442000122100/.-,,,,-./00.......--,,.02442/.0011222221001122220/.-,---,**+,../111/.-++++++,*)(('&%$#%&&''&$%#######!yqlgc_\YXXYYXXWWWWVVVVVVVVVVUUUUUUUUUUUUUUVWXYYZZ[[\^___`bceghijlmmmlmmnnooonpqqrrstuvwwxxwvuvuttrrpokhea^^]^\ZYXXY\]__^^_a`]]^^^^^^^_bfjoqqolllkkkkjkkjjjiihffffffffeeeeeeefhkmnopqstuwxz{|}~tbNIIGIIIHILJJJJKLLMNNNNNNNMLLLLLLLPSTVXZ\]`bcefhjkmmmllllmoppqqqqpponnmmmmmmmlllmnmnnnnnoooopppqqqrrrrrqqrrrrrrrrrrrrrrrqqqqqqqqqqtsrqpoppppooonmlkjiihhhhhhhhhhgggfeffecaabbb``^][YVSRRSUVXXYZZ[\``bbbcddddddcceebbba`_][\]^`acdeeghhijkjjlmnoppoopqqrsssrsrrrstuz|~xronmnqqpuuvy|xnvzk\PPKLOQQQPKIEBAACCFHIJGFEFGECA@@ACHIHHIHHJIFDCBA@=??@?=;:988999998556666666310135530.//0110/.-,++++,./000.-..---,,+,.02442..//0011100////012210.-,+,,,,++,..//000.-,++++++,+*)('&&%$''''('%&$%%&&%$! ~wqlgb^[YYYYXXXWWWWVVVVVVVVVUUUUUUUUUUUUUUVWXXYYZZ[[\]]^_`bceffhijkkkllmmnnnnoopqqrsttuvvvvvuxxxwwwwwtrnkhfba_][ZYZ\]___^^_]Z\\\]^^_`bdimppomkkkkkkkjjjjiiiihffffffffeeeeeeefhjlmnoqrsuvxyz{}}}~y_MIGHHGHGKJKKKKJJLNNNNNNNLKKKKKKKOPQSUXYZ\^_abdfgiihhhhijmnnnnnnmllkjjjjjkkjjjijkjklkkkllllmmmmnoppppoooopppqppppppppppoooooooonmqponmlmmllllkkjigfeedeeddccccccbdcbbba_^]^]][[WVTQNKKKMNPRRRSSTUXYZ[]^________``]^]]\ZXWXZ\^`abcceffghihiklmmnnmmnooppqqoppppppqxyyy~wpoqrsstvx||sekzocZQNOLLPQOOOMKHGEEFGLMMLKJJJIHHFEGHIIGFDDEDFECA@??>;@?>>>=<:9999988744333333420013541.-.//00/.-,+**+,,-/00/-,,,++***)*,.0232/---..//.//..../0111/.,****+,-.//000//.,++,,,,,,,,,+)(''&&((('((&'%&'(('$"! }wrke`]ZYXWWXXXWWWWWWWWWWWVVUUUUUUUUUUUUUUVWWWWXYYZZ[[\]^`abcdefghijjkklllllmnoppqrssttuuuuuwxyyyzzz{zzwtsljfc_][[\]]]]]\[YZ[[\]^^_aeimoonmlkkkkkkkjjiiiihhgffffffffeeeeeeefhjllmnpqrtuwxyz|{|}~tZMHFIKJIIJKKKLLLMMMMMMMMLKKKKKKKMNPRSUWXZ[\^_abcdddddefhjklllkkjjiihhhhhhhgggffhfhihhhhhhhiiijklmmmmmmmmnnnonnnnnnnnmmmmmmmmllkjllkjihihggggffedbaa```````__^^]]^]\\[ZXWWWVVTTONKHECBCEFHIIIJKLLPPSUVXYY[\[[[\]]WWWVTSRRUXZ]^_`abcddefgfhjklmmmmklmmnnnnmnnonnnopsstwz|{z~~xtsssrstw}~~~~|xlchqc[VSLKKIIMOMNOLKIHHIIJKLKJJIHHHGFGGGHJGDCAAAAAC@>??>=;=<<=>>><;::9876643211111210013421.-..////.-,+**+,+,....,+*))))((()*,.010.+*++,---./..-.../00/.,*))*+-/010110.-,,,,----,,---,+*)('(((((((''%&'(('%#"! ~~}xpid_\ZXWWYXXXWWWWWWWWWWWVUUUUUUUUUUUUUUUVVVWWXXXXYYZ[\^_`abcdefghhiijjkkklmnnopqrqrrsssstvwwxyzyz~~}yuqlgc_^^^]]]\ZYYX[\\]_`adhlnnnmlkkkkkjjjiiiihhhggffffffffeeeeeeefhjkllmopqstvwxy{yz|~gXNJKLLMKLKKKLMNMLLLLLLLKJJJJJJJKLNOQRTUVXYZ\]^_```aaacehhiihhggffeeeeeeeedddccdcdeeedeeeeeeffhijjjjjjjjjkkkllllkjjjjiiiiiiiiihfiiggeeeeccccbba`]]\[[[\\[[ZYXXXWXWVUTSQPPPONKJDCA=;99;<==>>>?@@AFFHJKLLMOQPQQRSSMMLLKJJLORVY[\]^_``abcddfijklllljkkklllljkklkkklnooqrrstttuxy~{uussssw{~~}}|yxwrlfiugZWVUQJHGGJNPMKIJJJIKLMMIJGFFFFEFEEEFFGIECBAAA??@?>>>=<;<;:;<==<;:87765542100000110013320.-..///..-,****++,-..-,+*)))(((()*+-./.,*))**++,-.--,---....-+*)*+-.012111/.,,,-.....---..-,+*)()))(()(''%&'(('&$"! }}~ungc^[ZXYYYYXXXWWWWWWWWWVVUUUUUUUUUUUUUUUVVWWXXXXXXXZ[\^_`abcddeefgghhijjkklmnnpqqrssttttuvxyzz{|}~{vpid_]\\\[[ZZYZ[\\]_adhinoonmkjkkkkjjjiiiihhhgghhgggfffddeeeeffhikkllnopqsuvwxz{|}~~l[PLKKJKJKLMMKJJKLMMLLKKKKKKKKKJJJKMNOPQRSTVWXY]]]^_``bdeeeeddcbbaaaaabbbbaaaaaaaa``aaa`aabdeeefggggggfeefffgggggggfeeddcccccccdbbb`_`a^]^^^^][][[ZYYXXUUUUSRRQRQONNLIHDDB?=:741/-./1111100001112345789;<>>@BCDCBAAAAADIMQUXXY[]]_abcdffghijijkiiijjkkjggfeefghjjklnnnmmmmmoqv{}yusuxz}~}||}|zyxsusldam~hXTSSSRPLGHHIJJKKLIHILMKHFDDEFFFDDDBCCCDDDCAAA@@@?>=;>>==;;99:<==<:9665555653210//000/02200.-..///..-,****+++++++*))))(((''((((*+**)((())*+,,+***+++,,,+*)*)*,-/022210/-,,./0////..///.,+***)))((((''()))(&$#"! !! {||}~unic^\ZXYZZZYYYXWWWWWWWWVUUUUUUUUUUUUUUTUUVVWWWWWWWYZ[\^^_`abcddeffgghhijjklmmnoppqqrrssuvxyz{{|}~{wsplhdba_][ZY\\]_`beijkklkjjlkkkjjiiiihhhgggghggggfffddeeeeffgijkkkmnpqrtvwxyyz|}~ucRKJILKLKIIJKKKKKKLLLJJJJJJJJJIIJKLMMNOPQRSTUWXYZ[\]^`aaaa`__^^]]]]]^_^^^]]]]]]]]]]]]]^^_`aaabbbbbbbbaaabbbccbbbaaa`___^^]\\\_^]]\[[[YZZZZZXVWVUTTSSSQQPPONMLLKIFDB?<7641/,,+*)(()*+++++*)('&&%%%&&''))*,-..//0//0137>BGLOQSUWXZ\_`bcdefgfffffgggggffedcccefggghijjhhggghkoruqxwtvz}~~~~}|{|}}|xxyzwrsuqmf^bu~jZQPQRRRONKHHHJJKKLLHFFGGDBABDEFFEDCDBBAABAA@????>>>=<:===<:9889:;;;986655668754210010/0011//.-..//.---,*****++++**)(((((('''''''())('''(()**+**)))*******))))*,-/02221/.-,,.00////../0/.,+***))))(((''()*)('%$#"!!!! zz{{|}~|uoic_\YZZZZZYYYXWWWWWWWVUUUUUUUUUUUUUUTTTUVVVVVVVWXZZ[\^^_`abcdddeefgghiijkllmnooppqqrtuvxz{|||}~~ypmjfc`^^]^_bcdfhghiiiijikkjihijjgggfffffhhhhgggfddeeeeffgijjkklnpqrtuwwxxyz|}~}jWJFIHIKLLLLLKLLLKJJJIIIJJJJIHHIJJKLLLMMNOPQRSTUWXYZ[[[[[ZYYYYYYYYY[\[[[ZZZZZZZZZZZZZ[[[\\^^_^^^^^^^]^^^^___```___^]]\\[ZYYYYXWWVVVUVVVWWVSRQPPOOOONPNMKIIGDA?<851.,)'&%$$$$%%%%&&'())('%#!"!!"""""#!!!!!"!$###$%&*/48=CGJMNQSVY[]_bdddcccbbcdccbbcbaaacdefedefgfddaabdhnqsqqv|~vux{}~}~}}}||{||}}}}}}{zyxyzzyxvttsrqqo`T[rjWOMOQUQQQPMLIIJJIJKKLGECB@>>@DEEEDCCACBBA@A??>====<<<;::<==<998788888675655779865320/0//000/..--....---,+*))**+++*))((((((''''''&&'(('&%&&'(())))((())(())))()()+-.01210.----/010000//00/.,+*)**))))(((()***)'&%$#""""!!!!!!!! yyzz{|}~{tnhb^\[ZYYYXXXXXXXXXXXVUUUUUUUUUUUUUUTTTUUVVVVVVWXYYZ[\]^_`abbccddeffghiijklmmnooppqrttvxy{{{{|~~zwtromnlkjhfffffiigghiijjkkjihggggfffffhhhgggfeddeeeeffhhijjklmopqstuvwwxyz||}r_RJHHJMNLLILJIIJKJIHHHHIIHGHHIIJJKKKKKLLMNNOQRTUUVWWWWVTSSUUUUVVVXXXXXXXWWVVVVVVWWWWWXXYZYZYYYYYYYYZZZZZ[[ZZYYZYYYYYYXVUUUSSRRQQQQQRRRRROLKJJIJJJHIHEBA><8641-*(&%$$#$%$##%&&&&&&'((&%"! !""""$%%$#""!!$"""!"#&%),059<@FHKNRTW[^`aaaa`````^^\\___``abddcccddca`[[]`chknroptvvuswz{zzxyyxwutuuwwxxxxwvutuvvuutrqrpkiaSUovZMJMOPQQOOPONMKIIIHHJJJHDA?==@CFDCCAA??ABBBA@>=<;;;:::988:<>><98877665435454567986421/.///00/.---....--,,,+))))**+*))('''((('''&&&%%&''&%%%%&&''())(((((''((((((()+,./111/----./0111110010.-,*))*****)(((()***)('&$####"!!!!!!!! wxyyz{||~~|tnhb^\[YXXXXXXXXXXXWWWVVUUUUUUUUUUUTTTTUUVVVVWWWWXXYZ[\]]^_``bbbcddefghijkkllmnoopqrstvwyz{{{|}zsokddcceggihhijjihgffgghggffhhgggfffffgggghhhhiijjklmnpqstuvwwxyz{{}~~~~~~~re[QIGHIHKKKJHFEFJGGIKIFFFFGHHHHIIIIIIJJJJKMNPQQQQQQQPNMMOPPQRRSTTTTTTTTSRRRRRSSSSSSSTTUTUTTTTTTTTUUUUUUUVVUUUTTTSSRRQPPPNNMMLLMLMMMNMLJFDBAAAAA?><8630.,(%"""" !"$$$$%&'()))))(()(&#! !"#$#$&((('&&&&'(('(())((*+,,-1;=?CFIMQUVY\\ZYZZZYWWVW[\^__`````````_^\YXY]_achnromoqysqtwxwwtuutsrqqqsstttusrqpqrrqmnnlklg]NWpygUFIOQPNPNMMNNMMLHGFFFHJIF@;::>CDCA??>>>>@ABB@?=;;::::99988:=?><:9876542102333466875310.-.//00/.--..//.-,+,,+))))***))((''(((('&&%%%&&'&&&%%%$%%&&())(((('&&'''''(()*+,./0/----.../00000//0.,++*********)((()***)('&%$####"""""""""! vwwxxyzz||}}~|uohb_]ZXXXXXXYXXXWWWWVVVUUUTTTTTTTTTTUUUVVVWWWWWXXXY[[\\]]^_aaabccdffgijkkkklmnopqqssuwxyzzz{}~zskgeeeedefgfffffgefghihhgffffeeeeeeffffggghhiijjlmnoqrstuvvwxyyz{||||||}}pcXPJDBDCDFHFEHGEEEFGECCDEEEFFFGGGGGGGFHIJKLLLKKKKIHFGJKKLMNOPPPPPPPPPOOOOOOPPPPPPPPPPQPPPPPPPPQQQQQQQSSRRRQPPONNNNMMLKKJIIIIHHHHGGEA=;97666530.,*(&$#" !""$&'(()*+,,,,++)(('%" $%(()))*,-,--...0000011/,-,++*+-2358;>AEILOPPOQSRRQPPQTX[\\\]]]\]]\\[[ZYYWWY[[]bfijlnnptsqoqqqrrroponmlkklnnnooomlkjkllkhghffd[NUr|k]UPKKMMNMMMKLLMMLKGEDEFGIGE=77;BFE@>>===>??ABBA><9:;;;:999779<>=;977654321/1221234765420-,-...--.....00/-,+++*)(()***)(((''(((('&&%%%&&'&&%%%%$$$%&()(((''&&&&&&&&''()*+,---,,---../00///.-.,******))****)((()***)(('&&%%%%$########""! uuvvwxxyz{{|}~~~{unic_\ZYYXXXXXXXWWWWVVUUUUTTTTTTTTTTUUUVVVWWWWWXXXY\\\]^^_``aabbccefghijkkkklmoppprrtuwxyyyz|}~{wrnieccbdefeeefdfghhgfffffeeeedeeffffggggghiijklmnpqrsttuuvwxxyzzzzz{|}qcXMICBACCEACDEEEEBBAAABBCDDCCDDDDDCEFFGGFGFFFFEDCCFGHIJKLMLLLLLLLMMMMMMMLLLLLLLLMMNMMMMMMMMNNNNNNNOOOOOONNMMLLLLKJHGGFFEDCBA@@>;73/+)('''&%$##""!!  "$%(*,-./001110/.,*)(%!!$'(+../0124556889:::9999632110/02001358:<>@BA@AEJMLJIJLOSWYYZYZXXYYXXWVVVVUUUVWY]_dhlmmjddhknomlmljjjihfefgiiiijjihfefffeb_`dbWPTto^URSONMLKLLKKJKKKKKJEAACDEFD?857>EHE><<;;<>@>@AA?<:89:;;::88678:<;976544332201111112565420-,-..-,,-.../120.-,,*)((()***)((('''((''&&%%%&&'&&%%%$$$$$&'((''''&%%%%%%%&&'()*+++++,,,----./....--+)))))))))***)((()***)('&%%$$%%$#########""! ttuuvvwxyzz{{}}}~~|vpga^[YXWVWXXXWWWVVVUUUUTTTTTTTTTTTUUUVVVWWWWWXXXY[\\]]^^_```abbcdefghijjjklmnoooqqstvwxxxy{|~wpjcedcbbddfgfeeeefffffeeedcddeeeeffffgghhijklmnpqrrrstuuvvwxxyyyz|~yk]OE@??@?@BCBBBA@??@@@AA@@AAAAAABBBBAABAAAAAA@ADEEFGHIJIIIIIIIJJJJJJJJIIIIIIJJJKKKKKKKKKLLLLLLLLLLKLLJJKJJJHGFFDCCBA@><988653-(#!!!!##%%$##%&&(*-/012344665321/,*'# %**.14789;::;<=>?@AABCCDDB=;97433311123567577337=CIGDDDGJOSTUVUUSRUTTSSRRRSSRQRUWWZ]`cefdbfhjjihijggfeedbaacccdddeddba``_^[Y]_WMUnl]RQTTRJIJLMLLNJJKKJKJIB>>@A@@=845:BHIE><:::;>?>???=:8799;:;:8867689875444432210111111256531/-+,,,,++,,-.01220/.-*)((()***)(((''''''&&&%%%&&'&&%%%$$#$$&&'''&&&%$$$$$$$%%%&'()**)*+,,,,,,-----,,,*(((((((()***)((()***)('&&%%%%%%$$$$$$$$###"!  sttuuvvwwxyyz{||}~~{umfb^[YWUVWWVVVUUVUUUTTTTTTTTTTTTTUUUVVVWXXXXYYYYZZZ[\\]^__``aabcdefghhiijklmnnnppqstvwwxyz{}~~tihfeefghfffggfffeeddccdceeeeeeeeeeffgghijklmnopqqqrsttuvvwwxxy{~xeSJAD?>=?@>>@???>=<<=====<===>>>==>>=???@@BCCCDEEFGGGGGGGGGGHHHHHHHHIIIIIIIIJJJJJJJJKLLLLLLLKKKKKKKJIHGFECBA><:97631.-,**(#!"#$&&'&%%&(*+.0478889:;;:97531.+%  %-5689<;<<<>>?ABDEFIIJKKLLIFC@=965564334455443.,1>IMID@>ADJLNPPQQPPMNNNNNNOOOOOPPQQVY\\\[[\beffdcbb`a__^\[Y[]\[Z[\]Z[ZYXWVTORUOMZut]PMKMNLHEFGHIKKJJIIGFDB?>;;==:84139@GLJD@<;::<=>?>><:76688;:;:8876567765544332210011011244431/-,+++****+,.012321/,)('''(()))(()(''&&&&&%%%%&&'&&%%%$###$%&&&&%%%########$$%&&'()(()++++,++,,,+++**)(((((((()***))(()***)('&&%%%&&%%%%%%%%%$$$$#!  qrrsstuuvvwwxyz{||}}~}umgb^ZWVTTTUUUUUVUUTTTTTTTTTTTTTTUUUVVVVXXXXYYYYYYZZ[\\]^^__`aabcdefgghhijklmnnnnpqstuvwxy{|}~woifebddccccddabccccb`ddddddddddeefgghijklnoopppqrsstttuuvwxz}~n_NHCD?=>?@BBBBBCDEEEEEEEEFEEEEEEFFFFFFFFFGHHHHHHHHIJJJJJJJKKKJIGFEBA?=:886210.,++)''&&$#"$%')*+(&'),.169<>?>>?@?A@>;864/*# &.7=?BBBAAABBCCEFIJJMNOPQQQOMJGC@=::9754445540+*0:DHGC=989=ADFHJLLMMKKLLKKLLMLMMMMOOOPPPQQTY^`bbb_^]\ZZZZXVTTVUVVUVVUVTRQPONQKEIc|iZPNOLKNOKFDDDGHIJIHFEEDCA?;::::73.-2:@FKJD@<;::;<==<;874456799::887767776553221100/010001134431/-,++*))))*+-/01221/,)('&'((())(()(''&&&&%%%%%&&'&&%%$#####%%%%%%$$""""""""$$$%&&'''&(****+*+****)))(('''''''()****))()***)('&&%%&''&%%%%%%%%%&&%$"!  pqqrrsttuuvvwxyzz{|||}}}~~|tohc^ZXUTUUTUUUVUTTTTTTTTTTTTTTTUUUVVVVWXXXYYYYYYYZZ[[]^^__`aaabcdeffghhijklmmmnoprstuvwxz{|}~wrkgecaaabbbabaabcdcccddddedddefffgghjkmnnooopqrrsssttvwwz|~qi\TKC<>9787653545443237:>@CDDBDBA@??@@CBBA@AABCCCCCCCDDDDDDDDDCCCCCCDEFGGGGGGGIIIIIIIIFFFDA?>=88730/.,(''&$$$###""!  $%'(+-.-)(*-159=?BDDDDEEEGFC?;96/&!"'.8@EFHGGEDDEDEGHIKMMOPQSTUUTSPLIFCA>;96444443,'-9CFC?:520037;=@BEGHJIIJJJKKKLLKKKLMMMNNNNOSX]^_^][YVVRSTUSOLRSOPOOQRQPMLKJGC=;Lk}jYNKKJJKLKJGFGDEGGHIHGECCCCB@;;:9961..4;?DIHEA<;:9::;;:75443455778877677777655322110//000000134421/-+***))))**,-./111/,('&&&'(())(()(''&%%%%%%%%&&'&&%$$##"##%%%%$$$#"!!!!!!!###$%&&&%%')))****)((((''''&&&&&&'()*****)()***)('&&&&''''&&&&&&&&&'''%#"  opqqqrssttuuvvxyyzz{{|||}}~~{voic^ZWUUTUUUTUUTTTSSTTTTTTTTTTTUUUUUVWWXXXXXYXXYYZ[[]^^^__``aabcdeefgghijklllmnoprstuvwxzz{}~yuoic_^_aa````_aaabbccddbcdeffffghijkklmnnnopqqqrstuvwz|~~th[ZQIFCA@>==@CEHKLVWYZZZZVPNLJIHFFFECA???@@@@@@@@@@@@@@@@ABBBBBBCCDCCCCCCCDDDCBA@@:964210/**('%$%%#####""!!!"!! $')+-.//.+,.27;>BDFGHHHHJJKJGD@<6,$ %-8@EKLLKHGFFGGIJKMNOPRSTVWXXXVSPNKHEB?<964330.((1963/+**+.247:AFHE?=<;988887533334435566554566655532100//.//00/00134420.,+***))))**++,-/00-+('&&&'((((((((''&%%%%%&&&&&'&&&%$#####%%$$###"! !!!!!!""#$%%%%$$&(())))*)('''&&&&&&&&&%%&'())**)))***)('&&&&'((('''''''&&'('&$"! ! nooppqqrrstttuvwxxyyz{{{{|}}~~~xqjc]ZWVUTTSSTSSSSSSSTTTTTTTTSSTTTTTUVVWWWWWXXXXXYYZ\]]^^_````abcccdffgghijkklmopqrstuvwyyz|}~{tpja_^_^__^^`b```a`abddeeeefgijjjjlmmmnopppqsttuwy|~|}{wrqooonqtwz}~ysnkjf\[XVSQPOLIE@><<<;;;:::99;;;::99:;;;;;;<:=;;;;;;;88764210*)(&$##" !"! """########"#!! #'*-.0110/.26;?ADHKKLLKKMNOPOLHD=5*##+7AGJNONLHFEHKJKLNOQRSUVWYZ[\[ZXUSPMJGD@;7420,'&+5;95220.+*)()*,.1469;>?ACEFGHGGEDDEEEGJMMLLMNLNONNMLKJHJJHFC?=@CEEGIJHEA;8:BMk|iUMMKLLLLJHGGFFGGGEDCDDDDEDCCCCCB><:6310369=>@DGE?==;977665422333313345443444333311100/..//0///0023320.,+***))))***)*+---+)(''&''''''''((''&%%%%&''&&&&&&&%$#####$$##""!  ""#$$$$$##%((((('(''&&&&&&&%%%%%#$%%&')***))))))('&&&&'(((''''''''''('&%#"!!! !!!! mnnoopppqrrsstuuvvwwxyyyz{{||}~zrib\YWUTSRQSRRRRRRSSSSSSSSSSSSSSSTUVVVVVVVWWWWWXXX[]]]^__````abbbceeffghijkkmnopqstuvwxyz{|}~{wpjc_]\]^`^^^_`abcdccccdegiiijkkllmnoooprsstvx{}~~}~}}}~~|vngeb`a`]\YXWTPKF@=;::8876554422210//000000//./.----,,)''%$"!  %%&&&(''&%$#" "&*.113554337;?CEGKNNONMMOPRSROLH@5*$)3>GKMNNMJFEFJMLNNPRSTUXYZ\]^_^\ZXVSPMJGC>940,'%&-5720100.-,+*)'()+/13567:;=?@ABBA@AACDGKONLJFEHJJHECCBB>BDCB@<;>=>??>?<;9;J_x|l[MFGJKHHHHGGFEDEEEEDDCABBBBBAABAA@>;840/269;<>@BDB<<<;96544333334320223433233211110232110/////...024431/-,,*))))))*)((()))(''''&''&&&&&'((((&%%%%&''%%%%%%%$#########"!!  !!""""""""$&&&&&%%%%%%%%%%%%$$$#"#$%%'()*)((((((('&&''''''(())((''''('&%$#""!!!! !!"!  lmmnnnooppqqqrsttuuvwxxxyyzz{|}~~~~~zskc]ZWUSRQRRRRRRRSSSSSSSSSSSSSSSTTUUUUUUVVVWWWWXXZ\]]^^____`aabbcddefghhijjlmnoprstuvwxyz{|}~woh``_^\ZZ[^^_abbbccdefghhhijjklmnnnoqrrsuwz|}~~|{zyxxzyyxw{{|}~}~~}|zywrlfca`_`_]][ZYVQMGA=;:96653210/--,*)))(%%%%%%$#"!!!  !#$'()*+,*))'%#" !&*.24679989:?BFHJMOQQQQPPRTUVUSPKA4)&.;DKNMNMKHEDFKPORSSUVWX[\]_`abba^\ZWTQNJE?93-'#%(.2302420/0..,+++++-/1223569:;;<<<=>@ADGLOMIFC@DDDC@?===?>>?=:666762239==<;9950,.49;==<=@@>:9886443322444320/22332212210000/244310////.---.12331/.-,*)))))))(''''''&&&&&&&&&%&&&'((((&%%%%&'&%%%%%%%$########""!  !!!!!!!!!!"$$$$$##$$$$$$$#####""""##$%'((((((((((('&'''((('(())(('''''&%$#"""!!!! !! ! kllmmmnnoopppqrssttuuwwwxxyyz{||||}}}~|ukd_ZWTRQRRRRRRRSSSSSSSSSSSSSSSTTUUUUUUVVVVVWWWXZ\\\]^^_^__``aabccdeffghiiklnopqrstuvwxyz{|}~plhd^[YY^_`````bcddefgghhijkllllnpqqrtvyz|}~~~~~~~~}|{ywwwvvxxwuwy{{zz{{|}~}}|{|}{{zxxvvsnhc`___]\[[ZZZVRMHB=;;:76420/.,+*('&%%$   !"!"$$%&')++-.00.,-*($" $(,168:<=>>?BFJMOPQSTSSSSSTWXYXVSLA2'(3BJNONMMKGDDGNSSVWWYZ[\^_`bcdedda_]ZWTRMG@91)#!%*.123594333331/00////1233456897668:=?AEHLNLFA>:;;<<;8788::9751/,/216>IRiyzk[LCBFJDEHIHFDDCCCCCCBBDB@>>>??<<;:9987563.+-5;===<<>=<87765332223445310011221101110///02554310///.-,,-/2331/.-,*)))))))'''''''&&&&&&&&%%&&&'((((&%%$$%&%$$$$$$$#""""""""""! ! !!!!!!!"""""!"#######"""""!!!!!"#$%&''''''''''&&'''((('(())(('''&&%$$#""""!!!!!! ! jjkklllmnoopppqrqrssstvvvwwxyyzz{{|||}}~tmg`ZVSQQQQQQQQRSSSSSSSSSSSTTTTTTTTUTUVVUVVVVWXZ[[\\]]]]__``aabccdefgghiijkmnoqrstuvwxyz{|}~~xrkf[\^`ba`_`bccdfghijklmlllnpqqrtvyyz||||||{{{zyxurqpppqrtuuvxyzzzzzzzzyyyxyxxvvwvupkfba`_^[[ZZYYYVQLGB?=;::8531.-*'%#! !!!!!  !"$%%&'),.0123455621.*&""',/5;=@BDEFHKNPSVWXXXWVTSTVXYXXVTL>-$+8ELNONNLKIHIMRVXY[\^_`babcefggfda_][XVSPHA7-%!'-14556466788986321112334678:9979:;>@ADFFINMB=><7777789;9997530-/4:FYm~~ukUMFEFEDCCDGHGDBADDEGGFEDB@><;::;;9775433232.,.5:::=>=<9997764332233444310022332212110////144320//00/.,,--/1321/--,*))))))&&&&&&&&%%%%%%%&&&&&'''('%%%$%&%%%$$$$$$$#""""""#""!   !!!!!!! !! !"#$%$%&&&&&&'&&&&&''((())*))(('&&%%$$####"""!!!!  ! ijjkklllmnnopppqppqrrstuuvvwwxxxz{{{|||}~wpha\WRRQQQQQQRSSSSSSSSSSSTTTTTTTTTTUUUUUUVVVXYYZZ[[\\\^^__``abbcdeefghhiklmnprrsuvwxyyz{|}~~}uqkfb_]__^_`bccfghhiiijlmnnoqsuxyz{{{{{zyyxwvsonllmnopqrtuvwwxxvwwxwxxvwvusstsrmhca__][YYYYXXXUQLHC@?=;;8641/,)&$"  !  ""#$()*,.02456789::852,'" %*.28>BFIJLORSVXZ\]^]ZYWUTUWY[YYWSI9)$.;FMQRRRPNMNQVY\]^__aabcdeghijjgca_][YXUQI?4) #*158;;9:<=>@AA?>:86545678:;<=><:==>@ABDFFHLJ@:963333231233468=CIZfu~~~~vfVKEFGHGDA@DFHIHDCCEFHHGECB@>=;:999:9875433220-,.4889<=;:6676654322123344310/223322122100//013431/..010/-,,-.0110.--,*))))))''''''''%%%%%%%%%&&&'''('%%$$$%%%$########"""""""""!     !#$$%&&&&&&&%%%%%&&''())*))('&&%%$#######"""!!!  ! ghhiijjklllmnnoooppqqrsttuuvvwwwwwxxyzz{||}}yqib\VSQQQQQQRSSSSSSSSSSSSSSSSSSSTTTTUTTUUUVWXXYYZZ[[\]]^___``abcddefghijlmnpqrstuvwxyz{|}~zrkhc`__`abcbdeghhhjlnnnoqstvwyzzzzyxxwvutqmljijkklmoqqqrtuvuuuuuuuttssqqrqokfa^]\ZXWWWWWWWSQLHDA@><;:8510-)%#! !!!###%(*,-/135799:;<=>>=:72,&"&+/5;AFJNOPQTWXZ\^_`a^[ZXUTUXZ[Z[XSG5(&2>HOUWXWUTTVZ_aaaaaabbdffhjkklkgca_][ZYVPE7,")27@BBCA@A@ABCCDFEFIF>8740234356=BITbls|~~~~~}}}~ym^RJHGFGFFDCB@EHHHFEDFHIJJHEB@>=<;98889::9765531/,+/4767:;974465555432111233310.223322122100//002330-,.010/-,,-./00.-+++*))))))''''''''&&&&&&&%&&&&'''('%%$$$$$$$###############""!   !"##%&&&&&&&$#$$$%%&'())*))('&&%$#########"""!!  ! ffgghhhijjkklmmnnooppqrssttuuvvvwwwxxyy{|||}~zrjc]WSRQQQQQSSSSSSTSSSSSSSSSSSSSTTTTTTTTUVVWWXYYYZZZ\]]^^__``abccdfgghiklmnpqrstuvwxyz{|}~}xpmjfeccddddedehlnnnoqsuvwxyzyyxvvusqomjigffggijlmnopqrsrrrqqppppppoooolfb^[ZYXWVVVVVVVSPMJFDCA>=;9631-(%"  !! "$&&&(*-/2368:;=>>@ABCB@=94-'""*05:@EIMPRTVX[]_abcdea\[YVUWY[\\\YQD3)+6BKRXZ[]\\]_beffeddcccefhjklmnnjea_]\[ZVQE4'#-6>ADFHIFHIKLOOMKHEB?=<=>@BDEEFEDDCDDDDDFEFE@83321115=:851.($"!!!!! ""! !%%(*,/1467:<>@@CDEFFFFEB=81+&!&/6;AFJLPSUWY\^_`bcdefd`^\ZZ[\]\]]WM?0*0AAABCEECA><=<<<;989:<<;<=<;82-+-15656763213446665431/./000/.-001100/0//..-.-.011.+*,./.-,+,,-,,,+*)))))))))((()))('&&&&&&&&&&&&&'''('%$$##"!!! !!!!!"#####$$$""!  !""##$$$####"""#$&''(((''&%%$###########"""!!!! eeeeffffhhiijkklklmmnnoppqrrssssttuuvwwxyyzz|}}}}~~~|uof_ZVUSRRSSRRRSSRQQQRSSSSSSSSSSSSSSSSSTUUVWWXXXYYZZZ[[\]^_`abbcdfeghijkmnopqrstuvxyz{{|}~~ywupmighmlkklortvvwvvvuurpnkheb`_\[\]^`bdfgijkkkjkkkjihhihiihhfc_\YWVUTTSSSSSSSPPONKJIFB?=<952.)%#"####"!!"!!!##!  #')+-0247:=?BDEEHKLLKIHFB;4/*%#,5;@EJNPSUWY\^_`acdeffda`^\\\]_]^\TG9.-7BJRY]^bfhijlmnonmmlkiihhkmnnoqplf_\[\[YVL9&!0:BIMQQPRQRTWYZ[YWVSPLGDAACGIIJKKKJIHFEDDFDC>731033?Panvwwuy|{}~}}{zzzzzz{{zzz{||}}~}}}{{{zzz{|{|}|unf\TMEEGFFEDDDCCDDEGHFD@>=>>>?@BBB@?>==<<;:9::;:;<>>=91,,/25644652213445665432/-..//.-,--....-.,,--,-,,-//-+*+,--,+++,+***)((((()))))((()))('&&&&&&&&&&&&&&&''&%$$#!  !!"""###$$""!!   !!""######"""#$%&'((''&&%%%$$$$$#####$$##""""! eeddeeeeggghiijkjkkllmnooppqqqqrssstuuvwxxxy{{||||}}}~~~~~yoha[XURRSRQQQQRRQQQRRRRRRRRSRRRRRRRRSSTUUVVWWXXXYYZZZ[]^__`abcdeefgijjlmnopqrstuwxyzz{|}|}~~~~|yvtsqponoqstvvvutsrrpmkieb_\[XWXZ[]_acdeffggffgggfedeeffedc`\YVTTSRRQQQQQQQOPONKJIGDA?=962/*'%%%&%%$##$#""##!  $'*,/1368IPW\`cgklmnoqqrqqponmlkilnoopqokd][Z\[YUF0'4AJPSWUTTVXZ\_`_^\[XVQLGCBCGJKLMMMKJIGEDDECB;53016=;;:<=>???>>==;::::::8889;=<80,/145433553322334565422/---//.-+*+,,+,,-++,,++*+,./.-+++,,++++**)))(('''((((((((()))('&&&&&&&&&&&&&&&&&'&%$#!  !!!"""""###$$#""!  !!  !""$$$$$#####$%&''&&%%%%%$$$$$$$$$$%%%$$$##"!!!!!!!!! ffeeeeddeffgghiiijjkklmnnooppppqqrrssttuvwwxyzzz{{{||||}}}~xqha\WSRRQPOOPQQPPPQQQQQQQQRRRRRRRRRRSSTTUUVVWWWXYYYYZ\]^_``abcddeghijklmnopqrstvwxxyz{{{|}~~~~~~~~~~||}}}~~xxwvvwxxwvvusrpnkigeb_[WVSRTVVXZ]__`abccbbcccbb`aabbaa`^YVTRRQQPOOOOOOOMONMLKJHFDA?;740,*((()((('&&%$#$#"! $(*-1368;=ADFIKMNOQRQQOLIC<71,'"!'.7>DHMQUWY[]_acdddefghhgeedcbaa`^]WL?3/8FPV[`dhlnnopqrstttsrqonmlnooooonib\ZZ[ZXP=&-8EOUYYXXXZ\_bcddc`_^[VQKECDGJLMOONLKIGECBCA?83334:GMQV]cfhmkmswyy{|}{zzxxuxwxxyzzzyyzz{{|{}|||{yyxwwvvuvvwvwvusoja]SNKKMMMHFCA@@??AACDDDEGFCA><:99<<==>>>><;999:::966668:94-,0245434443332223454322/---//.-+)****+++++++++*+-./.-++*++****))))((('''((((((((()))('&&&&&&&&&&''''''''&%$#!  !!!""!"""###$#""!  !!  ##$$####""#$%%&%%%%%%%$$$$$$$$$$&&%%%$$$#########"! hhggggfddeeffgghhijjkklmmnnopppppqqrrsstuvvwxyyyyyzzz{{{||}}}~~~~{rjd]XTQPPNOPPNQQPPQQPPQQQQQQQQQQQQQRRSSTUUUVVVWXXXXY[\]^__`abccefhijkllmnnoprsuvwwxyyz{{|}~~~~}}}}}}~~}|{}}||}}|{{||zyyyxxwvutrplgec`]ZVQQOOOQQSUXYYZ[\^^^^___^^\[]_a`^^ZXTRPPONMNNNNNNNMMLMMLLJIFC@=:741.,,,---,+*('%$##"! "'*-/258;>@DGHKMOQQRRTROKHA:5.*% $+1:BGKOSVY\^_aceffghijjjjihgfecba_\TH;14@OV[`dgknopqrsttuvutsrqqqpqrqpomkf_[XXXYUJ.!3>ISW[\[ZZ_adfghfdb_^\ZWQIEDFIKLMMMNLJGEDAA>:63258=BHMQU[`eiijmqvyzyxwwvuutuuvwxyyzzzzz{{{y{zzzxwwxvuutuusonmjc\USPOLIJLMMLIGFA><>BBBBC@ABDEC@=;:;;======<<:988899996555574/++034533344334423344321/-,,,//.-*)))))*****+,,+*,-//.-++*+***)))))(((((((((((((((()))('&&&&&&&&%%''''((''&%$#!  !""""!!"""#####"!  !!  ""###""" !!"####$$$$$$###$$$%%%''&%%%##$#########"!! iiihhhgdcddeefghhhiiijkkllmmnnoopppqrrstuuuvwwxxxxyyyzzz{{|||}}}~|tng`ZURQONOONPPOPPPOOPPPQPPPPPPPPQQRRSSTTUUUVWWWWXYZ[\]^__`accdfghijkklmmnopqtuvvwxxyyz{||}~~~~~~~~}|||||}~||}}}}}}}||}}{zzyyxxwvtrpnkifc`[WPLIIIKJJLPQQSSUUUYXYYXWVUVX[]]\\XTQNMMLKJLLLLLLLKLLMMLLKJFDA>;9741//0000//.+)'&%$"!  &+.0369;>ADGIKMPRSTTTUSOJF?71,(#!")/7?FKNRVZ]_abdfhhhijklllkjjjigecb_ZPA53;JX^cfjmprsstuuvwwxwvutssrqrqonmkic]ZYXXWO=!$5AKVZ^_^\\aegijjheca`^]ZUMGEEHJJLLMMKHFCB?><955579>=;:::8877788998554440+)*/3564433333443334420/--+*+...,))))))))(*+,--,,,.//.-+*)**))))((((((((((((((((((()))('&&&&&&&&%%''''((''&%$#!  !""""!!!"""####""!  !!  !!""!!!  !""!""#####"""##$%%%('&%%%##$#########""!!!  kjjihggedddeeefggghhhhijjkkllmnnnnoopqqrssstuuvvwxxyyyzzzz{|||}}|}}~~~xpiaZVROMMONPPOOOONNOOOPPPPPPPPPPPQRRSSTTUUUVVWWWXZ[[\]^^_`bcdeghijjkklmmnoorstuuvvwxxyz{||}~~~}}}}}}~}}||}}}~~}}|{{{yyyyyxxwvusrrrpnkifb\YTRPMJGEEEGJLLMNSTTSTRQPORUXYXYUQNLJJJIHKKKKKKKJJKKLLKKIGDB?=;:73212222221.+)'&%#" #).036961+'# !)3==:75689;;<@DHLQW^`dfjortrrssssttuuvvwxxxz{|||}}{wwwwtponhjjga]XRSPNJGFFIFFEGHIIHGDB><<<<>@BBA@@A??>;::::;<<:9877777888899:86552-)().357663333344334430/.-,*)+---+)***))))'+,-./..-./0.,**)))))((((((((((((((((((((()))('&&&&&&&&%&''''(('(&%$#!  !""""!!!!"""#$##"!  !!  !  !! !!""""!!!!"##$%%''%%%$############"""!!!  kjihgfffeeeeeeefffggghhiijjkklmmnnnooppqrrsstuuuvvvvvwxxyyyyyyzz{||||}}}}}~yska\WRONONOONOOONNOOOOOOOOOOOOOOPQQRSSTTTUVVVVWXYZ[\]]^_`abcdfghiiijklmmnnpqrrstuuvwxxyyz|}}~~~~~~~~~~~~~~~~~}}||}}}}}|{{zyyyyyxxxwvuuuuuuussrmmjgd`\YVMGD@?@CEJKMMMNNLILOSTSTPNLJIHHHHGGGHHHHGIJKLMLJJHECA?=<96555565432/,)('&#"  ',/258;?BEHKLMORTVWXWVVSOID=60*&" #.7BIOSVZ]acdfhiklmklmnnnmmmmlkifc_WSE42?P]djnqssruxvwxxyyyyzyxwvuuspnljigd^XVXYUM;$,9DLUZ^bcadgklllkigfaa``_[SMGEGHIIKJIGDA?==;<:65666788;>BGMRX\`cfilmnoppqqrqqpqrsuvvyyyxwwyxusqnkige`_^ZSONKLJKIGFGHCBABDEFEEB?=;;;;=@A@@>=><<;:99999::98766667888888::8761,((*/35766423334544432/-,,+)*+-.-+*+***)))(,-////.../0.,*)())(((''''''((())((((((((()))('&&&&&&&&%&(((())((&%$#!  !"""! !!!!"""$$#"!  !!   !!!  !"##$%%&%%$$#"##########""""!!  jihgffffeeeeeeeeeeffffgghhijjklllmmnnoopqqrrssttuuuuvvwxxxxxyyyzz{||||}}|}~~}vke^WRQONPPOOOONMNNNNNNNNNNNNNOPPQRRSSTTUUVWWWXYYZ[\]]^_aabceefhhiijkllmmnopppqsttuvvvwxyyyz{|}~~~~~~~~~}~~~}}}~~}}}|||{{{||}|{{{zzzzzyyxxwvvvvvvwwwxxvussssrrqf`YPJFDDEEEEFGGFCFIMONMJJIGFFEEEDDDEEEEEGHJLLLKKIGECA?=<:999:;9753/,)))'$"!$+.147:=AEILNOPQTVXYZZYVRMGA;50)$!"(4>FKPTX[^bdgiklmnprqqqqppppomlkga\UM>37JZdjorssssvxwxyyzzzyzzyxwvusqnljgda\WVXWOA-.:FMSX]cfghikkllkihfba```\UNHFFGHGJHFC@><;:898544334468:>DINUY]`bdghhjlnopponnnopqrruuuutstsnkhd_]\YVSRNJIKJKJKIGFFF@@??ABDCDA>=;;<<>>@@><;:99987777677776666678876679;9861,'(*/36777422334544431/,+++++,//.,+++***)))-/0000/../0.,*)((((('''''''(()))((((((''(())(('$$$$$$$%%'((())(('&%#"! !!""##""!!!!""#"$$#"!!  !!   !!!""#####""""#########"""!  iihgfffeeeeeeeeeddeeeefffghhiijkkkllmnnnoppqqrrrstttuuvvwwwwxxxyyz{{{{||{|}}~~vof_XUSQPPOOOOMMNNMMNNNNNNNNNOPPQQRRRSSTUVWXXXXYYZ[\\]_`abcdefgghijkllmmnooppqrsstuuuvwxxyyz{|}}}}}}}}}}}~~~~~}}}}}|||{{{zz{{{{{zzzzzzzyyxxwvvvuuwvvvvuuussssssssqpmic^XTLIEB@?><>=<=>>=:840-+*)'$"  '-137:=@DGILNOPQTVXYZZXTPKE?94/'#!%.:BINSWZ^adgikmopqrrrrqqqpppomlkga\TG93ATaimprssttvxwxyzz{{z{zzxwvtroljgeb^[XVUQD3"-:GMRW\djlkklkkkkihfdbb`_[UPIGGHGFHEDA><;:98875333233568>?BBBA>=;;::;>>??=;9877765555445666665567865568:9861+'(*.25666421124434431.,++,-,-///,+++***)))./0210.....-+*)(((('''''''''(()))))))(('''((((''$$$$$$$$&&'())((('&$#"!!####$$#"!!!"##$#%%$#""!! !  !!!!! !""""""""###"!!  iihgggfdddddddddccdddddeeffgghiijjjkllmmnnnooppqrrrssttuuuvvvwwwwyzzzz{{z{||}}~~xqia]WTPPOOOONMNNNNNNNNNNNNNOOPPQQQQRSSUUVWXXXXYZ[\\]^``abcdeffghijklllmnnoopqrrsstttuvwwxyzz{{{{{{{{{{|}}~~~~~~}}}}}|||{{{yyyzzzzyxxxyyyxxxwvvuuttvvutsrrrtsqomjihjjiihfebWSMF@;63469=@ACACBAA@@@@@@@@AAAAADEHIJJJJHFEDBA@@???@@?<:63/,+)&$" $,147;>ACFHJLNOPQTVXYYZXSOJC=82,&#"'1>FLQVZ]adhkmprtuvutssrqqqpppnlkgaZO@58L]fkorsstuvwxxyz{{||||{zxvusqliheb_\XXUPF6&-8EKRW]eklllllkkkihffdb`]ZTOJHHHFFFCB?<;:988652333223457:>AFKQVZ]_bbadfgihhhggghhhhhiihhfdb`VTQONNNLMJJJJIIHGHGFDDC@@?>>?@B@?<<<;;;;<>===;866654444423345554556776555798851+'(*-0234531//02213541.,+,,---...,+****)))*./121/.--,+*)(('''''''''&&&''(()**))))('&''((''&$$$$$$##%&(())((('&$#" "###$$##"!""##$%%%$##""!  !""""""""#"!! hhhgggfdccccccbbbbcccccddeeffghhhiijjkkkllmmnnoopqqqqrrssttuuuuvvwxyyyyzyz{{{|}}}}}}}}~|tle^XQQOOOONNNNNNNNNNNNNNNOOOPPPPPQRRTUVVWWWXYYZ[\]^__`abcdeefghjkkkklmmnnopppqqrrstuuvvwwxyyyyyyyyyy{{||}}}~~~~~}}}||{{{zzzxxxyyxxwwwwwwwwvwvuuttsrqqpooooorqpomlkidbbbaa```]XPF>700//269=<>>>>====????@@@@>?BEGIIIIHGFEDDCCCCCCCB?<951.+)&$!")168;>ADFHJKLNOQRTVXYYYWQMGA;60*%$%+6CIOTY]`dgknpsuwxyxvutsrqqpppnljf_VH83@Vejnqqrrstvwwyyz{||}}|{zxvtrpkgec`]YUVRI9)+5BIQV\flmmmmlkkjigfgeb_\XSOLIHGEEC@?<:988874321121245669;>CHNSWZ]````cdeeefdddccccccca_\ZXVLJIIJJJIKJIIIHHGDEEEDB@=?><=?@@?;;<<<;;;;<=;<9755433344324455555667777666787750*&'),.023420../1003541.,+,,------+**)))(((+./010/.,++*)('''(((((((''&''''()**))**('&&'(('&&$$$$$$##%&'())'(''&%#"!!####$$##"!"##$$&%$$#"""!!!  !!!!!!!!!!! eeeeeedca```````abbcccccddeeffghhiijjjjkklmmmnnoooooppqqrrssstttvwxxxxyyyzz{||}}||||||}~~~woha[XVTRPPNPPPOOONNNNNNNNNOPPPPPPQRRSTUUVVWWXYZ[[\]_`abcdeeffgijklllllmmnnoooppqqrsuuvvwwxxxxxxxxxxxyyzz{{{|||}}}~~~~}}}}}|||{{zyxxxxxxxwvvvvvuuututtssrqqponnmmmlmmllkkkiifc`^_``][[YTLE?2,*+-049:;9:9::;;<<<>>>>;;>BDFGFHHHGFEFGGGGGGGDA=:840+'%" !)28:>====<===<;;:;:::::976543334554555566788888877887653.)'()+-/1221/--.../2541.++,,-,,,++++*))(((')+-....,**))(('()((((''(('''''''))(()*)('&'((('&%$$$$$$##$%&&&&)))('&$###########""##$$&&%$$#"###"""!!! ! ccbbbbbba`______`aabbbcccccdeefgghhhiiijjkllllmnnnnnnoppqqrrrsssuvvvwwwxyyzz{{{||||||||}~~~~zrka\YWSQPPPPPPOOONNNNNNNNOPPPPPPPQQRSTUUVVWWXYZZ[\^_`abcddeefhijkkkkklmmmmmnnooppqsstttuuvuuuuuuuvvwwxxyyyzzzz{|}~~~~~}}}}}|||{{zyxxxxwwwwvvvuttssssrrqqpponmlkkkjiiiiiiiihgffeb_\[XYXWUPMKF:1)%'+034444545567888::77:>ACDDFFGGFFHJJJKJJJGC?=;82-)%")/7;=@BCDFIJKLMNQRSUWWXXVPLGA<61,&"$)3AJQWZ^bfjlpsuwyzzzzxvutsqppqrnlgbYN:1BZgknmpqqqruwxxyyz{||}}|{yvsqomhec`]XTLG;+"1>FMUZbilmlkjiihfedddb^ZUPMLIFCA?<98754555400000013566678;BGJNQRUZ\ZZ[\[\[[YWWWXZXVQQQRRQONKIGFFFFFGHIJIGDA@?>==>>>?>>>==<<====<<;;9::::987543224555665567899:99887885442-)'(*+-/0110.,,--,.2430-++,,-,,,++++*)))((((*,,--,+**))))())((((''((((''''&((((())('&''(''&%$$$$$$##$%%%%&'((('%$#$#$$$$$$$$""##$%%&%$$#"####""""! ____^]]\\]]]]]]]^__``aaabbbbcccdeffffghiiijjjkklmmmmnnopppqrrrssstttuuuvwwxxyyyz{{{{{{{|||}}}~~~~vnha\WTRPPPPQPPPOONNNNNNNOOOOOOOOQQRSTTUUVWXYYZ[\\]_`abcdefgghiijjkkllmmmmmnnooppqqqqrrsssssssstuuuvvwxxxxyyyz{}}}~~~~~~~~~~}}}}}|||{{zyxyxxwwwvwvutssrqqrqqppoommlkjiihhhhhhhggffhjjifb^ZXVTSSRRQKC;40-,.-..////01333456456:=@BBEEFFGGJLMMNMMLJGC@>;5/*%# "05:<>ABBAFIIIJMPQRTVWWWVSOIC>93/)&#%.:HOUY\aehlnruwxyzzz{ywutsqpqrqoje^TG4>Tcgjmnoqqqrttvxxyz{{||||{xuronkgdb_]WOE9.!-=;:;<==>>>>==<<<=>>>=<;9:::;;:8643224666766678899:99887874221,('(+,-/011/-++,,+-0320-++---,,,++++**)))(()**++++***))))**)(((('')((('''&&((''())('&'''''&%$$$$$$##$%%$%%'((('&%$$#$$$$$$$$###$$%%&%$$######"""""!   ^^^]]\\[\]]]]]]]]]]^_``abbbbbbbcddeeefghhhiiijjjjkllllmmmnoppppqrrssstttuvwwwxxxyyyyzzzz{{{{||||}~~~ysme^YVSRRQQQPOOONNNNNNNOOOONNNNPPQRSSTTVWWXYZ[[\]^`abccddefghhiijkkllmllmmnnoopoooopqrrrrrrrrstttuuvwwwxxxyyz{|}}}}}}}~~~~}}}}}|||{{{zyyyxwwwvvutsrqqpppponnnllkkjihggggfeedccbabdeddb`\YVSRQRSONKGA;5/.**))(''+,,,-./011248=:9:;==>??>==<;<=>>>=<;999:;;:8642235777777788899:9987675210/+('),-.0111/,**++*,/10/-,-.--,,,+++++****)))))****)(())))***))))(()((((''&&''''()))'&&'''&&%$$$$$$##$$$$$%&''''&$#$"$$$$$$$$$$$$$$%&%%$$$##"""""""!  !! \\\\[ZZYZ[[[[[[[\\]]^_``abbbbbbbccdddefggghhhiiiijkklllmmnooooppqqrrrssstuvvvwwwxxyyyzzzzz{{{||||}}}~~~}vogb\YUTSRRPPPPPPPPPOOPPPPOOOOOPPQQRSTVWWXYZZ[[]^`aabbccdefghiijjjkkkkkllmmnnnnnooopqqrrrrrrrsstuuvwwwxxxxyyzz{|||||||}~~~~~~~~}}}}}}||{{zzyyyyyxwwvvtssrqppooonnmmmjiiiihffeefeeedddca_][ZZ[ZXVUSRQQOKJHEB?;92.,*(%#!$$$%%&)*,,,.27;?CCCEGILOPQRRQQNKHEA<60+'")2;;=?@??>BCEFHKMPSUVVVVUSOJD?:6/+'$#)6DNTZ\`eiknpsuvwxyzzzxvusrqpppomgbXJ8EMTZafhhhhhgfeba`^\XTOKIFC>95532/0//-.0201222222566678:=DDGJLNNOMNOOOMJHHFA>@CGJFGHGGGHGBAAABCBBA@??>>?@><;::;<=>??>>=<;<=>>>=<;8778::975322357888889998989987655410/.*''*-./0111/+))***+-/.-,,.0.-,,,+++,,,+++****)))))('''(())**))))(()(((('''''''''(((&%%%&%%%$$$$$$$###$$$$%&'''&%$$$"#$$$$$$%$$$$$$%%%%$$$#"""""""""!  !!  [[ZZYYXXXYZZZZZZ[[[\]^_``aaaaaaabbcccdefffggghhhhijjkkkllmnnnnoooppqqqqrsttuuuuvwwwxxxxyyyyzzzz{{|||}}}~ysmfaZXVTSRPOQQQQQQQQQPPPPOOOOOOPPQQSVVWWXYZZ[]^_`aabbcdeffghhiiijjjjjkklllmmmnnoopqqqrrrrrrrsttuvvwwwxxxxyyzzzzzzzz{|}}}}}}}||||||||{zyyxxxxxxwwvvutssrqpoonnmmllkihhhgfedccddcccbba`^\XUSRPQONNOOPPLJGCA?>=;741.+'%! "#'&&'*/5>BCCDGIKNPQRRQQNKHFA;5/,'!&08:;;=>?@@BCEGILORVWXWVTSPLGA<84-)&#$-;HPU\^bfiknprtuvwxyzywutrrqpqomlf_Q?7I\fhknopqsstuvuuvwyzzzzyyyxuromjie_ZSJ<. !08BKRW^befgggfeca_]ZWSPLHEA>963421////.-/100122322456778:;@ACEHIJJJJJHGDA?@<;AKPNHDDCCBBBABBBBBBA?=;<<<=>?><<<<<<=????>=<;:;====;97556887533223578898899988676544332/..,)&&*,./0111.+*)****-..-,,.1/-,,,++,,--,,,++++*)))(('&&'''(*+))((''(((((''''''''''''%$$$$$$$$$$$$$$#"##%%%%&'''&&%$$"###$$$%&%%%%%%%%$$$###"""""""""!  !!  ZZYYXXWWWXXXXXXXYYZ[\]^_````````aabbbcdeeeffffggghiiijjjjklllmmmnooooppqrssssttuuvvvvwwwwxxxxyyyzz{{{{|}~~~~xskc_[XVSQQQQQRRSSSQPPOOONNOOPPPQQRUVVWXYYZ[\^_`aabbcdeefggghhhiiijjjkkkkllmmnnopqqqqrrrrrrssttuvvwwwxxxxxyyzzzzzzz{|||||||{{{{{{{{{zyxwwwwwwvuuttssrqppoommllkkjhgggfedcaacbbbaaa``_\YTQMIHEEEFHKMLKJHECA@A><:640,%"!! """! !#&.8?BCCEFILNNOOONLIGD@93/+'! %/9=:;;>@CDEEEHILOQUWXXXVTRNID?:51+(%$(3@KSX\_cgjlmorstuvwxxxwusrrqpsqmicYH9>Sbhjlnqrruuvwxwwxxyzzzzyxwvtqnkigb]VNC4(*2@CFGGGFCA><=?AIP\oyr]ICABBBA@?CCCBB@?>=;:;;=>>=<<==<;<====<:998:<==<;86445666432123568898877766432111120-,,+&$&*,-/1111/,+*++*))*++,,--,,,,,,,+,---,+****)((('(''''''(*+*)'&%&(((('''&&'&&&&&&&%$$$$$$$$%%%%%%####$%%%&&&&&%$$#"###$$%%&%%%%%%%%$$$###"""""""""! ! ZZYYXXWWWWWWWWWWXXYZ[\]^_`````````aaabcdddeeeeffffghhhhiiijjkkklmmmnnnoopqqrrrsstttuuuvvvvvwwwxxyyyyzzz{|}}~~~~xnhc^[XVSRRRRRSTTQPPPPOOOPPPPPPQRTUUVWXXYZ\]^_`aabcddeefggghhhiiijjjkkkklmmnnnoppqqqrrrrrssttuvvvwwwxxxxxxyyyyyyyzzzzzzzzzzzzzzzzzyxwvvvvvuutssssrrqponnmmllkkkhhhhgfed``aa```__`a`][WROKEA@?@ACFIJIGFEEECB@@=;840-+*('('! ")2;@BBCCFIJKKKKJHFDA=72.*'"#+6>?<=>CFIJKKJMNPSVYZYYYWSPKE@<72.*(%&,8FOW[_aeikmnoqrstuvwwxwusrqpprqme_QB:J^gjlnorrruuvxyxxyyzzzzzywutrpmjge_ZRI>1)$$-7BIOUZ_`bbb`_][WTQNKID>;541110/.0000/0000012222255676689=>??@@=9=>?EO]kt}oP?A@@?>>??CCCB@?>==<;:;<==<;;;;:99::::987789;;;:97544555543111346788876654420/////0/+++*%#%)+,.0000.+***+*)*+++++,+,++++++++,,,,+++**)((('((''''()**)('&%&'''''&&&&&&&&&&&&%$$$$$$$%%%%%%%$$###$%%&&''&%%$#"""#$$%&'%%%%%%%%$$$###"""""""""! !!! ZZZZYXXWXWVVVVVVVWXYZ\]]^_________```abcccddddeeeefffggghhiiiijjklllmmmnopppqqqrssstttuuuuuvvvwwwwxxxyyz{||}}}}~~~{voga[XVRSSRQRTTRQQPQQQPPPPPPPPQRRSTUVVXZ[\^_`aabbcdeeeffggghhhiijjjkkkklmmnnopppqqqrqqqrsstuvvvvwwwwwwxxxxxxxxxwwwwwwwwyyyyyyyyxxwvuuuuttssrrqrrqponnmmnmmllljjiihgfeaa__^^^]]]`_\YUQOKFA=:9;>@ACCDEEEFDDBAA@AA=;841.,)$#"""#%,6=@AAABEHHHHHGEC@=84/,)'$'19>=>CHNRTTTURTTVY[]][\[WSOHA=93/-*)%)1>LT[^cehkmnnopqqrsuvvwvutrrqqpojbXJBH]fjjmnotttttvxyy{{z{{{zyywusqolifb\UND:2000,&(1<;<>@BCBA@?>=<;<;;;==<<<=<:97887777778888776555566654210123568987653231/.---.-,+**)%#$(*,./000-+***+*)**++++,,,++++++)+,--++***))((('((('''())))(&%%&&''&&&&%$%&&&&''&%$$$$$$$%%%%%%%$$###$$$%%%%%$$##"!"#$$&''%%%%%%%%$$$###""""""""!  !"""! \\\[[ZYYYWVVVVVVUVWXZ[]]^^^^^^^^___```bbcccdddddddeeefffggghhhhijkkkklllmnnooooprrrssstttttuuuvvwwwxxxxyz|||}}}}}~~~|umf`[ZXUSSTUTTTSSSRRRQPPPPPPPQQRSTUUWYZ\]^_`aabcddeefffgggghiiijjjkkklmmnnoppppqqqqqqrssttuvvvvwwwvvwwxxxxxxwvvvvvvvvwwwwwxxxwwvutttsssrrqqpqqpponmmlmllkkjjjiihgfeccbaaa```_^]ZWTPNKIC?<9888:>?ABCCBBABABBBBAA?<952.*)''&&'+39=?@@ABCCCCCB@?=:62/,*()-7=@@GLRX\^]]^[\[\^_a`]][WRMG?;60-,+*',6DPX^afilnnoopqpqqstuuvvutrrqpnlg]RGIUhhjkkmoqrsrruwyy{|}}|{zyxvsqpnjgc^YOE=9678951*# ,6?FMPTU[\[YXUQKGB=:85219;>><830/347861,2233223676666655139@JZlz{U??@==<;;<=?ABA@?>=<;;<<;:;<;<<=;875654567789865543345555544210013457876653121.-+++,++***(%#$(*,./00/-*))*+*))*++++,,+******)**+,++*+*))((('((((''((((('&$$%%&&&&%%%#%&&&&''&%$$$$$$$%%%%%%%$$$##$$$%%%%%$$##! "##%&'(%%%%%%%%$$$###""""""""! !!"""" \\\[[ZYYYWVVUUTTTTUWY[\]^^^^^^^^^^___`abbbbbbbccdddeeeffffgggghhhijjjkkkllmmmnnopqqqrrrrrsssttttuvvwwwwxy{{{|||||}}}~~~ume`[XUSRRSUSRQRRRSQQPPPOOOPPQRSTTVYZ[]^_``abcddeeeefffgghiijjjkkkkllmmnoopqqqrrrrqqrrsstuuuuvvvuvvvwwwwwwwvvvvvvvvvvvvvwwwvvutssrrrrqqppoqqponmmlkkjiihhhhjjihgfffeedddcca_^ZWRNKHIGEB>;8769;;<<;;=>?@AAA@BAB@=962/--,+**,16:>ABA@@AA@@@=;:741.,**-4>DGISV\aefeega`_^_``_^^\XRLE=83.,,+**1@A@@?>=<<;;==;:;;:;;<:764544457899743211223443322210012346655442121-,****)+***(%#$),-./0/.,))))**))*++,+,,+*******+*+,++*+*))((('())('''((('&%##$$%&%%%%$#$%%%%&&%$$$$$$$$%&&&&&&&&%$$%%%%%%%%$$##!!"##&&''%%%%%%$$$$$$$#"""""""""! !"""" [[[[ZZYYXWVVUUTTRRTVXZ\]^^^^^^^^^^^__`aaaaaaabbbbbccccccccdeeeefghiiijjjkklllmmnoooopppqqqqrrrsstuuvvwwwxzz{{|||{{|||}}~~zkb^XSRRTQQRRRRQPQQPPOOOOOPPQRSSUWXYZ\]^_`abbcddeeeefffghijjjkkklllmmmnooppqqqqrqpqqqrssssstttttuvvvvvvvvuuuuuuuuuuuuuvvvvutssrrrrqqpooopooonmlkihgffeefefggggghhhgggfffdb_\YSOMKLIIHDA=:4344567989;<>>==?@@@>;9510//.--./26:>@@?=>>>>=:87520-,+-08BGLQZ\afijkklgecbaaa`__]XRKC;60+++*).6ANY_dhkmoqqqqqqqrstuvwwvutsqppli`SPWbggikkikmlnooqsvwx}~~|zywtqomjfb\THA;7:<>=>>=<83/)%-5=CIKOPPPNKE?5/++-2:@LNQVZ_dg[J<:;97611121111.15<7568:=?A@??>=<<<::<<;:;;9::;:76454456778753210011343221221011234554433212/,*)))))*+**)%#%*,-./.-,+)(()**)()*+,,--,+++++++++++****)))((('())(''''''&%$""#$%%%%$###$%%%%&&%$$$$$$$$%&&&&&&&&%$$%%%%%%%%$$##""#$$%&'&&&&%%$#$$$$$$$#""""""""!!  """"  [[[ZZYXXXWVUUUUTSSUVXZ\\]^^^^^^]]^^^__`aaaaaaaaaabccccccccdddeeefghhhiiijjkkkllmnnooopppqqqqrrrrsttuvvvwxyyz{{{{zz{{{|||}~~xnd]YWUSRQQQQQPQQPPOOOPPOOPPQRTUVWXZ[]^__`abcdddeeefffgijjjkklllllmmmmnooopppqpppppqrrrrrrrrssstuuuuuutssssssssttttttuuutssrrrqqqppoonnnnnmlkjhfedcbbbbccdefghhiiiiiiifa`]ZVRPLMKLLKIFD>973011434689999<<=<<::73321100//147:=>><<>>=<:87531/..18@JPV]`cgknoqqqljgdbaaa`_\WPI@82-**+,+2>?@ABA>;6*"$*37?BGHIGE?83+'(,06?EMQU[^chjm_K<:;:73100126;AKVeucF>@=95569;=?@?>>=<<;;99;;;;;;9::;:76565555555432100001232211121112334564432100.+)(()))*+++*&$&*,-..-+*)((()***))**,,--,++++++,,+********)((((())('''''&&%#""##$%%$#""!#$$%%&'%%&%%%%%%%&&&&&&&&%$$%%%%%%%%$$##""#$$%&&%&&&%$$####$$$$#""""""""!!! !"""   ZZZYYXXWWVUUUUUUTTUWXZ[\\]]]]]]]]]]^^_````````aaaabbbbbbbbcddddeeffgggghhiijjjjlmmmnnnnoppppqqqqrsstuuuvwxxyzzzzzzzz{{{||}}~|nc\WSQPOPQRSPPPPOOOPPONNOPQSUVWXYZ\]^_`abcddddeeefffhiijkllmlllllllmnnnoopppopppqqrrrrrrrrrsssssssssqqqqqqqqrrrrrstttssrrrrqqqppoonnnnnmljjgfedcbba```abccddgggggggedc`]ZUSONMNOONMLJFA:510/11223344788988864433231001358:;<;<>@?><:97642114;DMSZ_dgjnqqrrsomjfdbaa`^[UNF=4/+))+-/9DQ[`cgjmopqrssstuvwxyzzzxvvuspnjaUPXdjgkknppqrsqrqpptvwz|~~}{ywurplhc]XPD97679=?@CEHIHHFA80%#(,26=>=:70)&%$'-39AFKPT[^befkkZB:<;6/./6BMYdowxP;=@=:778:<>>?>==<<<;;989:;<<;89:;:876765554322111100002211001122233345544320//,*)))*)(*+++*&$&*++,,+**))(()*+*))**,,-.,,,,,,,-,,*)**+++*)(((()))(''''''&%$"!""$%%$"!! "#$$%&'&'''&&&%%%%%%%%%&&%$$%%%%%%%%$$#$##$$$%%%%&&%%$$#"###$$$$#"""""""!!!! !"""   YYYXXWWVVUUUUUUUTUVWYZ[[\\\\\\\\\]]]]^_```````````aaaaaaabccccdddeeeefffghhhhiijkllllmmnooooppppqrrssttuvwwxxyyyyyyzzzz{{||}}~~si^VRNMOQRPOOPOOPPPONNOPQSTUVWXY[\]^_`abcdddeeeeffghijkllmlllllllmmnnnoopooopppqqqqqqqqqqrrssssssrqqqqqqqqrrrrrssssssrrrrqqqppoonnnnnmljifdcbaa`_^]]]^__``bbbbbbbba`^\YUSQROOPQRRSSRNIB<741//.../035666665443232210123568::;>@A@>=<:976558?ISY`eilorttuuvrplheca`_]YSLD:0*(()+.2ALW^ceijmopqrssstvxyz{||{xvvusold[OS`hmmtu{}~~|}xvrquvwx{||{ywuspmic]VQH=4567:<@CIKNPPONID>3( #%)/..+)&"!%(,15;DIMOTY\`dgdldL;9;=:CN[ivd=5>B?;9:;<>>>====<;;:98789:;<;88899876765543211111111100///00013334333432100/./,*)***)((*++*'$%)**))(((*)'&()**()))**++)*))+,,-,+))**,,,+))((()))('''''''&%#"!!$%%$"!! "#$$%&'&&'''&%%%%%%%%%%&&%%%%%%%%%%%%$$%$$$$$$$$%&&%$$##""###$$$#""""""#####"""! !!""   XXXXWVVUUTTUUUUVUVVXYZ[[[\\\\\\\\\\]]^________````aaaaaaaabbbbcccccdddeefffggghijjjkkkllmnnnnooopqqrrsstuvvwwxxxxxxyyyyzz{{||}}~~~~xmbWPNPQPNNOOOQQONNNNPQSTTUVWXYZ[]^_`acddddeeeefghijkkllkkkkkkklmmmnnoooooopppqqqqqqqqqqqrrrrrrqppppppppqqqqqrrrrrrrrrrqqqppoonnnnmmkjieccbaaa^][[ZZ[[[[^^^^^^^]]\ZWURPMPNNNOQSUSVVSPJDA8530/./12334444321210111011222478:>BCCBA?><;:99JepzX:7<<==<<>?><;<<=<;:987778889:;8668877555555432111111110/./00001344433232//...-/-+++++)()++++(%%)**))(((*)'&())))))))))))*))+,,.,,*)**,,,+)(('())(('&&&''''&$"!!$%%$"!! "#$$%&'&&'''&%%%%%%%%%%%%%%%%%%$%%%%%%%%$$$$$$$$%&%%$$#""""###$##""""""##$###""!!!!""!   YXWVVUTTSSSSTTUUVWXXYZZZZZZZZZZZ[[\\\]]^__________````````aaabbbbbbcccddefffggghhiijjjkkklmmmmnnppqqrrsttuuvvwwwwwwxxxxyzz{{||}}}}~~~~{i]WROLLNPPPQQPONNOPQSSSSTUVWXZ[\^_abcdddeefghiijjjkkjjjjjjjjklllmmnoonnooooppppppppppqrqqqqpopppppppoqqqqqqqrrrrrrrrrqqppoonmmmmlkjgddddcccca\[ZZYZZZ[\\\[YXWXWVTRPNLLLMNPQSTUUTSQNKGIE@=:7522022120/0/00101200000//125;ADEDCB@?=<<;AHS\ahlnqtwzz{{zvrmgdcb`]\WPH>3)$""%'1=RY_cefhjkmmpqrrrswy{}}}|{wssrlfbTFPdns{xutvuxxxwvtqpnke_WNF>745678;?CJRW[]^_`_\[ZTG6&"%*-37CKTZ_ceghhjmmmk`Q<&"%*048:@EIJOPOOPRRSU\enu{}|||}lG88<<<=;998889:;:99887767766666433553200/0256543100000000011110022222222200//001000//...-,++*'%%(*))))))*)'&&())'&&&&&&&'''(*,--,**)))**)('&&&'''''&&&'))(('%#"!$%%$"!!!##$$%%&&&'''&%%$$$$$$$$$$#######%%%%%%%$#$$$$%%$%%%$$##!""""#####""""!!"#####""" !!   YXUSRQPPSSSSTTTTVWXYZZZ[ZZZYYZZZZ[[[\\]]^______________`_``````aabbbbbbcdeeefffgghiiijjjjjklllmmnoooppqrrsssttuvvvwwwxxxyyzz{{||||||}}}}~~~~~}gVRNMNMNOONMNOQQRRRRRSTUVXY[\^_`bcccddeghhhhhiijjjjjjjjjkkklllmnnnnnnooopppppppooppoooonnnnnnnnnnnnnnnnopqqqrrrrrqppoonmmllkjhfedeeeddddca_^[ZYYXWXXXWVVUVUTSQNMJJIIJLMNPPQQQQQPOPMMMLKJIEA=;963/--,,,,++,-....,+*/6>CFFGGEDBBBDIQ[chmqsux{~|ytmhca`_\YRI?3)" #.@P\_bdfhknpqsssrrqstx{||{{ytponi^SAMdu}}yxwvywttsrokhbZMB930/05568;?DKSY^bfhjpssuy}|qWC.#'*.38=BFHHKKLLJIMV`lv|~}|{{{|}}`>68;;<=<;;;;;;;;7776666655443331224421///13787641000000000111100111222223221122221100000/-,+*(&%'))))))))('&&'(('&&&&&&&'''(*,,,+*)(((()('&&&&&&&'&&&&')))('%#"!$%%$"!!!##$$%%&&&'''&%%$$$$$$$$$#"""""""$%%%%%%$#$$$$%%$%%%$$#"!!""""####""""!!"#$###""" !!  ! XWTRQPOORRSSTTTTVWXYYZZ[ZYYXXYYZZZZ[\]]^^_______^^^^^^^_____````aaaaaaabdddeeefffghhhiiiijjkkllmmnnnoopqqrrrsstvvvvwwwwxyyzz{{{{{{{|||}}}}}}}}~~vgYQONLMOMLLNPPQRRRRSSTUWXZ\^_`bbbccddfggghhhhiiiiiiiiijjjkkklmmmnnnooooooooooooppoooonmnnnnnnnmnnnnnnnoqqqqrrrrqppoonmmllkihfedfffffeedcb_^[ZZXVVVUUTTTUTSRPMLIIHGHIJKMNNNNNNOOMLLKKKJIKHDB?<84420/.,+*+,,---,*(,3;BEFHIGFDDFHMT]einswx{}~zumfb`_\[UMD:.%$4JX_aegjmruwuxwvusrsrw{|{z{wspomh\N;Qlz{ywvxutrpnkfaZOD:20//05568;?CKSY_cfkosvyyyy{|{|n[D0  $')-26;?CGHHEDFKUbotyzzyxxx{|}T869;<<<====>==;;55445555333322113322101013589864100000000121110/0011111221111223221100120.,+*(&%&)))))**)''%%&((&&&&&&&&'('')+++*)(''''''&&%%%&&&&&&&&')))('%##!$%%$"!!!##$$%%&&&'''&%%$$$$$$$$#"!!!!!!!$%%%%%%$#$$$$%%$%%$$##"!!""""""##""""!!"###""!!! !  ! VUSQPONNQQRSSTTUVWWXYYYYYXWWWWXXYYZZ[\]]^^^^^^^^]]]]]]]^^^^__```aaaaaaabcddddeeeeggghhhiiijjkkllmmmnnnoqqqqrrrsuuuuvvvwxxxyzz{{zzz{{{||||||||}~~~yfZSNLKLLNOOOQRRRRRSTTVWXZ[\^``abccdeeffghhhhhhhhhhhhhiiijjjkllllmmnnonnnnnnnnoonnnnmmmmmmmmmmmmmmmmmoppqqrrrrqqpoonmmllkjihgfhhiihhgfdcb_^\[YUUTRRRSSTTSQPNLJHFFEFGIJKKKKKKKKKJIHHGGFHHFEDB@?;9741.,++++++++)')/7>CFIJIHFGJLQXaimrvz{}~~ztkfc`^[YRI@5)"'=R^cfknsw{~~~}xttrsvz{{{{uronj`P=A\x~{xvvusqljgb\RE<50.../5568;?CIPV\bfjpvy|{xvwvwz{|xdF,!$'(-058:=@BBELVbnw|yxwxxwxy{|~vJ6689;<<==<<<<;98223344442333332244210023345886420///////012111//000001121////113332111231.-,+)&%%()))***(''%$%'(&&&&&&&&'((()*+*)('&&&&&&&&%%%&&&%%%%%&()(('%$$"$%%$"!!!##$$%%&&&'''&%%$$#######"!!! "##$$$$$#$$$$%%$%%$$#""""""""""##""""!!"##"""!!! !  ! TSQPONMMPPQRSTTUVWWWXXXWWVVUUVVWXXYYZ[\\]^^^^^^^\\\\\\\]\]]^___````````accccdddeefgggghhhhiijkklllmmmmopppqqqqrttttuuuvwwxxyyzzzyzzzz{{{{{{{{|}}~wfXQNMMMMMPQQQQQQRSTUVXY[\]__`abbcdeffgghhhggggggggghiiijjjjkkllmnnnnnnnnnnnoonnnnmlmmmlllllmmmmmmmnoopqrrsrqqpoonmmmmlkjiiijjkkjihgfdca_^][USRPNOPQSSSQPNMJGEEDEFGHHHHHHHHIHIIHIHIJHHGFDCA@?=;741-,*)((((('&(,2:AEIKJIGHKNU]flqvy||}}~~yslgdb_ZXND;0'! -CWbimsw{}wutuwyz{{{uqpkgYE6Ie|}ywusqnifb[SF:51/...05568;?CHMSY]chmsz|zxwwwvruy~{mP/ #&(-/1357;ALXfqxzzyxxyxxxyz|}lE6689;;<=;98877551122333333444445430//03444566420////////011000.///011110/,--.0122211000/.,+*)'&%%(())***(&&%#%'(&&&%%%%%&('(())*)'&&%%%%%%%%%%&&&%$$%%&(((('%$$#$$$$#""####$$%%&&'''&%%$$######"!!!  !"###""##$$%%$%$$####!!!!!!!!""!!!! !""""!!!!!"!!!  ! RQONMMLLNOPQRTUUVWWWVVVVUTTSSTTUWWWXYZ[[]^^^^^^^ZZZZZZZ[[\\]^___```````abbbbcccddefffggggghiijjkkkllllnoooppppqssssttttvvwwxxyyyxyyyyz{{{{{{{|||}~~~ve]UPNNNONOPPQRRRSUVWXZ[\^^^_`abceeffgghgggggggfffgghhhhhhijklmmmmmmmmmmmmnnmmmmllmmlllkkklllllllmnnopqrsrqqpooonmnmlllkllllllljihedcba`_]VSPNMLNNQQQQONMJGEDDEFFGGGGGGGGGFFEEEEFGFFGFEDCC@?<952/-*(''&&&%%'(-4=DHKKJIJMQX`inswz{||}~|xsnjgd_YUK?5+%" !#2H\fquz~{xvwwzzzyyuqngaO84Vo~yvsqlhe`[RG<1/../0126679<@DIKQUZ`ejnsvyzxwuroqqotvhD!$%'(*.7BN[grwxxxyz{{{yyz{||~dB9999::::97432100012233434455676731.-./243444421///....--.//...----/1210.,+,,-/0110//.-,+*)(('&%%%''(())))''&%&''&&%%%$$$%'''((()('&&%%$$$$$%%%&&&%$%%&'((((&%$$##$$$$$$$$######%&'&'&%%$$$###"""""""!!!! !"""! !"#$%%$#######!!!!!!!!""!!!! !!!!!!!!!!"!!!  ! POMLKKKKMNOPRSUUVWVVUUUTSRRQQRRSVVVWXYZZ[\\\\\\\YYYYYYYZZ[[\]^_________`aaabbbbccdeeefffffghhiijjkkkklmnnoooopprrrrssssuuvvwxxxxxxxxyzzzzzzzz{{{}}}}~~~~wgZTQOLKMOQRSSSRTUVWXYZ\\]]^_`bdddeefffeeeeeeedeffffgggfghiklmlllllmmmmmnnmmmmllmlllkkjjkkkkkkkllmnopqqqppoonnnmnmmmnnoonnnmmkjhedccbaa_VTQNLKLMOOOONMLIGEDDDEEEEEEEEEEFFFEDCCCDCCDCDCCBA@=:731.+)'&%%%$$%%'.7@FJKLKLOS[ckosvyzz{{|}~~{xrqnke_VQE90($###"&9Ocmx|zxyxzzywwtpjd[C0;`w}wtoleb^YRG<2--./12346679<@DHIMQU[`fkkpvxwvttpqppqtoT!$ (3BRanuuwvxxxyxxxyyyzz{~|^?;:8888787530.-,,00234433445688661/-,-.233333210///..--,,-.-,,,,,,-.010/.+*,,-.00/.--,+***((('&%$%%&&''(()'''''''&&%%%$$$%'''((()('&&%%$$$$$%%%&&&%$%%&'((('&%$$#########$######%&&&&&%%%$$$##"""#""""!!! !""!  !"#%%#"""""""!!!!!!!!""!!!! !!!!!!!!!!"!!!  ! !! MMLKJJKKLMNPRSUUVWVUUTTSQPPOOPPQTUUVWXYYYYYYYYYYXXXXXXXYYZ[\]^^^________```aaaabbccdddeeeffgghiijjjkkklmnnnoooppqqqrrrruttuuvvwvwwxxxyyyyyyyyzz{|||}}}}~~~nb[VOKPRRTSRQPRSTUVWXYZ[[\]^abbccddedddddddccceeeeefedeghijkkjkkklllmmnnnnnnnnlllkjjihjjjjjjjjjjkllmnnmmmmmnnmonnoopppnnnnmkjhgffecbb`]ZVQMJHGLNONNKIHEDDCCDDDCBBBBBBCCDEEEEEEEDDDCBBBBA>;8531/-+((''&&&#!%0;BHLMNORX`dknqtvwwyxyyz{ywtrpmf_SL?3*%#"""")?Ylv}~{yyxzyvtsqlgaR6,Ei}|vqjea\WQH<3,+-./12246668;?DHIMPTY^dilotwvusqqqrqopn\*#0AN]jsxyxutuvvvwwyzz{{|}vQ979863356642/-,,,0012221233467752/.-,-.13333321000/.-,,,+--,++++,,-.///.-+*+,-.//.,,+**)))('''&$$$%&&''(()(((('''&&%%%$$$%&'''(())'&&%$###$$%%&'&&%$%%&'(('&%$"!"###############%&&&&%%%$$$####"###"!"""! !!"!!  !"$#"!!!!!!!!!!!!!!!!!!!!!!! ""!!"!!!   !!  ! LKJIIIIIKLNOQSUVVVUUTTSRPONNNOOQTTUVVWXXWVVVVVVVTTTUUVWWXXY[\]^^^^^^^^^___`````aabbcccddeeffgghhiijjjklmmnnnnoooopppqqqssttuvvvvxxxxyyzzyyyyyyzzzz{{{|||}~~sha[SRQRRRRQQQRSTUUVXXYZZ[\^```abbccccccccbbbcdccddcdeefghiiiijjklllmnnnnnnnmllkkjihghhhhhhhhhhhijjkkjkkkllmmmmnooooononnmkjhgfedccba^][XUQNKLLKKJHGGEDCCBCCCBBBBBBBCCDDDDEEEEDDDBBAAA@=;864210.+*)))&%"!)5@FKMOQU[bflnprttuvwwwxyxusqplcZNF8-'$$#$$%.Gbr{{{zyzxurpnjbYE/3Rnyrmd_\UNF<1)'*,-/0245567:>CFHJNQV\binoruvutrppnnlkkjb>%$.=Ocptwxvsqrsttuuvwwyz{{|}~pK546531134431/-,,,//0011123345652/--,,,.13212221111/-+*+++--,++*+,---..-,,**++,-..,+**))(((('''&$#$%&&''(()(((((''&&%%%$$$%&&'''())'&&%$###$$%%&'&&%$%%&'(('&$#!!"###############%&&&%%%$$#######$$#""""#! !!""!  !#"!!!!!!!!  !"!!"!!! !   !!  ! IIIIIIIIKKMOQRTTTUTTSSSQONMMMNOPSTTUUVVWUTTSSSSSPPQRSUVVVWXZ\]^^________```````aaabbccccddefffgghhiiiikllmmmmnnnnoooppprrsstuuuvwwwwxxyyxxxxxxyyyzz{{{{|}}~~{qjaZVTSQPPQSRSTUUVWWXXYZ[]^^__``abbbbbbbbaabbbbbcccdeffgghhhijjklllmmmmmmmmllkjihgfgggggggffffggghhghiiijkkmnnnooopnonnlkihfeddcbba_`__\YVSPPMKHGFFDDCBBBBCCBBBBBBBCCDDDEEFFFEECBBAA?>;9754431.-,,,&# #0=DJMPRV\bgkmnpqrruuuuvvvurpni_UI?1($%%$%&(4Pjxyy{{{yuqnje]M9/?^p{~zskf^ZULA8-%"#)+-.0134567:=BFHKORX^ekqqsuusqolljigfggbSCDQ_ku||ywsrpqruuvvwwxyyz{||}~jF334310022310/-,,,..//01236556530,,,+,,.021/0001111/-*)**,.-,,+++,-----,++*)*++,--+*)))(((('''&%$#$%&&''(())))((('&&%%%$$$%&&&''')('&&%$###$$%%&'&&%$%%&'((&&$#!!""##############%%&%%%$$$#######$$#""#"#"!!"""!  ""!!!!!!!!  !"!!"!!! "!   !!  ! JJJJJJJJKKMNPRSTTTTSSSSRONMLMNOPSSSSTTUUTSRQQQQQOOPQRTUVVWXZ[\]^________`````````aaabbbbbcdeeeffgghhhhjklllmmmmnnnnooooqqqrrsstuvvvvvwwwvvvvvwxxxyyyzzz{{|}}~~zpf_[VRPNMQRSSTUVWVWWXYZ\]]]]^^^_````aaa``abaabbbcdeefffghhiijkkllmmmmmmmmlkjihggfeeeeeeeedddeeeeeeefgghijkllllmmnmmmmkjhgfddccbba``a``^]ZXWTQMKIGDCBAAAABBBBBBBBCCDDDEEEFFFGFDCBBB@?=;:8776410///($##"!)8AIMORV\`eiklmnpqsstttvsspmkdYOB7*$#%&&%'-=Yp{wy~|{ytole_VE11Kenz|{|ztme`[SL@2& %(+-/134567:=BFILPSX_gmsrtutqnkigfdbbbcc`clw|zwtsrrrstwwuvwwwxyyz{|}}~dA23330.02221/..-----./1235877642/,,+++,./00././/011/,*))*+--,+++++,,,,,+++*)**++,,*))))(((('''&%##$%&&''(()**))(('&&%%%$$$%&&&&''(('&&%$###$$%%&'&%$$$%%&('%%$#!!"""""""""#######$%%%%$$$$#"####$%$$""#"#"!!"""!  !""!!!!!!!! !!!  !"! ""!   !!  ! JJJJJJJJKLMOPRSTTUUTTTTSONMMMNOPSSSSSTTTSRQPOOOONNOOQRSUVVXYZ[\]^^^^___________```aaaaaaacddddeeffggghijkkklllmmmmmnnnopppqqrrstuuuuuvwwvvvvvwwwwxxxyyyyz{||}}~~{riaZSOMOOPQRRRSTUVVWYZ[[[[[[[\\\]^__`__`a``aaabdeddefggghiijjklmmmmmmmmkjihgfedddddddddccccdddcbccddefghijkkkklkkkjjhgfedcccbba`````_`^_^\ZWTROIGEDCBAABCCDDDDEEFHHHHHHHHIHGFFFCBA@?>=<:8643222+''(&"&2>GLNQUZ^bfgiijmnopoppqnmlie\PF7.$"#%%&&'2Hdu|w|~|yupkhaXI4(8Virw}{yxvxwtoi`YTJA5&!&*,.0244569=;:9880//.,**.7@FKNQTX[^abbbffffeeefdca\VM?2%"!"###$'1D]py}xv}|xtnhc^TI8**=Xgjmty{|{{xvusqonmklkf`YRK?3%!&+/243458;@DHKNQTY^ceeefeca_]ZZXWWVWWUXbjmmnqpqtvxyzzyzz{{||}}}~vX:5320--.1111000011322122356432210.-,,--../0/.,+*+++*))((()+,,,,--,,,,++**)())***++****)))**)&%$#"""$%%&'(()**)(''&%%%%%%%%$$%%%&&('&%%%####$$$$%%$$$$####%%$##"! !!!"""""""##""""#$$$###"""""##$$%%$#"###""##"! !"##"!!!!!!! !!"!!  !"!  !  !NNNNNNNNOOPQSTTUVVVVUUUUSQPPPQSTUUUUTSSRPNNLKKKLLLLMOPQSTUUVWXXZ\\]^^____________```````aabbccccccddddeffggghhijjjjkkkklllmnnoopqqqqqrssrrrrrsttuuuuvvvvwxyyzz{{{{{{{{{{{|||||||||}}}}}}~~~~~~~~~}|||||{{{{{{{{{|||||}}~~vnb]XTRQQRQQQRSTUUUUUUUUVVVWXYZ[\\]^]]^^^_abbbbceffgghhijkkllllllkiihgfedccbbaa```__^^]]\]^^^^^^_`_`abccdefffeeedddccbbbaa``aaa`_^_^]]]\]^^\ZWUSQNIHHIIIHHIIJJJJJJJJKKJIIIIHHHGGFECA@?><<;4441//.-5><;985332127=BHKLNRTVWXXZ\\\ZZYYWTRLC:2*%" "!!"+>Wgrx|{xywsmf_WOD80*)2CQZ`fkmnnmmnnligfeca[TME=2$ (/3557:=BEHKNPRTW[]\[[ZZYXYWUQNKLEMYdjmoqttuvwxyzy{{||}}~~|kO;653100/011112333333321112332100/----.//0//.,+***)(((((((()*+,-./-,,++***))))))**)****))))((&%$$$!!!!"#%&'(((''&&%%%%%%%%%$$$%%%%%%%%$$$####$$$$%&&%$#"!!##"""""""""#####""!!  !"#$$###""####$$$%$$###""""  !""""!!!!!!!! !!""!!!"! PPQQQPPQRSTUVVWXVVUUUTTTTTSRRTUVYYXWVUTSQONMLLLLLLLMNOQRSUUVVXXXZ[\]^^___^^^^^^^^^^^_^^_`aaaabbbbcccdddeeeeffffghiijjjjjjkllmmnnnnnnnopqqqqqqqrrrrstttuuuuvvwwwwwwwwxxxxyyyyz{{{{{{{{{{{|||||||{zzzzzzzzzyyyyyyyzzzz{{|}|uld\VRNPOOOOPQRSSRRQQQSTUVWXXYYYYZZZ[\^____`bbcdeeffghggggggggggfedcbaaaa`__^^]]\\[[[[[[[[[[[\^^_`abccbbbbbaaaa````__`___________^^^][\ZYXVTRRRRRQOMLKLKKKKKJJKLLKKKKKLLLLLKJIHGFDCBB@?=<9776446:?EJKLPQRSTTVWXXVVVURPLE=6/*&$! #1H_jpuz~~~}|}|yyxsnh`XOF<1+*,1BFHKNQRSUWVUUUTUVUROMGBCIW`jnpqsuuuvwyzzz{{|}}~~{jO:5421//./001234433333211122211//.---.//000/.,+*)))(((((((()*+,,--+**))))(((((((((')))))(((('&%&&%"! !"#$%''(''&%%%%%%%%%%%$$$$$$%$$%$$$######$$%%&&%$#"!!""""""""""######""!!  !"#$$###""#$$$$$$%$$###""!  !""""!!!!!!!! !!""!!!"! QRSSSRRSTUVWWXYYYYXXXWWWUUUUVWXYZZYXWVTRPONMLLLLLLMMOPQRSTTUVXXXYZ[\]]^^__^^^^^^^^^_`___````aaaabbbbcccddddddddeeeeefghhhhiiijklmmmnnnnnnnooopqrrrsstttuuuuvvwwwvuuuvwwwxxxxyyyyzzzzzzzzzzzzzzzzyyyyyyyyxxxxxxxxyzzz{{{|~~tkbZURPNLMLMOQPPOONNQSTUUVWWWWXXXWY\]^^^^_`aabbccdeffffffffffeedcba`aa`__^^^\\[[ZZZZZZZZZZZ[]^_`abcc`````aaa````___________``__^^^\YZYXWVTSTVVUSQOMKKJIIIIHHJKJKKLKLMMMMNLKLLJIGFFECCA><::9866860--,/7?HRZ_aaacefgdb`]YSJF=4,%&,158<>BDFHKNOPPPQQRRQOQNKID=?FTbjorstuxvvwxyz{{||}~~~zlQ:5410/.--/012344343321100221100/.,--.//000/.,+*)))(((((((()******(((((((''(((('''&'(((('''''&&&'&#! !"#$&'''&%%$$%%%%%%%%$$$$$$$#$$$$#######$$%%'&%$#" ""!!""""###########""!!!!"#$$###""#$$$$$$%$$###""!  !""""!!!!!!!! !!""!! !"! RRSSSRRSTUVWWXXYYXXXXWWWUUUUVWYY[[ZYXWURPONMLKLLLLMNOQQRSSTUVXXYXXYZ[\\]^_____^^^^_`a```aa``````aaaaaaabccccccccddeeefgghhhiijkllllmmmmmnnnoooppppqrrrrrrrstuuuuutttuvvvwwwwxxxxxxxxxxyyyyyyyyyxwwwwwwwwvvvvwwwwxyyyzz{|}xog[VQNLKLMMNNNNNNNPQRSSTUUVVVWWVX[Z\\]]^^^__`abbcdcddddeeedcccba`_`__^^]]\[[[ZZZZYYYYZZ[[\^__`abbb_____```````_______________^]][YYXWWVUUVWWVURPMIIHGGGGFFHIIJJKLLMMMNNNMMMLKIHHGFECA><<<;:879?GKLKLMNOOONNMLLKKIEA:520-)%"  (=ZjnpqrttuvvvuuttusrstrqngbZRI@9400/./37@IPVZ^_`aba_\YUPJB;2*#%,049>AACFIKLMOOPOOOMJJGA:9;GUdlpstuuvxvwxyz{||||}~~|ymT<5410.-,-/0112333333210000000///.,---.//0//.,+***)((((((((((((((('''''''''''''&&&%&'''&&&%%%%%&&&$! !"#$&&&&%%$$$%%%%%%%%$$######$$$####"""#$$%&''%$#" !!!!!""####$$$$$$$$#""""""#$$###""$%%%%$$%$$###""   !""""!!!!!!!! !!""!!  RSSSSRRSTVVWWXXXXXXXWWWWUUUUVWYZ[[ZYXWUSPONMLKKLLLMNOQQRSSTUVXXYXXXYZ[\\^_________`abaaabaa`````aaaaaaabbbbbbbbcccdddeffggghhijjjkkkllllmmmnnnopppqqqqqqqqrttttttsssttttuuuuvwwwwwwwwwxxxxxxxxxwvvvvvvvvuuuuvvvvwxxyyzz{}}~|mc[TPMJKKLLLLMNNNNOQRTSRRSSTTSTVWYZ[[[[\\]^_`aababbccccdcaaaa``_^]]]\\\[ZZZZZZZXXXYZ[\]]_``aabbb______```````__`___________^^^\YXWVVUTUVXXWURPMIIGFDDDCCEFGHIJKLLLMNNNMMMMLKKJIHGEB@>==<==:9=EJMLMNOOONMLKIGDB?<85320/,(!&>^klnnppqpqqqpppqqooppnlf`ZRJB;6323212228?DJNRW[\[ZUPLF?81*#$).6:?>@CFIKLLMKJIGEC@>73>A@<:=DKMMNOPPPONKJFC@?<963210/,)# %>^iillonnmnnnnnnoomlllif`ZTLE>964333222247;?BEILMNNIC>81+&" #(,278;>BEHIHHGEA?=9767?O_krvuuvwvwwxxyz{|}}}}~~~~{zwjQ<6531/.--/00/0122332100////0/../.+,,--...//.-,+++*(((((((('''''''(((((((('''&&%%%%&&&%$##""!""#$$" !"#%%&%%$$#$%&&&&&&%$$##""""""""""!"#$%%%%%&&%$"! !"""""###"!""#$$$$#""!!!"######""#$$$$$#$" !!"!!  !"""!!##""!! RSTTTSSTUVWWWWWWXXWWWVVVWWWWXYZ[[\[ZYXVSPNMLKKKKLLMNOQQRSSTUVXXYYXYZZ[[\]^^___``acddeedddcbba`__```````aaaaaaaaaaabbbbcddddeeefffggghhijjjjkkklmmnoooooooopppqqpppppqqqqrrrrstttvvvvvvvvuuuuuuutttttttttsrsssstuvxxyyzz{{|}~}sh]TLLLJIIJKMLKKKLLNPPPPPPPQRSTVWWWXYYZ[[\\]^^_```aaa_^^^]]]]ZZZZZZZZYYYYYYYXXYZ[\]^^_``aabbb___________________________^^^\YXWVVUTUUVVUSQPMJIGECBAA@ABBDEGHIJJKLLLMMMLLLKKJIIHFDBA@@DB?<>CJMOPRSSSRPJFC?<;85432221.+%"#;86437>IVckswxvvwwxxxxxyz{|}~~}}}~~~~~~~~{yvjQ;66420/../.../011221100/0////....+,,-...--,,+++++*('''''''''''''(()(((''''''&&&&&&&&&%$#"!!!!"""#"  "###$#####$%&'''''&%$$###""!!!!!!""$%%%%%%%&$$"! !!"""""""! !!"#%%$##""" !##$$$#########"! ! !"""""###"!!  STTTTSSTVWWWWWWWWWWWVVVVWWWWXY[\\\[ZYXVTPNMLKJJKLLMNOQQRSSTUVXXYYYYZZZ[[\]]^_``abdeffffeedcba`_^_______````````````aaabbbcccddddeeeffghhhiiijjklllmmmmmmmmnnnoonooooppppqqqqrsssttttttttssssssssrrrrrrrrrrrsssstvxxyyzz{{{|}~zodYUSNLKLKMLKKKLLMNNNNNNMNPQRTUUUVWWWXYYZZ\\]^^^___]\\\[[[[YYYYYYYYXXXXXXXXYYZ[\]^^_``aabbb___________________________^^^\YXWVVUTUUUUTSRQOLKIGECBAAABBCDEFGHIJKKKKLLLKKKJJIJHGEDCBBFEA=>CJNOQSTTTSPKGC?=;:96544430-'#  6P]`dfjjigiiiiiiihhfd`]YTLG@<9888766677644741-*(&%$#!   "! !!!""!"##$&*,/25678543225=DUahmrvxzxxxxxxxwyz{||}~}~~~~~~~~~~~}}~~~}~|yukR;56420/...---.0101111111100//....,--..--,+********('''''''&''''((((((('''&&&&&&'''&%%$$##"""""""""!!"#$#$#####$&&''(''&%$$###""! !!!"##$$$$$$$%%$$#! !!!!!!!!! !!"#$$$##"""! !##$$$##"""""""!  ! !""""""""! STUUTSTTVWWWWWWWWWWVVVVUVVVVWYZ[]^[ZZXUSPMLKJIIIKLMNOQQRRSSTVWXXXXXYYYYZ\\]^_`aadfghhhggfedca_^^_______``_________````aaabbbcccccddddeggghhhiijkkkllllllllllmmmmnnnnooooppppqrrrrrrrrrrrrrrrrrrrqqqqqqqrrrrrssstvxxyyzzz{{{|}~xmbZTOMLLMJIJJKMMMLKKKLKLNOQRSSSTUUVVVWWXZZ\\\]]]][ZYYYYXXXXXYYYYYXXXXXXXXWXYZ[\]]^__``aaa^^^^^^^^___________________^^^\YYXWWVUVVUTTSSRQNNKIGEDCCDEDCCDCCFGHHIIHIKKKJJJJJHHFEDDCBDCB@@BFINQSUUUTQKGB?=<;9875431.-+($*=QX^beeeddeeefffea`^]ZVOGC><;:;:998887765530+&" !!!"#$$$####%&''&&()**,--/0026=FQZelquuuuyxxxxxxxyxyz{{|||}}}}}}}~~~~~}}}~~~~}|ywkP955520.--//../02201222222210/.....//..-,+********)((((((('&''''(('(((''''&%%&&&'''&$$$$$#$$$$$$##"" "#$####"""#$%&&''&&&$"##$"!"! !!"##$$$$$$$$$$$$#"! !!!!""##"""!!!!!!!!##$$$##"!!!!!!  ! !"""""!! TTUUTTUVWXXXXXXXWWWVVVUUVVVWWYZ[]]\[ZXUSOLLKJIIIKLMMOPQQRRSTUWWXXXYYYYYY[\]^`abbehijjihggecb`^]]]]]]]]]^___________````aaabbbbbbbcccdefggghhhhiiiijjjjjjjjkkklllmmmmnnnnnnnnopppqqqqqqqqqqqqqqqqqqqqqqqrrrrrssstuwxxyyzzyyz{|}~}qh_YTPNKKJJJLLLLKKJJKJKLLNOPQQQRSSSSTTUWWYYYZZZZXWWWWVVVVVVWWWWWWWWWWWWWWXYZ[\]]^__``aaa^^^^^^]]^^^^^__________^^^^^^^\ZYXXWVVVVUUTTSSRPPNLJIHGGHGGFEEEDEFFFGFFFIIHHHHIHGGFEDDDCDDC@>@EHMPSUUTSQKFA>>>=::97530..-+'#1FNUZ^_`aaabbbcba\[YWSOJDB?>===;:::987774332-'!!"""###$$%&'()**()*,-/0/0////0237?CFKORTTTSRLFA==>><<;9752/..-(!!7BKQVX\\^^^]]^]^ZYVSOJFBA@??>><;<;997664543/*%  !!! #$$$%%&&''()*+,--.0134568989;>>BDJQX_ejnsuuuwxxyyyyyyyyyyyz{||}}~~}}}||}~~}}}||}}}}}}}~~~~}}|||{zxviM5/..-,,,,.--,-../-.00000000///..-,,++****)))))))))((((((('&''''(('('''&&&&''&&&%%%$$$$$$$$$$$$###""!!!"##"!"!!!!!#$%&&%$#$"!""#"!"!!""###$$$$$$$$#$$$$$#! !!!!!!"##$$$##!  ! !""""" SSRRSTUUWXXXXXXXYYXXWVVUVVVWXZ[\]]\\ZXUSOLLKJJJJLMMMMNOPQRSTUWXYYZZ[[[[[\^^_`bcdgjllkigeda`^\ZZYYYYZZ[\]___^^]]]^^_______________```abccdddeeeeeeeeeeeeeeefggghhijjjkkkkkkkklmmmmmmnnnnnmmnnnoonoooopppppppqqrrsuvwwxyyyyxyyz{|}|||||||||}~ypf^UPOMLLLLKLKKKKJIHIIJKLLLMMMMMMMNQPRRSSSTTTSSSSQQQQQQRSSTTUUVVVUUUVWXYZ[\]]]^^__`_]]]]]\\\\\\\]]^^_______^^^^^^^^][ZYYXWWWVVUTTSSRRQQPOOOOPONMLKKJIGFDCBAAAAAAABCCCCCCCCCBDDC@>>?BJNQSSSSRLE@<<=>>>=<:851..-*"'3>HMPVWYYYXXXVXUROLHECBBAAA@?=<<;:87652321/*% !!"""!"#&&''(())*+,-./0023579;<=@ABCFGJMPV]dilosvvvvxyyyyyyyyyyzyzz{|}}}~~~}}|||}}}}||||}}}}}}}}}}}}}}}~}~}}|{{{{zywuhL3,+*++++,--,+++,-,---------....-,++**))**********)'''''''&&''''((''''&&&&&''&&&&&&%$$$$$$$$##$$##"#"!!"""!!!! !"$%%%$#""! !!"!!""""##############$$$$#"! !!!!!!! !!!"""##$$$##!  ! !"""""!! QQQQQRTTVWWWWWWWWWWVVUUUUUVVXZ[\]]\\ZXUSOLLKJJJKMMMMMNOPQRSTVWXYZ[[[[[[\]___`bdehkklkigdb_^\ZYYXXXXYZ[\]^^^^^]]]]^^^^^^^^^^^^^^____``abbcccdddcddeeeeeeeeeffffgghiiiijjjjjjjjkkklllmmmmmllmmmnnmnnoooppppppqqrrsuvvwxyzzyxyyzzz{{{{{{{{{z{|}~ysk^WRPONMMLKLLLJJJHHHIIJIIJJJJJJKMLNNOPPQQQPPPPOOOOOOPQQRSTTUUTSSSTTUVWXYZ[[[\\]]][ZZZZYYYYZZZ[[\\]]^_`aa_^^^^^^^^]\[ZYXXXVVUTSSRRSTSSSSSSSRRQPPOOLKIFDA??====>>?@@@@@@A@@BBB@?=<>EJMPSSQOKE@=??>>@@>=;96320,'$&0;AFMOQRRRQPPOLIHFDCBBCCCBA@?==<:86531432/+&" "#$$$$$%(()**+,,../01233579;=?@CFGJLNOPRW]djnqsuwwwxyyyyyyyyyyyzyz{||}}~~~}}|{|}}}||||{|||||||||||||||}|}||{zzzzyywtgJ2*))*****++*++**,,,,++++++,-..-,++*)((())********)'&&&&&&&&''''(('''&&&&%%&&&&&'''%$$$$$$#####$$$#$#""""! ! "#$$$$"!!  "!!""""#######"""##"##$$$#"!!!!!!!!!!  !!!!!"""##$$$##! !!!  !!  NNNNOPRSUVVVVVVVSSSSRRRRSSTUVXZ[\]\\[YUSPNMLKKLLMMMMMNOPQRSTVWYYZ[[[\\\]^```acefhjkjigec`]\ZYXXXXXWXYZ[\]]]]]]]]]]]]^^]]]]]]]]^^^^___`aaabbbbbbccddcccccccdeeeffggggghhhhhhhhiiijjkkkllkkklllllmnnnnoooppppqqrrsuvvwxyyyyxxyyzzzzzyyyyxxxyz{}~~{og`[WSPKLKKKKIIIHHHHHHHIIIIIIIJKKLLMNNOONNNNNMMMNNOOPPQQRRSSRQQPQQRSTUVWXXYYZZ[ZXXWWWWVWWXXYYYZZ[\]^^_______^^^^^]\[ZZYXWVUTSSSSTTUUUUUUUUUUTTTSNMKHEB@?;;;;<;;<====>????@A@=<;???@BA?>=;7511.)%!&.59@FHJJIHGGD@>@AABBEDDDDBA@?=;:976532331-*%  !#$$%%%&'**+,--./11234566:;=@CEFIMNPRSTUV]ekprttuwwwxzzyyzzzzzzzzzz{|}}~~~}}|{|}}}|||{{{{{{{{{{{{{{{{{|{|{{zzyyyyywtgJ1(((()))))))))**++,,+++++++,--,+**))(''(()*******)&%%%$$$%%'''(()'&'&&&%$%%%&&'''''&%%$#"""#####$$%#""""! ! !#$$$#"!!  !! !!!!"""##"!!!!!"!""###"""!!!!!!!!! !! !"!!! !!!!!"#$$$"!! !!!!!!!!   KKKKLMOPRTTTTTTTQPPOONNNOPPQSUWXY[[\[YWURPONMMNNONNNNNOPQSTUVXYY[\\]]^^__aaabdfgijiigeca^[ZXWWWWWWWWXYZ[]]]]]]]]]]]]\\[[[[[[[\]]]]^^^^_````aaa`aaccbbbbbbbcccdddeeeeeffffffffgghhhiiijjiijjjkkklmmnnnnoooppqqrrsuuvwxxyyxxxxyyyyyxxwwvvvvwxy{|}}ume_WQOMKKLJIIIIIHHGGHIIIIIIIJIJJKKLLLMLLLLKKKLMMNNOOPQQQPPONMNOPQRSTUUUUVVWWWVVUUUUTUUUVWWWXXYZZ[\]]]________^^]\\\ZYWVVUTSTTUUVVVWWWYXXXXWWVQPNLIECB=;::;989:::;<===>??=<;9:AEHKMOONLGB?@@ADEDBA?=:832/,)# ! "#'*-39;==<:996017;?BDFFFFEDCA@=;;9987544530-'! !""$%&''()),,-./0114456789:=?ADGJKMNPRUWWWYbiptttuuvvwxxxxyzzzzzzzzzz{|}~~~}}}|||{||||{{{{{{{{{{{{zzzzzzzz{{{{{zyxwwvutshL3))***+++*))***+++,,,,+****++++*)))((''''())))((('&%&%$#$$&''()))&%&&&%$#$%%&&'''''&&%$"" !""""""$$##"! !#$$$#"!!  !  !""##"! ! !"""""""!!!!!!  !"#"!!  !"$$$#""! !!!!!!!!!  !HHHIJKMNPRRRRRRROONMLKJJJJKMOQSTVYYZZYWUSRQPOOPPQPOOOOOPRSTUVXYZ\]]]^__``bbbceghiihgeca_\YXWVVWWWWWWXYZ[\\\]]]]]]\\\\[ZZZZZZZ[\\\]]]]^^^^___``_``aa```````aabbbcccccddddddddeefffggghhhhhhhiiijklmmmnnnooppqqrrsuuvvwxyyxxxxxxxxwvvuutttstvwy{{}wphaYTPMLKJJJIIIHHGHHHHHHHIJIIIJJJKKKJJJJIIIKKKLLMMOPPONMLKJKKLMNOPQQQRRSSTTSSRRRQQQQRSTTUVVWXXYZ[[\^^^^___^^^^]]][YWWVUTSTUVWWXXYYY\\[[[ZZYTSQOLIFE?=;:98678789::;<==<<;:77=ADGKMNNLHCAA@CGIHEDB@=9331.+&##"#%'''*,,//,+)(("&/7=@DHGGGFEDBA><;99::9677620*# !"#$%&'())*++./012345789:;<==@BDGJMOPQRUWYZZ\eksuuuwvwwxxxxyyzzzzzzz{zz{{|}}|||||||||{{{{{{{{zzzyyyyyyyyyyyy{z{{zzxwvusqrsjO6---..///,+++,,-,-,,,+*)))*******))))(''&'''''''''&&'&%$$%&''()))&%&&&%$$$%%%&&''&%&%$$#"!!!!!!!!##""!  !#$$$#"!!  !  !!""#"!   !""""""!   !#$"""  !####"!   DDEFGHKLNPOOOONNKJHGEDCCCDEGILNPSWXYYYWVTSRQPPQQQPOOOOPQTTUWXZ[[[\]^_``acddefhikjihecb`_[YXWVVWWWXXXXYZ[[[\]]]_^\[[[[ZYYYYYYYZ[[[\\\]]]]]]^^^_^]]``________````abbbbccccccccdddeeeefffffffggggiklllmmmnooppqqrrstuuvwxxxxxwwwwwvuutssrrrstvxyz{}~~}wjbYSNLKJKKJIHGGGGHHHHHHHIIJJJKJIHHHHGGHIJKKLLMNONMLKIHGHHIJKLMNNMNOOPPPPPPPOOONNLMPPRSTVVWWXXYZ[Y]]]^^^_^]]]]]]\XWWWVVVWXXYZZ[[\\\\\\\\[WVTROLJHDA>=<:884456789::;:98767;>ADJNQROJFCB?KKLKJHGC@;8420-*''())((**('&%#  *5<;;;=?>><940+%! !"#%&')*++,,-./012345689:;=>>@DHJMOQSTUVXY[]^`ilpsuwxxyyxxxxwxxxyyyyyz{{{{{{{|||||||{{{{{{{{zyzyyyyxxxwxxxxxxxyzzzyxwvrooqqgP911111000/..////...-,+**))*))))))***))('&&%&&&&'''''('&&&&'''())(&%%&&&%$$$##$$$$%%%%$$##"""##$$$##""!  !#$$$$#"!  !!""!  !""#""! ! !! !#$##"!!  !###""!   BBCCEFHJKMLLKJHHGEDA?>=<;;<>AEGJNPRTUVVUUUUTSSSTRQQPPQQRTUVXYZ[\[\]^_`abdeefghjkiffdb`]\ZYXWWVWWXXXXYYZ\[[\\\\]]]\\\[[ZZYXXXXXXX[[[\\\\\\]]]^^^]]]]]]]]]]___```abaaaabbbbbbbbcccccdddeeeeeefffgikkkklllnoppqqrrstuvwxyyyyxwwvvvuutssrqqqrsuwyz{{{||}~~uld]WSPMJJHHHHHHHHHHHHHIIIIIIIHGGGGGGGGHHIIJJJKKJIHFEEEDEFGHHIIJKKLLLLLLLLKKJIHFHJKMNOQRSTUVVXYY[[[\\]]\\\]]]]\YXWWVWWYYZ[\\]]^^^^^^^]]ZYXURPNKHDA?=<98544345688998755358:741.+))()**+,+***)&#! !*5=<=>@BA@?<61-)#""#$%'(*++,--./012345689:;<>?@CILNQSTUVWZ[\^`bcjmqsuwxxyyxxxxwwwxxxyyyzzzzzzzzz{{{{{{{zzzzzzzzyzyyyxxxxwwwwwwwwxxyyxwvurnoqqgR<20000///.//00000/-,+*)('()))))))**))(''&%%%%&&&'((()(('(((((())'&%%%&&%%$#######$$%$$$$$$$$$$$$$$#""!  "#$$$#""!!  !!!"!  !"###"!  !! !#$##"!! !"#""!!   @@ABCDFGHHHHFDA@A@>;975311248;8754433467887644237:>DJOPOLIGC@JMNNMMKIE@=863/,*+,.012210/..,(%%$!%,5==>?ABBA@=83.*&#"#$&')*++,-./02356789;<=>@BCDHLOQSUVWXY[\]_abcknrtvxxwwwvvvvvwvwxxyyyzzzyyyyyy{{{{{{zzzzzzzzyyyyyxxxxxwwwwwwwwwwwxwvvurooqpgS=20///..--./01110/-*)(&%%&()))))))))(('''&%&&&&''(()**)))))))))('%$$%&&&%%$#####"#$$$$$$$$%&&&&&&&$""!  !"#####"!!!  !#"!  !"###"!  !! !#$##"!!! """!!  ?@ABCDEFEEDDA?<;<;96421/+++-036:?CGJLNOQQSTUVVWWWVUTSSSTUVVWXYZZZZZ[\]^_`bccdefhgeda_]ZYXWWWXXXXYYZZ[[\\[Z[[ZZZZYXXWWWXXXXXYYZZ[Z[[\\]^]]]\[[ZZZZ[[[[[[[[[[[[[[[]]]]]^^^^^^^^__________`abbcdeeffghiijklmmnopqrstttuuuuutssssrrrqqqppppopqrsstuwwwxxyyyz||||}}}}~}}}}}}}}~~yne[TOLJJJJHHHHHHIIJIIHHHFEEFFFFFFFEFFGGFEEEDCA@@???@@AAAABBCCDDDDDDCCCBBA?==>?@BDHIJLMNNPSUVVWXXYYYYZZ[[[[ZYYXXXYZ[[\]^___`aaaaa``]\[YVTQMJHECBA>;<:76412556665542334:@DKMMMJGECJLOOONNKGC>:852/-,/13577644320.+*)&(.5;>BGHIJIGFECCA?>?BDDEDDA=:4.(%##$&'(*,-./0124578:;<>>?ACEGHKNQSUWXYZZ\]_`acelortvwxwwwvvvvuvvwxxyyyzzzyyyyyyzzzzzzzyyyyyyyyyyyxxxwwwvvvvvvvvvvvwvvutsqppogU?300//..---./01110.+*(&%%%'))))))((((''((''''''''()********))))(&%$$%&&&&%$####"""#$$$$$%&&'&&%%%&$""!  !""""""!!  $#"  "#$#"  !! !#$"""  !""!! @@BCCDEEDB@?=:765420/-,)%$$&(+-05:>AEGJMOQRSUVVVVUUTSRRSTTTUVVWWVWWXYYZ[]_`abcegeba^\ZXXWWWWXXXYZZZ[[\]][[[[ZZZZYXXWWWXXXYYYZ[[\[\\\]]^]]\\[[ZZZZZZZZZZZZZZZZZZZ\[[[[\\\\\\\\]^^^^^^^^^_``abcddeefgghijkllmnpqqrssstttuutsrrrqqqpppoooonoppqrrstttuuvvwxyyzzzz{||||||||||}~~~~|jbZSNLKJJHHHHIIIIHGGFFEDDEEFFGFFEEEEEDDCCCA@???>>>>==<<<==>>>>=>>>====<:998789BHIKKJFDDILPQQPPNKFA=9630-.13579998877530//+,06;?BFHIJJHGFDCA@?@CEGGGGD@=81+'%%%&&(+,./0234679:<=>@ABCEHIKNPSUWYZ[\\]_`acdflortuvwvvvuuuuuvvwxxyyyzzzyyyyyyzzzzzzyyyyyyyyyxyxxxwwwwvvvvvvvuuuuvvuuttqppngWA3//..-,,++,./1111.,*('&%%'(((((('''''((())))((('(()****++**)))'&$#$%&&&&%$$$###""###$$%%&&&%%$$$%$""!  !!"!""! ""!  !"""!  !!!! !!"#"!!  !! AACCDDEECA><:743/.,+)''$! !#%'+048<@DGJMOQSUVVUTSRRQQQQQQRRRRRQRSSTUUVY[\]^`bcb_^[YYWWWWXXYYYZZZ[[\]]]\[\[ZYZYZYYXXXYYXYZZ[\]]]]]]^^^]\\[[ZZZYYYYYYYYYYYYYYYYYZYYYYZZZZZZZZ[\\]]]]]]]]^^_`abbcddeffghikklmnopqrrrsssttsrqqqpppooonnnnmnnoppqqrrrssttuvwwxxxyyzz{{{{{{{{|||}}}~~{rh_WPLJKJJJIIIIIIHGFEEEDEEFFGFFFEDDDCCCBA@?>>>=>=<;::999888887888877775432001369:<>@BEGIKNOPQSTTUUUUVVVWWWWWXWWYZ[\]]^_```aaaaaa_][YWSOLJGFFGFEDB@=964533322221//049=CEHHHFDDHKPRRRQPLHC>:731/0368:<===>=<:85330027?@BCDEGJKMPQTVY[\]]_``acdegloqstuuuvvuuuuuvuwxxyyyzzyyyyyyyyyyyyyyxxxxxxxxxxxxwwwwvuuuuuuuuttuuuutttqponiZC2--,,+*))*+-/00020.-+)('&'(((((('&&&''()))))((((((()****+**))('%$#$%&&&&%$$####"""#####$$$%%$$$$$$""!  !!""!    !!!! !!!!  BBDEEEEDC@=:7520+*)'&%$"!#(,048<@DHKNPRSSRQPOONNNMMMMMLLLLLLMMNNNPTVWY[\^^[ZWVVVVWWXXYZZZZZ[\\]]]\[[[YXYYWWWVVVWWYZZ[\]^____^^^^]\\[[ZYYXXXXXXXXXXXXXXXXXXWWWWXXXXXXXXYZZ[[[[[[[[\\]^__`abbcddefgijklmnopqqqrrrrrqppppooonnnmmmmlmmnnnoppppqqrrstuvvvwwwxxyyyyyyyyzzz{{{||}}}~~~ynbYQLNLKKJJJIJJIHGGFFEFFFFFFEEDDCCBAA@?>==<<;<:976665543222223222111/.,*)'(*,./1368;>ADGIKLNPPQQQRSSSTTUUVVVVXYYZ[\]^__``aaaa`_][YWTPNLKJIJIHFDBA>;7632210000.-.069?AEGGFFEGJPRTSQPLID@<952117:<>@ABBAAA?=9764469=@CFGIJJIHFDCBA@BEILLLLKIFB=4.)'''(),./02468:;<>@ABCEFGILMORTWY[]^__`abcdefhloqsstttttsssssvtwxxyyyzzyxxxxxxyyyyyyxxxxxxxxxxxxxwwwvvuuuuuuutssttttsssrqpnj]G3--,+*)))*+,-..-..-,+*)((((((((''&&&&'((((((((((('''(()))*)(''&%$#$%%&&&%$$###"""""""""""$$$$####$"!!   !""!!     CDFGGFFEB?;753/-)('&%$#" "&),048=<<<<<:87543221/......--,,++)(&$" "$$')+.037:>ACDFJKLMMNOOOPQRSUUUUWWXYZZ[]]^^__`aa`_]\ZXURQONMMMMKIGFDA>;84320/./.-,,/25<@CEGGGFFIOSUTSPNKEB?;74338;>@BCDEDDDB@=:8789;>@BEGIJJIHGDCBBACFJMMMMMKIFB92,(''')-/02468:;<>@ABCEFGIKMNPSVY[]_`aaabbddeghloqrssttuutttttvuwxxyyyzzxwwwwwwxxxxxxwwwwwwwwwvwvuuutttsssssssrrrsstssrtrrqnk^G3--,+**)))*+,,++++***)((((''''&&&&&&&''(()))))))('&&'''(())('&%%$#$%%%%%%$$$###"!!!!!!!!!"#$$$$$##"  !  !"!!!   EFFHHHFD@<841/,***)('&&%%$"!  #%(,04:>BEGIIFFEDCBBBBA@?>===<==>>>>>@BDGIKMMMLKKJKKLPSTUVWWXY[[\\]^^]]\[ZXWWWWWVVVWWY[\]^_`abbaa`__^]\\ZYXXWXXXXXXXXWWWWWWWWWVVVVWWWWWWWWWWWWWWWWWWWXXYZ[[\]^__`abbdfghikklmnooppppponnnnmmmlllkkkkjllllllllkkllmmopqqrrrrtutuuuuuuuuvvwwwxxyyzzz{{{{{{{{{{}}}}}}}~~vh\RMMMMMNNMMLLLKIIHGHGHHGGEEDCCBBB@?>>==>=;:9875420.-*)(('&&%$$#"!!#(,1479;?EFHIJJJKOPPQRTUUUVWVWWXY[\]]^__```^][YWURQQQQQPNLKIGDA<99741.-,++*)*-08<@CEEEEFJNRUUTRQOKFB>96559<;;<=>?BEGJKKKJHFCA??CGKMMMNNMLJG@6.('(),-/13579;=>@BCDEGIJKMOPRUZ\]_abcccccdddefloqrrstuttttttuvuvwxxxyyxwvvvvuuwwwwwwwwvvwwwwvtusssrrrrrrrrrrrrqrrsssrrrrsrnj^G3-,,+*))())***)))***)(('''&&&''&'''''''())))))))('&&&''''((('&%%$#$$%%%%$$$$###"! #$$#####"!  !! !!!   !!!HHIJIHEB=:62/,*)+++++***)('&%$#! #&*.48>=;:9876553444444479;>@BDDDEEEEEEEKPQRSTUUVYZZ[]^^]]\[ZYXXWWWVVVWWY[\]_`abcbba`__^]]\[ZYYXYXXXXXXXWVVVVVVVVUUUUVVVWWWWWWWWVVVVVVVVVWXYZZ[\]^^_`aacffghjkklmnnnoooonnnnmmmlmmlllkkkkkklllkjiijjkkmnoopppqrtrttttttttuuuvvvvwwwxxxyyyyyyyyy{{{{{{{||}}}}}}~~uhZTTVUUWVWWVVVUTSPONLKIGFHGFFEEEEFEDCC@?=B@=;997810/-,,*)+*)(('&$$(-0247<=>??@CEIJKMOQSSRSTTTUVWYZ[\]]^___]\ZYWVTTTTTTSQPNNLJGB;:752/-*+*('(*.3:>BCDDDGINQTUTSRPLHD@;7569<@BDEHKKKJIGDA><:88:=@EGJKKJJIGFCA@BFILNNNNNNMLF>4,''),.13469;=?@BDEFGJLMNPRSUX\^_`bcdddddeeefgloqrrstuttttttuvtvwwwwxxwvvuuuttvvvvvvvvuuvvvvuttssrrrrrrrrrrrrrqrrsssrrrrrqnj^H3,,+*)((())***)(((((''&&%%%&&&'''((((((())))))))('&&&&&&''(''&%%$#$$%%%$######""! "$####"""!  !!    !!!IJKJIGD@;851.+)),,,---.,+*))(''%$! !$)-2589998765442/.-,,,,+++******+.02468:;<<==>??DHJLMNPRSUVWXZ[[Z[ZYYXWWVVWWWXXXZ\\]^_`abbaaa``^^][ZYYXXYXXXXXXXXXWWWWWXWVUUTUUUVVVVVWWWVVVVVVVUUVVWXXYZ[\]]^__acdefghijklmmmnnnnmmlllkkiiiiiiiiiiiiiiihgggggghijklmnnoppsssssssstttuuuvuvvwwwwwwwwwwwwyyyyyyyz{||||||||}}}}}}}}}}~~~xojjkmlllnnnnnnmlihfdb`^\[ZYYXXWVVUTRQOLJGC@=<;<;87655443543210/-)'$#!$,059;<=>>>;::::;@CGJNQQQOOPRRTUWWXYZ[\]^^]\[YXWWVUVVVVVUSQQOMJE=<:730.,)(('(*+-3;?@BBCGILORTSSRPMJFA<866:<>@BDGKKLLJIHE@=95459>DGIJJJJIIGDAABDILNOOOOOONLE;/('),/2468:=?ABDFHIJKMNPRTUW\]__abcdddddeeefgloqrrsttsssssstutuvvvvwwvuuuttttuuuuuuuuttuuuutssrrqqqqqqqqqqqqqqrrsssrrqqrpmj^J4,+**)('()*****)(('&&%%$$$%%&&&''()))(((()))))))('&&&&&&&'''&&%%%$$$$$$##"""""!! "#"""!!!! !"!    ! JKKJIFB?:741.+**---.../-,,++*))(&$#"!!!!"$'+.0113210..-+('&$$$$$$#######$&(),.0235567899>BDFHILNMOQRSUWWVWWVVUUTTUUVVVWWY[\]]^_```___^^]\[ZYYYZZZYYYYYYYZXWWVWWXWVUUTUUUUUUUUVVVVVVVVVVUTUUUVVVXYZ[\\]^_`abdefgijjkllmmmlkkkjjjihhhhhhhhhhhhhhhgffffffghiijklmmmnooopqqrrssssssstuuuuuuuuuuuuuuvvvvwwwyz{{{{{{zzzzzzzzzz{{{||}}~~~~~~~~~~~~~~~~|{zyyxssrrrqomkjhgfc`_WSOLIGEBDCB@@@@??>><;;:8640.+'$  (0:DGKMMNMNKJEA=9657;?EILMMLLMNOPRTTVWXY[\\\]\ZYWWWVVWWWWXWVVTRPNIB><:9852-(&&'&$')28=?ACHIKNQSSSRPNKGB=978<>?@BDHKLMMMKKHEB=7336ABDFHJJKLNORTVXZ]^_`abcdddddeeefgloqrrsstsssssstustuuuvvvuttttsssssssssssrrsssssrrqqppppppppppppppqrrsrrqqppolj_L5++*)('''()*****)(&&%%$$$$%%%&&&'(())('&'(((((((''&&&%%%%&&&&%%%%$$$$$##"!!!!! !!!! !"""!   JJJIGD@=9530.,++...///0/...--,,+)('&$#""""#&(*++*)('&%%#!  "$&(*,-./02269<>@BEGGIKLNPQRRSRRRRRRRSSSTTTUWYYZ[[\]\\[[[ZZYZYXXXYYZZZZZZZZZZYXWVWWWWVUTTUUUUUUUUVVVVVVVVVVUTTUUUUVWXYZZ[\]^_`acdefghijkklllkjjjiiihgggggggggggggggfeeeeeefghhijkkllmmnnooppprrrrrrrrssssssstttttttuuuuvvvwxyyyyyyyxxxxxxyzzzz{{{||}}}}}}}}~~~~~~~~~~~}}}|||zxwwuutsqonmjfc^YTOMMLJIHGHFEEDCCB?=;963/+'$$&,6@HMRTVVVUUUVTQLHD@<88:=AEGIIHHJKLMNQSTUWYZZ[[[ZXVVWVVWWXXYZZZXUSPLGB@?@@?:6.*&$"!#)-27<>>FHJLNPQRPOMKGC>:8:<>?@BDGJLMMMNNMKGB;544:@FHHHIIJLKJGDBBFLQSTUVWYZXSK?4,*+0259<>?BCDFHJKLNPQSVXZ\^_``abcdddddeeefgloqrrsssrrrrrrstrstttuuutssssrrrrrrrrrrrqqrrrrrqqpoooonnoppppppppqqrrrqqpoonkjaN6+**)(''&'()*****('&&%%$$%%%%%%%&''(''&%%''''''''&&&%%%$%&&&%%%%%$$$$$#"!!   "#"" KKHFDA=:642/.---/0000111100000/.,++)(&%$#""$$%%%##"!!#$%'(*+,/1479;>@@CEFHJMMMNNNNNNNOOOOOPPPRTTUVVWWWWVVVUUUUUUUVWXYXYZZZZ[[[ZYXXWWWWVUUTUUUUUUUUVVVVVVVVVVUTTTTTUUVWWXYYZ[\]^_abcdfghhiijjjjihhhgggeeeeeefffffffffeddddddeffgghijjkllllmmnooqqqqqqqqqqqqqqqrssssssssstttuvvwwwwwwwwwwwwwxyyyzzzz{{||||||||}}}}}}}}~~~}}||{zz|||{{zxxwvuutqqprqnmie_[TSRPNMKKJIHHGFEBA><9741//46.& %'*06::CFHJLMMNMLKIFA=99:<>?@BDGJKLNOPPQQLGB;869?EGGGHHJMMLIFDCGLQSUVWY[][XPE90++1369<>@BDEGIKLMNPRUXZ\]_`aabccdddddeeefgloqrrsssrrrrrrrsrrrsssttssrrrrqqqqqqqqqqppqqqqqpponnnnmmnooooooopqqqrqqponomkjcR;.*)(''&&''())***)'''&%%$%&&%%%$%%%&%%$$%&&&&&&&&&&%%%%$%%%%%%%%$$####"!!  "#"#  JIHDA=:7420/.-./1112222332222221/.-,,*)(&#""!!!  !#$%(*-/14799<=?ADGHGGHHHIIIJJJJKKKLNOOPPQQRQQQPPPPOONNOPRSUTWWXYZ[\\[[ZZYYYXWVVUUUUUUUUUVVVVVVVVVVUTTTTTTTTUUVWWXYZ[\]_`abdeffgghhhhgfffeeeccccccdddddddddcbbbbbbcddefgghijjkkklmmnnooooooooppppppppqqqqqqqqqrrrstuvvvvvvvvvvvvvwxxxyyyyzz{{{{{{{{||||||||}~~~}}{{{|{{{{yxxvutsrqppnnmmlkhd^][YWTQPNNMLKJHEDB@>=>>AHNW\`bdd_````^]]ZYZZZZYWNIC><:89;:;<>?BEHKMOPRSTVWWWVUVWVVXXYZ\^^]\ZWUROKJMPQQONMF<2(#!%)/39=ACEGHHGGGGEB?;99:;=>?ACFHIJMNPQRSPMHC><=?DFGFGHKNPNLHFDGLQTVWXZ\^^[UK?4,+1369AEHJLNOPQRSSTTTUUUWXYZ]___^\YXVSNOTWXYXWUQLC7.# !#'.36;>ACDDCBBBA?<:89:;=>?ACFHHIKLOPQRRPNKGDBADFGFGIKOPPNJGEHLQTVXZ\]_`]XOD8/,246:=@CEFGIKMNOPRTWZ\^_aabcccddedddeeefgloqrrsssqqqqqpqqqqqqqqqqqqppppooooooonnnmmnnnnnnmmmmmlllmmmmmnnoppppoooonmnmkkfZE3*(''''''''((())(&%%%%%&%$$$$$$$$%&&&%$$$$$$$$$%%%%%%%%%%$$$$####"""!  "#!"  EEC@<85210/./012333334444444444444432100/-+)'&&## "$%')**,/036999:::;<=<<===>>>@AAAAAA@AA@@@??=<;;;;<=>AEHLOSUWZ\]]]]]\[ZYXXXXXXWWWWVUUVVVVVVVVTSRRRQQQRRSTUUVXYZ[\^_`abbccddedcccbbba```````abaaaaaaa`aaaaaaababcdeeghghijjkklllkkkkkkklllllllmmmmmmmnnnnoooqrssssssssutttttuvwxxxxyyyzzzzzzz{}~~~~~~~~~}}~~~~}}|zyxxwwvvuusrrqppqpmkjhec`a`_^]\ZZYZ[_afjkijlllkkjjiigedbaa`__^^]\]ZUOJD<6/,+++.26;?DFIIIHJKLMOPQQUVWWXXZ\_aa_][YVTTVY\]]][YVPG>4,"&+/58;=>>=;::998889;=??@BCEHIIJKLNOQPPOMJGECDFGFGJLOPOMLKKKPSVXZ[]^_`_ZSI?62347:=ACEFGIKMOOQUVXZ\^_abcdeeeeeedddeeefknpqrrssrrrrqqqqppppppqqppoooooonnnnnnnmmmmmmmmlmmmmmmmmkkllllmlppooonnnmlmljifZH6,))('%&''(('(''&%%%%%%%$$$$$$$$$%%&%%$$$$$$$$$%%%%%%%%%$$$$###""""!! !"  !! CB?<8421/////01233333444444444445554433320/-,+*('$!  !#"$&(*-02344556784455666788888886988877754333344257:>CGKMTZ[\^^^^]\[ZYYXWXXXXXXXXWVVVVVVVTSSSRRQQQQRSTTUVXYZ[]^_`abbccdcbbaaa``_^______`aaaaaaaa````````aaabcddefffghiijjjjiiiiiijjjjjjjklllllllmmnnnoopqqqqqqqqqsrrrrrsuvwwxxxyyyzzzzzz{}~~~~~~~~~~}||{{zzyyxxwwxxwvttutrpomkigfedcddcccdfhjkjjmnmmmlkjkjjigedca`_^^]]\^\YTOIE;2,)'')+-158;>ABCCDEFILNOPQRSUWX[^`baa^[WUVZ]`acdba^ZSKB9.'"!$(+-0246666410/02369<>?@BCDEHHHIJKMNOPPNKHFCDFGGGJLNPQQPPPONQUX[]__^^]ZUMD:3236:>ADFGHKMNPQSVWY[]_`abdefffeeeddddddeknpqrrssrrrrqqqqpoppppqqponnnnnnnnnnnnnlllllllllllllllllkkllllmloonnnmmmlklkjif\K8,))('%&&&''&'&&%%%%%%%%$#$$$$%$$$%%%$$$$$$$$$$%%%%%%%%%$$$###"""!!!  !!!  B@=95210///001123344445544444444666666554310/.-,+)'&%#"  "#%'&**+++,,++,,---,.......,..--,,,**))((((()*+-27=AEJNSWZ\^^]\[Z[[[ZYYYYYYYYYXXWVVUSSSRQQPPPQRRSSTVWXYZ[\]^_``aaaa``___^^]\^^^^^^_````````________`aabbcddeeefffggggggggggghhiiiiijjkkkkkklllmmnnoppppppppqqqqqqqqtvvwwwwxyyyzzzzz{~~~~}}}}}||{zzxyvvvvutrttrqonlkkjjijjiijjlllllklllkkkkijiihgfdcba`_]\\\]\ZVRLH?92+&#"!$*+.0368:;;=@BEHKMMNOQTW[]`aaa`\XVXZ^acefffda\VNE=4+%$%'(,--/00/-+**+-037:<>?ABCFGHHIIKMMOOONLJGDDEFFGJLNQRSRRSPNQUXZ^``\[ZWSMG=4247;?BDGGIKMNPRUXY[\^_`abdefffeeeeeeeeeeknpqrrrrrrrqqqqpooppppqqponnnnnnmmmmmmmllllllllklllllllljjkkkklkmmmmmlllkjkjihg^N9,(('&%&&&''%%$%%%%%%%%$###$$%%%$$$$$$$$$$$$$$$%%%%%%%%%$####"""!!  ""!  @>;7410//00012234555566655555555777777766543210/.,*)('%##!  !"###$$$%#$$$%%%$%%%%%%%$&&%%$$$##""!!!! #&+14:@DINRVZ]^^^^^^^][[[[ZZZZYYXXWWWTSSRRQQQQQRRRSTUVWWXYYZ\]]^^^_^]]]]\\\[Z[[\]^__^^^^^^^^________`aabbccdeeeeeeeeeefffffffggghhhhiijjjjkkkkklmmnoppppppppqqqqqqqqsuuvwwwxyyyz{{||}~~|zzyzxxxxwvuutsrqpnmnnnnnommmmmmmlmlmmlkkiihiiihgfecba`^]\[[[YWTPMJA=6-'"! ""#$(*-/00259=@FHJKMOQSWY\^`a`^\XXY\aeikkjigc^XQJ@6.(&&&))*+,+*('(()**+/37;=?@BDGIJJKLNOOOONMLIEDDEFGJMOPQSUWXVTSSTVYZZWVUROKF<335:=@BDGHJLNOQSWYZ[]^_`abdefffeeeeeeeeefknpqrrrrrrqqqpppooppppqqpommmmmmllllllljjjjjjjjjjjjjjjjjiijjjjkklllllkkkjijihgg`P:+'&&%%%%%&&$$#$$$%%%%$$###$$%%%$$$$$$$$$$$$$$$%%%%%%%%%$###""""! !""! =;9520//011123345666777766666666777777787776554210.-,+*((&%$"! #%+15:@FLQW[]_aa```___^]]\\[[ZYYXXVUUTTSSRRRRRSSTUVVWXXXY[[\\\]]\\\[[[ZZZYZ[\]]^^^^^^^^^^________`aabbccdeeeeeeeeeeeeeeeeefffggghijjjjkkkkkllmmnoppppppppqqqqqqqqstuvwwxxyyyz{|}}~}{zzzyyyyxwuutsrqponqpppqqppoooonnmmnnllljihhhggfedcba_]\[ZZYWTQNKK@>7/)%"  !##%&(*/47CJQUZ^`aaceeddcbba_^\[ZYYWWVVUUTTSSSSTTUUUVVWWWXYZZZZZZZZZYYYXXXXYZ[\\]]^^^^^^^^________`aabbccdeeeeeeeeedeeeeeeeefffggghjjjkkkkkklmmnnoppppppppqqqqqqqqstuvwxxyyyzz{|}}~~|{zzyyyyxxxwvuutsrrqqqpqqpppooonnmmnnnllljhhgfeddcba`^\ZXWWXVSNLHG=<6/*&#!!"#$#"#""$&)-2:?CFJMNQRUXZ\]_^[[[]^aegkmnnlhc]XQIA91+*..//010-****)))+))+-18;ABEHJJKLMMMMMLKIECBCCEHKNOPRTVXYWXXVVUUVRSRPOKD=769=@BEGJKMOQRTVY\\^_`abbcdefffeeeeeeeeefknpqrrrrqqqpppponnoooopponllllllkkkkkkkhhhhhhhhihhhhhhhhhhiiiijikkkkjjjigffffefaT=+%$%$%$%$%%##"#$$%%%%$#""##$%%%$$#"#$$$$$$$$$$%%%%%%%%%#""""!!!  !!!"""!  !#" 986421112334455667778889999999987777778898888876653321100/-,+)'%$"! !%*05<::BDEHKNRTVWY[]_``abcddcdddeeeeefgggggggilopqqrqpppooooonnooonmmllkkjiihgfffeeedddddddddddddddddeeeeeeefffffeeededcbaa`^YL;0*'&%$$##"##$%%%%%%%%&%$##"""#$$$$$$$$#######$$##"! !!"""""!  665556777888999:;;;;<<<<<<<<<<<;;;;;;;::9987777778877777777654331.-,+))'&%$#"!  #&*/5;@DFJNSVX^abcddeda``__^^\ZZYXWWVVXXWWWWWXYYXXWWWXWWVVVUVXYYZ[\]]^_______````````aabbbccdeeeeeeeeeefffffffggghhhhikkklllmmmmnnoooppppppppqqrrssttuvvwxyyzz{{||}~~}||||{{{zyzxwvvttttsssrssrrqpoolllkkkjjjigeda_]\\\\\[[Y\^_`bcdhmmljhgge_^[YWURPPNKIGFDB??<97889=@DGIJLOQUWXZ\^_`bbccddeeffffffffggggggghilopppqqpppoooooonoonmmllkjjihgffffeeeeccccccccdcccccccceeeeeeeeeeeedddcdcba``_]ZO?3+('&$$##"##$%%%%%%%%&%%##"!!#$$$$$$$#"""####$$$#"!  !!!"!!  7777789:;;;;;;;;<<<<<<<<========;;;;;;;;::9877777788888888777777643210/.-,*)('&%$" !#&(-259>DIMQUY\`bdeddddbaaa]]\[ZYYZ[[ZZZZYZZYYYXXXYYYYXXXZ[YZZ[\]]^_______aaaaaaaabbcccdddeeeffffgggggggggghhhiiijkkklllllmmmnnooopppppqqqrqrrsttuuvvwxyzzz{||}~~}}}}}~}}{{zyxwvvvuutsssrrssqpoolllkkkjjjiihgebaaaceeffgklmlmlmnnnljfdcbca^][ZXVXXWWWWVVSPJC<768@CFJKMOQSVX[]^^]\\]`dilmndXLB:766654332000/.-,*'%$$%'()+,-01367:??????><8751.-.19@GKLLMPQRQPONNKJFB>;9>>>>>=<<<<<<<<;;:9877777888888899999998665432110-,,+*)'%##"!  !$'),05:=BIOSW[^`abccbbbbaa`^]]]^__^^^^]\[ZZZYYZZZZZZZZ\^^^^^^^^_``````abbbbbbbbbccccdddeeffgghhihhhhhhhhiiijjjkklllllllmmmnnnoopppqqqrrrrrsstuuvvwwxxyyz{|||}~~~~~~~~~}||{zyxwvxwwvuttsrrqrpooolllkkkkjjkjjjhedghiklmmnmmnnnnnmnnljgedbcb`^^\[Z\\\]]^^][YUOG=7379=AEHJKNPRUXY[[YYZ[]aca]SJA;76765432111210.-,*'&&&'()*,.//13455666665434320/./116;?CFJLLMMLKJJFC?;:;>CIKMNOQSUXZ[\^_abbddddeeeffggggggghhhhhhhiilopppqqqpppoopoomnmmllkkihgffeddddccccbbbbbbbbbbbbbbbbbbbbbbbbcbbbbaaaa``^]\\[ZXPB5,(('%$##"##$%%%%%%%%$########$$$$$$$#"""####%%$""    "#$%&(),,-,*:::::;<==>>>>>>>>>>>>>>>>??????>========<<;:98888788888889::::::98987666542110/.-*('&%$#""! !$(+-19>DHMRUX[^``abb`aa`__````____^]\\[[[Z[[[[[[[[]^^^^^^^_`aaaaaabccccccccccccddddeeffgghhihhhhhhhhiiijjjkklllllllmmmnnnoopppqqqrrrrsstuuuvwwxxxxyz{||||}}~~}~~~~}}}|{zyxzyxwvuutsrqppoonllllkklllllmmkihjkklmmmmjjkkllmllljifecbaaa``_^]^__^^_^\[[ZVNF=5357;>ACFHJLORTWXVVUUVUSOHB=98776654322100/.-,,******+-.//124666544444333554433320369=?CEEEFEDCB>=;:;>BHNOPQRTUWY[\]_`abcdddeeeefgggggggghhhhhhhiilopppqqqpppoooonlkkjjiihgffeddcccbbbbabbbbbbbbbbbbbbbbbbbbbbbbbaaa```___^]\[[ZXXQD7,(((&$##"##$$$$$$$$$$#######$$$$$$$$$#######%$"!!     "$%'*,-/0112220-<<<<=>>>???????????????????????????????>==<;::98888888888:;;;;;;::;;:999986654432/-,+*)('&$#"!  "$%).38=AEJNRVY[]^^`aaabbaaa````_^^]]]\\\\\\\\\\^________`aaaaaabcccccccccccdddeeeeffgghhihhhhhhhhiiijjjkklllllllmmmnnnoopppqqqrrrrssttuuvwwwxxxxyz{{|||}}}}}~~}}}}}}}{zyyxwvusrrrppppmmmmlmnnnmnoonlklllllllkmmmmllljkjjihhfcddccbaaaaa`_^^\Z[ZYUOHA833468;=?BDGIKMNPOPOOLIFA<:9877645543221///.--,,----./01102589::9:::::98899999985235678;===>>==;:<<=@CGLPSTUVXYZ\\]^`abccddeeeffgghhhhhhhhhhhhhhiilopppqqpppooonmlkihhggffedddcccbbaaaa`aaaaaaaaaaaaaaaaaaaaaaaaa`````___^^\[ZZYXXSG:-'('&$$#""##$$%$$$$$""##$$%%$$$$$$$$$%$$$####!!  !    "$%'-/13678::97542.*=>>?@@@@@AAAAAAAAAAAAAA@@@@@@@@@@@@@@@@??>=<;;:9988888888:;;;;;;::<;;:;;::99887763110/.--*(('&$#" #%),039=BGKOSV[^`abcdcccbbbba`___^^^^^______`a```````abbbbbbcccccccccccdddeeeeeffgghhihhhhhhhhiiijjjkklllllllmmmnnnoopppqqqrrqrrssttuvvwwwxxxyz{{{{||}|||}~~~~~~~~~~~~||{{zzyxvuttrqrroonnnoppnoppponmnnmmmmlkmmmmmmmlkkjijjgdeeddedddcba`_^\[\[XTNGA>:864567:=?BCEFFFGGFDA>;8877666433322111000////////001233147;>?>?AAAAA@?=;;<<==<:8777767889::::9;=@CFJNRTVWXYZ[\^^_`abcccdeeefffghhhhhhhhhiiiiiiiilopppqqpooonnmlkiggfeeddcccccccaaa````aaaaaaaa`aaaaaaaa_____________^^^\[ZYXXWVXUL>0(''&%$$#"!!#%%%$$##"##$$%%%$$$$$$$$$$$$###"!  !""   !"#&)+-/1369;=???>>;851,'"??@ABBBBBBBBBBBBAAAAAAAAAAAAAAA@@@@@@@@@??>=<<;;::::::::::::::::99;;;;<<<<<<;;;:976653212/-,,*)(&$##"! !#%*-15:>BGRW\_adeeffeeeedbaaa````_``````aa```````abbbbbbcccccccccddddeeeeeeffgghhihhhhhhhhiiijjjkklllllllmmmnnnoopppqqqrqqqrsstttuvvwwwxxyyzzz{{{||{{|}}}~}~~~~}}||{yxwwutstqqqppqrrpqrrrqpnnnmllkjjkkkllllklkjjjjgeeeeedddda`_^]][YXXWSLFCCA>;742469;=>@@>?@@=;988797754332110000000001111233223455258=@BCCDEEEDCA>???>>>?>=<;98877899:;<<@CEHKORTVZ[[[\]_a``abcccdeeeffffhhhhiiiiijjjjjjjjjlnoppqpoonnmmljhggfeedccbbbcccdbaa`````____`````____```__`_____]\\\]\\\[ZZYWWVVWUNA2)('&%$$#"!!#&%%$###"##$$%%%$$$$$$$$$###"""!!  !!!  "$&(*+.0369;=?@BCCCA@?;60+%!?@ACDDDCCCCCCCCCCBAAAAAAAAAAAAAAAAAAAAAA@@??>==<;;;;;;;;;;;;;;;;;;;;;;;;<=====<<;;:9887554420//-+))('&%$#!!  "%).28BGOV[_bfhhggggfddccbbbba``````aaaaaaaaaaccccccccdddddddddeeeeffffffgghhiiiiiiiiiiiiijjjkklllllllmmmnnnooppqqqrqqqqrrssttvvvvwwwwyyzzz{{{{z{|||}~}}~~~~}|{zyxvussssrrsttsssrrqoonmlllllkkklllllkklkjhgegfffeeeedcb`_^\YYYWRMGDCCCA?;63223456789<;9887767775432110/..//01112233445543446678;=@CFCEEFECAA@>??>>>>>>==<<<<=>>?@BCEHKMORTVX[\\\\]^`abbbcccdcdddeeeehhhiijjkklllkkkkllnopppponmlkjihfeedccbbaaaabbbbba``___^_^^^^_^^````a`__]]]\\\\\[[ZZY[ZZZZYYYVVUTSNB5+((&%$$##""$&&%%$####################"""""!!  "$&(*,-/1258;=>ACDEEFFECA?;61*% ABBDEEEEFDDDDDDDCDDCCCCCCCBBBBBBCCBBBBBBBAA@@?>>=<<<<===<;;;;;;;;;;;<<<<=>>>>=======<;:998875542111/-,+)('&&%$#"!  $(.4=GPW\_gggggggggffeedccbbbbbbbbbbbbbbbbccccccccddddddddddeeefffeffgghhiiiiiiiiiiijjjkkllllllllmmmmnnnnopppqqqqqqqqrssttuvvvvwwwxyyyzzz{zz{{|||}|}}~~~}|{zyxwwvvvuvvvuuuttsqomlkkkkkjkkmlmkljkjjihgefffeeeeedca`^]\YYXUQLHGHIIGEB<7400122345765556666654210/....///0011233357775556679:;?ADFFHHHHFEDAAA@@@?@?????@@@AABCEFHJNQRTVWXZ]]^^^_`abbcccddedddeeeefhhhijjkklmlllkkkmmnoppponmljihgfdccbba```````aaaa`_^^^^]^^]]^^]]______^^\\\[[[[[ZZYYYYYYXXXXWUUUSSOD7,('%$$$$$##&&&&%%$##""""""""#############""#" !#%&),-03568:<>@CEFHIIIIHHEB?<82+#BCCDEEFEGFEEEEDDDEEDDDDDDDCCCCCCDDDDDDDDCCBBBA@@>=>>>???>===<<<<<;;<<==>>=<===>>=>>?>>>===<:98768864310/.-,+*)(('&%$"!""! ")09DMV^`cfghhfdeedddcddbbbbba``aaabbbbbbbbbbbbcddddddddddddeeefeefffggghhhhhhhiiijjjkkllllllllmllmmmmmnoooooopqqqqqrssttuvvvvwwwxxyyyyzzz{{{|||||}}~~}|{{yxxxwwwwwvvvuutromlkkkkkjklmllkljkjjihfdeedddddccba_^][YYWUQMJJLLMMKHB=70112223465555666654310//,-01223422345557998867769:;@BCEFHIIKLKIHFEB<841+$BCCEEEFFHFFFEEEDDEEEEEEEEEDDDDDDDEDDDDDDDCDCCBBA????@@@@@?>>>>=<<;<<<====<<<==>>>>????@@@A@>=<;;;;:876543210/..-,+)('&&&&%$#""! %,4?HPX_beffgebcdcccdffddeedcbabbbbbbbbbbbbbbbcdddddddddddddddedeeefffggggghhhhjjjjjjjkkklllmllllllllmmnnnnnnopqqqqrrrsstttuuuuvwxxxyyyyyzzz{{{{||}}~~~~~}{{{zzzyyxxxwwwusponmlkkkkkkjkkjkjkjjihfdedddccccba`^]][YYWTQOMNOMNONJE?933344445666555675431/../,-/1257889:;<<<=>=<;:997<<<<>ACEGGHHGGFDCCBDCCCDDDEEFFFFGHIKNPRTVWWYZ[[]__``aabccccdeeffeffffggghhijjkkklmlllkklllmmnmmljhgfdcba````__^^^^^___``a`__^^^]^]\\[[ZZ\\\\\]]][[[ZZZZYYXXXXWWWVVVUUTTSSSPI=1($&&''(())'&%%$###########""#########$$$$%%#!!!  """#$$""#$%%&(+-/3579<>@BDEFGHIJJIIJJHHEBA;72,($CDDFFFGGHGFFFFEEDEEEEEEEEEDDDDDDEEEEEEEEDDDDDCBBA@AAABBBAA@@@??>>=<<<====<<<=====>>???@@@AA@@@??@?>=<;::98766544310.-,,,,+*))('&%%%#""#"!! #*2<;9<===>@BCGGHGGFEECCCEDEFFGGHHIIIIJJLNPRTWYZZ[\]]^_``abbbcccddeeffffffgggghiijjkklmmlllkkkllllllkjgfedba`__``_____^^^___```__^^^]]^][[ZZYY[[[[[\\\ZZZYYYYYXXWWWWWVVVUUUSSRRQPKA2)%&&&''(((&%%$##""#########""""""""###$$$%$#""! !"%(())(''''()*,,.0369;=?ACCEGHIJKJKKJHGHGEC@>;61+&#&DEEGGGHHIHGGFFFFEFFFFFFFFFEEEEEEEFFFFFFFEEEEDDCCBBBCCCDDCCCBBBA@@?>>>=============>???@@@AABCCBBCBBAA@@?>=<<;;::98643222210//..-,++)('('&&%$###"!!!!!  &-4>FNU]`bccca^][YXY[_acdefffeedccbaaaabbbbbbbcdddddddddddddddedeeefffgggfggghhiiiiiijkkkklllllllllllllmmmmmmnooppqqqrrrrrssstttuuvvvwwxxxyyyzz{{{{|||}~~~~~}|{zxwwvurqponlkjihffggeffgfgfedabaaabbba^]\[ZZXVVURPONNOPPPNKFA>:7666665777777654321000//146:?CFKKLMMMMMIIGEB@>;<;<<>>AAFFFGGFFEDCDFEFGHIJJKKLLKLMNPRTWY[\\]]^^_`aabbcccccdeeffffffggghhiijjkkllmmlllkkkjjkkjihgedca`_^]]^^]]^^^]]^^^^___^^]]]\\]\ZZZYXXZZYZZ[\\ZZZYYYYXWWVVVVVVUUUTTRRRQOOLC6*%&&&&&&''%%$##"!!"#######"""!!!!!!""""##$##""!  !"##$&(*-....-.,**++,,.01369>>>>>=====>???@@?@ABCCCCDDCCCCCCBAAA@@??>=<:988886555432111/.---+++*)))('''&&&%%%$$$#"""""!  !%+18AHPW^acccb`][WTQPQTW\_acdeffeddddccbbbbbbbbcdddddddddddddddedeeefffgggffggghiiiiiiijjkkkllllllllllllmmmmmmmnnppppqqqqrrrrsssstttuuuvwxxxxyyyzzz{{{{|}}}~~~~~}|{zxwwvusrqpolljgfeddddeefeeeca__^^^__][ZZYXXWVUUSRPNMMNNNMLHDA?;:88998788877776765554547:>BFILOOPPQQQQPPNLJHFB=:::;=>??EEFGGFFEDEFFHHIKKLLMMNNMNOPRTVX[]]]^^___abbccddddddeeffgffggghhhiijjklllmmlllkkkhhhhgfedca`_]\[[[\\\\\\\[\\]]]]^^]\\\\[[\[ZZYYWWYYYYYZ[[YYYXXXWWWWVUUUTTTSSSSRQQONOMD7+&%$$$$$$$$$$##"!!!""""""""!  !"##$#""!  "%'(*,.011122/000///146789:;<>@ADEEFGGHIIIIIIIIFCA@?=;62.)$ !.>>>>>???@@?@@ABBCCDDDDDEEEFEEEDDDDCBA@?>>>><;;;::9877543432221100/---,,+++*))('&&&&&&%$$$$$$$##"""  !#%&*05;DKRX`ceedca]ZUQMJJKMTWZ\^`abbcccddecbbbbbbbcdddddddddddddddedeeefffgggfffgggiiiiiiijjjkkklllllllllllllllllmnnooppppqqqqrrrrssssstttuwwwwxxxyyyyzzzz{|||}}}}~~~~~~~~~~}|{zxwwvutsrrpnmjifebccccddcdcb`]]\\\]][YWVVVVVUUUTRQPONNNNMJGDA@@?>>>>>=>>=<<<<<>====<<<=?DIMQRQQRRSSSSSUTQPOMG?99:;====CDEFGGFFEFFHJKKMMMNNOOOOPQRTVXZ\^^^^___`bbcddeddddeeffgggggghhhhijkkklllmmllkkjigfeedcba`_^][ZZZZZZZZZZZZZZ[[[[[ZZZZYYXXYYXXXXWWYYYWWXXXVVVYYXVVWWUUUSSSRRRRQQQQQNOOI=1)&%%%%%%%%$$$##""!!!!!!!!!  !"##""!! !""#%&')+,,-4566544523343346679:;=>?ACCEFFFGFGGHHGFFFEC@?>:852-'$&3BMYGGGGHHIJKIHHHGGGGGGGGGGGGGFFFFFFGGGGGGGGGGGGGFFEEFFFGGGGHHHGGGFGGGGFEDCCBAA@@?????????@@?@AABBDDDDDDEEFGGGGGFFFGGGFFEDDDDCBBBA@??>=<;;;:::988876444332110//.-,,,,,,+****,,+**)((&%%%$$$$%&&&(*,-17@ABCDEFFFGIHHIIHGFDC@=:841.*'$!*9FQ[dHHHHHIIJJIIIHHGGGGGGGGGGGGGGGGGGGGHHHHHHHHGGGGFFGGHHIIIIJJIIIIIIIIIHGFEDCBAAA@@????????@??@@@ABCDDCDDDEFGHHHHHIJJIJKJIIIHGFFFEEDDCCCBAA@@?>==<<;887766654454322223210000210//.-,+*)))(((*+++-/127AFF=2)''%%%##$$$$$$$$#"""""""!     ! !#$%%'(*+,,-/1122235567887554432334577788;;<=>?@@BCDEFFFEGGFFGFDB@?<9730,($!"-9IV^fnJJJJJJJJJJIIHHGGGGHHHHHHHHHHHHHHHHHIIIIIIHHGGGGGHIJKKKJJKKKKKKKKKKKKKKKKKIHGFEDCBA@@@@@@??@@@@ABCDCCCCDEFGGHHIIJJJJJJKKKLLLLLKKJJJJJIIHHHGGFEDCCBBAA@>>=====;::::;;:9999::987765210/..../0013589>BGMSY^bijllligec^YURQQQQPPOONNNPPPQSSSVXZ\]`abcddddddccccddddddddeeeefffeeeeeffhhhhhhhiijjjkkklkkkkkkkkkkkkkklmmnnnnooopqqqqrrrrrrsssstttuuuvvwwwxxxxyyzz{{{{||{{{{{||||}}}}~}}|{yxxxxvvttsrtsurmidba`]]]^^^_bggihiijkiiiiiiiicbaaaaaa_`_^^___acdddddca_\YWTPNNNNMMNOONNNOQRUWZYYYYYZZXZZWWWJ74466789>?CDEFFFFHIIJKKMMPPQQRSTUWWYZ\]_``aaabbcdeeeeeeeefgghhiiijjkkkllllllllllkjjihgfedbba`_^^]]\ZYYYYYXXXXXXXXXYYYYYYYXWVVVVUUVWWWWWWWVVWUUUVVUUUVUUTSSTQTRSQQPQSPME?>GVfqqcH3*'&&%$#####$$$$$$#######"!   !""#"#%%%&&')*++--.001124555556677665321112124568899:<<<=??@@CCDFFFEFGFFFFDA?;9741/*'!$2>JXahnuJJJJJJJJJJIIHHGGGGHHHHHHHHHHHHHHHHIIIIIIIIHGGGGGHIJKKKJJKKKKKKKKKKKKKKKKKJIHGFEDDCCBBBAA@@@@@@AACCCBBCCDFGGHHIIJJJJJJKKKLLLLLLLKKKKKKKKKJJJIHHHGGFFEEDDCBCCBA????@?>====>>====;:7543222245679;>?BEKPV[`dijlmljgfda]ZWVVVVUUUTTTSQQPPPPOPQSUWZ\^`ccddccbbbccddddeddeeeefffeeeeeeegggggggggghhhijkkkkkkkkkkkkkkklllmmmnnnnnpppqqqqrrrrssssssttttuvvvwwwwxxyyzzzz{{zzzzz{||||}}}}~~}}|{yxxxxvvvtttvuwtsolhfeddffhilpssssrqqrqpppoonlgfeedeeeecbaaaabdfhhhhhhheb_]ZVSPPQPPQRRRQQQRSUVYXXXXXXYWYXVSM?2345567:>@EFGHHHHIIIJKKMMPPQQRSUWYY[\]^_aaaabbbceeeeeeeeffgghhiijjkkkkllllllllllkihhfedcba``_^]]\\[ZYXXXXXXXXXXXXXWWWWWWWVUUUUUUUVWWWWWWWUUVTTUVVUUUVUTTTRRQRNRQRQSGC?J`ynM4+('&$$$#"""##$$$$#######"!  !"$')*++,.,-.-./1002323454555545556665432101223346779:;<===?@@AAADDFGGFFEFEDCCA><753.+(%""*4BOYcinsyKKKKKKKKKJIIHHGGGGHHHHHHHHHHHHHHHHIIIIIIIIHGGGGGHIJKKKJJKKKKKKKKKLLLLLLLLKJIIHGFFEEDCCCBAAAA@@AACCBBBBCDEGGHHIIJJJJJJKKKLLMMMMMLLLMNOOONNNNMMMMMLLKKKJJIIIJJHFFFFFEDCCCCDDCDDBA@><;:8888:;<=?BDFHKPUZ_dgjkmmmkiggfc`^]]]]\\\\\\[XWUTRPNMKMOQTUW[_`abbbbbcccdddddddddeeefeeeeeeeegggggggggghhhhjkjjjjjjjkkkkkkkkllmmmmmmmmnooppqqrrrrrrrrrrsssstuvvvvwwwwxxyyyzzzzzzzzz{{|||}}}}}~~~~~~~~}}|{yxxxxvvvvvvvvxvvusqppnpqsuvz}zzyyxxvuvvtsrromlkjiiijjgfdccbccfhjjjkkkkigec`][VVUTSTVWVWWWWWWWXWWWWWWYXXXWN?0-223469;>@BDEEFHIIIJLMMNOPQRSSTVXZZ\]^_`aabbbccdefffffffffgghhiijkkkkllllmlllkkkjhgfedcba`__^]\[[[ZYXXXXXXXXXXXXXWVVVVVVVUTTTTUUUVVVVVVVVTTUSSTUUTTTUTRRRPPTUSSMKFBJa}zhR=/('%$$$#"!!"##$$########"!  """#%'()*-0244547666545535666666566642443443211101223456788:;<==<:841-*%"$/;ER\dkptz~KKKKKKKKKJIIHHGGGGHHHHHHHHHHHHHHHHIIIIIIIIHGGGGGHIJKKKJJKKKKKKKKKLLLLLLLLKKKJJIIHHGFEEEDCCCBAABBCCBAABCDEGGHHIIJJJJJJKKKLLMNNNNMMMNOPQQQQQQPPPPPPPOONMMMMMNMLKKKKKKJJJJJJJJJIHGFCBA?>===>?@BDGJLMPUY]bfilmnnnlkiihgdcbddccccccccba_\YVUSOOPQQQQS[]^_``abccddddddddddddeeeeeeeeeefffffffgggghhhijjjjjjjjkkkkkkkklllllllllmnnoopqqrrrrrrrrrrrsssstuuvvvwwwxxxxyyyzyyyyyz{{{{||||||}}}}}}}~~~~~~~~~~}|{zzzzwwvvvwwxxwwxvuuuwwxyzz|}xxxxxxwvxwutsrqopnmllmmmjifecccdghikllllmmligeb`\[YWUVXY[\]]\\[ZYXXXXXYZ[Z[YK4')11357:?@?@E@><99884345456554433223110/1001122347899::;<>>>?@@@ABAACDDCBB@??=<:9743/,)$ (3>IS]ejptx{~LLLLLLLLLKIIHHGGGGHHHHHHHHHHHHHHHHIIIIIIIIHGGGGGIJKLLLKKKKKKKKKKKLLLLLLLLLLLLKKKJJJHHGGGFEFDCCCCDCBBBBCDEGGHHIIJJJJJJKKKLLMNNNNNNNOPQQQQRRRQQQQQQQQQQPPPOPPPPOOOOPPPPPPPPPPPPONKHFECA@@@ABDFHKNQSUY]`dhjmnooonmlkjihghiihgggghhiihfea`_\WWVVUUTUY[]^__`bccddeeedecccddddeeeeeeeefffffffffggghhijjjjjjjjkkkkkkkklkllllllllnnooppqqqqqqqqqrrrrssstuuuuvvvwwwwxxxxyyyyyyyzzz{{{|||||||||||}}}}}}}~~~}||{{{xwwwwwwxzzyyyyxzxyyyyzyxyyzzzzzzyxwvutsrsqponmmmljheedeehijllmmmnomkigdb`^\YVVXZ\^___^]\\[[[[[\]__`ZG0&*0136:;=@BFHIJKLNJKKLMNOOTUVVWXY[]^^_`aabbcccddeffffffffffgghhiijkklllmmmkkjjihhgdcba`_^^_^^]\[[ZZYXWWWWWWWWWWWWWVTTTTTTTTSTTTUUVUSRRRRRRQQRQPQRRRSRQRSTUUUVNCNaĽn_VJ7)'%###$!!!"""#########"!  #!!!%$#$$$%&)'(())())*)*-,-.021.0/--.-*(*++++,-/0358:>>??@?@BBBAAAAAA@@?=;986530.)&# "(/:CMU]flptwz{|LLLLLLLLLKIIHHGGGGHHHHHHHHHHHHHHHHIIIIIIIIHGGGGGIJKLLLKKKKKKKKKKKLLLLLLLLLLLLLMMLLLKJJIIHHHGFEEEECCBBCCDFGGHHIIJJJJJJKKKLLMNNNNOOOPPQQQQRRRQQQQQRRQQRRRRRSSSTTTTTTTSRRRRRSSSSRONKHGEDBBBDFGILORUXZ]`cfiknooppoonmlkkklmmkjjjkkllmkjjhhge__^^\\\\\]_```accddeeeecdcbcccdddeeeeeeeffffffffffggghijjjjjjjjkkkkkkkkkkllllllllmnnooppqqqqqqqqqqrrrsstttuuuuvvvvvwwwwxxxxxxyyzzz{{{{{{||||||||}}}}}}}~}~~}}|{yyyxxwxw}|zzyyz{xyyzz{zyyyyyyyyy{zyxxwvuvtsqponmmkigfgfffgijkkllppomkhecb`]ZWVXZ^`abba`__^^^]^_`decWC0(+/038:=>BDHJKLMNPLMMMNOOOVWWXYZ[]__``abbcccdddeeffffffffffgghhiijllllmmmmjihggfeecba``_____^]\[ZZYXXWWWWXWWWWWWWWUTTTTTTTTTTTUUUURQQQQQQQQQROQRSSRSUSUTTRQO>Jczk^TI>1(! ""!!!!"""""""""""!  !""""!"!"""!"!"#%&'''&'',+++//01224589:;<<==>>><==>?@CEE<96442/.0.--./3768>>>>?A@@A@@@@A@A?=<:97541//-+'$! !%)3:JTZaenqtwxyyxLLLLLLLLLKIIHHGGFGHHHHHHHHHHHHHHHHHIIIIIIIHGGGGGHIJKKKJJKKKKKKKKMMMMMMMMNNNNNNOOMMMMLLLLKIHGGGGGECCBBBCCFGHHHIIJJJJJJKKKLLMMMNNONNNOOPPPQRRQRRRRSSRSSRRRRRSSTTTTUTTTTTTTUUVUSRPNLJIGEDCCCEGILPSV[]acehlnpqpqqpoommmmmmnlkklllmmossqqooolljihgfeeddeeeccdeeeddddccccdddeeeeeeeeeefffffffffffggghjiiiiiiijkkkkkkkkklllllllmmnnoopqqqqqqqqqqqrrrrsstttuuuuuuvvvvwxxxxxxxxxyyyzzz{{zzzzzzz{{{{{{{{|~}~}|{zyyyyzz{{||{{zyyzzzzzzzyyyyyyyzyyxwvvututsqppomkjhffefffgijllllmnnlkigdca_[YXY[\^`babba^__`a`__^baR<-(*1348<@CHKLMNOPQQQRQRRRSVXYYZ[\]___``aabbdddddeeffgggggggghhhiijjjkkkkjjjhfeeddccba``````_^^]\[ZYYXXXXXXXWWWWWWWWTTTTTTTTTSTTSTSTQPPPPPPPQOPQQPRRQQPQTSOQCRhĿteXPJB5*" "!!!!!!""""""""""!  ! "! "$$$&'(*,--.../..//.0101322235799;>?@CDFGGKJKKNPQRTVY[\\[YURNLKID@<8631///./268:BEEJNRUVX[\[[[YVJFA;6433233222111/-..../-.//0123356789:;<>?????@@@@AA@??>==<;9867520/+)%#! "%)1;CLT[bgkquwxxwtrNNNMMMLLKJIIHHGGGHHHHHHHHHHHHHHHHHHIIIIIIHHGGGGGHIJKKKJJKKKKKKKKMMMMNNNNNNNNNOOONNNNMMMMLJIIHHHHGEEDCCCDFHIIIJJJJKKKKLLLMLMMMNNOMNNNOOPPQRRQRRRRSSRRRSSSSSSSSSSSTUUUUUUUVXWVUSQOKJHEDBAABADHLPTW^`cfgjmoqqqqqqpponnooopnmmnnnnopsssssssoppoonnmljkkkiigghgfecbbbaabbbdddcddeefffeeeeeeeeffgggghhhiiiiiiiklllllllkkkkkkkklmmnnoopppppppppqqqqrrrssttttttuuuuvwwwxxxxxxxyzyyyzzzzzzzzzz{{{{{{{{}~~~~~~~~~~~}{{{{{{{|||||{zyyyyyyyyzyyyyyyzzzyyxwvvuuutrqpnljhfeeeeeghikllllmmmlkhfcca^[YXZ[\^`bbbba`accbba_`]VE4+),0259>BEIKMNNPQRSSSSTTTUWYZ[\\]^```aabbbbddddeefggghhhhhhhhhhiijjjjjjjiiigfddcbbba```````_^^]\[ZYYXXXXXXXYYYYYYYYWWWVVUUUSTTTTTTRQQQQQQQQOPPQRSSSRSSURJBIe¾{naVPKF9,$!!"!!!!!!!!!!!!!!!!!  !!! !  ! ! !"   !"##"!!!""""$$$$##""&),.1468:;<=>?@@?@@??@>>???@BCEHJJNOQSVWXY^]^`acdegiklnppolg`ZTPFB>:730.--.0479@FHKPUX[]\]```_\WNHB;754311100/.--,+,,--../01233446789;<=>>>??@@@@@@@@?>><<;9865331.,)'%#$$%',27?HOW^dilosvwxvqliOOONNMLKKJJIIHHGGGGGGGGGHHHHHHHHHHHHHHHHHHGGGGGGHIJKKKJJKKKKKKKKMMMMNNNNNNNOOOOONOOOOOOONMMLLLLKIGFEDDDEHJJJJKKKKKKKLLMMNMMMNNNNMMNNOOPPRRRRSSSSSSSSSTTTTTTTTTTTTTTTUUVVVVUUTRQOLJHEBA@@@@BGLQVZ]`fiklnorrqqqqpppoopppppnnooopprtuuvwwwvwvvuutturrqpnnlkkigeb`^]\\]^`abddeededeeeeeeeeeefffggghhhiiiiiiikllllllllkkkkkkklmmmnnoopppppppppqqqqrrrsssssssttttuuvvwwwwwwwwxwwxxxyyyzzzzz{{{{{{{{|}}~~~~~~~~~}}||}}}~~~}||zyyyyyyyzzyyyyyyzzzyyxwvuuuutrpoljgfdddcddgijllllllmlkigdcc`^\[YZ[^_acdddcaabcaa_^]SE7-+++/26;?CFJLMNOPRSTTUTUVVWX[[\]^_`aaaabbcccdddeefggghhhhhhhhhhhiiiijjiiihhhfeccbaaa`_________^]\[ZYZYYXXXWWWWWWXXXXVUUUTTTSTTSTSSSRQQQQQQPQOQSRRRSSUSTOFKc¾vi^TOKG=0($""!!!!!!!!!!!!!!!! !! !#$"   "! !!!"!!! "#$%&&&()+(&&%$"""""#$$$%$$$$$$%%&&%%%%$$+/37<@DGIJKLNOPQRQQPOMLKKLLNPPRTVVZ[^``bfejjlmnppprtvvx{|}{xqi`YJC>830,)'()-39>>>>????A@@?>===;;97532/--+)(&%&')-18?DKTZafknprsuutpjdaQQPPONMMLKJJIIHHHGGGGGGGHHHHHHHHHHHHHHHHHHGGGGGGHIJKKKJJKKKKKKKKLLLMMMMMMMNOOOOONOOOOOOONNMMMLLLJHGFFFFGIKKKKLLLKLLLMMNNONNNNNNNNNNOOOPPRRSTTTTTSTUUUUUUVWWWWWWWVUUUVVWWVVVUTRPOKJHEB@??@?AFKPVZ^agjlmoprrqqrrqqppppppppoooopqstuuvwxxxxxxwwvvvvwwutrrqpnligc_\ZXXY[]_bdffffedefefgggfecdeeeffgghiiijjjkkjjjjjjjkjkkkkkkklmmmmmnoooooooopppqqqqrrrrrrssssttttuuvvvvvvvvvvvvwwwxyyyyyyyyyyyyyy{||}}}}}}}}~~~~~~~}}~~}|{zzzyyyyzzyyyyyyzzzyxwwvututsrpnkhfecccbccfijlkkkkklkjhfdcb`_]\\\]^_abccdcaabba_]ZRE7-++++.27<@DFKMNPQSTUUVVVVWXXZ\\^_`abcaabbbbbccdeeffgghhiiiiiiiiihhhhhhhhhhgggedcbbaa``__________^][ZZZYYXXXWWWWWWWWWVTTTTTSSSSSSRQQPPQQQQQQPQRRSUUTTUTQIFX¿~pcYRNKI@4+&$# !!! !!"! !#%$$#""!  !!""""""###$(&$%&&'())*+++,.457789;=961.*'$###"####$"""####$$$$$$###-3:?EKORUVXY[\]^^^^][XVUTUVWYYZ\]\_`beghmkonpqruvvvy{{z~~~{umcUKA8/*&$!$(-39>GIKNSUY[]]_abcc`\RJB92/.,,,,+++*****+,,-.02334566699:;<=>>>>?????@@?><;::986431/,)))(((),/38>EKQW]chloqstrqplgb][RRRQPOONMKKKJIIHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGHIJKKKJJKKKKKKKKLLLMMMMMMMNOOOOONOOOOOOOOONNNMMMKIIHHHHIKLLMMMNNMMMMNNOOPOOOOOOOOOOPPPQQSSTUVVVVUVVWWWXXXYZZZZZZXWWXXXXYXWWVTRPNKJHEB@?>??AEKPVZ`cilnoqrtsrrrrqqqqqqqqqpppppprtuvwxyzz{z{{zyyxxxxwvuussqpmkhea^[ZXWXY\`begghggggfffffeeddefffgggghiiijjjjjjjjjjjjjjjjjjjklllllllnnnnnnnoppppqqqqqqqqrrrssstttttuuuuuuuuvuvvwwwxyyyyyyyyyyyyyy{||||||||||}}}~~~~}|{{zzzyyzzyyyzzzzzzywwvuuttsrqomjgedbbbbcbehijiiijiihgfedcba_^^]]]^_`abcdca__`^\WPD:0,,+**.37<@EGLNOQRTUVWWWWWXXYZ\]^_`abcbbbbbbbcceeffghhhiiiiiiiiiihhhgggggggfffddccbaaa`````````__^]\[ZZYXWWWVUVVVVUUUURRRRRQQQQQQQQQQQQQQQQQQQTUUTUVWUPGKjxj^UPMKJB8/*%#!!!!  "$&%$%'*,,+*)(((('&%%%&&))**+++++,,-.---01257:;>????>>>>?><;:8875420/-,))*++-/158=BGNTY_eiloqrsrolgb]ZXWTTSSRQPONLLKKJJIIHHHHHHHIIIIIIIIIHHHHHHHHHGGGGGGHIJKKKJJKKKKKKKKLLLMMMMMMMNOOOOONOOOOOOOOOOONNNNLKJJJJJKMNNNOOOPONOOOPPQQQQQQQQQQQQQQQRRTTUVWWWWVWXXYYZZ[\]]]]]]\[[[[[[[ZZYXVTQOMLJGDB@???AEKPW\cfkoqrttuuttssrrrrrrrrrqqqppqsuvxyz{||}}~}}||{{zzyxwwvusromjgda\[WUTUW[_beghjjijhggffffffffffgggghhhhiiijjjjjjjjjjjjjjjjklllllllmmmmmmmnoopppqqqqqqqqrrrrssssssttttttttutuvvwwxyyyyyyyyyyyyyyz{{||||||||}}}~~~~~}|{{{zzzzzzyyzzzzyyyxwvuutttrqonmjgedbbbbcbeghihgghgfeeddcba`___^^___`abccb^]\][WPF80--.++,/48=AFINOPQSTVWWWXWXYYY[]^^_`abcbbbbbaacceffghhiiiiiiiiiijihhgfffffffeeeddddcbbbaaaaaaaaa`_^]\[ZZYYXWVUUVUUUTTSSOOOOOPPPPPPPQQQQRRRRRRRRRVUSTWVSGV{rcXRNLJHB;4-'#!""!! "$%$&,5=FFFECBBBBA@????@DDDDDDEEDCDDEEEFKORUWZ\]adeghjlnqrrrqokfWOB6,$+3;DJQW[_`abdefgeefda^\\YZ[\]^_`bbdeegiinnopqsuuwwwyyzyz||~{wpdWH8+ #*17<@DINRVWZ]__bdeggc`WOE;0+))()))))**++,./012344567888;;<<=>>??>>>===<;:8754300.-,+*)+-/147;@CGLQW\afjlnppppmid_YUTUVUUTSSRQPOMMLLKKJJIIIIIIIIIIIIIIIIHHHHHHHHHGGGGGGHIJKKKJJKKKKKKKKLLLLMMMMMMMONNOONOOOOOOOOPOOONNNLKKJJJKLNPPPPQQQPPPQQRRSSSSSSSSSSSSSSSTTVVXYYYYYWYZ[\\]]^_``````________^]\[YWTRPPNKHECBA@AFLRY^einrstvwwwuuttsrsssssssrqqqqrsuwxyz{|}~~~~}}||zzzzyyxwvsqolifc^[VTQPQTW[`dikmnmkjihggggggggfffffgghhhiijjjjjjjjjjjjjjjjklllllllmmmmmmmnoooopppqqqqqqqqrrrrsssstttttttttstuvvwxyyyyyyyyyyyyyyz{{{{{{{{{|}}}}~~~~}}|||{{{{{{zzzzzyxxxwvuuttssrponljgedbccbcceghhgfefeddccbbb``_`_______`aaa`][ZYUNF<2.-/.,,/15:?BGJOOPRSUVWXXYXYYZZ\^^_`aabcccbbaaacdfgghhiiiiiiiiiijjihgffeeeeeeddddeeeddccbbbbbbbba`_^]\[ZZYYXWVUUTTSSRQQQNNNNNOOOOPPPQQQQQQQRRRSSUVVWZWPRgzk^RNLJGGB<6/'#""##"!!!  ! #&*.5CUbttusqppqlkjiiijjmmmllllljiiklmlmmpqtxz|~{}zsk[QB6+!)1:AIQX]^_`acdeffeeca^]]ZZ[\]^_acbddeffgkjkmoqtuwwxxxyz{{{|~}xpeXH7)"#+39=ACGLPTWZ^``cfghifc[SI>2+(''(())**++,-/0234455678999;<<==>>>>>=<<;:99764320---,+++,0258CEHJMOSX]adgjkloomaZQF9-&%''()*+,,-./01234467789::::::;;;;<<;:9876333212100125799=ACGJNRVY\_begikmmlifd_YTRPOOPRTVUUUTTSRQPOONNMLLLJJJJJJJJJJJJJJJJJIIIHHHHHGGGGGGIIIJJJKKKKKKKKKKKKKKKKKKKKLMLLMMMOOOOOOOOOONNNMMLKKKLLMNPSSSSSSSSSTTUVVWWWWWWWWWYYY[[\\]]]_accccadfghijkjkkkkkkkkkjjiiihhggfedba``_]ZWTRKJJMRX`empuxz{|}yyxwwvuuwwwwwwwvutsstvwxyz|~~~}}zzyxwwvvusrqoligcb]ZTOJFDCFMV]gmrxyxxxvuuqonmlkjjihhhhihhiiiiiiiiijjjjjjjjklllllllllllllmmmmnnnoppppppppqqrrrrrrrqqqqqqpppqrtuuwxyyyyyyyyyyyyyzzzzzzzzzzz||||}}}~~~~}}}|||{zzyxwwusrrrqqqppoonmlkjigggfeedcdedddccbcccccccbbccb`^\ZYZZZYXXWTPMF@:5412210-.24:=BFINQTRSUWWYYZ[[[\\]\^```aaabbccdddddffghhhiiiiiiiihhhgggfeddbabbccddddfffeeedeedcbbbbba`___^][YWUSSSRRQQPOONMOPPQQRRSQRRSSSSSSSTUVVWXXXUUewfYNJHEB?<963.)&$"""! !""##%&&&''()**/48?HS`j}|voh]QD8-'#!#$-2:CKQW]_`acdefgfeecb_]]\[[\^^`cdcfghjkkhjkmoooqvwxyyz{{{{|{{zz{~~|ytodVG9459;@BEHLNQW]beinpqssre_WK>0'%()**+,-./0012344577889::::::::::<::9764323221222247:=?@DILNQTX\^acfhijkkhfb_\WRONNNOQSUVVVUTTSRRQPOONMMMMLLLKKKKKJJJJJJJKKJJIIHHHGGGGGGGIIIJJJKKKKKKKKKKKKKKKKKKKKKLLLLLLMMMMMMMMMMMMMMMLKKKLLMNQSSSSSSSSTUUVVWWXXXYYYZZ[[\\]]^^__`ceeffegjklmopppppppppqrrqqpppnmmlkjjigfeda^[XSQPSW\bhpsx|}}}}{zyxwwwxxxxxxxxwvuutuvwxz{|}~}||{zyyxwvuutsqqomjied_\XRMGDBBFLT]flvyz{|zzywtsrqponljiijjijjiiiiiiihiiiiiiijkkkkkkkllllllllmmmmnnnoooooooopqqqqrrrrqqqpppqqqrstuvwxyyyyyyyyyyyyyzzzyyyyyyyy{||||}}~~}}}}}||||{zyxvuusqpppooonnnmlkjjjhhhgfeedcddccccccbcccccccddca^\YWVWXWVUTTOHB>9766543210148=@EILQSVUUWYYZ[[]]]]^^^__``aabbcdddeeffgghiiiiiiiihhhhggfggfeddbabccccdddfffeeedddcbaaaaaa`_^^^\[ZWTSSSRQQPOONMMPPQQRSSSTTTTTUUUUVVVWXYZ[V[nsbTKGEB?:9531.+(&""! ! !!!!!!!"##$&),.46;?BFIJN[dmu~~ztnh^RF;2-)&$"  !#&)+38>EKPV^_`acdefgfeedb`^^\[\\^_`cedghijkkiknnnopotvwxyzz{{||||{zz}~}zxqeZNC<99=BCGKORX^bhmqttvvvke]RC4)'**+,--/013334556798889998;;:988786654432223344568:=@CEGLPRUX[^`bcdffggheb_[XUROMMMNPRSUVVVUTTSRRQPOONNMMMMMLLLLKKKKKKKKKKKKJJIIHHGGGGGGGIIIJJJKKKKKKKKKKKKKKKKKKKKKKKKLLKLLLLLLLLMMMMMMMLKKKLMNORTTTTTTTSTUVVWWXXYYZZ[[\\\]^^^__``bdffghhjklmopqqrrrsssstuuttsssrrqpponnlkkigda^XVUX[`fkqty|~}}}{zyxxwxxyyyzzyyxwwvvvwwyzz|}~~~}|{{yyxwvuutsrqpomllhgc`]XSNKEBDFKS\cntx{}~|{zxvuttrqpnmkkkkkjjjjjiiiiiiiijkkkkkkklllllllllllmmmmnnnnnnooppqqqqrrrqqppppqqqqrstuvvwwxxxxxxxxxxxxyyyyyyyyyy{{{{{||}~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||{zyxwutsrpooonnnnmmlkkjjihhhgfeeddcccbbcccbbcccccdeca`\YVUTSSTTROMGC>;866654421245:ACHMPTUXXXY[Z[\]^^^_______`abccdeefffgghghiiiiiiiihhhgggfggfeddbbcccccccdfffeeeddcba``````_^]]][[YXUSSRROOOONNNNRRRSSTTUUUUUUUUVXXXWXZ\^]dt{n\OHDB>;7510/.-*("!!  !!!!!!""!!!!!!"#"#######%$&),3:?NU`hosv|x|xsmf^TI?73/,*'&%$#!"!#$&).37AEILOQT\_`acdefgfeedca__\\\]__acfehhjkllklmooooostvwxyz{{||||{zz|}}}}|yrj`TH?;=@BEKPTZ_dlqtwxzzzoibVG8,)+,,-./0124445667798999999::9887676554332234568;>?BDGILORUVY\_abdddddccc`\ZWTQOMLLMNPRTWWVVUTTSRRQPPOONMMMMMMLLLLKKKKKKKKKKKJJIIHHGGHHHHHIIIJJJKKKKKKKKKKKKKKKKKKKKKKKKKKKLLLLLLLLLLLLLLLKJJKLNOQSUUUUUUUTUVVWWXXYYZZ[[\\]]^^^__`aabegghijlmnoqrtstuvvvwwxyxxxxwwxwwvvuuusrqpnkif_]\^adjnsvz}~~~~{{zyxxxxyyzzzzyxxxxxxxxyyz{|}~~~}}|{zzxxxwvuttsrqqonmmjifea]XSPJECCFLTZentx{~}||zxvusronnmllkkjjiijiiiiijkkkkjjjkkkkkkkkklllmmmmmmmmnnnoppqqqrrrqqpppppqqqqrstuuvwwwxxxxxxxxxxxxxxxxxyyyzzzzzzz{||||||||||||||||}}}}}}}~}}}~~~~~~}}}||||||||~~~}|{{{{{{{zyxwusqponmmmmmmllllkjiihggfeeddddabbbbbbbbbbbbbbdda_\XUSQPPPNLJHFB@=::75432212489=CEJNQUVYYZ[\[\]^_____`___``abccdefffggghfghhhhhhhhhhgggffggfeddbbcccccccdfffeeeddca`______]\\\[ZXXWUTSRQNNOOOOOOSTTUUVVWVVVVVVVWYYYYY[^bduvhVJEB?;741.---.,(#!  !""""""#$$$$$$$%&%&&&&&&&&'*.5>GN_ht}|wrkb]TJA97410.-,*)((##"!!!!"$&(,18@EQTWXWVUY_`acdefgfeeddb``]]]^_`bcffhijklllloonnoorstvwyz{{|}}|{zz{|||}}|yundXLD@?BEKQW^chpuy{|}~~rleYK;/+,-.//011245566778:999:9998887766655544434578:=@CFHKMOQTVWY[]_abcccb`_^]ZXVSQOMKKLMNQSVYZVVUTTSRRQPPOONNMNMMMMLLLKKKKKKKKKKKJJIIHHGHHHHHHIIIJJJKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJIJKLNQRUWWWWWWWVUVWWXYYYZZ[[\]]]^^__``aabcfhijklnpqrstvuvxxyyzz{||{{{{{zzyyxxxxxwwvurpmfdcdfhmqux|~|{zyyxyyzz{{{{zyyyyyyyyyyz{|}}~~}}|{zyyxxwwvuttsrrrponnmljjgc_\YRKHEDHNS^gmuz}}{xutsqponmllkkkkkjjjjjkkkjjjkkkkkkkkllmmmmnmmmmmmnnoopppqrrrqqpppppppppqrsstuvvvvvvvvvvvvwwwwwwwxxxxyyyyyyyyzz{{{{{{{{{{{{{{||||||}}||}}}~~~~}}}||||{{{{{{{}~~}}||{zzzzzzzzxwvtqomllllkkkkkllkjiihhfeeddccbc`aaaaaaabaaaaaaaa][WSPOMNMMLJFC?;6542110/01248;=@EGLPSVXZZZ[\\]]^_____`__``aabcdefffgggghffgggggggghgggfffggfeddbcdccccbbdfffeeeddba`_^^^^]\[[[ZYVUUSRQPOOOOPQQQQUVVWWXXYXXXXXXXYZ[[[[]`fpxqbQGA?;741/,,,./,*%"  !"""""""#######$&&&&&&&'('(((((((&)+/5>HO\ckqvyzz|{{{}||z~ytnh_[SIB;98453310..-)(('&''')+-29CLS]_aba\XY^`acdefgeeeeecaa_^^^`abeffhiijjkkllmmnnorrsuvxyz{|}}}{{{{{z{{|||zwqg\RJDFHMSZbhmrw{|~vpi]O@3../001233356677889:999:99987776656555566679<=@CFILNPRSUWY[[]^_aaaa`^\ZXWUTSPOMLKJLMORUY[\VVUTTSRRQQPPOONNNNNMMMLLKKKKKKKKKKKJJIIHHGHHHHHHJJJJJJKKKKKKKKKKKKKKKKKKKKKJJJJJKKKKKKKKKKKKKKKKIHIJLORSVXXXXXXXWWWXXYYZZ[[\\]]^^__``aabbcdgijklnpqrstvwwxyyzz{|}}}}}~~~}}}||||||{{{zwurmkiijlosxz~}|{zyyyz{{||||{zyyyyyyyyyz{||}}}}|{{zyyxxwwvvuttsssrqqqppomlifb`[UNIGGIMV`gow|}{ywvusrpponnmmmllkkkkkkkkkkkkkkkllllmmmmmmmmmmmmnooopqqqqpppooooppppqqrsstttuuuuuuuuuuuuuuuuvvvwwxxxxxxxxxyzzzzzzzzzzzzzzzzzzz{{{z{{{|}~~~~~~~~~~}}}|||{{{{zzzzzzz|}}~~~~~}}|{{zzzzzzzzyxvusqomkkkkkkkkkkkkjihhgfdddcba`_^```````ba``__^]\YVSPNMMMHJKJF>6.*+**+,,-/137;>ACHJNRVYZ[[[\]]]^^_____`__``abccdeggggggggfffffffffggggffffggfeddbcddccbbbdfffeeeddb`_^^^^^][ZZZYXUTSRQPPOOOQRRTTUWXXYYZZZZZZZZZZZ[\\\]_aht{}l\MC>=941/,++*,.-*(&"!!""""""$&&&%$$###$$$$%&&''''''('(((()))&))+06=CSX\aegijnmmostuuwwxyzzzz{{{{{{{{}}~~~~~~~{vqkd\WPGB<;;7777542210/..--...028AKV\`begf`[Z^_abcefffeeedcaa`___abeggjjkkllmnoooooopqrrsuvxyz{||||{{{{yz{{{|}|yskbXPNORV^flptw{}yslaSD70012234555777899:99999:99988877778777899:<>ADGILOPRSUVWY[\\]^^__^[[YWUTSRSRONMLKJLNQUY\__VVUTTSRRQQQPOONNNNNNMMMLLKKKKKKKKKKJJIIHHGHHHHHHJJJJJJJKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJIHIJMPRUWXXXXXXXXXXYYZZ[[\\]]^^__``aaabccdegijkmoprstuvxwxyz{{|}~~~~~|zwspnnooruz}}}{zzzzz||}}}}|{zyyyyyyyz{{|}}}}}||{zyyxxwwwvvuuututtstssrqpmkhfb\WPKHHJQYaiqx~~}|zxwvtssrqqppmllllllllllllllllllllllmmmmmmmmnnoooppppooonnnnooooopqqqrrsssssssssssssssstttuuuvvvvvvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyz{||||||||}}}}}}}}}}}~~~~~}}}}}}}}|||{{{{zzzzzzzzzzz{||||}}}~~}||{zzyyyyyyyyxvtsqomkkkkkkkkkkkkjihggfedddca_]\\^^^^^^^_^^]\[ZXVRPONMMLLPMG@81.(&&'(*,.0026:>ADGJLQUX[]]\\]^^^_^_____`__`aabcddeggggggggffeeeeeeefggfffffgffeddcdddccbbbcddddddcc`]]\\\\[\ZYXXXWTRQOOPQRQQRTUVVXYYYZ[[\\\\\\\\\\[\\\]_bhn{ygWI@;84/--,+++,-,*+*%"!  !"$$$$$#$&'&&%$$#"!!""""#$%&&&&&&'&''((((''''(+18=GKQW\]^```acfiiimmmmnoooqqqqqqqquuuvvvwwxxxxxxxxxyyzzzz{y{}}}}}}{{{{|||{zxuqlf^XQLEA<<<:9886644443211122458>GR[a`befe`\Z]_`bcdefffeeccbbaa``aeghhjjkkllmnoooooopqrrstuvwxyz{|||{||z{||{z|~}{ung^WWW[ahnrtw{~~ytmcVH:223345666678899::::99::999::99999:::;<=>>ADGJLNQSSUVVWXYZZZZZ[[[ZXWVTSRQQSRONMLKKMPTY]acdVVVUTTSRRQQQPPOONNNNMMMLLKKKKKKKKKJJIIHHHHIIIIIIJJJJJJKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJIIIIIIIIGHIKMPSUWYYYYYYYXXYYZ[[\\\]]^__`__``aabbcdgijklmmnpqrstvwxyzz{||~|zxutsstvz|~~|zyyz{{|}~~~~~}|{{{{{{||}}}~~~}|{zzyyxxwwwvvvvwvvwwvvwwwvuspnkgb]VQLKJMSZajqy~~~}|{{zyxwtsrqpoonmmmmmmmmmlllllllllllllllmnnmnnnmnnmmmmnnnmmnnooppqqqqrrrrrrrrrrrqrrrrsssuuuuvwwwvvvvvvvvwwwwwwxxwwwwwwwwwwxxxyz{{{{{{|||||||||||||||||{{{{{{{{zzzzzzyyyyyyyyyyz{{{{{|||}~~}|{{zzyyxxxxxwwvvusrqomkjijiikkkkkkihggfeddfec_\YXX[[[\\\]\[VXXSQONKJKLLRSPD?80*'&&'')+.01236:=@CEHMPRUXZ\^^]\\\]]]]]]^^^__`abccddefffffffeeeeeeeeeefffffffffedddcccccbbbbbbbcccbba[[[ZZZZ\ZYWWWVUSRQPPOPPQRSTUVVWYZZ[\\]]]]]]]]]]\[[\\]_bhrxsaRF@<72/,***)*,+*'--)$ !! ! !#"$$%%%%$$$$$#"!  !!""###$%%%$%''((('''&%%&')+,@CGIJMOOUUUVXZ\[___````abcccddfhjklmnnnononnnnnoooppooqsttvvvuvwwvwwwwvruslhc]VOID@=;::9::9776666543334478?AABEILNPRSTUVWWWVWXVVVUUUUUVUUTSSRRRQPNMMLLPTY_dgiiVVUUTTSSRQQPPPOONNNMMMLLLKKKKKKKKJJIIIHHHIIIJJJJJJJJJJJJKKKKKKKKKKKKKKKKKKKKKKKJKJJJJJIIIHHHHGFFEGHJMPSUXZZZZ[[[Y[[[[\\\]]^^^_````aaabccdegijkmnnopqrsuvxxyz{|}~}{ywwwxz|}~|zyy{{{}~~~~~~~~}~~~~}||{{zzyyxxxxxwxyyyyyyy||{zxvtqnje`ZUPOLOT\env|~{zxvutsqooooooooonnmllllmmmmmmmmmmllmllmnnnnmmnnkjjjjkklooooppppoooooooqqqrrrrqprrrrstuuuuuuuuutuuuuuuvvuuuuuuuvvvvwwwxyyyyyz{{{{{{{{{{{{{{{zzzzzzzzyyyyyyxxxxxxxxxxyyyyyyzzz{{|}~~~}|{zzyyxxwwxwvvutssrpnmkihgghijkkjjjihggfeeddb^\YVVWYXXYYXWVUQONKJKLJKIILLF?5-)&%%%((*-/13487:=@BDGJORTWYZ\]]]]\[[[\]^^^^___`bccdddeeeeeeeeeeeddddddeeeeffffffeddcccccbbaaaabbccba``[[ZZZYYZYWVUUUTQPPPPPQRSTUVWXXY[\\]^^__````__^^\Z[[\]_bhqx~n]OC=83.,+*+*))++**./-(#  !!""#$"""#$%'&''&&%$$"!!  !!#"#$$$$$$&&&'''''''&&')**/158HV_ceiiiiid^Z]_`bcdeffeeefecbaabbdfehjkkllmmnnooppqqqqrrrstuuvxyz{{||{{{{{z{{{{|||zywqnighlorwz}{vofZMA93466789899::;;<<<<<<===<===>>>>???@ACDFFILOQSSTVVVWVUTTUTTTTTTTUUUTSSRRRRQOMMLMOTY_eiklmUUUUTTSSRQQPPPOONNNNMMMMLKKKKKKKKJJIIIHHHIIJJJKKKKKKKKKKKKKKKKKLKLLLLLLLLLLLLLLKLLKKKJJJIGGGGECBCEGJNQTVY\\\]]]]\]]]]]]]]]^___`a`abbbccddegijkmnnopqrstvwxyz{|}~~}|{z{|}~}|||||~~}||{zzyyxyyyyz{||}}~~}|zyvuqlgaZTQMMOXbksy}{zyvttttttsrqqponmmmmmmmmmmmlllllllllkkkjjkllkjjkklmmmmnnnooooooooonpqqqrrssrrrrrrrrrrssssssttttttuuttttttuuuuvvvwxyyyyyzzzzzzzzzzzzzzzzyyyyyyyyyyyyyxxxxxxxxxxxxyyyxxyyyzz{|}}~~}|{{zyyxxwwwwwvuttsrqpomljhgfgghijjijjihgffeeda^YWTRRSSRRRQPOMLKKKLLKILLKKHA70/*'''((),.03567<;<@BEFJLQTVXY[\\\\\[[[[\]^^^___`abccdddeeeddddddddddddddddddeeefeeddcccbbbbaaaaabbbba`^^ZZZYYYYXWVUTSRQPOOOPQSSTTUVXYZ[\]]^^____^^_``^\XWYZ[]`ciqw{kYM@:4/,+*+,*)*+,,-///,&! !!"#$$%%%%%%$$$#$%'&'&&%$$#!  !!"!"######%%%%&'''(('&'''&)*,.00017899;=>>EDEEFFFGFFFHJKNRRTWY[[\\YZZZZZ[Z\]^^__`aaaaabbbaab`aba_]c]ZWPKEA<:954445445555555444556788=IXdhhjkiigb]Z\^`abdeffeeeffdcbbcceffhjkkllmmnnooppqqqqrrrssttuvxyyz{{||{{zzzzzz{{|||{yvspoopsxz{}~~{unf]QF=77899:;;;;<<==>>>>>>>??>?>????@AABCDFGHJNPQRSSSSTTSRQONMOOPPQRRTUUTTTSSQPONMNOQV[aglopqrUUUUTTSSRQQPPPOOONNNNMMMLKKKKKKKKJJIIIHHHIJJJJKKKKKKKKKKKKKKKKLLLLLLLLLLLLLLLLLLLLKKKJJJHGGGFDCACEGJNQTVZ]]]^^^^^^^]]]]]]]^__``aabbccccddehijkmnnnoqrstvwxyzz{|}~~}|}~~}||}}}~~~}||{|z{zz{{{|}}~~~}zyupkf_YTNNOV`iqw~}}}}}|{{zxvutrrsrrqpoonllmnnooonmlkkjjjjjjjkklmnnmmmmmmllmnnoonpppppoopnnooooooqqqrqppqrssssrrrqqrssqrtttttttuuuuuuvwwwxxxxxwxxxyyyxxxxxxxxyyyyyxxxxxwwwwwwwwwwwwxxxxyz{||}}~~~}|{zyxxwwwvvvvuttsrponllkigfeehhjjjiiiihggfedb\XTQONNNLLLLKKJJJIJKKMLIIKNJ@71,+*))*+-.13567:>>>?CEHIMOSVXYZ[\\\\[[ZZ[\]^___```acccdddeedddddddddccccccccccdddeddcccbbbbaaa````aaa`_^\[YYYYXXXWVVUTSRQPOOPQSTTUWWXZ\^_`````_^^]\\^_^\WTUWY[]`cjrwveSJ>92/,+*++*)*,-///01/*%!!""##$$%'''''''&'&#####%#$$$#"""  !"!"######$$$$%'''(('&'&&%%$$%%&'(+,,,../066677899999;=?CGHKMOPQQQPOPPQPQQQRRRRRRRSTTTUUVVWVUUWUSPTPNLHB<9543../00111222333333467898>>???@@@@@@@@@@AAABBBBCDDEGHIJLMOPQRRRQQPPONMLKLMMNOPQSUUTTTTSQOOOOQSW]cgmqtvwwUUUUTTSSRQQPPPOOOONNNNMMLLKKKKKKKJJIIIHHIJJJJKKKKKKKKKKKKKKKKKLLLLLLLLLLLLLLLLLLLLKKKJJJHGGFEDBACEGJNRUWZ]]^^^___^^^^^^^^^^__`aaabccccdddehijkmnnnoqrstvwxyzz{|}~~}}}~}}~~}}}|||||}}}~~zuqkd^XQOOU]fou~}{zzyyxwwvuuttsstutuuttsrrqppmmllllllnnmlllklllllmmmmnnnnnnnnooppponnnoooppppqqrrrqrrqqrssrrsssssssttttttuuuuvvvvvvvvwwwwxxxxxxxxxxxxxxxxxxwwwvvvvvvvvvvvvvwxzz{{|||}}~}|{zyxwwvvvvuuutssrpomlkkjhfeddghjjjiihhgfedb`]UROMKKKLGHHIJKKJJJKLLLKLLNKB7/-*,+*++-.03579:BEGHHIIIFFGHGGHGIIIIIIJKKLLLMMMLJIJIJHFDEB@?:731,++(()*+,-...//00001245788;BO`illjhd_[WX[]`abcefffeffdedcccddgjjjkkllmmnnooppqqqqrrrsrrstuvwwy||{{{{{{{{zzzzzz{|~~}|{zyxxyzzzzzzytng`VKB;<<<<===>>???@@A@@@@@@@ABCDDDDEEFFGHIJKLMNNOOOONMLLLLKKKLMNOQRSTUUUTTTTRPPQSUY]cimrvyz{|UUUUTTSSRQQPPPOOOOONNNMMMMLLLKKKKJJIIIHHIJJJKKKLLKKKKKKKKKKKKKLLLLLLLLLLLLLLLLLLLLKKKJJJHGFEDCBACEGKOSVX[^^^______^^^^^^^^__``aaabcccdddefhijkmnnnoqrstvwxxyz{|}}~~~~~~~~~~~~~yupic\TQPT[clr}~~~~~~~}|yyxxwwwurqpoonmmmlllkkjjkkjjjkkkmmmnnoopoppppppopppqqpppopqrrqrrrrrrrrsssssssrrrssssttttuuuvwwwwwwwwwwwwwwwwwwvvvuuuuuuuuuuuuuuwyyyz{{{{||}~}|zyxwvuuuuuuuttrqqqonmkjihfddddfgijihhgedcb`]YUONLJIJIJHHIIKLLKJLMNMJJMXPC71.+*,,-..13479;<=?ABDFILNPSVVXZ[\\\\]\[[[\]]^_```aaabcccdddeeddddddddcbbbbbbbbaabbbbbbbbbaaa``_^]\\]]]]]\[YYXWWWWVVUTSSRRQQQSSTUVWWXYYZ[]_`bbaaa``___^\WROOOTWYZ\`djszwgWIB:6200.,+++++,./0221220+&"  !!!"""$$$$$$%')))))))''&&%$$""!!  !  !"""!"#######$$$%&''&&''''''&&%%%$$##########""!!""$$#%&)-17;>@ABBBA???@??@?BBBBBBBABBBBBCCB??===;9795430.*($#$!""#$&'(())*+,,,-/12568;?IU_efdb\WUTX[]`abcdeffeffefedddeehjjjkkllmmnnooppqqqqrrrsssstuuvvwyy{{{{{{{{zzzzzzzz||~~}}|zzyxwwwxxsnhaWLB<<====>=>>???@@AAAAAAAABCDDEEEEFGGHIJKKLLLLLLLKKKJJKKKKLOPQSTUVWVUUUTTTRQRTW[`ekpty}UUUUTTSSRQQPPPOOOOONNNMMMMMLLLKKKJJIIIHHIJKKKKLLLLKKKKKKKKKKKKLLLLLLLLLLLLLLLLLLLLKKKJJJIGFEDCBBCEHKOSVY\^____```__^^^^^^^_``aabbcccdddeefhijkmnnnoqrstvwxxyyz{|}~~~~|xsmg`XTQSX`io{~}{{yvtsrqqpnmmmlllkkkllkkkklmmnnnoonmnnnnnnoonnnooooppppppppppppppppqqqqrrssstttuvwwwwwwwvvvvvvvvvvuuutttsssssssssstuwwxyyyzz{{|}~~~~}}}|{yxwvuttsttsssrpoooomljigfdcbbcdeghgeeda`_][WRMLKKJJJIHIIJJKLMNKLJLOSZZZE810/-.-.012689;=?@ABDFFHKNPSUXWY[\]]]]^][[\]^__``aaabbbcccdddeedccccccccbbbbbbba``aaaababaaa```_]\[[[[[[[[[ZYXWWWVVVUTTTSSSSSSTUUVWXXYZ[\]^_`bbaa``__^^ZUOKJKNSVXZ\`djsztaQE?85221/,,+,,,./002212330,(# !"""""""!"#$$$%%'''''''(((((((('%$##"!!   !!!""########$$$%&''''(((('&))(((('%##$##" !$(-28;==>>>=<<<<;;;<;;;;;;<<<<<<<<;:564311.,+)(''&"! !"""#$%&''()+-.138>>>>???@@AAAAAAAABCCDDDDEFFGGGHIIIIIIHHHHHHGHJKLMNPQRTVWXWVVUUUTTRRTW[aglrw{~UUUUTTSSRQQPPPOOOOONNNMMMMMLLLKKKJJIIIHHIJKKKLLLLLKKKKKKKKKKKKLLLLLLLLLLLLLLLLLLLLKKKJJJIHGFDCCBDEHKPTWZ]___```a``_^^^^^^^_``aabbccdddeeefhijkmnnnoqrstvwxxyyz{|}~{vpjc\VRRW_gnz}||{ywvuutqppqponmlkkmmlllllkkkkkklmmlllmmmnnnnnnnnnnnnnnoooopppqrrrrssstuuuuuuuuttuuuuuuuttssssrrrrrrrrrrrrtuuvvwwxyyzz{|}~~~}||{{{zywvutsrsrrrrqqponnnmlkihfeca_^]ccddcaa`\[YXVRNLIIHHIJKMILLKKKKJHIMXerwjR90//./1/2467:<==?ACDEGHJLNQSUWZXZ[]]]]^^^]]]^_aabbbbcccccccdddeedcccccccbaaaaaaa````aaaaaaaaa``_^\ZZZZZYZZ[ZZYXWVVVVUUTTSSTTTTTVVVWXYZ[[\]^_```aaaaa`^ZXRKGFHJORUXZ\`djszq\LB<753210--,--./11222222210-'$#####"#######$%&''''&'''''''''&&&&&&%#"!!   !!"""""""""$$$$$%&&''())(&&))((('&%&%%$%#!!  $)/69;<<<<:88898888:99999997777777611..++('$"""%$  !""#$&'(,05>???@@@AAAAAAAAABCDDEEEEFEEEEFFFFFFFEEFFGFEFHJLNNRRSUVWXXVVVUUUUUUX\bhmrw|UUUTTSSRRQQPPPOOOOONNNMMMMMLLLKKKJJIIIHHIJKKLLLLLLLKKKKKKKKKKKLLLLLLLLLLLLLLLLLLLLKKKJJJJIHFDDCCEGIMQVY\]___```aa`_________`aabbcdeeefffefhijkmnnnoqrstvwxxxyz{|}~}yrlf`YSRV^goy~}zyxwusrqonmmljkkkkkllllljlnmllmllllllllkllllnoopooopqqqqrrrrssssssssssssttttttssrqqqqpppqqqqqqrrssttuuvwwxyzz{|}~~~~}}||{zzzyxwutsrqppoppoooonnmlkigeiebaa``addba`][ZVTSQPNLKKLNNONLMIKHEDFP\lwpW>0./000134679:<==@BDFGGIILPQTVVWWZ\\]]^^^^^^^__`abbbbbccddddcdddddcbbbbbbcbaaaaa`______````aaa`_^^\ZYYYYYWWWWWWWWUUUVUUTSSSSTTTTUWWXXYZ[\]^_````_`a`_^^]XMIDCEHLPRUWY]aekszziUF=9654310/////0122344443321.,*)(((('''''''''&'))('&&&&&&&&&&%%%%%%$#"!!  !!!!!!!! !!!!!!!"########$$$$$%%%&&&''''(((())(&%(('&%$##"""!!! "&*04688888777788888777777889999987633220.,*)'$### ! !"#'-29ADEGGEFGGILRWZ\_`abceeeeffeeefffffhiijkkkllmmmnnnooppqqqrrtuuvvwwwwvvxyyyzzz{{{zzzyyzyzz|}}wtqopnje`WKA<====>>???@@@AAAAAAAAAABBCDDDDEEDDDDDDCCCCCBCDEFGGHJLOQRSTTUVWXXVVVVUUUWZ^chnsx|UUTTSSRRRQQPPPOOOOONNNMMMMMLLLKKKJIIIHHIIJKKLLLMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKJJJIHGEDCDDFHJNSW[]^____``````_______`aaabbddeeeffffghijklmmnoqrstvwxxxyz{|z{|~~|vpic\VTV]emw|{ywvtsronnnnnmllnpmkllklkkkkkkkkjjjjkmmlmnnnoooopppqrrrrrrrrrrrrssssssrrqqqppppoooopppppqqrrsstuvvwxyyz{|}}~~~~~~~~~~~}}}}|{zzyxxxwwutsrqponnnnmmmmnnmljhfca]YZ\]^_b_\ZYWUSPNNMMLOQMKJLNLJJIHLXm{cH5,*0011135779;<=>@ADFGIJJKOORSUWWXX[]]]^^^^____`aabbbbbbbcdeedccdddcbbbbaaabaaa```_^^^^^___`_```_^]\[YXXXXXWWWWWWWWUUVUUVVUUUUUVVVWXYYZZ[\]^_`aaa``_^^^]XPG>>@BEHKNRUWY]adjszr_NA87665310000001233333332100/.-,++++++++++++*))))('&%$$$$$$$$$$$$$$$#"!! !!""""""" !!!!!!!"########$$$$$%%%&&&''''(((()))(')(('&&&%$$$#""!"'*03466666566677889<<<<<<==>>>==<;9664421/-+*('%$"#""! $*/6>CEHHFFFFHLRWZ\_`abceeeeffeeefffffhiijkkkllmmmnnnoopppqqqrstuuvvwwvvvwxxxxxyzzzzzzyzzyyxyz|}{upmmkie`WKA<<=====>???@@@AAAAAAAAAAAAABBBBBAAAAAA@AAAAAABDEHIJLNPRTUUVVWXXXWVVVUUUX^cinsw|TTTSSSRRRQPPPPOOOOONNNMMMMMLLLKKKJIIHHIIJKKKLLLMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLKLLKKJJIHGFDCCDEGJMQVY\^^___```a`a`````````aaabbddeeeffffghijkkllmoprrsuvvwxxyz{z{|}}}~}xsle_YUV\cjtz~}|{ywpjjiiljjjjjjjjiiiijkkklmmmmmmnnnopqqqqrrrrrrrrrrrrrrqrqqpppoonnnnooppooopqqrrtuuvwxxyz{||}~~~~~}}}}}}}}~}}||||zyxxwvvuuuusrqponmmlllkkkklkjjigdaWWSQRTUVTTTTRPPQLMNPPOPSJJIHJKOT[pnTB2*).223333468::<>??ABCFHJKLLLSQSTVXXYY\]]^^^_____``aabbccbbbcdddcbcdddcbbbaaa`aaaaa```__^____^_^__^]\[[ZYXXXXXVVVVVVVVUUUVVWWXWWXXXYYZZZ[[\\]]^_aaaaaa_a`^WNC;9;?BEILORUWY]`dksz|iWH?87654321112222333332221/..---,++,---------+))(('&%%$$$$$$$$$$$$$$$#"!!  "!"""""""!!!!!!!!!!!!!!!"########$$$$$%%%&&&''''()))))(''*)))('&&&%%$##"  #'+/245555445556778:>?????@AAAAA@@><<<996532//--))''&&$%$#!! "(.5=BEIKIGGGILRVY\^`acdeeeeffeeefffffhiijkkkllmmmnnnoopppqqqrsttuuvwwvvvwwwwwwxyzzzzzzzzyxxxxxy|yrmjigc_VKA<<<;;;;<==>?????@@@@@@@????????????????@?@ABDEGHJLMNOQRSWWWWWWWWVVWXZ]`efjoty|TTTSSSRRRQPPPOOOOOONNNMMMMMLLLKKKJIIHHIIJKKKLLLMMMMMMMMMMMMMMMNNNNNMMLLLLLLKKKKLKKKKJIIHHGEDCCEEIKOSW[]____```aaaa`````````aaabbcdeeeffffghijkkllmnpqrstuuvxxxyzzz{||}~~}xtmf_ZVW\cktzobhkhiffeedddcddddeffgghhijjkkkllmnnnnoooonnopppqqqqpqpooonnnmmmmmmnnnmnnoppqrstuvwwxyzz{|}~~~~}}}||||||||||||}|||{{{zwvvuuttsstsrpoonmlkkkjjjjjgghea]\Xajmkf`YLLNNLLNOIJKJJKKNJIJP\k|sK3&,)-/465466678:;<=>@ABDGGIJLMMPRSRUVXYY[Z]^^^____``_`aabbccccbbcddcbabcccbaaaa```a``aaaaa``___^^^^]]]\[[ZYYXXXXXXVVVVVVVVUUVVXXXXXXYYYZZ[[[[\\]]^`bba`a`^^_YOC:547:?BEHLORTVZ]`dkszs_NC=8765432122333333332211/---++***+,,--------*(('&&%$%$$$$$$$$$$$$$$$#"!!  "!"""""""!!!!!!!!!!!!!!!"########$$$$$%%%&&&''''()))))(''))((((''&&%$##"! !%),/344554333445789;==>?@ABDEEEFFFEC?>;;86433311..,,+*)*)(&&%$#"! !'.7>CFIKJHGGILQUW[^`acdeeeeffeeefffffhiijkkkllmmmnnnoopppqqqrsttuuvvwvvvwwwwwwxyyyyyyyzzyxxxwwwy~wnihfb]TI@;;;::::;;<=>>>>>???????>>==>>>>>>>>???@@@ABCDFHIKLMNPQRSVWWWWWWXWXY[]adimpty~TTTSSSRRRQPPOOONNONNNMMMMMLLLKKKKJIIHHIIJKKKLLLMMMMMMMMMMMMNNNNNNNNMMLLLLLKKKKJKJJJJIHHGGEDDCDEGJMPTX[^____```aaaa`````````aaabbcdeeeffffghijkkllmnpqrrstuvwxxyyyz{{||}}~~~ytmg`[WX]dktzt_eighfeedccccddddeeefffgghiiijjjklmmmnnnnmmnnnnoooooonnmmmmmmlllllllkkllmmnopqrstuvwwxyz{||}}}}}}}}}}}}}}}}}}||||||||||||||||{{{zzz{{{{{{{{z{yyyxxxwtrqpppqpoqqponmlkiiiiihhhffgf_XZbyndYQMMNNLLMNQUVUckxĠi:%#+145535789889:<<=??@ABCFHHJLMNNPRUSVWYZ[\\^^____``````aabbcddcbbcddba`aaaaaaa````_````aabba_^^^^]]]\[[ZZYYXXXXXXXXVVVVVVVVVVWWYYYYYYYZZZZ[[[\\\\]^`aa`_`a_^VK?74577:?BEHLORTVZ]`dksz{jVG?<8765543223333333324310.--,*))(()*+,,++++++('&&&&%$$$$$$$$$%%%%%%%$#"!!  "!""""""""!!!!!!!!!!!!!!"########$$$$$%%%&&&''''((((((((())))))(((('&&&%$""!"%),03444431000134679<<=?@ABDFFFGGHGECC@@<<;9753222110/-,,+)(('&%$#! "(/7>CGJLJHHGHKQUW[^`abcdeeeffeeefffffhiijkkkllmmmnnnoopppqqqrssttuuvvvvvvvvvvvwxyyyyyyyzyxxxwwwwztkhe`[RH?;;::::::;<==>>>=============>>>>>>>???@@ABCDFGIJLLMNPQSSVVVWWWXYY[]_bfinqtx|TTTSSSRRRPPOOONNNNNMMMLLLLKKKKKKKJIIHHIIJKKKLLLMMMMMMMMMMMNNNNNNNNNMMLLKLKKKJJJJIIIHHGGFEDCCCEFHLORVZ]^____```aaaa`````````aaabbcdeeeffffghijkkllmnpqqrsttuwwxxyyyzz{{||}}~~~~~~~ytnhb\XX]dktz^_gfeddccbbbbaaaaaabbdeeeffffggghjkkkllllkklllmmmmnnnmmllmmmmlllkkkkjjjkkllmopqrstuvvwwxyz{{||||||||||||||||{{{{{{{{{{{{{{{{{yyyyyyzzzzzzzzyyxxxwwwvsqppoopponmllkkihhgggfeeefgd]Y^oýyurptvz}˲tD$#,3686778989:::;=>>?AABDEFHJJLMOPPRSVUWXZ\\]]____```aaa`aabbccddcbbccca`_`````````_____````aa`_^]]]\\[ZZZYYXXXXXXXXXXWWWWWWWWWXXXXZZZZZZ[[[[[\\\\\\\^`__````]YK=413688;?ADGLORTVZ]`dkt{rbOC><866654433444443332431/-,,+)))))))***))))))'&&&&&%$$$$$$$$$%%%%%%%$#"!!  #"#######"!!!!!!!!!!!!!!"########$$$$$%%%&&&''''((((((((())))))(()))((('&$#"#%),0333331/-++,.01358:;<>?@BEFGHIJIHEFCC@??=;976554431/0/.-,,+)('&%#!""""""%*08?DHKMKIHGHKQUWZ]`abcdeeeffeeefffffhiijkkkllmmmnnnoopppqqqrrsstuuvvvvvvvvvvvwxyyyyyyyzyxxxwwwvy}|nhc^YPF=::999999:;<====<<<<<<<<<<====>>>>>>???@@BCDEGHJKLMNOPRSSVVVWWXXYZ\_bfjnruwz}TTTSSSRRQPOONNNNNNNMMMLLLKKKKKKKKJIIHHIIJKKKLLLMMMMMMMMMMMNNNNNNNNNMMLLKKKJJJJIIHHHGGFFEDBBCCEGIMQTX[^_`___```aaaa`````````aaabbcdeeeffffghijkkllmnoppqrstuvwwwxxxyyzz{|||||||||~~~~~~~~~yuojc]YY^eltz`[eedccbaa```____```abcccccccdddeghhhhhhhgghijkkkkllmllkklmmmmllkkkjiiijjkkmnopqrstuuvvwxyyzzz{{{{{{{{{{{{{{zzzzzzzzzzyyyyyyyxxxxxxxxxxxxxxwwvvvuuutrponnnnnnmmlkjihgffffedddb_[^i~ÿͼW+%.57;=;?>=<:;<<==>?@AACCDEFGJLLNPQRRTUWVXY[]]^^__````aaaa`aabccdddcbbbcca_^_____``___^^________^^]\\[[ZZYYYYXXXXXXXXXXXXXXXXXXXYYYYYYZZ\]]\\\\]\\\\\\\^`__```_XNB8435569;?ADGLORTVZ]`dku|zgYJ@><97666554455554432131/-,,++))))))))))))))(('&&&&&&&&%%%%%%%%%%%%%%$#"!  #!"#######""""""#""""""""########$$$$$%%%&&&'''''(((((((((((((((()(((''&%$$##&),022111/-*(()*,./14678:<=?ACDFGIHHGHFFDCCB?>=<99887534320//.-,+)(&&&&&&&&'+29?DILNLIHGHKPUWZ]_abcdeeeffeeefffffhiijkkkllmmmnnnoopppqqqrrssttuuvvvvvvvvvvwxyyyyyyyzyxxxwwwwx{qhb\WND<998888889:;<=<<;;;;;;;;;;<<===>>>>>???@ABCDFGIKKMMNOPRSTUUVWWXYZ[^beimquwy{~TTTSSRRQPONNMMMMNNNMMMLLLKKKKKKKKJIIHHIIJKKKLLLMMMMMMMMMMMNNNNNNNNNMMLLKJJJJIIIIHGGFFEEDCAABCFHJOSVY\_`a___```aaaa`````````aaabbcdeeeffffghijkkllmnoopqrrstuvvwwxxyyyzz{{|||{{{{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~yupkd^YZ^eltzcXabdbba``_````````aaaaaaaaaabbbcefffffffeefghhiiijjkjjjjklllmllkjjihhhijjklmnopqrssttuuvwxxyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxvvvvvvvvvvvvvvuuttssssqonmlllmmmkkjihgfeeddddbcbYX_sh:"+289;;>B?@@>>?AA??@ABBCDDEGHIKNNPQRSTUVXWYZ\]^__``aaaabbbaaabbcccdedcbbbca^]]]^^^^__^^]\]^^^^^^]\\\[ZZYYYYYXXXXXXXXXXXXXYYYYYYYYYZZZYZYZ]^^]]]]]]]\\\\[]__^^_`]RB<8666549;?ADGKORTVZ]`dkv}u]QE?>=:8766655566655442220.,++++))))))))((((('''&&&&&&&&%%%%%%%%%%%%%%%$#"!!  !!$"######""""""""""""""""""########$$$$$%%%&&&'&&'(((((((((((((((()))((('&%%$$'*,0210//,*'%%%')*,-/124679;<>?ACEFFFGEFDDDCBBBB>>=<;:776533210/.,+))))(((()-3:@EJMNMJIGGJOTVZ]_`abdeeeffeeefffffhiijkkkllllmmmnnooppqqqrrssstuuvvvvvvvvvvwwxxxxxxyzyxxxwwwwxy~th`ZULA:8777766789:;<<;::::::::;;<<<===>>>>???@ABDEFGIKLMMNOQRTTUUVWWXY[]`dhlpswz{}TTSSSRRQPOONNNNNNNMMMLLLKKKKKKKKJIIHHIIJKKKLLLMMMMMMMMMNNNNNNOOONNMMLLKKJJJJIIIIGGFFEEDDBAAACFIKPSWZ]^____```aaaaaa`````````aaabbbcddeeefghijkklmmnoopqrrsuuvvwwxxxyyzz{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~~~~~~yuqke_[\_fltzqWaf_ccbaa``a______``aaa```aa`aabdeeeeeeeddeeffggghhihhhijlmmllkjiihhhhiijjkmmnnopqqqrrstuuvvyyyyyyyywwwwvwwwvvvvvvvvwwxwwvvvxvttttttssssssssrqqqpppnkkkllkkkhhggfeffdbbcbb_^[hÿåvD+(37;;<@@=<97777666777765443220.,+**))*))))*+))*))((('''&&&&&$$$$$$$$#$$$$$$$##"!!  !!"!#######"!!!!!!!"""""""#############$%%%&&&''&&((((((((('((((((())(((('&&%$$&)-021/.,)&#!!"$&'()+-.02467:<=?BCBCCDDEEEEECBBA@?>>>=<<;:87653210/.--,,,++,/5;@EJMONKIGGJNRVY\_`abdcefffghggghhhiijjkkkllllmmmnnooppqqqqqrrrssttuuuvvvvvvvwwwwwwwxyzyyyxxxwvwzwh^XSJ?87666656778::;;::88888889<<<<===>>>???@ABCDFGIJKLMNNPQSRRTUVVWXY\`dhlpsvyy{{|}~~TTSSRRQPONNMMMMMNNMMMLLLKKKKKKKKJIHHHIIJKKKLLLMMMMMMMMMMMMNNNOOOONNMLLKJJJIIHHGGEEEDCCBB@>?@CFJNRUX[^_`___```aaaaaa`````````aaabbbcddeeefghijkklmmnooopqrrttuvvwwxxxyyzzzzzzzzzz}}}}}}}|||||||||}}}}}}}}}}}}}}}}~}}}}}}}}~~yurjd_[\_gmtz[\eacbba```a```____````___`````acddddddddccddeeefffffffghijkkjjiihhhhhiijjkmmmnnoppppqqrsttuvvvvvvvvuuuutuuussttttuuvvvttrqprrrrrrrrqqrrrrpooonnnmljkjkkkkiihgedddedcddca]Z\rȬ}M.'17:<==@A@ABCCBA??BCCDDDDEFGIKMNOPQSSTTUVWYY\]^^_`abcdcccbbbaaaabbccedddca__`]]]]\Z[]^^]\\[[\]]\\[[[ZZYYXWWWYXXXXXXXYYYYYYYYXZZZZZZ[\\]]]]^]^]]]]^^_^]\ZZ[]^VSYgpk`PF=7455569=<;:9865432100///....05;AFJNOOLIGGIMRVY\`aabdcefffghggghhhiijjjkkkllmmmnnnooopqqqqqrrrsstttuuvvvvvvvwwwwwwwxyzyyyxxxwwwx}yg\VQH>766555556789:::::9999999:;<<<===>>>???@@BCDFGIJKLNOOQRSSRTTUVWXY\aeinqtwyyzz{{{|}~TSSSRQQPNMMMMMMMMMMMMMMMMMLKKKKKJIHGHIJKKKLLLMMMMMMMMMMMMMNNNOOOONNMLKJJIIHHGFFEDDCCBA@?>=>?BEKPTWZ\^``___```aaaaaaa````````aaabbbcddeeeffhijkkllmmoooppqqrstuuvwxxxxyyyzzzzzzzz|||||{{{{{{{{{{{|{||||||||}}}}}}}}}}}}}}}~~~~ytrjd_]]`gmu{cXddcbbaa``a````________^^_`____`bbbcccccccccdddeeeeddefffhiiiihhhhhhhiijjkllmmnnoopooppqrssuuuuutttrrrrrsssrrssssssttsrrrpoqqqqqqppooppppnmllkkkkihhijjiihfigffeeedacb^[]gxħM/*07:;>ABBCDDDDBBAA@@BCDEEEEFGHLMNPPPSUUUUVWY[\\]^^_`abcdcccbbbaaabbccceddcba__\[ZYZYYY[[\\ZYYZZ[[[[[ZXXXXXXXXXXXXXXXXXYYYYYZ[[Z[[[[[[\\]]^^^_[]_]]]\]`_][]\WPNShzwj_PE<7444569>>>???@@@@@@@@@@@?<:::9987542211000137=BGKOPOLHGGILPTY\`aabccefffghggghhhiijjjkkkllmmmnnnooopqqqqqrrrsssttttvvvvvvwwwwwwwwwxyyyxxxxyzxxz~zgZSNE=755444445689:::::::::::::;<<==>>?????@AABDEFGHJLMOPPPQRRSTUUVXYZ]bfjortvxwxwwxyzzzyz{|}~SSSRRQQPNMMMMMMMMMMMMMMMMMLKKKKKJIHGHIJKKKLLLMMMMMMMMMMMMMNNNOOOONNMKJIIIIHGFEEDDCCBA@@?>=>@CGLQUX[^_``___```aa`a``a`````````aaaaabddeeeffhijkklllmooopppqrsttuvwxxxxyyyzzzzzzzz||{{{zzzzzzzzzz{{{||||||||||||||}}}}}}}}}~~}xtqhd`^^ahmv|oW`dccbba``a````________^^^_^^^^__``abbccbccccdddeddddddeefghhhgggghhiijjkklllmmnnnonooopqrrsssssrrrqqqqqrrrqqrrrrqppppoooooppppppoonnoooomlkkjjjjiggffffgfecdefghgeda]Z`p¾Ū~R0&)4;>=ABCEEEEECCBAABBBCCCDDEFHIJNOPQRRSUVVVWXZ\]^__`aabccdcccbbbbbbbbccccddcba_][ZYYYYXXZZZZYWXYYZZZZZYXXXXXXXXXXXYYYYXXYZZZ[[[\[[[[[[[\\^^^^^^^\\]\\\]^Z\^[SMQavj`SE<744456:=@CFIMORUWZ]agnw~xhYLD>;;<99999999:::9998630.-+)))))))))))))*)*))((('''&&&&&%%%%%%%%%%%%%%%$##"!!  !!"!"#####"""""""""#######""""""""#########%%%&&&&&&&&&&&&&&&&'''(()))((((''''&&)-0--+)&$!!#%%((*,/13346789:;;;;;<<===>>>>>>>==<<<;;::987665544446:?DIMOPPKGEEHKNRW\_aabbcefffghggghhhiijjjkkkllmmmnnnooopqqqqqrrrsssstttuuvvvvwwwwwwwwwwxxxxxxxyywxy{xfXPKC<644444445689:;;;;::::::::<<==>>?@@@??@ABCDEEFHKMOQQPPPQQSUUUVXZ[_cgkortuvuvvvvwyyyxyz{|}}SSRRQPPONMMMMMMMMMMMMMMMMMLKKKKKJIHGHIJKKKLLLMMMMMMMMMMMMMNNNOOOONNMKJIIIHHGFEEDDCBBA@??>=?BEINRWZ\^```___``````a``a`aaaaaaa`aaaaabddeeffghjjkklllmooopppqrsttuvwxxxxyyyzzzzzzzz{{zzzyyyyyyyyyyzzz{{{{{{{{|||||||||}}}}~~|wsphc`^_binw}\Zbbccbaa`a````________^^^^^^^^^^__`aabbabbccccccccccccccceeefeeffhiijjjkkkkllllmmmnnoooppqrrrrrqqqpppppqqqnnoooonnnnnmmmllmmmmmmllkklkkkjiggghgfffeefgfecb`bdghfba^]_můO/&*4;;<=@CEEFFFFECBCBBCCCDDDBDFHJLMOPRSSTUVXXXXY[^__aaabccdddcccbbbbbbbbbbbbbbba_]]ZYXXXXXWYXXWVUVXWXXXXXXWXXXXXXXXXXYYYYYYZ[[[[\]]\\\\\\\]]______b^]]^^_^\[XSNQb~~ul`QE<744456:=@CFIMOQTWZ]agnw~scTHA>;::8:::::::;;:9998630.-+)))))))))))))*)*))((('''&&&&&%%%%%%%%%%%%%%%$##"!  !"!"######"""""""#"""""""""""""""#########%%%%&&&&&&&&&&&&&&&'''(()))((((((''''*-/,,+)'$! !"#%')+-.012345666778899:::::::::;<<<;;;;;;;998887778;@EJMOQPKFDDGJMPV\_`abbcefffghggghhhiijjjkkkllmmmnnnoooppqqqqqrrrrsssttuuuvvvvvvvvvvvvwxxxxxxxxvwwwwy|}tcULGA;644444445689:;;;;::::::::<<==>>?@@@??@AABCCDFHKNPSSQQPPQSUUVWY[]`dgjnqrstsssstuvwwvwxyz{|RRRQPPOONMMMMMMMMMMLLLLLLLLKKKKKIHGHIJKKKKLLLMMMMMMMMMMMMMNNNOOOONNMKJIIHHGGFEDDCBBA@?>>=>@CGKPTZ\^_```````````a`aaaaaaaaaaaaaaaaabddeeffghjjkklllmooopppqrsstuvvwxxxyyyzzzzzzzzzzzyyyxxxxxxxxyyyyzzzzzzzz{{{{{{|||||}}}~|vrogc`_`djox~gX^accbaaaa````________^^^^^^^^^^^^__````aabbbbbbbbccbbbbabccddeefhiijjjkkkkkkkkllllmmnnnnoooooonnnmmmmmnnnllmmmmllkklkkkkkkkkkkkjjiiiiihhgfffgfeffeffedcbcedda`][]_k~ÿƶ[4'/689::544444445689:;;;;::::::::<<==>>?@@@??@AAABCEGIMPTWVUTRQPRUUWY[]^aegjmopqqpqppqrstttuvxyyzRQQPPOONNMMMMMMMMMLLLLLLLLKKKKKKIHGHIJKKKKLLLMMMMMMMMMMMMMNNNOOOONNMKJIIHHGFEEDDBBA@@?>==>AEIMRW[]_`a``aaaaaaaaaaaaaabbbbbbbaaaabbbddeeffghjjkklllmooopppqrssttuvwxxxyyyzzzzzzzzzzyyyxxxxxxxxxxyyyzzzzzzzz{{{{{{{{{||||}}~~{vqnfc``belq{xZY_bca`aba````________^^^^^^^^^]^^^^^___``aaaaaaaabbbaa``acccddefhiiijjjjjjjjjjkkkkkklllmmnnnnnmmmkkkkkllljjkkkkjjiiiiijiiiiiiiihgffgfffffeeeedcccfedca`^^^^_^_bgp¼õh?-3<<:;===>AEHHHHGGGGEEDEDDCCEFGDFHJMOPRSTUVVVVYZYZ[]`a`bbbbcccddcccbbbaaaa````^__^]\ZXXWVVWWUSUUTSSTVVVVVVVVVWYYYYYYYZYZZZZ[[\]]^^^^__^^^^^^^_^^__``a_\]__ZRMLUl~sj_RE<744456:<<<<<<<<;:997530.-+)))))))))))))*)*))((('''&&&&&%%%%%%%%&&&&&&&%$#"!  " """###################""""""""#########$$$%%%&&&&&&&&&&&&&'''(((((((((()((()*+,*++*(&$! "##&'()*+,,./01233444444556677778889:::::::;;:>?@@@??@ABBCEGIMQUY]\ZYVTRSUVXZ\^_befiklnnononnopqrsttuwxyyQQQPOONNMMMMMMMMLLLKKKKKKKKKKJJJIHGHIJKKKKLLLMMMMMMMMMMMMMNNNOOOONNMKJIIHGGFEDDCBAA@?>===?BFJOTX\^`abaaaaaaaaaababbbbbbbbbbbbbbbbbcddeeffghjjkklllmooopppqqrsstuvwxxxyyyzzzzzzzzzzyyyxxwwwwwwwwxxxyyyyyyyyzzzzzz{{{{{{|||~~~~~zupmfbaacgns|aV]`cb`aca````________^^^^^^^^^]^^^^^^^^__````````aaa``__`abbcddfhiiiijjiiiihhiiijjjjjjkkklllllkkkiiiiijjjhhiiiihhggggghggggggggfeccdddddeeedcba`_[]\[YXWXWZ`eo{¿é{N21:===>>>==>=?=======<;:997520.-+)))))))))))))*)*))((('''&&&&&%%%%%%%%&&&&&&&%$#"!  ! !"""##################""""""""#########$$$$%%%&&&&&&&&&&&&'''((((((((((((''()*+)***)'%#! !"#$%&'')*+,-./0001112334555666667788888999>??@??@ACDEFHKNRW[`dca_\YVUVXZ\^_`acefijklmmnmmnopqstuvwxyyQQPOONNMMMMMMMMLKKKKKKKKKKKKJJJIIHHHIKKKKKLLLMMMMMMMMMMLLLMMMNNOONNMKJIIHGGFEDCCBA@@?>===?CGLPUZ\^`abaaabbbbbbbbbbbbbbbbbbbbbbbbcccddeeffghjjkklllmooopppqqrrstuvwxxxyyyzzzzzzzzzzzyyyxvvvvvvvvwvvwwwwwwxyzzzzzzzzzz{{{||}~~~~~~zupmebabdhot|tT\`cb``__````_________^^^^^^^^^^^^^^^^^^^```````a````````__acdeefhhhhhhhgffefffgghhiiijjjjjjjjiiihhggfgggihihgfddgggfeedddeeeffffedfeeedbcb`^ZWX[X\^_`aaenuȽb=6>?ACB@?@?=@BCFKHIHFEEDDFFHGEDDEGKLJILNPQSTUVVVVVXYYY[\_a_aaaaabbbdffebbba`]\\\][ZZZZWTTWXWWWWXYYVUTUVWYYXXXXXXXXYZZZZZ[\Z[[[\\]]`_`_^]\\__``_^]]``_^_`b_RLMVk|sj^PC;644568:<@CFIMOQTWZ]agnv}}rfZME@@?>?>@>>>>>>>=;:986420/.,*****))))))))*))((''''''&&&&&&&&&&&&&&&&&&&&%$#"!   !""###$$########$$$$$$$""""""""##########$$$$%%%&&&&&&&&&&&'''((((((((((((''(()*)***)(&%$"!  !"#$$%&()*+,--./0011232233333356666778>@@ABDFGIJMPTY]bgkkkhd`]ZXY[]^__`abdfgijlkmmmnoprttuvwxyzPPOONNMLLLLLLLLLKKKKKKKKKKKKJJJIHHHHIJKKKKLLLMMMMMMMMMMLLLMMMNNOONNMKJIIGGFFEDCCBA@@?>==>BEJOSW[^`abcbbabbbbbbbbbbccbbbbbbbbbccccccddeeffghjjkklllmnooopppqrrstuuvwxxxyyyzzzzzzzzzzyyyxvvuuuuuuvuvwwwwwwxwxxxxxxzzzzzz{{{}~~~~~~|xsnkfcacfjpu~aY]da`a_^`````````````_^^^^^^^^^^^^^^^^^^_______`_______^[]`acdfggffffffeeeeeeffffghhhiiigggggffffffeeffffffeeeeebbccddeeffdefgeedca_^]\^YXY[]^elyÿĻY=;CFGGFEBAAA?@EGHIIJIGEEEDFFHGGGGGHKMMLMOPRSTUUVVWYZ[[\]^`__aaaaaaabdcbaaaa]__[YXYXXYYZXXWVWVVXYYWVWVUTUVXXYYYYYYYXXXXXXXYZ\\\]]]^^__`____`__^^^__^___`_[RNPa|»~ti]PD<755678:<@CFILPRUWZ]agnw~xnbUNAAAA@@@@>>====<<<:986420//.,+**********)*))((''''''&&&&&&&&&&&&&&&&&&&&%$#"!   !"""##$$$$$$$$############################$$$%%%&&&&&&&&&&&'''(((((((((((('''())(****)(&%$#"""##!!!  !"$%&')*+,--.///00001123334445557;>CHMQSSPJC@>?AGMTY^`accccdeefgghhhiiijjjjkkklllllmmmnnnoppqqqqqqqqrssssstttuuutttttttssqrstuvwwwvspnkfbZRH=755344444555689:;;;:;;;;;;;;;<<<<==>@@BCFHJLNQTX^chlpqqnjfa]YZ\]^___``acehikklllmnoqsuvwxz{{OOONMMLLKKKKKKKKKKKKKKKKKKKKJJJIHHHHIJJKKKLLLMMMMMMMMMMLLLMMMNNOONNMKJIIGGFEDDCCBA@@@>==@EINRVZ\^`bccccbccccccccccccccccccccccccdddddeffgghjjkkllmnnooooppqrrsttuvwxxxxyyyzzzzzzzyyyxxxwwwvvuuuuttttttttuuwwwwwwxyzzzzz{{{|}~~~~~~~~{vrmjecbdhmsxvXYa__a^]`````````````__________^^^^^^^^^^^^^^^^^^^^^^^^]Z]_aabdeedddddccddddeeeeeffgggghggfffhhhdddddcccccbccbabcdcbcdccdcedca][YYZZXWX[cdjr{¿Ž[=@KOJIIHFDCCBAAEGHIIJHGFFEEFGHHHHHIJKMNNNNOQSTUUVWXZ[\]]^_`__````````bbbaa_^^\YXYYWVMKKOSTTV\]\YYXXXWYXXWWVUXXYZZXXYYYYYYZZY\\]]]]^___^^^^`b_^__`_]_]^^WOLUiþ~ti]PD<7545789<@CFILPRUVY]afmxuk_SLCBBBBAAA??>===<;;:875420//.-+**********))))((''''''&&&&&&&&&&&&&&&&&&&&%$#"!   !"""##$%$$$$$%$$$$$$$###################$$$$$$$%%%%%%%%%%%&&&'''((((((((((''''()(((((((((((''''&$$$$$$###"!! !$$$%&''(())*+,----000122359=DINQSSOIC><=@ELSY]_accccdeefgghhhiiijjjjkkklllllmmmnnnoppqqqqqqqqrsssssstttttsssssssssssttttuuuutqmif_VPG=645454445667789:;;;;;;;;;;;<===>>>>?AACEHKNQSVY^djosxxwtplhc^^]^^_____`bdghjijkmoqstuwxy|~OONNMMLLKKKKKKKKKKKKKKKKKKKKJJJIHHHHIJJKKKLLLMMMMMMMMMMLLLMMMNNOONNMKJIIGGFEDCCBBA???>=>BGLQUX\^^`bcdcccddddddddddddcccccccccdddeeeeefgghhijjkklmmnnooooppqrrsttuvxxxxyyyzzzzzzzzyyyyxxxxxwvvuuutssssssstuwwwwwwwxyyyyzzzz|}}}}~}}}~~~zuqliecbejouzcV\_`a_^`````````````````````________^^^^^^^^^^^^^^^^^^]]^``abccccbbbaaaabbbbcccddddeeffddcccdddccccbbccccdedcbbbba`__]^ZZ\YXVW\X\`eimu{¿¤gBENOOJIHHFDDDBBBEHIIHIIIGGFFGGHIIHHJJJMOONMNQSTUUVWXZ[\]]^_`__```````ca__^^]]\[YYVQKHDJPRLHGJPRUXYYZZYXYYYYXYXYYXXYYYZ[[[[[[[]^__^^^_\^```_^_`^^^___]YQMO_xľ}si]PC;743469:=@CFJMPRUWY]bgnx|pdZPICCCBBBAA@?>>=<;::9754320//.-+**********)*)(('''&''&&&&&&&&&&&&&&&&&&&&&%$#"!   !!"""#$%$$$%%%&%&&%%%$$$################$$$$$$$%%%%%%%%%%%%&&&''(((((((((('&&''()))))))))((((((('''''&&%&%$$#  !!!"####$%&''()*+,,,-./037CJRY\_abcccdeefgghhhiiijjjjkkklllllmmmnnnoppqqqqqqqqrssssssssssssssssssstssttttuuutsqmif_VQG=744455556778889:;;;;;;<<<=====>>>??@CCEGKOSVW[_djpuz}zvrokeb`^^__^^_`adfhiiklnprstvxy{}ONNMMLLKKKKKKKKKKKKKKKKKKKKKJJJIHGGHHIJKKKLLLMMMMMMMMMMLLLMMMNNOONNMKJIIGFFEDCCBB@?>>>>@EJNSX[^``abdddcceeeeeeedeedddddddddddeeeeeeefghhiijkkklmmnnoooopppqrrsttuvxxxyyyyz{{{{{{{zzzzyyyyxxwvvvvttttttttuuwwwwwwwxxxxxyyyz{}}}}}||}}~~~~ytojgdccfkpw|z\W^aba_`````````````````````________^^^^^^^^^^^^^^^^^^\_aaaaabbaaaaaa```aabbbaabbbcccdddddccdddcccbacbaba`__]\\YZYXWWWY[^]bfhknw}®|NENLJJHHHGFEEDCCCFHIIIIIHHGGFGHHIIIJJJIMOPNMNQSTUUVWXZ[\]]^_`_^_______a`^^^_]\\WPIIM[g}pgUNKOVYZY\ZXXYYZ[YZZYXYZ[\\\\[[\]]^__^^]]^__^^^`b`^_]WRNOO]k}si\OC;743469:=@CGJMOQTWY]bhoxwlaVLFDCCCCBBBA?>>=<;:98754320//.-+**********)))(('''&''&&&&&&&&&&&&&&&&&&&&&%$#"!   !!"""#$%$$%%%&&&&&%%%$$$#######$########$$$$$$$%%%%%%%%%%%%&&&''(((((((((('&&''())))))))))******)))))(((((''&####"!  !!""#$%&)))*,-.37>?>>??@@AABDEGJNSWZ\_chntz~}xurojgc`^^____`bdfhjkmnprtuvxz{}NNNMMLKKKKKKKKKKKKKKKKKKKKKKJJJIGGGGHIJKKKLLLMMMMMMMMMMLLLMMMNNOONNMKJIIFFEEDCBBA@>=>>?BGLQVZ]`aaabcddddeeeeeeeeeedddeeeeeeeeeeeffffgghhiijkkllmnnoooopppqqrrsttuvxxyyyzzz{{{{{{|{{{{{{zzyyxwwwwuvvvvvvvwvwwwwwwwxxxxxyyyz{||||||||}}~~~}xsnifdccgmry~mY[`ab``````````````````````________^^^^^^^^^^^^^^^^^^]_`_^^`ba````````_``aaa`_aabbbbbbbbaa``__^^^]\\\\XXYZYY[]]`abdhko{YCJKDEGGHHGFGGFCCDFHIJIIIHHHGHHHHHIJJJJJLNPOMMPSTUUVWXZ[\]]^__^^_______]___]ZUQLJJVhüu`RMR[[[ZZZZZZYXY[[[ZZZ[[[[[\\]]^_``^^]^_^_`_^[WQNLOZo}si\OB;643369:=@CGJMOPTWY\bhoxsh_SIEDDDCCCCBA@?>=;:987653220//.-+**********))((('''&''&&&&&&&&&&&&&&&&&&&&&%$#"! !!"""#$%$$%%%&&&&&%%%$$$$$$$$$$$########$$$$$$$%%%%%%%%%%%%&&&''''''''''''&&&&&'(((((()****+++++***++**)**)))('''%$$#!  !!$$%&(*,17=EKNQRRMF?978:@IQW[^`abccdeefgghhhiiijkjjkkklllllmmmnnnoooppppppppqrrssssssssssssssssssrrssssttttsqnjgaXRJ?63454444566788999::::;=?@ABBBBCDDEEEFHKNSY]`behmszzwusolhc_^^_``acegikloqrtvxy{}~NNMMLLKKKKKKKKKKKKKKKKKKKKKKJJJIGGGGHIJKKKLLLMMMLLLMMMMLKLLLMMMOONNMKJIIFFEDCCBBA?=<=>@CINSY]_acaabccdddeeeeeeeeefeeefffffffffffffgghhhiiikllmmnnopoopppqqqrrsttuvxyyyzzzz{{{{{{}}}}}}}||{{zzzyyvwwwwwwwxwwwwwwwwxxxxxyyyz{||||||||}}}~~~|wrlgddcdiou{dY^aaa`````````````````````________^^^]]]]]]]]^^^^^^^^^__^[[]^^^___````aaaaaa``_______ZZZZZYYXUVWXY\^]`cdgjlqv}¹rICHGEDEHHHHHJJHDDEGIJJJIIIIHHHHHHHJKKKJJJLPPMLMSTUUVWXZ[\]]^__^^^^^^^^^^_]ZWTTT^i|ǾoXJRSUY\^]][Z[\\ZYZ[[[[[[\\]^^^^__^\[XWVSOLLVZi~|si\OB:632368:=@CFJMOPTWY\bhox{ndZPGEFFEEEDDDB@?=<;9876543210//.-+**********))((('''&''&&&&&&&&&&&&&&&&&&&&&%%$#"! !! !!"""#$%$$%%%&&&&&%%%$$$$$$$$$$$########$$$$$$$%%%%%%%%%%%%&&&''''''''''&&&&&&&&''''''()***++++,+,,--,,,-,,,,,+**)(''%$$#"!!  !"$'*06=EKOQRQLE>8568>HPW[^`abccdeefgghhhiiijkkkkkkkklllmmmnnnoooppppppppqrrrrssssssssssssssssrrssssttttsqnjgb[TLA8344333345567778899:;=@BDFGHJJJJJKKJJLORW]begjmqw}|zxvsqmhb_^`bbcegiknpstvxz{|~NNMMLKKKKKKKKKKKKKKKKJJJJJJJJIIIGFFGGHIKKKLLLMMMLLLLMMMLKLLLMMMOONNMKJIIFEEDCBBAA?<<<>AEKQV[_bceaabccdddfffffffffgfffgggggggffffghiijjjjjjlmmnnooppppppqqqqrrsttuvxyyzzzz{{{{{{{}~~~~~~~~}}||{zyxwwwwwwxwwwwwwwwxxxxxyyyz{||||||||}}}}~~~~|vqkfcdcekrw}u[]``a`````````````````````________^^^]]]]]]]]^^^^^^^]^bge_ZZY\\]]^___`_````__^]\]]^_`YZ[]_^__jknquvyzYBJKFIJGJIJIJLLKEEFHJJJJJJIIIIHHIHIJKLKJJJMPQNLLSTUUVWXZ[\]]^__^^^^^^^^^_^ZXZcozŶy_RMPV[^_Z[\\\]\YZ[\\]]][_``_^[VTOMLNQXbk|rh[NB:632358:=@CFJMOPTWY\bhoxxj_VLFCFFFEEEEDCA?=<:8655432110//.-+**********))((('''&''&&&&&&&&&&&&&&&&&&&&&%%$#"! !! !!"""#$%$$%%%&&&&&%%%$$$$$$$$$$$########$$$$$$$%%%%%%%%%%%%&&&''''''''''&&&&&&&&&&&&&&'(())**+,--../////00000/...-,++*((''&%%" !$'.6=EKOQRPKD=7457=GOVZ]`abbcddeffggghhhiikkkkkkkklllmmmnnnoooppppppppqrrrrssssssssssssssssrrssssttstsqnjgd]XNC944443344566667789:<;<>BFMTX]addecdddeeeffghhhhhhiiiiiiiihijjjjkjjjikllkklllmnnnopqqqqppqqqrrrrstvxyyzzzz{{||||||}~~~~~}}}{{zyyyyyyxwwwwwwwxxxxxyyyz{{{{{{{{{|}}}}~~~~~ysnidcdcflsyiU]dbb``ab`````````````````________^^^^^^^^^^^^^^^^^]\c|l^WUSSTUYZY[\\[YXXXVWYY[^_bfjptzĿyLDLOONNMMLMLJLONMGDFHJLLMLKKKJJJIIIIJJJJLMNOPPOOSSUVVWYZZZ[]]]\\]\\ZZZ[^]ckuǸfUMKPOZZ\]_^^]_`_^^]\[YVRONMOTajv|rh[NB;643368:=ADGJMPRUX[^cjrw~tf\SJDCFFEEDDCCCA?=;9754432111000/.,+++*******)))((('''&&&&&&&&&&&&&&&&&&&&&&&&%$#"! !! !!!"#$$$%%%&&&&%%%%%%%%%%%%%%%%$$########$$$$$$$$$$$$$$$$$%%%&&'''''''''&&&&&&&&&&&&&&&'(())**+-../00111111111000//..,,,+***)%$#""!!  %-6>FLPRROJB;4236>FNUZ]`abbccdeefgghhhiiijkkkkkkklllmmmnnonnoooppqqqqqqqqsrrrrrrrrrrrrrrrrrrrrrrrrsrqoligd`[RF;545544556667789:;<@CGLPSVXY\\\ZYWWWZ[\^aehkknqux{~~||zxxvtplhfhhikmqstz{}MMLLKKKKKKKKKKKKKKKKKKKKKJJJIIIHGFFEEFGHJJKKKKMMMLLLLMMMMLKLLLMMOONMKJIHFEDDCBA@>=;;<>CGNUZ_ceefeffffffffhiiiiiijjjjjjiiijkkkkllllklmnmmnmmnnoopqrqppqqqrrrrsttuwxyzzz{{{|}}||||~~~~}}}|zzzzzzzyxxxxxxxxxyyyzz{{{{{{{{|||}}}}~~~}xsmhdcdehnu{aW]`_`aa`__```````````````________^^^^^^^^^^^^^^^^^]ZYbxqgc`\ZZY\[Z[^_acjosux|uMELPQQQRRRQRQPQTUVKFGHKLLMLKKKKJJIIIIJJJKLLNOPPOPTTUUVVXY[[[\\\[[ZZZYYY[^`gpz±ocVNKLNQTUVWXXVSPLJNS\it{rh[NB:632368:=ADGJMPRUWZ^ciqwsd[RIEDFFEEDDCCB@><:8655432211100/.,+++*******))((('''&&&&&&&&&&&&&&&&&&&&&&&&&##"""!!  !!!! !!!"#$$$%%%&&&&%%%%&&&&&&&&&%%%$%########$$$$$$$$%%%%%%%%%%%&&&'''''''''&&&&&&&&&%%%%%%&'''())*,,-.//0000111111111110/---,,,,*)''&%%$#" $,6?GLQSROH@92/04@EILQVZ^`abbb``_^^aabcehjkkorvxy{~}|||{zxwuqnkjjlnpsuw|}LLLLKKKKKKKKKKKKKKJJJJJJJJIIIHHGGFEEEEFGJJKKKKLLLKLLLLMMMLKLLLMMOONMLJIHFEDCBA@?><;;<>DJQX\bfgfgffggggggggijjjjjjjjjjjjkkkllllmmmnmnoooopppppqqqssssssssssttuuvwxyz{{{|||}~~}|||~~}||||||{zyyyyyyyxyyyzz{{{{{{{{||||}}}~~~{vpkgdcbgnty~~^W[`aa`_^`b````````````````________^^^^^^^^_^^^]]\[[XL_}{z{{{į~XGKRTSSTTTUTUTSTXZXQJGGJLLNMKKKKKKJJIJJKLMNMLMNPQRUTUUUVXY[\[ZZZZYVWXWWXY_fltzǺyl`YTRPOORU[clt¾{rg[MA9532358:=ADGJMQSUWZ^ciqw}sfYNHGDGHGHHGFEB@><:87665432222110/-,,,*******)(('''&&&%%%%%%%%&&&&&&&&&&&&&&&%##"""!! !!!"!!""""###$$$%%%&$%%%&&&&&&&&&%%%$$$$$$$$$$$$$$$$$$%%%%%%%%%%%&&&&'''''''''''&&&&&%$$$$$$$%&&'()))**+,-.//01122222222221///////..,++*))(&$#"!  %-7@GLPQPLE=61,-2;CLSX\_`bbccdeefgghhhiiijkkkkkkklllmmmnnonooooppqqqqqqqqsrrrrrrrrrrrrrrrrrqqqpppqqqqoligfd`XK=668;<<<;::<;<<>@BEKOSY^bfiimmmmjhfegghjkkllkoquwxzz}}|||}}~~}|{ywurppqrtwy|MMLLLKKKKKKKKKKKKKJJJIIIJJIIIHHGFFEEEEFFIIJJJJKKKKKKLLLMMLLLLMMMOONMLKIHGFECB@@?><;:;>EKSZ_ehihigghhhhhhhhijjjjjkkkkkklllmnnnnoooppqqqpqrrrrrsssttttttttttttuvvwyz{{{|||}~~~}||~~~}}}||{{{zzzyyyzzz{{|||}}}}}}}}}}}}}}}ysnifedeipv{¾y]T\`b_\_`_``__````````````________^^^^^^^__^]\\[YXVTNCi¾t\WUUUUTUVVVUVUTUY[[UNIGIKLMMKKKKKKJJJKKLMMNMLMOPQRUTUUVWXY[[YYXXWWSTTUUWY_gmsz¿ǿ{qgZM@9643468:=ADGJMPRUWZ^ciqx}sfYNHGDGGGHHGEEB@><:876655432222210.--,+****)))(('''&&&%%%%%%%%%%%%%%%&&&&&&%%$##"""!!! !!!!"""""""""###$$$%%$%%%&&&&&&&&&%%%%%%%%%%%%%%%%%%%$$%%%%%%%%%%%&&&%%%&''''''''&&&&&&%$$$######$$$%(()**+,-../0111222223321111100000/.---,+('&%$$#!""! !%*1:CIMPQOKB:4/++1;BKSX\_`abccdeefgghhhiiijkkkkkkklllmmmnnonoooooopppppppprrrrrrrrrrrrrrrrrrqqqpppqqqqolihgfaYM?89=?@AA?>>>???ACFJPUZ`diloprrrqomkhiijkllklknpsvwxwxxxy{~}{zxwvvvxz|MMMMLLLKKKKKKKKKKKJJJIIIJJIIIHHGFEEDDEEFHHIIIIJJJJKKKKLLLLLLLMMMOONMLKJHGFEDBA@?><;:;>EKT[`fjkjjhiijjjjjjjjjkkkklllllmmmmmnnoopppqrsssrrsttttuuuuuvvvvvvvvuuvvwxy{|||}}}}~~}}|~~~}||||{{{zzzz{{{|||}}}~~~~~~~~~~~~{wqlhfeehlrx}ĺu[W\a`^``\````````````````______________^__^\[[YXWQWXFB|ƹpaXYXWWXXWXWWXWUVY\]ZSKGHKMMMLLLLLLKKKKLMMNONMNOPQRTTUVVWXYYYXWVVTRQQSSUVY^cjt|{qfYL@9643468;>ADGKMOQUWZ^djrx~tgZNHGDGGGGGGEEB@?=;987765433222210.--,+***)))(('''&&&%%%%%%%%%%%%%%%%&&&&&%%%$##"""!!! !!!""""##########$$$%%$%%%&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%$$$$$$$$###$$$%%%&''''''''&&&&&&%$$$###"###$$$&&''(()**+,-./01111223211122222221100//.,+*)(('&''&%&*/5>EJMPPNI@82.*+0:BJRX\_`abccdeefgghhhiiijkkkkkkklllmmmnnonoooooopppppppprrrrrrrrrrrrrrrrrrqqqpppqqqqolihggc\PB<>CFGGGFEDCAABDFJNTY^dimquvyyxwusqnnmmnnnmmlnpstuvutttvy}~}||{{{{}~NNMMMMLLKKKKKKKKKKJJJIIIJJIIIHHGFEDDDDEFHHIIIIJJJIJJKKKKLLLLLMMMOONMLKJIGFEDBAA?><;:;>ELU\agklkkjkkkkkkkkkkklllllllmmmmmmmooopqqqrsttuttuvvvvvvvwwxxxxxxxxwwxxxy{|}}}~~~~~~}|~~~}}}||{{||||}}}~~~}xtojgeegjnuz~÷w`XY^aba`_````````````````______________^^]\[ZYXWTWWP8JƳqaUYZYZZZZZXXYXWWZ\^]XNHGJMNNNNNNNNMLKLLLMMNNNNOPQRTTUVVVXXXXVTSRPNNOQRTWY^dkt}zqfYL@9643468;>ADGKMOQUWZ^dksxvh[OIGDFGFGGFEDBA?=;:98776543322210.--,+***)))(('''&&&%%%%%%%%%%%%%%%%&&&&&%%%$##"""!!! !!!""""###########$$$%%$%%%&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%$$$$$$$$###$$$$%%&&&&&&&&&&&&&&&%$$$###""""###$$$$%%&&&'(*++,..//011222233332232222110//.--,,*++*)+.39AGKMOOMF<50,(*0:AIQW[_`abbcddeffgghhhiiijjjjjjkkklllmmnonoooooopppppppprrrrrrrrrrrrrrrrrrqqqpppqqqqoligggd^SF@DJNOOONMKIFEFHKOTY^cjosw{}~~~|{ywtrqqqponomoprsrsrqpprv|ONNNMMMLKKKKKKKKKKJJJIIIJJIIIHHGEEDDDDEEGGHHHHIIIIIJJJKKKKKKLLLMNNNMLKKIHGFDCAA?><:9:>EMU]bhklkkklkkkkkkkkkllllmllmmmmnnmnopppqrsttuvwvvxxxxyyyyzzzzzzzzzzzzzzz{|}~~~~~}}~~}}}}~~~~zuqmhfeehlpv{ƹhYX]_ad`aaa`_````````````````````````_^]\[ZYXWVVSRQB7Wűxe\^^\[[[[\ZZZYYYZ\]_ZRKHJMNOOOOOOONMLLLLLMNNNPPPQSSTUUUUVWXUQOMKJKMOPRTVY^gmt{zpeYL>8421257;>ADGKMOQUWZ^dksxwi[OIGCFFFGGFDDB@?=<;:9876554332210.--,+***)))(('''&&&%%%%%%%%%%%%%%%%&&&&&&%%%##"""!!!!!!""""############$$$%%$%%%&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%$$$$$$$$###$$$$%%&&&&&&&&&&&&&&&%$$$###"!"""######""###$%&&'()*+,-./02223334212233332211000//.///./37<:9:>FNV]bhklkklmlkkkkkkkllllmmllmmmnnnnopppqrrtuvwwxxxyzzz{{{{||||||||||||||}}}~~~}~|xsokgefgjosy}¦l[X\_]_`aaa`````````````````````````_]\[ZYXWVUTSSOL>7421247:=ADGJMOQUWZ^dksyxj\PIGCFFFFFFDDA@?=<;:9887654432210.--,+***)))(('''&&&%%%%%%%%%%%%%%%%%&&&&%%%$##"""!!!!!""""#############$$$%%$%%%&&&&&&&&&%&&%%%%%%%%%%%%%%%%%%$$$$$$$$###$$$$%%&&&&&&&&&&&&&&&%$$$###"!"""####""!!!! !!""#$%%&()*+-0011233212333443211122111232236:=@DGHJJG?6.*(%'.8?HPV[^`abccdeefgghhhiiijjjjjjjjkkklllmnonoooooopppppppprrrrrrrrrrrrrrrrrrqqqpppqqqqoligghfbXKGOY^___^\ZVRQQSVZ]bglrv{~|xwvutrrsrrqqpnnllkknsy~OOONNNNMLLLLLLLLLLKKKJJJJJIIIHHGEDDCCDDEFFGGGGHHHHIIIJJJIIIIIIJJNNNNMLKJHGFECBA?=<:9;?EMW\chkkkjllllllllllkkkllllllmmnnopppqqqrruuwxxyz{{{|||}}}}}}}}}}}~~~~}{wqmidcfgmrv{Ǵ}dWVZ__aa_^^_`___``__``````````````_]\[YXWVVUSRQNKE=<;::98766432210.--,+***)))(('''&&&%%%%%%%%%%%%%%%%%&&&%%%$$##"""""""""""####$$$$$$####$$$%%$%%%&&&&&&&&&&''%%%%%%%%%%%%%%%%%%$$$$$$$$###$$$$%%&&&&&&&&&&&&&''&%%%$$#""""#####""!  !#$%&(+--../00//001122123334444344347;>BEFGIHD=3-*(%'-6?HPV[_abbccdeefgghhhiiijiijjjjklkklllmnnnoooooopppppppprrrrrrrrrrrrrrrrrrqqqppppqqpnlihgjie\NISaefffec`\XWVXZ]`dimrvy}~|zyxwvuvtsqomlkjkklosx|QQPPPOOONNNMMMMLMLLLKKKJJJJJIIIGEDDDDDDEHGGGGGGGGGGGGHHHHHHIIIJJMMMMMLKJIHGFDCB@><::;?FNW]chjjkkllllllllllkklllmllmmmnnopqqqqrrruvwxxzz{{|||}~~~}}~~~~~~~}zuplhddfhnsw|Ƽwe[XY_a`_`b_`a`_^_````````_______^\[ZYXWVUTUQRMJKDEhà|dZ_``__`_`]]]]]\ZZYZYXTPMLPRSSSSSSSSSQPPQQQQRRSRRQSSRRQMGA?>?@BEHJLNPRTX[`hox¿|rfZL?94212479>==<;:977532210.--,+***)))(('''&&&%%%%%%%%%%%%%%%%%%&%%%$$$################$$$$$$$$###$$$$$$$$%%%&&&&&&&&''%%%%%%%%%%%%%%%%%%$$$$$$$$###$$$$%%%%%%%%%%&&&&&''&%%%$$#"###$$$#""!  "#%'()*+,,--...//01113334455567767:=@ACDDEC?8/+(&$&,5>GOV[_`bbccdeefgghhhiiijiijjjjklkklllmnnnoooooopppppppprrrrrrrrrrrrrrrrrrqqqpppppponkihghhf]OKVflmmmlkg`[[Z[]_beimqux{~|{{zxwxvuspnljjklnptx{RRRQQQPPPPPOONNMNNMMMLLLKKKKJJJHEEEEEEEEHGGGGFFFFEFFFFFFFFFGGHHHKKKKKKKJIIHGFDCB?=;;<@FMW^dhjkllllllllllllllllmmlmmmmnnopqqqrrqruvwwxzz{|}}}}~zvrmifdeilrx{öq`WVZ^aba`aa`_^^_```````_^^^^^]\[ZYXXWVUTTQRMIMLBKɯd[`aaa__aa`^_^^][ZYXWXURNMPSSTUUUUSRSSTSRSQQRSUVVUTQOMIC=89;=?ADGJLNPRTX[aipy|rfYK?8421246:=@CFILNQUWZ^dks{sdWLGEAAABCCCD@@@???>>=<;98753210/.-,,,++**))(('''&&&%%%%%%%%%%%%%%%%%%%%%$$$$################$$$$$$$$$$$$$$$$$$$$$%&&&&&&&&''%&&&&&&&&&&%%%%%%%$$$$$$$$###$$$$%%%%%%%%%%&&&&&''&%%%$$####$$$%##""!!!  "#&'(('&(*++,,-...001234567899:<>??@A@?=93*'%##%*4>FNUZ^`abccdeefgghhhiiijiijjjjklkklllmnnnoooooopppppppprrrrrrrrrrrrrrrrrrqqqppppppomkigghhg^RO^lrtttrplha`__`abehlpswz}~}|{{yxuromkjkmpsvxz}RRRQQQQQQQQPPOONONNNNMMMLKKKKJJIEEEEEEEEHHHGGGFFFFFFFFFFGGFGGHHHJJJJJJJIIIHGFDCB?=;;=AFMW^dhjkmmmnllllllllllllmmllmmmnnnoppqrrrsvvwxyyz{|}~~|wsokgedglouy|Ķv`VW\`ba`__`_^]```````_]]]\[[ZYYYXXWVUTQQPOLKMJ>kͽk[__``_`baa___^]\ZYWVVTQNMPSUUUUUTVUUTTTTSUVUROOQVWRLEA;889;=?ADGJLNPRTX[ajq{}rfYK?84212469<@BEILNQTWY^dkt|ufXLFEAAABBBCC@@?????>=<;98753210/.-,,,,++**)(('''&&&%%%%%%%%%%%%%%%%%%%%$$$$#################$$$$$$$$$$$$$$$$$$$$$%&&&&&&&&''&'''''''''&&&%%%%%$$$$$$$$###$$$$$$$$$$$$$$$$$$$%%%%%%%%$$$$%%%&$##"!!!  "$&'('&%%%'''((()*,-./13566789:;;;;<=<964.'%$""$)3>EMTZ]_`bbcddeffgghhhiiiiijjjjklkklllmnnnnnnnnnooopppqqrrrrrrrrrrrrrrrrrrqqqppppppomkigfggg_RP_ouwwwusoigeccccbcfjmqux{~}{zwtpnmkkmqtwxy{|~SRRRQQQQRRRQQQQPOOONNNMMLLLKKKJIFFFFFFFFIHHHHGGGGFFFFFFFGFEFFFFFHHHHHHHHIIHGFDCB?><;=AGMW^dgjkmmoonnnnnmmmmlllllmmmmnnoooppqrrstvwwxyyz{|~~zurnjgeehmqvz}ǶuaXZaa`__``_^```````_]]]\[ZZYYYXXWWUSQPOONKNRBLȦx^```^_`_`__`_^]\ZYWTTRPNMPRTSTTTTUUSRTUTQLLKHHOYa[RH@:7689;=?ADGJLNPRTX[akr|}rfYK?83212469=<;98764210/.---,,++**)(''''''&&%%%%%%$$%%%%%%%%%%$$$$$#################$$$$$$$$$$$$$$$$$$$$$%&&&&&&&&''&'''''''''&&&%%%%%$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%$$$$%%%&$##""!!  "$&''&%$"!""###$%&)*+,.03566899998788741/+&$#"!$)3=DMTY]_`abbcddeffggghhhiiijjjjklkklllmnnnnnnnnnooopppqqrrrrrrrrrrrrrrrrrrqqqppppppomkigfghhbURarxz{{ywsmhfdddcbcdfilptx}|{xuronlkmptwxyzzz{}~SSRRRQQRRRRRRRRQPOOOONNMMLLLLKKJHHHHHHHHIIIIHHHHHGFFFFFFFEDDDDDDGGGGGGGGHHHGFDCB?><<>BGMW]cgijllooopoonnnmmmllllmmmmnoopppqrssstvwwxyzz{|~~}xsoligffjosx|lYR\_`___`a`_______^^^]\[[ZYYXXWWUSPNMNNNPUN@yκg_`a`___a_`a`_^\ZYVSRPPNMOPQRSTTTTUUSPMJGFHP\kutl]QE=878:9;=?ADGJLNPRTX[bls}~sgYK?83212469>>>>?>>=<;98764210/.---,,++**)('''''''&&%%%$$$$$$$$$$$$$$$$$$$#################$$$$$$$$$$$$$$$$$$$$$%&&&&&&&&''&'''''''''&&&%%%%%$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%$$$$%%%&$$##"!!  "$&''&$#! !"%&')+.14567887653331/,*(%#" #(2<<>BGNW]cgijllnnopooonnnnmmlllnmmnnopppqqrstttwwxxyzz{}~zupmkhffglrv{~μcZ\^_^_`a^^^^^^^^^^^]\[[ZYYXXWWUSQNMNMNQSS?]ɩ~a]^a`_```aaa`^\ZYWSRPPPOPPQRTTSRTRPLHEISey{k]OD=97789;=?ADGJLNPRTX[bmt}¿thZK?83212469><====>>>=<;987643210/.--,,++**)('''''''&&%%%$$$$$$$$$$$$$$$$$$$$################$$$$$$$$$$$$$$$$$$$$$%&&&&&&&&''&'''''''''&&&%%%%%$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%$$$$%%%&$$##"""!  "$&&&%#! "#%(+/255665431//.,*('%#" "(1:BKRX\^`abbcddeffghhhiiiiijjjjklkklllmmnnnnnnnnooopppqqrrrrrrrrrrrrrrrrrrqqqppppppomkigeegieXTbt{}~|xsolhgfecbabcehlqx}~{ywtqnmoqsvxyxvuuux~SSSSRRRRRRRRSSSRPPPOOOOONNMMMMMLJJJJJJJJKKKJJJJIIHHHHHHHGFEEDDDDEEEEEEEEFGGFEDCC@><<>BGNW]cgijllnnoppooooonnmmlmnnnnopqqqqrstttuwwxxyz{|}|wsnligfginuy}ʳb[Y[^``^^^^^^^^^___^]\\[YYXXWWURNMMOOMNSRFLğoa[`_aa_`aba`^\ZYWTRPQQQPQQRRRRQNIFISd{uk[MB>:8889;=?ADGJLNPSUX\cnvuh[L?83212469>==;;<<<====<;987643210/..-,,++**)('''''''&&%%%$$$$$$$$$$$$$$$$$$$$################$$$$$$$$$$$$$$$$$$$$$%%%%&&&&&''&'''''''''&&&%%%%%$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%$$$$%%%&%$$##""!!  "$&&%$"  !$'+.2344431/,,+)(&%$#" "'09AJRW[^``bbcddefghhhiiijiijjjjklkklllmmnnnnnnnnooopppqqrrrrrrrrrrrrrrrrrrqqqppppppomkigedfheYSas|~~zvrokigfcb```aeinty}~{yvtpopqsuvwwutssv|TSSSSRRRQQQRRSSSRQPPOOOONMMMLLLLKJJJJJJJKLLKKKJJJJIIIIIIHGGFFEEEEEEEEEEEEEFFEDCCA=;72222478:>ADGKMORUX^eoyqbRGB@??>=<;:9:;<<<>>=<;987643210/..--,+*)*))''''''''&&&%%%%$$$$$$$$$$$$$$$$#############################$$$$$$$$$$$$$%%%%%&''''''''''&&&%%%%%%$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%&&%%%$#%%$##""!!! !#&&&&%$"  !$(+/133431.,)'&$#"""!#" "(19AJQW[]_`abbcdddefgghiiiiiiiikkklllmmmmmmmmmmmmnnnoooppqrrrrrrrrsssssssrrqqqpppppponljhfbeff_T\r{~}ysqmkifd`a`__aeiouz~|zxurqrrstuutsrqrtzSSRRQQPPPPQQQRRQQPPPOOOOONNMMMMMLJJJJJKKLLKKKJJJKJJJJJJJIHGGGGGFGGGFFFFEEEEEEDCAA=<:867;72112478:>ADGKMORUX^ep{qbRGB@?>=<;::89:;;;==<;:8765443210/...,,*****''''''&'&&&%%%%$$$$$$$$$$$$$$$$$############################$$$$$$$$$$$$$%%%%%&''''''''''&&&%%%%%%%%%%%%%$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%&&%%%$$%%$$##"!!!  #'&&&$#! "$(,0234531.+(%$"!!!"!" "'08@IQVZ]_`abbcdddefgghiiiiiiiikkklllmmmmmmmmmmmmnnnoooppqqqqqqqqrrrrrrrrrrqqqpppppponljhfbeggaT[r|~zvsoljgea_^\[]`ejrw{~{ywussrrrrsrrqppsw|SRQQPOOOPPPQQQRQPOOOOOOOPPONMNNNMLLLLLLLMMMLLLLKLLLLLLLLJIIIIIIIIIIHGGFFFFEEEDB@A><=@EKQY_ehjlnnoopqqqqrrrqpppopppqrqrssstttuuvvvwxyz{|}}zuqnkgccbejpw|ǙfTZ^`^^__^^^_^^]\[[ZYYXXVVURPQQPPPQQQRRDgÞwa\afbbbcba`_ZXXYZYUPPLNZpǾxk^PD>:866;72111368:>ADGKMPRUX^ep{udSGB?><:8766778999;<;:987765543210//.-,*****''''''&'&&&%%%%$$%%%%%%%$$$$$$$$############################$$$$$$$$$$$$$%%%%%&''''''''''&&&%%%%%%%%%%%%%$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%&&%%%%$%%%$$##"!!!  #%&&%$"  ""$&*/244543/+'$""!! !!!!!&/7?HPVZ\_`abbcdddefgghiiiiiiiikkklllmmmmmmmmmmmmnnnoooppqqqqqqqqrrrrrrrrrrqqqpppppponljhgcfhgaUYp}|yurnkied^][YZ\`dmrvy{||}~}{ywvutrqqqqqppqsvzSRQQPOOOPPPQQQQPPOOOOOOOPPONMNNNNNNNNNNMNNNNNNNMMMMMMMMMKJJJJJJJJJJIHHGGGGFFEDCAA><=@EKQZ`fikmooopqrrrsstsrqqqppqrrrsrstttuuvvvwwwyyzz|||~~~ytoliebbcflry~_S\a^^]^^]^_^^]]\\[YYXXVVURQQQQQQQQQTUD`ɭk][[XYXXXWXZYUQMKJO`sžyk^PD>:866;@DGKMQSUX]ep|veSGB>=;987666677779:9988777665532100/.-+*)))''''''&'&&&%%%%$$%%%%%%%$$$$$$$$###########################$$$$$$$$$$$$$$%%%%%&'''''''''''&&&%%%%%%%%%%%%$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%&&%%%%$&%%%$$$#""!  "$%%%$"  "#%(,0344542.)%"!!!!!!!  '.7>GOUZ]_`abbcdddefgghiiiiiiiikkklllmmmmmmmmmmmmnnnoooppqqqqqqqqrrrrrrrrqrqqqpppppponljhfdgjhbWWm}}yvspmjfd_]ZXYY]`jnruwyz{}~|{yxwutrqppppqrtvx|SRQQPOOOOPPPQQQPPOOOOOOOPPONMNNNNNNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLKKJJIIIHHGFFEDBB><=@EKQZ`fikmooopqrrsstttsrrqqqrrsssttttuuvvwwxxxyyzz|||}~|wrmjfcaaehnu|ֽYR[a`_^]^`^^^]]]][YYXWVUURQRRRRRRRRVUD]Ǯ}skd\XVURQTZcozl^PD>:866;=;976655566668:9988877666543210/.-+**))''''''&'&&&%%%%%$%%%%%%%$$$$$$$$###########################$$$$$$$$$$$$$$%%%%%&'''''''''''&&&%%%%%%%%%%%%$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%&&&&%%%&%%%$$$#""!  #%$$$#" !#$&)-1455542-)%"!!!!!!!!  '-7=FOUZ]_`abbcdddefgghiiiiiiiikkklllmmmmmmmmmmmmnnnoooppqqqqqqqqrrrrrrrrqrqqqpppppponljhfcgjhcXWk{}zxtqnkhe`]YXXXZ]eimortvy{~~}{zywusqpppprstvvz~SRQQPOOOOOPPPQQPOOOOOOOOOPONMNNNOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNMMLLKKJIHHGGFECB><=@EKQZ`fikmooqrrrsttuuvuttsssrsststuvuuvvvwwxxyzzzz|||}~}zsniea^^`gkqyԶ|XR__ba_^`_^^]]]][YXWWUTTRRSSSSSSSTVUEZº½zn^PD>:867<=?@BFHLMPRTWY]fr}|n^K=61000259:>ADGKLQSVX^gsziVIC=<:87655445555798888887766543211/.-+**)*''''''&'&&&%%%%%%%%%%%%%%$$$$$$$###############$###########$$$$$$$$$$$$$$%%%%%%&''''''''''&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$$$$$$$%%%&&&&&%%&%%%$$$##"!  !$$$$$#" !#%'*.2555541-)%#"""""""!&-6>?BGNT[ahkmprrrstttuvwwxwvwvvvuutuvvvwuvvwxxyyyzzz{{|}}}|ypkfa][]`jnu|ҭ{VUU_b_^^]^]]]\\ZYXWWUTTSSSSSSSSSVVUEY{n^OD>:758=>?@BFILMPRTWY]gr~|n^K=5100025:;>AEHKLQSVX^hu|jVHB=<:865444455557888888887765432110.-+**)*''''''&'&&&%%%&&%&&&&&&&%$$$$$$$###############$###########$$$$$$$$$$$$$$%%%%%$%''''''''''&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$$$$$$$%%%&&&&&%%&%%%$$$##""! !$$$$$#"  "%')-14675541-)&#""""""""&-5;DMTY\^`aabccddeffghhiiiiiiijjjkkklllllmmmmmmmnnnoooppqqqqqqqqrrrrrrrrqrqqqpppppponljhgcgiheZVgz|ywtrokib^ZWUTUW[^adgknrvy{}~|zxvtrpoqsttsrtwz}SRQQPOOOOOOOPPPOOONNNNNNNONMMMMNOPPPPPQQRRRRRRRRRRRRRRRRPPPPPPPPQQQPPOONLJIIIHFDA?>@BGNU\bhlnprrstuvvwwxyyyxyxxxwwvwyyxxwwwxyyzzz{|{|}~~~{xnid_[[^cnqxѪpWT\^^^^]]]\\\\ZYXWWUTTSSTTTTTTTVWVE]}o]NC>:758=>?@BFILNPRTWZ^gr~¾~o_K;4100025:?@BFILNPQSVZ_gtÿ}n^K=52202368;>ACFJLORVY_ivlVGB=;986434333333467888999877654322//.-,+**''((('((&&&%%%&&&'&&&%%%$$$$$$$$###############$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%&&&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$$$$$$$%%%&&&&&%%&%%%$$$$$#"!  !##$$$#"  "$%'*-14666530-)&$"""""""#! !"%*4:BKSX[^_`aabccdeeffgghiiiiiijjjkkklllllllmmmnnmmmnnnoopqqqqqqqqqqqqqqqppppppppoonnnmkifffjif^T_u~}zxvspmgc]YURRTTVX[^bgkosvxz|~~~~}{yxvsrqqqqqpqqtw{}SRRQQPPONNNNOOOOOOONNNMMMMLLLMMNPRRRRRRRRRRRRRRRSTTTTTTTTSSSSSRRRSSRRRRQOLKJIHGECA?ACHPW_ejmprssuvwwxxyz{{{{{{zzyyyz{{{|zzz{{{{|}~}}~~|vqlga\[]disw~ЧxYSX\][[[[[[[ZZYXXVUUUUUUTTUUUWVTGv¾}m_OD?;67;=>?ACFILNPQSVZ_iv¾~o^K=52202368;>ADGJLORVY_ivjTFA=:9764343333334677889998876543220/.-,+**(())((('&&&&&&&&&'&&&%%%$$$$$$$$###############$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$$$$$$$%%%&&&&&%%&%%%$$$$$#"!!  !##$$$#"  !#%&(+/25766530,)&$########!  ""$)39AJRX[^_`aabccdeeffgghiiiiiijjjkkklllllllmmmnnmmmnnnooppppppppqqqqqqqqppppppppoonnnmkifffiig`S]s}}{zxuqnkf`[VSSTQSUX\`ejnqtwy{|||}~|{ywtsrpooppqrtwz|RQQQPPPONNNNNNNNOOONNNMMMLLKLLMNPRRRRRSSSSSSTTTTTTUUUUUUTTTTSSSRRSSSSSSSQNLJIHFDB@?@CHPV`fkoqsttvwxxyyz{{{{{{{zz{{{{{{||z{{{||}}~~~|vqke_[[^flvzسZSWY\]ZZ[ZZZXYXXWVVVVVVUUVVWWVPM|m\OE>:78;<>?ACFIKNPQSVZ_iw¿n\J<52201359;>ADGJLPSVY_jwjSD?<966543333333346677888888775543310/.-+**))))((('&&&&&&&&&'&&&%%%$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$$$$$$$%%%&&&&&%%&%%%$$$$$#""!!  !#$$$$$#"  !#$&(*-036876530,(%#"""#####!! "!$)39AJQW[^_`aabccdeeffgghiiiiiijjjkkklllllllmmmnnmmmnnnooppppppppqqqqqqqqppppppppoonnnmkifffgihbUZp{~|{yvspmic^YUTTSRSUX\`ejnrtuuvwy{|}~~|{yvtsqonooqrtwy{~RQQQPPPONNNNNNNNOOONNNMMMLKKKLMMOQRRRSSSSSTTTUUUTTTTTTTTTTTSSSSRRSSSSSSSQNLJIHFDA@?ACIQYbhmqrtuuwxyzz{{||||}}|||{{{{{{||{|||}}~~|vqjd^[[_gnw|⾌fTRY^][ZWYZXZYYXWWWWWWVVWXXXUJ^|l[ND>:78;<>?ACFIKNPQSVZ_iv|m[I;421/1469;>ADGJMQSVY_jykSD?<9766433333333466677788888766544211/.,++*)))((('&&&&&&&&&'&&&%%%$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$$$$$$$%%%&&&&&%%%%$$$$$$##""""!  !"#$%%%$#" !"$%')+.147977630,(%#"""#####!! "#$)28@IQWZ]_`aabccdeeffgghiiiiiijjjkkklllllllmmmnnmmmnnnooppppppppqqqqqqqqppppppppopooonljggeehidWXmx|~~}{xusnkfa\XUSTSSTW[_dhlprssttvxyz{}}|zwutrpnnnpstuxz}RQQQPPPONNNNNNNNOOONNNMMLKKKKLMMOQRRRRSSSSTTTTTTTTTTTTTTTTSSSSRRRRRRRRRRPNLJIGFDA@?ADJRYcinrsuvvvwxzz{{|}~~~~~~||||||}}|}}}~~|vqic]Z[`hoy}ФrVQV\[YYXYZ[[ZZZZYXYXXXXYYZRK{l\MC>:77:<>?ACFIKNPQSVZ_iu}l[H;311/1369:769<>?ACFILNPQSVZ_iu~lZH:311/0369CKT\ejotuwwyxyz|}}~|vqgb]Z\ajqzËdYV[]\Z]]]]^^^]\\\\[[ZZVQŽ|l^MC=9779<=?ACGJLNPQSVZ_itľ}lYF:310.0369;>ADGJMPTX[al{ǿhPCB=;88765555555556667778877776655543210/.-,+*)((('&&&&&&&&&'&&&%%%$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&%%%%%%%%%%%%%%%%$$$$$$$$$$$$$$$$$$$$$$$$$$###""#!"#$$%&&&&%$#"! "#$$%&')*,/258:9:862.)&$##$$$$%$$#!!$$&)06>HOUY[_``abcdeeeffgghihiiiiijjjkklllllllmmmnnmmmnnnooppppppppqqqqqqqqppppppppopooonljggedhjgZUcnux{}~|{usoje`\ZWUTUVXY\^acefghjlnqrtwy{}~~|yuqnllptwxxvuvx{RQQQPPPONNNNNNNNNNNNNNMLLKJJJJKLNPQQQQRRRRSSSSSSRRRRRRRRQPPOOONNNNNNNNNNLJHFEDB@A>;988655555555666677788777666655443210//,++)((('&&&&&&&&&'&&&%%%$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&%%%%%%%%%%%%%%%%$$$$$$$$$$$$$$$$$$$$$$$$$$######"##$%&&'&&%$#"!!#$%&&'(*+-/369;:;973/*'$$$$$%%%%$$""$%')06>GNTX[_`aabcdeeeffgghhhiiiiijjjkklllllllllmmmmmmmmnnoppppppppqqqqqqqqpooooooooppponmkihedhjh[T_lrvy|~~}xvrmhc_]XWVVWXYZ\^`abdefhkmprtwy|~~{wsommpsvvvtrssvz}QQQPPPOOMMMMMMMMNONNNMMLKKJJJKKLPPQQQQRRRRSSSSRRRRRRRRRQPOONNNMMMMMMMMMLKIGEDCA?<;ELRX\_`abbcdeddeeefffhhhiiiiijjjkkllllllllmmmmmmmmnnopppppppqqqqqqqpooooooooooponmmkgfdfhjdYT\fmquxz}zwtojeb_ZYWWWWXXY[]^_abdegjmortx{}~{xtqpoprsssrqppqsuy}PPOPOOOOMMMMMMMMMNNNNMLKKJJIIJJKOOOPPPPQQQQRRQQQQQQQQQQPONNMMMLLKKKKKKKJIGEDCA?=;;;;:76666666666667778876666666655443221/.-,*)('&&&&&&&''&&&%%%$$$$$$$$$$$$$$$$%%%%%%%%%$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&%%%%%%%%%%%%%%%%$$$$$$$$$$$$$$$$$################$%&''(((((''&&&'''&''(()+.259<==;84/*&$$$%%%&&%%$$$%&)*/5=ELRX\^`aabcdfddeeefffhhhiiiiijjjkkllllllllllmmmmmmmnopppppppqqqqqqqpppooonnnnnooonmlgfdfhkg[RWcjosvx|}zwrmheb]\ZXXWWWXZ\]^_abegjmortx{|~|zwtqoppqqqqpooopsw{~NNNONNOOMMLLLLLLNNNNNMLLKJJIIJKLNNNNNNNOPPPPPPPPOOONNNMMLLLKJJIIJIIIIHHGEDCB@><;::;;:766777766666666677666666666333333330/.,*)('&&&&&&&''&&&%%%$$$$$$$$$$$$$$$$%%%%%%%%%$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&$$$$$$$$$$$$$$$$$################$%'(())*)))(((((''&&&'')+.37;>@>=950+'%%%%%&&&%%$%$%&)*/5>EKQW[]_``abceddeeefffhhhiiiiijjjkkllllllllllmmmmmmmnopppppppqqqqqqqpppooonnnnnooonmlgfdehli_RS^glptvz}yupkhea_][ZYYXWXZ[\\^_dfilortwy{~}{yvroooopqqpnnmmoty}MMMNNNNNMMLLLKKKNNNNNMLLKJJIIJKLNNNNNNNOOOOOOOOOONNMMMMLKKKJJIIHHGGFFEECBBA@><:969>DNWagnrvwyz{zy{}~~}ztjc\XY^hpz~mXaca^_bab__\a^TmúqVI@<9779=?@BEHJMOQRTWZaj}żnWE:3101257;=@CFILOQUX[co˿]EDB?<;:8677777666555555556555555553333333310.,+*(''''''''''&&&%%%$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&$$$$$$$$$$$$$$$$$###############$%&()*****)))((((''&&''(*-05:=@BA?;61-(&&&&&'''''&&&'(*,/6=DKQV[]_``abceddeeefffhhiijiiijjjkkllllllllllmmmmmmmnooooooopqqqqqqqpppooonnnnnooonmlgfefhljaSPYbinrux}~{wsokhdb`^\[[ZWXXYZZ[]cehknqtvwz|~}{yvspoonopqpnnmlnrw{MMMMNNNNMMLLLKKKMNNNNMLKKJJIIJJKNNNNNNNNOOOOOOONNMLLLKKKJJIIHGGGFFEEDDCBA@@><:976:?FNXagmruwy{|{z{}~~~ztkd\XX]gnx}g_fefa`aa`^a[RiǾvWH@<8679=>@BEHJMOQRSXZblmVD93101257;=ADFILORUX[br¶cFDC?<<;8778777666544444445555555553333333310/-+*)('''''''''&&&%%%$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&$$$$$$$$$$$$$$$$$###############$%&()*****)))(((''&&&''(+.28<@CECA<72.)'''''(()(((())*+-05@BEHKNOQPSXZamúoUA83001258;>ADFIMORUX[bxĺhGCC@=<;9788777666544444444444444443333333321/-,+*('''''''''&&&%%%$$$$$$$$$$$$$$$$$%%%%%%%%%%%$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&$$$$$$$$$$$$$$$$$###############$%&()*****)))((('&&&&'((,/4:?CFGEB>94/+('((((())())**++-05;AHOUZ]_``abceddeeefffhhiijiiijjjkkllllllllllmmmmmmmnooooooopqqqqqqqpppooonnnnnooonmlhgfghkjbTMPY`flquy{~|xtpmjheb`_^]ZZZZZZZ\`beimprtuwyz{}~|zxwtrpnnoopponmnqtw|LLLMMMNNMMLLLKKKLMMNNMLLKJJIIJJKNNNNNNNNNNNNNNNLLJJJJIIIHGFFEEDDDCCBBAA@==;9865569AJS]flntwx{|||{|~~~~}wmf^XW[cjty׳l`cba_]]iǻYH@<8689<>@BEIKNOPPRXZbnƾrT@83001258;>ADGJMOSUYZa|ĻiGDD@==<:8987776665444444444444444433333333310.,+*('''''''''&&&%%%$$$$$$$$$$$$$$$$$%%%%%%%%%%%$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&%%%%%%%$$$$$$$$$$###############$%&()*****)))(('&&&&&'(),05@BEIKNOOQRY[at·wU>83001258<>ADGJMOSWYZdĻhDDDA>><:8987776665444444443333333333333333320.-,*((((((('''&&&%%%$$$$$$$$$$$$$$$$$%%%%%%%%%%%$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&%%%%%%%%$$$$$$$$$###############$%&()*****)))(('&&&&&()).17>DHJKIFA<61,*))))))))((()**+,04:AHOTY\^__`abdddeeefffhhiijiiijjjkkllllllllllmmmmmmmmnoooooopqqqqqqqpppooonnnnnooonmligffhlldVLJQZ`eintvy}{wtolifca`_\\[ZYYYY\_bfjnpqqsuvwy{}~|{zzxvtrpoonoooopqstx}JKKKLLLMNNMMMLLLKLMNNNMMKJJIIJJKNNNNNNNMMMMMMMMKKJJJIIIHGFFEEDDCAA@@?>>=;97543248=EMWajorwy{||}||~~~~~yog^XVY_fpu|־µfF><979<<>@BEIKNPQRVYYcƻ}Y?83001258<>ADGJMPSXZZhºhCEDA>>=:9987776665444444433333333333333333320/.-+)(((((('''&&&%%%$$$$$$$$$$$$$$$$$%%%%%%%%$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&'''''''&&&&&&&&&&&&&&&&%%%%%%%%#######"$#######$$####""$%&()***))(((''&&&%&'()*/39AGKMNIFB=72.,*****))()*)*++*+/49@GNTY\^_`aabdeeefffgghhiijiiijjjkkllllllllllmmmmmmmmnoooooopqqqqqqqpppooonnnnnooonmmifdehllf[MHMV\`ekprv{}zwrolheca`^]\ZYXXX[]aeimopprssuvx{~|{zzxxvtqpnnoooopqstwz~KKKKLLLLNMMMLLLKKLMNNNNMKJJIIJJKNNNONNNMLLLLLLLKKJJJIIIHGFFEEDDCAA@@???=;97533259>GOXbjpswz{|}}|}~~~}zsja[WX\bnszŹiE==988;<>@BEIKMOQSVYYcɾ]@94112369<;;888777654444444322222222333333332100/.-+))(('''''&&&%%%$$$$$$$$$$$$$$$$$%%%%%%%%$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&'''''''''&&&&&&&&&&&&&&&&%%%%%%%%#######"$#######$$####""$%&()****)(((''&&&%&'(*-26=DJMNOKHC>83/-+++**))))*)**+*+/39?GNSYZ]_aaabcddeeefffhhiijiiijjjkkllllllllllmmmmmmmmnoooooopqqqqqqqpppooonnnnnnnnnnnjgedglmg\KCHQV[ahmotx}|ytqnjgeca_^][ZYXXZ\`dhloooqrrtuwz}~}|{yyywurommnooqrstwz|LLLLLLLLMMLLLKKKKLMMMMMMKJJIIJJKMNOOONMMLLLLLLLKKJJJJJJJIHGFFEDCAA@@??>=;9753337;@IRZckoswz||~~}{|~~~{umd]XVX]kpxȼjE<<988;<>@BEIKMPQSUYYdaA94112369=?BEHKNPSXYYkĵYDGFDB?><;;998877765444444333333333222222221100//-+*))('''''&&&%%%$$$$$$$$$$$$$$$$$%%%%%%%%%%%$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&'''''''''''''''''''&&&&&&&&&&&&&%$$$$$$##$#######$$####""$%'()))))('''&&%&%%&'(+/49AHMPQRMJE@:41.,,,++****+*++,++.38?FMSXZ]_aaabcddeeefffhhiijiiijjjkkllllllllllmmmmmmmmmoooooopqqqqqqqpppooonnnnnnnnnnnkhecfkli]I@CKPW^djmrv{}zwtplifdb`_^][ZYYY[_cgkmnoppqruvy}}{{{ywsqnmmnnprtuwyz|~LLLLLLLLMMLLLKKKKKLLLLLLKJIHHIIJMNNONNMMLLLLLLLKKJJJJJJJIHHGFEECAA@??>>=;:853459>EOX_flpvwxz{}~z{}~~~vne^YUX\inv}ɾkE<<988;<>@BEIKNPQRWXXeödA94223469=?BEHKNQTXYYmósMCIFDB@><<<:99877765554444333333333222222221100//-+*))((''''&&&%%%$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&'''''''''''''''''''&&&&&&&&&&&&&&$$$$$$$#########$$####""$%'))))(''&&&&%%&&&''(,06;CJORSSOLGA;631/..-,++++,+,,-,,.27>EMRXZ]_aaabcddeeefffggghhiiiiiiiikkllllllllmmmmmmmmmnnnnoopppppppppppooonnnnnnnnnnnkhecejlj_J??DKSY_ejotx||zwsokhfdb`_^]\[ZZ\_behjlmnooqsvy|~~}}|zxuspmmmmoqtvwwxyz}LLLLLLLLMMLLLKKKKKKKKKKKKJIHHHIIMMNNNMMLLLLLLLLKLKKKKKKKJIIHGFEDAA@@?>><:974346:@FPY_flpvwxy{|}~z{|}~}~xof_YUW[fls{kD<<988;<>@BEIKNOQSWWWgŷf?75433469=?CFHKOQUWXYnhFDIFDC@>=<<;:9877776555444333333333222222221100//-+**))(''''&&&%%%$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&'''''''''''''''''''&&&&&&&&&&&&&&$$$$$$$#$#######$$####""$%'))))((''''&&&'&&'(*.29>FMRTTUPLGA;75310/.-,,,-.-..-,,.16><9864457=BIR[agmpuvwxz{|}yz|}}}~yqhaZUVYdjrz²lD<=988;=?ACFILNPPSWWWgȹg=6543346:>@DGILPRVXWYq̿_BFHGEC@?=<<;:9877776555444333333333222222221100//.,**))(('''&&&%%%$$$$$$$$$$$$$$$$%&&&&&&&&%%%%%%%%%%%%%%%%'''''&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%&&&'''''''''''''''''''&&&&&&&&&&&&&&$$$$$$$#$#######$$####""$%')))))((''''&&('')*+/3DJQV^cjpsx}}{wsoljhfecba`_]\]^acefijlnopsvy{}~|zxvsponmnoqrtuvwy{~LLLLLLLLMMLLLKKKKKKKKKKKKJIHHHIILMMNMMLLLLLLLLLLLKKKKKKKKKKJIHHFCBA@?>=<9865579@FLT]bhmpuuvwxzz{yz{||}~{sib[UUXbhpxƴkC<=988;=?ACFJLNOPSWVWgɼg<6543357:>@DGILPRVXVYsɼXCGHGECA?===;:9877776555444333333333222222221100//.,+**))((''&&&%%%$$$$$$$$$$$$$$$$%&&&&&&&&%%%%%%%%%%%%%%%%((((''''''''&%%%%%%%%%%%%%%%%%%%%%%%&&&'''''''''''''''''''&&&&&&&&&&&&&%$$$$$$#"$#######$$####""$%')))))((''''&'(((*+-15>CJQUVVWSOIC=85322110///./...-,,.05;CKQVZ]_`aabcdddeeeffggghhiiiiiiiikkllllllllmmmmmmmmmmmnnnooppppppppppooonnnnnnnnnnnlifdejmlbP?9;@EJQZ_flptz~}zvroljhfedcb`^]^_`bdegikloqtvyz}}{ywtqponmnoprtuvwy{~LLLLLLLLMMLLLKKKKKKKKKKKKJIHHHIILMMMMMLLLLLLLLLLLKKKKKKLMMLKJIIGDCBA?>=<986569@DGILORVWWXtŹRBHHGEDA?>==;:9877776555444333333333222222221100//.,++*))((''&&&%%%$$$$$$$$$$$$$$$$%&&&&&&&&%%%%%%%%%%%%%%%%((((''''''''&%%%%%%%%%%%%%%%%%%%%%%%&&&'''''''''(((((''''''''''''''''&&%$$$$$$#"$#######$$####""$%')))))((''''&')()+,.26AFLSWXXXSOJD>96433322110/0//.-++-05;CJPVZ]__`abccdddeeffggghhiiiiiiiikklllllllllmmmmmmmmmmnnnooppppppppppooonnnnnnnnnnnlifdeimldR@78;AELU[bhlqw{}zuqolihfedcb_^_``bccegjloqtvxz|}{yxvsrqonmnoprtuvwyz{NMMMMMMMMMMLLLKKKKKKKKKKKKJIHHIILLMMMLLLLLLLLLLLLLLLLLLMMMLKJJIGEDCB@>==;:978;@CJQX_dinqstuuvwwxyzz{{||}~wme]VUW]clt{ǵlB:=;8;;<>@BDHKNMQSWYWjb95444456:?AEHJMQSW[VVtŹ{OBIHGFDA?>==;:9877776555444333333333222222221100//.,++*))(('''&&&%%%$$$$$$$%%%%%%%%&&&&&&&&&&&&&&%%%%%%%%&&&'''%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&''''''''(((((((''''''''''''&&&&&&&&&&&%%%%%#################$&()))(**))))(((()+-/48DIOUXYXXQMGB>:876653220..///.-,,.16@BDHKNPQTYXUk±b85444456:?AEHJMPSWZWVuĵrLCJIGECA?><;:98777765555544333333333222222221100//.,++**))('''&&&%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&%%%%%%%&&&''''&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%&''''''''(((((((((((((((''''&&&&&&&&&&&%%%%%########$###""""$&()**))))))))))(')+-18=FLTXZ\\YQMHC><:88764321/////.-,,.15=9:;<>BHLPV[afjmoprsstuvvwxyyyzzz{|}}}}~~yph_WSRX_gow~ǵj@:=<9::<>@BDHKMORTXXVm`75444456:>AEGJMPSWZWVwjKEJJFECA@?=;98777776555555433222222221111111100//..-,++**))((''&&%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&%%%%%%%&&&'''''''''&&&&&&&&%%%%%%%%%%%%%%%%%%%%&''''''''(((((((((((((((''''&&&&&&&&&&&%%%%%#######$$$###""#%')*++++**))))))(')+.19>INUZ\]\[QMHC@=<;:9763211/00/.-,,.05;CKQVY[^`aaabdddeeeffffggghhiiiiiiijkkllllllllmmmmmmmmmmnnnooppppppoopooonnnnnnnnnnnlljfegklj]F4358=BHPV]chnsy~|yvsqonmkjigdcccbbbegjloqsuwy{}|zxwvwvtrponoqsuwvwxxPOOOONNNNNMMLLKKKKKKKKKKJJJIGFFGIIJKLMMLLLLLLLLLMMMMMMMMNNMLKJJIFFDBA@>=:;=>AEKNTY^cfjlnoqrrstuvvwxxxxyyz{||||}}yqh_WRQW^fmv|ƴi?:=;999<>@BDHKMORTXYWo^75444456:?BEHJNQSXYVWyɽeJHJGFEBA@?<;877667754444444332222222111111111000///.-,++**)((''&&%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&'''(((((((''''''''&%%%%%%%%%%%%%%%%%%%%&''''''''(((((((((((((((''''&&&&&&&&&&&%%%%%#######$%$$$###$&()*+,,,+***)))))(*,/4;AKRX[^^][RMHDA??>>=:863331110.-,,.15;CJPVX\^_`aacccdddeefffggghhiiiiiiijkkllllllllmmmmmmmllmmmnnoooooooononnnnnnmmmmmmmmllkgefjll]I5037;@ELSZ_ekpw||yvtrqomkjigfedcccegikmoqsvxz|~}|zywwxxwusqonoqtvwxxxPPPPOOOONNNMLLKKKKKKKKKKJJJIGFFGIIJKLLLLLLLLLLLLLLLLLLLLNMMLKJJIFFDBA?==<=?ADIOSY]aegjkmnpqqrsttvwwwxxxyz{{{|||}zqi_VQPV\dktzĴh@:=;899<>@BDHKMORTXYWo̾]75444456:?BEHJNQSXYVYyŷ]IIIHFDBA@><:876666654444444332222222111111110000000/-,,++**)(''&&%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&'''''''''(((((((''''''''&&&&%%%%%%%%%%%%%%%%%&''''''''(((((((((((((((''''&&&&&&&&&&%%%%%%$$$$$$$$%$$$###$&()*+,,,+***)))))(*-05ADHMSW\`cfhjklmnopqrssuvvwwxxxyz{{{{||zri`VQOTZbirx²dA:<;79:<>@BDHKMORTXYWoʼ\65444456:?BEHJNQSWXWZ{uSIJJIFDB@?><:766556654444444332222222111111110///////-,,++**)(''&&%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''((((((((''''''''&&&&%%%%%%%%%%%%%%%%%&''''''''((((((((((((((('''''''''''''&%%%%%%$$$$$$$%%$$$###$&()*+,,,+***))))((*-16>COU\^_`_\SNJFDCBBB@>;86554320/---/25CIPX_dipuz~zwvtrponmkjjihgggghijlmoruwz}~~}}{{zzyyz{|{zyxvtrpqsttuuvRRRQQQQQPPPOONNMKKKKKKKKJJJIGFFGHIIJKLLLLLLLLLLLLLLLLLLLMMLKJJIIEECB@?=<<>CGKQW[_behijkklnnopqrrtuuvvwxxyyzzz{{{~~yri`WQOSY`fov~ʿ^A;;9799;=?ACGJLORTXYWoɼ[55444456:?BEHJNQSWYW\~ʼbJILKHEDA@?>;:765455554444444332222222111111110///////-,,++**)(''&&%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''((((((((''''''''&&&&%%%%%%%%%%%%%%%%%&''''''''((((((((((((((('''''''''''''&%%%%%%%%%%%%%%%$$$###$&()*+,,,+***))))((*.16>EQV\_a_^\TOJHFEDDDB?=;8654321/.--025<<=?EJOU[_cegiijkllmnnopqqsttuvwwxxyyzzz{{}~~~yqjaWQPRW^dmu}Ǽ]A;:859:;=?ACGJLORTXYWoöY46433457;?BEHJNQSWYX]ŶzRFLKJEECA@?=;9654445543333333332222222111111110///////-,,++**)''&&%%$$%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''((((((((''''''''&&&&&%%%%%%%%%%%%%%%%&'''''''''(((((((((((((('''''''''''''&&&&&&&%%%%%%%%%$$$###$&()*+,,,+***))))))+/38@FQW]_a`_[TOKIHGGEFDA?<9864321/...026<@BDHKMPPSWYXlô[76323457<>ADGIMPRXZW\|gJHJJJFDB@?><:8654444442222222132222222111111110///////-,,++**)'&&%%$$$%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''((((((((''''''''''''&%%%%%%%%%%%%%%%%&'''''''''(((((((((((((((((('''''''''&&&&&&&&&&&&&&%%$$$###$')*+,---*)))((((&),/5:AFRWZ]^_\YTPLKJIIGEDB?=;9654310.../25:AFLQVZ]^_``bbbccddeefffggghhiiiiiijkjkkkkkkkllllllllllmmmnnoooooooonnnnnnnnmmmmmmmmlkihffhkmgV=-,01269?ELSY`hmsy~~}{yxvusqponmmlkjjiiiijmpsvyzyxvrrsvx{~~|{ywusqonnnUUUUUUUUTSRRQPOOMLLKKKKKJJJIGFFFGHIIJKKLLLLLLLKKJJJJJJJJLLKJIHGGCBBA?>>=AEIOU[`dhjjkmmlllmnnooopqrstuvwwxxyyyyzz{|}~}}|}~~}wqia[URRV[bjry̿ZB<;977:<>@BDHKMNQTUXZmY86323457;>ADGIMPRXZW^}Ƹ|ZHJIHIFCA?=<97643322223222222222222222211111111/.......-,,++**)''&&%%$$%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''(((((((('''''''''&&&&%%%%%%%%%%%%%%%%&''''''''''''''''((((((((((('''''''''&&&&&&&&&&&&&&&%$$$###$&()*+,,,*)))((((%)+.5;AGRVZ\^_\YTPMKKJJHGEC@><:754310/..025:AGKQVZ]__``abbccddeeeefffgghhhhhhijkjkkkkkkkllllllllllmmmmnnoooooonnnnnnnnnmmmmmmmmlkihffhkmhX?.+/0147=BHOU\diou{~|zywvussrqppomkkjjiiilnruxxwvtqqruw{~~}|{zwtqnmllUUUUUUUUTSSRQPOONMMLKKKKJJIHGFEEFGHIJJKKKKKKKKKKJJJJJJJJKKJHGEDD@?>===?@DHKQZ`cgikllmnmmmnnnooopqrsstuvwwxxxxyyyzz{|~}}|~~~}vpjb^XSSUZbjqx}ǻUB=:9789;=?ADGJLNQTVX[mƺ}U86323457;?BEGJMPRWZX_|ɽgQGKIGHFBA?<:7542210001222222221211222221110000/.--------,,++**((''&&%%$$$$$$$$%%%%%%%%&&&&&&&&&&&&&&&&''''''''(''''''''''''''''&&&%%%%%%%%%%%%%%%%%&''''''''''''''''(((((((((((''''''''''&&&&&&'''''''&%%$$$$$&'()*****))((((('&),/6<:854320/..025;AGLQVZ]__``abbccddeeeefffggggghhhijkjkkkkkkkllllllllmmmmmmmnnnnnnnnnnnnnnnnmmmmmmmmmkihffhkmhYA/+.//25:?EKQW_ekrx|~}{zyywvvutsrommlkjjjmnqtvvutqnoqsvy}~}}|{yvromkkVVVVVVVUTSSRQPPONNMMLKKKJJIHGFEEFGHIJJKKKKKKKKKKJJJJJJJJKJIGEDCB@?>==>@AEILRZ`cgjklmnnnmnnnnooopqrrstuvvwwxxxxyyzz{|~}}}~~~}vpjc_YTSTYaiov{µ{QB>:988:;=?ACGJLNQUVX\kuP86323457;@BEHJMPRVZY`yz]NGJIGHGBA?<:7542100/001111111101111222211000///-,,,,,,--,,++**)(''&&%%$$$$$$$$%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''''''''''''&&&&%%%%%%%%%%%%%%%%%&''''''''''''''''((((((((((('''''''''''''''''''''''&&%%%%%&'))******(('''((''*-07=CISWZ]^^[XSPMLKKJIGDB?=;98654210//025;BGLRVZ]__``abbccddeeeefffggggghhhijkjkkkkkkkllllllllmmmmmmmnnnnnnnnnnnnnnnnmmmmmmmmmliheehknh]E1+-/0148>>>?ABHLNT\adhjllmnoonnnnnooopqqrsstuvvwwwxxxyzz{|~}}}~~|vpkd`[VTTX`gmtyƽrMB>:989:;=?ACGJLNQUWX[iǽmL96323457;@BEHJMPRVZZaxƺlRLHIHFGGBA?<:754210/.//0////////0011122211000//.-,,,,,,,,,+++**)(''&&%%$$$$$$$$%%%%%%%%%&&&&&&&&&&&&&&&''''''''''''''''''''''''&&&&%%%%%%%%%%%%%%%%%&''''''''''''''''(((((((((((''''''''''''''''''''''''&&%%%&&()**++++*(('''(('(,.18>DJTX[]^^ZWSPMLKJJIFC@><:87764310//036;AFKQVZ]__``abbccddeeeefffggggghhhijkjkkkkkkkllllllllmmmmmmmnnnnnnnnnnnnnnnnmmmmmmmmmliheehknk_H2*+-.036:?DJPW^dipuz~}}{zyxwvusqonmlllmnpstsrqnklnqtw{~~~}|{yvspmmVVVVVVVVUTSSRQPPONNMMLKKKJIHGFEFGHHIJKKKKKKKKKKKJJJJJJJJJIHFDCBA?>>>?@CDKNQV]bdhjklmnoonnnnnoooppqqrstuuvvwwwwxxzz{|~}}}~|upkeb]YUUX_flsxjKA>:8899;=?ACGJLLQTVXZf}eH96323457;?BEHJMPRUY[bvɾ}]KKIIGEEFA@>;964310/.--./........0000111111000//.-,,,,,,,,,+++**)(''&&%%$$$$$$$$%%%%%%%%%&&&&&&&&&&&&&&&''''''''''''''''''''''''&&&&%%%%%%%%%%%%%%%%%&''''''''''''''''(((((((((((((((((((('''''''(((((((''&&%&&'()+,,,,+*(('''(((*-03:?EKUX[]^]YVROMKJJIGEB?=;987764310//037;AGLQVZ]__``abbccddeeeefffggggghhhijkjkkkkkkkllllllllmmmmmmmnnnnnnnnnnnnnnnnmmmmmmmmmliheehknmaK3*++-.148=>>?ADFNQSW^bdgiklmnnnmnnnnooopppqqrstuuvvvwwwwzz{|~}~}~{uplgd_[VVX^ekrwĺ~aIA=:8888;=?ACGJLLPSUWYcxŽ~_E:6323457;?BEGJMORUX[cslQIJJHFDCDA@>;96431/.-,,,-,,,,,,--//00001111000//.-,,,,,,,,,+++**)(''&&%%$$$$$$$$%%%%%%%%%&&&&&&&&&&&&&&&''''''''''''''''''''''''&&&&%%%%%%%%%%%$$%%%%&''''''''''''''''(((((((((((((((((((('''''''((((((((''&&&''()*,,,,+*(('''(((*.03:@FLUY\]^]YUQNLJJIHFC@><:877764310//037?@CFGPSTX^bcfhjllmnmmmnnnoooppppqrssttuuvvvwwzz{|~}~~}ztplhea]WWY^djqvȾuZHA<97777:=?ACFIKLPRTWX`rvZD:6323458;96431/-,,++,++++++,,.///000111000//.-,,,,,,,,,+++**)(''&&%%$$$$$$$$%%%%%%%%%&&&&&&&&&&&&&&&''''''''''''''''''''''''&&&&&&&%%%%%%%%%%%%%%%&&&&&&&'''''''''(((((((((((((((((((('''''''(((((((((('&'(()*+-..,+*(('''(()+.14;@GLVY\]^]YUPMKJIHGDB?=;9877764310//137>?@BDHJPSUX^acfhjjklmllllmmmnnnoppqrrsstttuvvwyyz{|}}ytpmifb]YXZ_ekqwkVJA>:8888;=?ACGJLMOQSUZdrünUD<5223459=>ADGILORSVY^j}ûtTIIHGFDCCBA@>;96431/-+**)*******+,-..////011000//.---------,+++**)(''&&%$$#######$$$$$$$$$%%%%%%%&%%%%%%%%''''''''&&&&&&&&'''''''&&&&&&%%%%%%%%%%%%%%%%%%%&'''''''&'(((((((())))))(((((''''''''()()))))))))(((()*++,.//-,+))((())*,/37<;9877764310/0248??@BDHKLSUVY]`acehhikkklllllmmmmnnoopqqrssstuvwyyz{|}{wsolheb]ZYZ_djqw~vdTHA<97777:=?ACFIKMNPRUZblxľ|eQC;5233459=>ADFILNQSVY^hycLKIGFECCCCBA><975320.,**)))))))))*,,,--./0000000/..--------,+++*))((''&&%$######$$$$$$$$$$%%%%%%%%%%%%%%%%''''''''&&&&&&&&'''''''&&&&&&%%%%%%%%%%%%%%%%%%%&'''''''&'((((((()))))))((''''''''''((())))))))))(((()*,+-/00.-+))((())*,/37CHLPTX[^__``bccddeefeeeefffggggghhhiiijjkkkkkkkkkllllmmmmmmmmmnnnnnnmmmmmmmmmmmmmmmmmkiecegjngU=,)*++-.025:?CIOV]ekqwy|}zywvuuuuxwvtsqonmlklmopppsvz~}||UUUUUUUUUTTSSSRRRQQPPOOMLLKJJJJKLLLLLLLLMMMMMMMLKKKJJJIIGFECA@?>?@BDHKOPUVXZ\^`adffhijjkkkklllllmnnooppqrrstuvxyyz{|~{vtpnkifd^\\_chnt{ſ}n^PG@;87667:<;9877652/..147<@DHLPTWZ]__``bbccddefeeeefffggggggghhiijjkkkkkkkkkllllmmmmmmmmmnnnnnnmmmmmmmmmmmmmmmmmkiecegjnhY@.))*+,-/137<@EKRZahnuwz~~|{zxxxxxzyxvtrpnmlklmnpporuy}~~UUUUUUUUUTTSSSRRRQQPPOOMLLKJJJKLMMMMMMMMMMMMMMMLKKKJJJJIGFDBA@??ABDGJNQRVWXZ\^``ceefhiijkkkkllllmmnnoopqrrstuvxyy{|}{vtqnljgd_\\_chnszýugZNG@;87668;=?ACEGJLMNQTY_foyļ}l\MB9533346:=@BEGIKNPSVY]dozlSJJGFEDDDDEECA>;875310.,+*)(('''''(()))*+,-....///...---,,,,,++++**))((''&%$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%''''''''%%%%%%%%(('''&&&&&&&&&&&%$$$$$$$%%%%%%%%%&&&&&&&&'((((((()))))))('''&&&'(((()))*********))(())*-.0100/,)(((()*+,-058>ELRW[]]][WRMLKJJJIECA?=;9877641/..147<@DHLPTVY\^_``abbccddedeeefffgggggggggiijjkkkkkkkkkllllmlllllllmmmmmmmmmmmmmmmmmmmmmmmmkiecegjmj\C0)))+,-.0359>BHNV^dkqtx{~}|{zzzzz{{zxvsqonmllmnoooqtx|TTTTTTTTTTTSSSRRRQQPPOONMLKKKKLMNNNNNNNNNNNNNNNMKKKKJJJJGFDBA@@@EFHKNRTVXXY[]^_`bddefhhijjkkklllmmnnoooqrrstuvxyy{|}{wurpmkhf`^]_chnsy¾wlaVMG@;87678;=?ACEGJKLNPSY^dlt|reXLA:533446:=@BEGIKNORUX\clv|`NJIFEDDDDDEECA><985421/.-+)((''&&&''((()*+,,------.----,,,--,,,,++**))(''&%$$$$$$$$$$$$$$$$&&&&&&&&%%%%%%%%''''''''%%%%%%%%(('''&&&&&&&&&&&%$$$$$$$%%%%%%%%%%%&&&&&&'((((((()))))))('''&&&'((())))*********))(())*-.0100/,)(((()*+,-05:?FMSX[]]\ZVQMLKJJJIFDB@>;:987641.-.047<@DHLPTVY\^__`abbccddedeeefffgggggggggiijjkkkkkkkkklllllllllllllmmmmmmmmmmmmlllllllllllkifdegimj]E1**++,,-/147;?EKRZagnquy}~}|{zz{{}}|zxuspnnmllmnoprux{~TTTTTTTTTTTSSSRRRQQPPOONMLKKLLMMNNNNNNNNNNNNNNNMLLKKKJJJGFDBAABBHJLORVXYZZ[\]^_`bccdegghijjkkklllmmnnooqrrstuvxyyz|}|xvsqomjhb_^`dhnsx¿xnf]TMG@;87778;=?ACDFIJLMOSX]cjqv~ÿyk_UKA9644457:=@BEGIKMPSUW\ajs~nVJJHFEDDDDDEEDB?<:965321/.,)((''&&%&&''''()*+++,,,--,,,,,,,---,,,,++*))(('&%$$$$$$$$$$$$$$$$&&&&&&&&%%%%%%%%''''''''&&&&&&&&(('''&&&&&&&&&&&%$$$$$$$$$%%%%%%%%%&&&&&&'((((((()))))))('''&&&'(()))***********))(())*-.0100/,)(((()*+-.16;@GOTY\^]\YUPLKKKKKJHECA?<;97653/---047<@DHLOSUY\^___abbccddedeeefffgggggggggiijjkkkkkkkkklllllllllllllmmmmmmmmmmmmlllllllllllkifdegiml`H2*)+*+,,-/259=BHNV]dkosw{~~}|{||}~~}|zxurpnmllmnoprvy{~TTTTTTTTTTTSSSRRRQQPPOONMLLLLMNNOOOOOOOOPPPPPPPOLLLKKKJJGFDBBBDELNPTWZ\]]]^^__``bbbdeffgijjjkkkkllmnnooqrrstuvxyyz|}|ywusqoljdbabeinsx{rib[TLF?:87779<>?ABDFIJKLORW\ainrx|qd[SI@8644457;=?BDFHKMPRUW[`hpx~ydQKKHEEDCDDDEFECA><;9653210-*)(('&&%%%&&&&''())***+,,+,,,,,,---,,,,++**))((&%$$$$$$$$$$$$$$$$&&&&&&&&&&&&&&&&((((((((&&&&&&&&(('''&&&&&&&&&&&%$$$$$$$$$%%%%%%%%%&&&&&&'((((((()))))))('''&&&'()))************))(())*-.0100/,)(((()*+-/27@ABDFHIKLORW\`fknsy~tj`XQG>8644457;=?ADFHJMPQTWZ_fmswo[PKJIDDCCCDDEFECA?><:764321/,*)('&&%$$%%%%&&''(((()++*+++,,,---,,,,+++*))((&%$$$$$$$$$$$$$$$$&&&&&&&&&&&&&&&&((((((((&&&&&&&&(('''&&&&&&&&&&&%$$$$$$$$$%%%%%%%%%&&&&&%'((((((()))))))('''&&&')))*************))(())*-.0100/,)(((()*+-/38=CJRW\__^\YTOKLMMMMLJGEC@=;97641.,,-047<@DHLOSUX[]^^_abbccddedeeefffgggggggggiijjkkkkkkkkklllllllllllllmmmmmmmmmmmmlllllllllllkifdegikneL5)(*****+-/148<@GMU\dglquy}~~}{yvtrpnmmmoqswz{}}~SSSTTTTUUTTSRRQQQQPPOONMMMMMNOPPPPPPPPPPPPPPPPPONMMMMLLLJIFEEFHJPRUY\_`abbbaabbbaabcdffgijkkklllllmmnnoprrstuvxyz|}}|zwutrpnjgefhkptyž}vrnic\VPJD>976679;=?@ACDGIJLNRW[`dhlpuz~|smf_WOE>9765568;=?ADFHJLOQTWZ_fkpqw}}ueUPLHFDCCCCDDEEDCA@>=;8654220-,+*(''%$$$%%%%%&&'''()**)*+++,,,-----,,,,+**))'%%%%%%%%%%%%%%%%%&&&&&&&&''''''''((((((((''''''''(('''&&&&&&&&&&&%$$$$$$$$$%%%%%%%%%&&&&&%'((((((()))))))'&&&%%%&)))***+*++****))))(())*-.0100/,)(((()*+-049=CKRW\^_][WRNJKMNNNMKHFC@=;97530-++-047<@DHLOSUX[]^_`abbccddedeeefffggggggggghhiijkkkkkkkklllllllllllllmmmmmmmmmmmmlllllllllllljgeefhlmeP8+)))****+-/259=CIQY`dinsw{}}{xvtqonmmoqtwz{||}~SSSTTUUVUTTSRRQQPPPONNMMMMMMOPQQQQQQQQQQQQQQQPPPOONMMLLLKJIGGHKLSVZ^`bcdddddddddcccdeefgiijkklllllmnnoppqrstvwxzz|}}}{ywvtqpljghjmqvz~~zvrplga[UOIC>976779<=?@ACDFHJKMQUZ_dhloswz}}unid\TLD<877678:;=>ACEGJLNPSVZ_ejnquvx{||ytjZOLJFCCCBCCDDEEDCA@?>;9765321/.-,*)('%$$$$$$$$$%%&&'((()**++,,----...--,,+*)(&&&%%%%%%%%%%%%%%&&&&&&&&''''''''(((((((((((((((((('''&&&&&&&&&&&%$$$$$$$$$%%%%%%%%%&&&&&&'((((((()))))))'''''&&'))**++,*++****))))(())+..000/-+)((()*,-/26:@FNTZ^__]ZVROKLNPPONKIGD@=;9643/,+,-159=AEIMPSUX[]^_`abbccddedeeefffggggggggghhiijkkkkkkkklllllllllllllmmmmmmmmmmmmllllllllllmmligffglmgT;-+**)))*+,.036:@FLT]aflquy{~{xvsqpooqrtwz{||||}SSSTUUVVUTTSSRQQPPOONNMMMMMNOPRRRRRRRRRRRRRQQQQPPPONNMLLKKKJIJMOVY]acdfgffffffffedeeeefgiijkklmmlmnnopppqrsuvxyzz|~}||{ywvutpnllmptv{}ż|yvsojd^YTNGA=977789<=>@ACDFHIKMPTX^cfjmqux{|zupkgbZSKC<887779:<>>@CEGJLNOQTY^dhlrttsrokgc]VRMIDACBBBCCDEDDCBA??<98754320/.-+*)(%$$#####$$$%%%&&'''())*++---..////.-+**)''&&%%$%%%%%%%%%%&&&&&&&&'((((((())))))))(((((((((('''&&&&&&&&&&&&%%%%%%%$$%%%%%%%%%&&&&&&'((((((()))))))(((((((()**++,,*,,++++*))((()*,.0010/-+*))))+-/137;BIOTZ]]][WSPNLMOOOONLIGDA>;9532/,+,.169=AEIMPSUX[]^_`aabbcddedeeeeeffffffffffhhiijkkkkkkkklllllllllllllmmmmmmmmmmmmllllllllllmmligffgmniW>/,++*))*+,-0359>CIPY]cinrvy~~{yvtrqqrsuwyzzz{{|SSTTUUVVVUTTSRQQPOONNMMMMMMNOPQRSSSSSSSSSRRRQQQPPPOONMMMLLLKKLNPX[_bdfhihhhhhhhhfeffffggijjkkllllmnnopppqrsuwxy{{}~~}|{zxwvurponpsvx|{wtrnic]WRMFA=977889<=>@ACDFHIKMOSW\`dgkorvy|~}xsoie`XQJC<888789;<>?@CEHJLNOQTX]bgkpssrqnke`[UQMJEBBABBBCDDDDCBA@?<:87654210/.,+*(&%$$#$$####$$$%%&&&'(()*+,--./000/.-,+**(''&&%%%%%%%%%%%%&&&&&&&&'((((((())))))))((())))*)(((('''&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%&'((((((()))))))(((((((()**++,,+--,,,,*))((()+,/0010/-+*)))*,.0147;9532/,+,.27:>BFJMPSUX[]^__``abbcdedeeeeeeeefffffffhhiijkkkkkkkklllllllllllllmmmmmmmmmmmmllllllllllmmligffglnjYA0,++****+,-/258@ACDFHIKLNQUZ^beimpswx|~zvsnhc^VOHB<99888:;=??ACEHJKMOQTX\aeimprqolha[WRPMJEBAAABBBCCCDCBA@?=:976543220/.-,*(&%$$$$#"""###$$%%%&''()*,--./0010/.-,+*((('&&%&%%%%%%%%%%%&&&'''''''(((())))))))(()))***)))((('''''''&&&&%%%%%%%%%$$$$$$$$%%%%%%&'((((((()))))))(((((((()**++,,,--,,,,+))((()+,/0010/-+*)))*,.0248BFJNPSUX[]^__``abbcdedeeeeeeeefffffffhhiijkkkkkkkklllllllllllllmmmmmmllllllllllllllkkmmligffgkmk\C1+)+****+,-/146;?DJPV]chmquz{~}{xvtsttuvxyyyz{|TTTUUVVWWVVUTRQPOONNMMLLLLLMNOQRSSSSSSSSSRRRQQQPOOOOONNNNNNNNORT[]adfhjljjjjjjjjiiiiiiiijjkkkklllmnnoppqrstvxyz}|~}|{zzxwuuvx{}ź~|xtqnkic^XRMID?<:9999:<=>@ACDEHIKLNPSX\_cgknqtvz}|xtqmf`[SLFA<9:989:<>@@ACFHJKMOQTW[_cgknpomjf_YUPNLIDAAAABBBCCCDCBA@?=;9876544320/.-+)'&%$$$#"""""""#$#$$%&'()+,-./01110/.-,+))((''&&%%%%%%%%%%&&&&''''''(((()********)))****+*))))(((((''''&&&%%%%%%%%%%%%%%%%%%&&&&&''((((((()))))))(((((((()**++,,,..----+**)))*+,/0010/-+*)))+-/1359=CIOSWXXWTQNKJKLNNNNNLIGC@=:8531.,+-/27;?CGKNPSUX[]^__``abbcdedeeeeeeeefffffffhhiijkkkkkkkklllllllllllllmmmmmmllllllllllllllkkmmligffgjml_F2+)++**++,./1258=BGMRY`einrwx{~~{xvuuuvwwxxyzz|TTUUVVWWWWWUTRQPOONNMLLLLLLMNOPRSSSSSSSRRRRQQQQPNNNNNNNNOOOOOPSUZ\`cegikkkkkkkkkkkjjjjiikkkkkkkklmnnoppqstuwxz{}}~}|zyxxy{}~ɿ}zwsoljfa[UPKFB><:9:::;<=>@ACDEHIKLMOQVZ]aehloquwz~zvrojc]XPID@<::999;=>@ABDFHJKMNORUX\`dilnmlid]WTPNKHC@AAABBBCCCDCBA@?=;:9765554320/.,*('&%%%#"""""""!"""#$%&&')+,-./01110/..,**))((''&&&&&&&&&&&&&''''''((()))************++++****))))(((('''&'&&&&&&&%%%%%%%%%%%&&&&&'(((((((()))))))(((((((()**++,,-..----,**)))*,-/0010/-+*)))+-0246:>CIOSVVUTROKIIIJMMMMNLHFC?<97421.,+-/37:>BFJNPSUX[]^__``abbcdedeeeeeeeefffffffhhiijkkkkkkkklllllllllllllmmmmmmllllllllllllllllmmligffgknocJ5,*,++++,-./1136:?EJOV\afkotvy|~{xwwwwwwxxyzz|TTUUVWWWXXWVTRQPONNMMLLLLLLMNOPQRRRRRRRRRQQQPPPOMMMMMMMMOOPOPQTVY[^acehjllllllllkllkkkjjkkkkkkkklmnnopprstuwyz{}~}|{{|}ĸ~{xuqnkhd_YSMHDA><::;;;;<=>@ACDEGIKKLNPSW[^bfjmosuw{~|wsolga[VNHC@<:::9:;=>@ACDFHJKMMOQSVZ^bfikkjfa[WTPNKFA@AAABBBCCCDCBA@?><;98766654310/-+)('&%%$###""""!!!!""#$%&()*+-./12110//.++**)(('&&&&&&&&&&&&'''((((())))*++++++++++++,,,,++++****))(((('''&&&&&&&&&%%%%%%%%%&&&&&'''((((((())))))(((((((()**++,,-//....,**)))*,-/0010/-+*)))+-0257:>CINRUUTROLJHGHIKMMMMKHFB?<97420.,+-037:>BFJMPSUX[]^__``abbcdedeeeeeeeefffffffghiijjkkkkkkklllllkkkkkkkkllllllllllllllllllllllmmligffgknpeL6,),++++,-./01248=BHLSY^chlrtw{~~{zzyyxxxyyz{|TTUVWXXXYXWVTRQPONNMMLLLLLLMNOPQQQQQQQQQQPPPOOONMMMMMMMMNNONOQSVXZ]_bdgjlllllllmlllkkkjjkkkkkkkkllmnnopqstuwy{|}ʽ|zyvtqlheb^YSMGC@><<<<<<<=>?ABDEFGILLLMORUY\_cgimoquy|}zvrmhd_XQKEB?=;:::;<>?BBCEFHJKMNOQSUY\acgjigd_YVSPNKGB@AAABBBCCCDDCBA@?=<;98766664321/-+)('&&%%##""!!! !!"#$$&()*+-./22110//-++**)((''&&&&&&&''''((())))***++,,,,,,,,++,,,--,-,,,+++***)))((('&&&&&&&''&&&&&&''''''''((((((())))))))))))))))))**++,,.-------,*))))+,.0010.-+*))*,.1368;>CHNRTTRPNKHGFGHKLMMMKGEB>;86320.,,-037:>BFJMPSUX[]^__``abbcdeedeeeeeeeeffffffhiijkkkkkkkkllllkkkkklllllllllllllllllllllllllllllmkhfefilnfQ:-)+,++++,-./1136:?EJOV[`ejosvy|~|{zyyyzz{|~TTUVWXYZYYXWVTRQONNMMLLLLLLMNOPQQQQQQQQQPPPOOOONLLLLLLMMNOOOPRTVWY[]`behjklllllmllllllkkllllllllllmmnnprstvxz|~̿~}|{yvurpmhea^ZVQKFB@>========>@ABDEGGIKKLLNQTWY\_bdinqtxz|}yvrnjea\UOIDA?>=<<<<=>@BCDEGHJKLNPQRTVY]dgihfc_ZUSQOLHDBAAAABBCCCDDCBA@?>=<:98777765322/-+)('&&&%$##"!  !"##$&&()+,-.111000/.,+++*)))('''''''(''()))*****+++,------,,++,,,--,-,,,+++****)))(''&&&&&&&&&&&&&&&&''''''''(((((((()))))))))))))))**++,,-........,+*))*+,./000.,*)))*,.1368;?DINQRRPNLIFEDEFIKKKLJFDA=:75210.,+,/158<@DHLORTWZ]^__``abbcddedeeeeeeeeffffffghhiijjjjjkkklllkkkkklllllllllllllllllllllllllllllmkhfeehlngS<.)+,++++,-./01248=BGLRX]bglqswz}~|{zzzz{{|~~TUUWXYZZZYYXVTRQOONMLKJKKKKLLLLNOOOOOOOONNNNMMMLLLKKKLLMPPQQRSTUWXZ\^acfhjjkkllmllllllllllllllllllmmnnprstvx{}~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}~~~}}||{zzyxwusqmliec^ZWSOJFC@?>>>>>>>>?@BCDEGGHJKLLMPSVXZ]_bfkmoruxz{}~}zurnjfb^YRMHCA??>>===>?@CDEFGHJKKOPQRSTWYacddc`]YTQOMJHFCBAAAAABCCCCCBA@?>=<;::9998765431.,+)('''&%$#"!  !!"""#$&')*+.///000/-,,++*))((((((((((()*++,,,,,,,--...----,,,,,---,--,,,,+,++****)''''''''''''''''''((((((('(((((((()))))))))))))))*++,,--.///////.+*))*+,.//0/-,*)))*,.1369<@EIMOPPNLIGDCCDFIJJJJHEB?<975210.,++.047;?CGKMPRVY\]____`aabccedeeeeeeeeffffffghhhijjjjjjkllllkkkkklllllllllllllllllllllllllllllmkhfeehknhV?/*,,,+++,-./00137;@DIOTY_djnqtx|~}|{||{{|~~UUVWXYZ[ZZYXWUSROONMLKJJJKKKLLLMMMMMMMMMNMMMLLLLLLKKKLLMPQQRSTTUWXY[]`bdghijklmmllllllllllllllllllmmnnprtuvy{~Ĵ}~~}}}}||||||{{|{{{zz{{{{{zzzzzz{zzzzzzz{{{{{{{{{{{{{{{{|||{{{{{{zzzzzzzyyzzzzzzzzyyyxxxxwvvutsromihea_[XUQNJFCA@?>>>>???@ABCDEGGHJKLLMPRUWY[]`chiknqtvwyz{||}~}|zxuqnjfc_\WQLHDB@@?>>>>?@ACDEFGIJKLOPQRSTWY_acca^\XTQOMJHFCBA@@@@AABBBBBAA@>==<;;::99876542/-,*)((('&$#""! !!"""#$%'()*,,-./000.--,++*)(((((((()))*+,,-.......////....-,---...-..---,,.--,+++*)(((((((((((((((((((((((((((((((())))))))))))))))*++,,--.0000000.,+**+,-..///-,+))*+-/1369<@EILMNNMJGECAACFIJJIIGC?<:864210.,+,./369=@DHKOQUX[\____`aabccedeeeeeeeefffffffgggghiiijjkkkkkkkkkklllllllllllllllllllllllllllllmkhfeehkniYB1+--,+++,-./00126:>BFKQV[`fknrvy|~~~}|}}||}~~UVWXYZ[[[[ZYWUSRONMLKJIIIJJJKKKLLLLLLLLLLLLLKKKKKKKJKLMMQQRSTUUVWWXZ\^`ceghijklmllllllllllllllllllmmnnprtuwy|~ŵz|||{{zzzyyyyyyyyyxxxxxxxxxxxxxxxxxwwwwwwwxxxxxxxxxxxyyyyyyyyxxxxxwvvvvvvvuuvvvvvvvvuuuttttsrrqpomjhfeb^\XURPMJFDA@?>>>???@ABCDEFGGHJKLLMPRTVXY\^`cdfilnpqstvwwwyzyyzzzz{{{{|||}}}~~~~~~}}~{zxwvtrpmjfb_[XUOLHECBA@????@@BDEFGHIKLLOPQRSTWY^_``_\ZWSQNLJHFCBA@@??@@AAAAAAA@>==<<;;;::9876530/-+*)(('&%$##"! !!!!!"#$&'()++,-.///.---,++*))))))))***+,--.///////0000////.---..//./..----...-,,++*))))((((((((((((()))))))()))))))))))))))))))))))*++,,--/0011100/-,+++,--..//-,+)**+-/1369<@EILMNNMJGEBAACEHJJIHFB?<:76410/-,+,-.358;>AEHLOSWY[^^_``aabbcedeeeeeeeeffffffffffefggghjjjjkkkkkkklllllllllllllllllllllllllllllmkhfeeikmk\E3-.,,,,,,,-.//0136:?CHLQW\bhkoswz|}}~}}}}||}~~VVWXY[[\[[[ZXVTSONMLJIHHHHIIIIJJJJJJJJJJKKJJJIIIJJJJKLMNRRSTUVVWWWXY[]_adfghijklllllllllllllllllllmmnnpruvxz}~~~~~~~~Ƶwz{zzyyyxwwwwwwxywvvvwxvvvvvwxxxxxxwwwwwwwwvvvvvvvvvvvvvvvvvvuuuuuutttttttsssssssstsssrrrrrqpoonlihfdc`\ZVTQOLJGEBA@????@@@ABCDEFHGHJKLLMPRTVWXZ\_`acegjklmopqqrsuuuvvvvwwwwxxxxyyyyz{{{{{{{|||||}|||||}}}||{||||||||||||zzzyyyxxxvtrqpomkheb_\XURNKHFDCBAA@@@ABCEFFGIJKLMOPQRSTWY\^_^]ZXVSPNLJHFCCA@@???@AAAAAAA@?>>=<<;;;;:9876420/-+*))('&%$##"""!!! !!"#$&&'**++,-...----,,+********++,-..//000000011000////....///.//...../..--,,++**)))((())))))))))))))))))))))))))))))))))))))))*++,,--/0111110/-,+++,--....,,+)*+,-0247:=AEHKMNNLJGDB@ACEHIIHHFB?<975310/-,,,-.2479>=<<<=<;:9875320.-,+*)('&%$#"#""!! !"#$%%(()*++,,,------,+******+--../011111111111000///////////.//////////.--,,+****))))))))))))))))))))))))))))))))))))))))))))*++,,--/1122211/-,+++,---...,++*++,.0247:=AEHKLMMLJGDB@@BDGHHHGEA>;864310/.---..1357:=ACHKQUXZ]^_``aabbccceeeeeeeefffffffeca`__`begijjkkkkkkklllllllllllllllllllllllllllllmkhfeeiknmaJ7/.--------../011249=BFKQV]bfkoswyy{||}}~~~}}}||}~~WWXYZ[\\\\[ZYVUSNNLJIGFFEEFFFGGHHHHHHHHHGGGGFFFFHHHIJLNOTUUVWXYYWWXYZ[]_acdfgijkklllllllllllllllllmmnnprvwy{~~~}}}}}}}}ñ{txzyyxxxwvvvvvvvvuuuuuutttttttttttutttttttsssssssstttssssssssrrrrrrsssssssrrrrrrrrrrqqqqpqqponnmkhgecb`\ZWTQOMJGECBA@@@@AAABCDFFGHGHJKLLMPSTUVWY[^__abdfghijllmmopqqqqrrrssssssttttuuuuuuuttuvwwwxuvwwwxxxwvuvvvvwuuuuuuuttttssssponlkjigeda^[YVSPMKHFEDCCCBBBCDEFGGHJKLMNOPQRSTWY]]]\ZXVTQOMKJHFDCBA@?>>?@@@@@@@????>>=====<;:986431/.-,+*)('&%#"#"""!!!  !"##&''()**++,,----,+++++++,.//01223333333211000///0///////.///////00//.--,,++****))*********)))))))))))))))))))))))))))))))*++,,--01222221/-,+++,--....-,+++,-.1358;=AEHKLMMLIFDA@@ADGHHGFD@=:753210/..--..02467:>@FIOSWY]^_``aabbccceeeeeeeeffffffeda]ZYZ]`cfikkkkkkkkkllllllllllllllllllllllkkkkkllmkigeehknncM8/........-../001136;?CHNSZ_chmquwxyzz{{||}~~~}}}||}~~WWYZ[\]]]]\[ZWVTNMLJHGFEDDEEEFFFFFFFFFFFFFFEEEEDGGHIJLNOUVVWXYYZXWXXYZ\]`bcefhijlmmmmmmmllllllllllmmnoqsvwy{~~~~~}}}{{{|||}~®ywzyyxxwwvuuuuuuuttuuuutttttttssssstssssssssttttttttttttttttssrrrrrssssssssssssssssrrqqqqpqqponnmkhheca_][XVSPNKHFDBAA@@AABABCDEFGGIIJLMNNPRTUVWYZ]__`bcefghiklllnoppoopppqppqqqrrrrrrrrrrrrrrsssstrssssttttttuuuuutttttttsrrrrqqqnnmkjihfdb_\ZXUSOLKIGFDCBBBBBCDDFHHIKLMNNPQRSTUWY]]\[YWUSPNLKJHFDDBA@?>>?????????????>===>>=<;:975320/.-++*)('&%#!!!!!  !!$%&'(()**++,-----------./0123444444444321111000000/////.....///000//..-,,+++********************))))))))))))))))))))))))+,,--..123333320-,+++,--00///.,+,,./1358;=AEHJKMMKIFCA??BDFGGGEB?<9642100//...../02457:>>????????????>>>>>>>=;:9754310/.,,+*)((&%!!!!!!!! #$%&'(()*+,,-........../1223456655555543322211000//....----..//1000/...-,,++++**+++++++++*******))))))))))))))))))))))))+,,-./0233444330-,+++,-.01000/-,,-.02458<>BFIKLMLKIFCA??ADFGGFDB?;864210000//////023469;BGLSWY^__``aabbccceeeeeeeeeeeeeeeb]WPLLRY^dhjlljjjjjkllllllllllllllllllllllkkkkklllkjhdcfjlmgS;/000//...////0011037;>BGLQW\afkorsvxxyyzzz|}~~~~}||}}~~}|{|}~WXY[]^^^_^^\ZXVUPNLIGDCBBBBBAAABCCCCCCCCDDCBBCCDDEHJKMNOTUWXYZZZYYYZZ[\\_`acdfghjkkkkkkkkkkkkkkkjllmmoqtvwy|~}|||||{{{{{|||}~˺vx{xxwwvuttttttttutttttuttttttuuuuutttttttttttttttttttsssssssssssssrrrrrrrrrrrrrrrrqqqqqqpponmmlljggeca_][WTSQNLJHFDCBBBBBCCDEFGGHIJKLMNOPQSUVWXZ\^`abcefghjkkllmmnopppppppopqqqqqqqqqqqqqqqqqrrrrrqrrrrrrrrrrrrrrrrrrrrrrrqqppoonnnmkjhgfd`_^\YVTRNMKJIGFDCCCDDDEGIIJKLMMNQRSTVWXYZZZYWUSQONLKIGFDCBBA@?>>>>>>>>>????????>>>>=<;97653210.-,,+*))('&&%$#""!""#$%&'()*+,--.......//023345667666555433221110///...--,,---...000///..-,,,+++++,,,,,,,,,+*******)))))))))))))))))))))))*+,./01211233330-,+++,-.01000/-,-./02468>>???????????????>>>><;98654210/--,+*))((&&%%$#"" !"#$%'))*+,----...//002345567877665543221100/...--,,+++++,,,,/0/////.-,,,,,,,,,,,,,,,,,+++++++**))))))))))))))))))))))*+,./01212233430,,+++,-./0000/-,-./02468>@BDDDCA=:86543211100000022334578?DJQWZ]__``aabbccceeeeeeeeeeeeeed_YQIDCIQX_ehjkjjjjjkllllllllllllllllllllllkkkkklllkjhdceilnjXA1100000000/////00/0369<@EIOTZ_djnoqtvwxyzyzz{|{{{yyyyyz{|~~|{{{{{{{|~XY[\^_____^][YWUPNLIGDCBAAA@@@??????????A@@@@ABCEGJLMMOPTUVXYZZZZZZ[\\]]_`abdefgijjjjjjjjjjjjjjjjllmmoqtvwy|~~~|{z{{{{z{{{|||}~̾|wzzxxwwvuuuuuuuuuuuuuuuuuuuuuuuuuuutttttttttttttttttttsssssssssssssrrrrrrrrrrrrrrrrqqqqqqpoonmllkiggeca_][WTSQOLJHGEDCCCCCDDEFGGHIJKLMNOPQRTUWXYZ\^`abcefghjkkllmmnopppppppopqqqqqqqqqqqqqqqqqrrrrrqrrrrrrrrrrrrrrrrrrrrrrrqqppoonnnmkjhgfd`_^\YVTRONLKIGFDDDDEEEFHJJKKLMNNQRSTVWXYZZZYWUSPNMLJHFEDCBBA@?>>??????????????????>>=;:8754210/.-,,+*)))''&%$##" !"$%'()*+,,-,-../0012345567877665543221100/...-,,++*++++,,,.//////.-,,,,,,,,,--------,++++++****))))()))))))))))))))*+,./01212343330,,+++,--.//0/.,,-./01368==?ACDDB@=:76543211100001122223456=CHOVY\^^__``aabcceeeeeeeeeeeeeeb]WOGA@FNU]cgijjjjjjkllllllllllllllllllllllkkkkklllkjhdcdilnl\E22100000000000000/0247:>BFKPV\agklnqsuvwyxyyz{zzzxwwwwxyz|~~}|zzzzzz{{}~XY[]^_```__][YWVPNLIFDBA@????>>>>>>>>>>>>>>>?ABCGIKMNNPQTUVWXYZZZZ[[\]]]_`abdefgiiiiiiiiiiiiiiijjllmmoqtvwy|~~}{{zz{{{z{{{|||}~Ŵ|yzzxxwwvuuuuuuuuuuuuuuuuuuuuuuuuuuutttttttttttttttttttsssssssssssssrrrrrrrrrrrrrrrrqqqqqqponnmlkkiggeca_][XUTQOMKIHFEDCCDDEEEFGHIIJLLMNPQQRTVWXY[]^`abcefghjkkllmmnopppppppopqqqqqqqqqqqqqqqqqrrrrrqrrrrrrrrrrrrrrrrrrrrrrrqqppoonnnmkjhgfd`_^\YVTRONLKJHGEDEEEEFFHJJKLMMNNQRSTVWXYYZYXWTRPNMKIGEDCCBBA@??????????????????????>=;:87643210..-,+****(''&%$$#  !#&'(()*+,+--.//012345567877665543221100/..--,,++***+++,,-......--,,,,,,,,,--------,,,,,,,+****)))))))))))))))))))*+,./0122334332/,,+++,,-.//0/.,,-./01368=<<>@BBBA?<976543222110011222222456>>====<<<<<<<<<<<<=?@BCHJLNOPPQTTUVWXYZZZ[[\]]]_`abdefghiiiiiiiiiiiiiiijllmmoqtvwy|~}}{zzzz{{y{{{|||}~ʽ~{zyxxwwvuuuuuuuuuuuuuuuuuuuuuuuuuuutttttttttttttttttttsssssssssssssrrrrrrrrrrrrrrrrqqqqqqponmllkkiggeca_][XUTROMKIIGFEDDDEEEFGHIIJKLMNOPQRSTVWXY[]^`abcefghjkkllmmnopppppppopqqqqqqqqqqqqqqqqqrrrrrqrrrrrrrrrrrrrrrrrrrrrrrqqppoonnnmkjhgfd`_^\YVTSPOMLJIHEEEEFFFGHJKKLMNNOQRSTVWXYYYYXVTRPMLKIGEDCCCBA@@??????????????????????=<:97643210/..-,++++)(('&%%$!!!  "$%&'(()+)++,../02345567877665543221100/.--,,+++)****+++--------,,,,,,,,,,--------,,,,,,,++****))))))))))))))))))*+,./0122334332/-,+++,,-.//0/.,,-./01368<976554322211112233334456;?FLU]dhjjjjjjkllllllllllllllllllllllkkkkklllkjhdccikmnaJ63000010000000000//0247:=@EJPU[`efiloqrtuuwxyyyyxwvuttuvvyz{|~|{yxxxxxxyyz{|||YZ\^_`````_^\YWVQOMIFCB?==<<<;;;;;;;;;;;:;;=?ACDJLMOPPQRTTTUVXYZZZZ[\\]]_`abdefgghhhhhhhhhhhhhhijllmmoqtvwy|~}}{zzzzz{y{{{|||}~´~{zxxxwwvvuuuuuuuuuuuuuuuuuuuuuuuuuutttttttttttttttttttsssssssssssssrrrrrrrrrrrrrrrrqqqqqqpnnmlkkjiggeca_][XUTROMKIIHGFEEEFFFGGHIJKKMMNOPQRSTVWXY[]^`abcefghjkkllmmnopppppppopqqqqqqqqqqqqqqqqqrrrrrqrrrrrrrrrrrrrrrrrrrrrrrqqppoonnnmkjhgfd`_^\YVTSPOMLKIHFEFFFFGHIJKKLMNOOQRSTVWXYYYYXVTROLKJHFDCCCCBBA@???????????????????@??><;98653210//.-,,+++*))('&%%##"!  "$$%&''('))*,-./1345567877665543221100/.--,,+**)))****+,------,,,,,,,,,,,---------------,+++***)))))))))))))))))*+,./0123434331/-,++++,,.////.,,-./01369;:99<=?@@>=;976554333321123344445555;AFMUXZ\^__``aabbceeeeeeeeeeeeeeb\UME>:BFLQW]bcfjmoqsttvwxxxxwwvuttuuvxyz{|}~}{zxwwwwwwxxyyzz{^^__``aaa`_][YXWRQNKGDB@<<;;;:::99999999;;<=@BDFKMPQQRTTSSTUVWXYZZZ[[\\]_`abdeefggggggggggggggghjklmnprtvx{}~|{{zzz{{{|{{{{|||~ȼ~{xxyxxwwvvvvvvvvvuuvvvuuuuuuuuttttttttttttttttssssssssssssssssssrrrrrrrrrrrrrrrrrqqqqqqqpppnnmllkjifdb`^\ZWUTQOMKJJIHGFFFGGGGHIJKKMMMNOPQRSTVWXY[]^`abcefghjkkllmmnoooooooopqqqqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqppoonnlkihgeca`^\YVTSQOMKJIHGFGGGGHIJLLMNNOPQSSTVWXXYYYYXVTROLKJHFDCCCCCBA@@??????????????????@@?>=;:87543210//.-,+++**)((''&%$#""! !  ""#$%%&'))*,-./1345567877665543221100/.--,,++*))))****+++++++,,,,,,,--.,---------------++++***))))))))))))))))*+,-/01234555430-,+****+,-../.-++-./01369<>BEGHHGDB?<:988:<>>>=<:865554343322233455455437:;>DNXafijjjjkllllllllllllllllllllllkkkkkkjjkkjgecbfjnneO711000////////////0001468:=BGMTY]adgjmoqsuvvvwxxxwvuttuuvwxyz{|}|{zxwwwwwwxxyyzz{^^__`aaaa`_][YXWSQNKHEA@<<;;998788888889;;=?ACEHKMPRRRSTSTUUVWWXXYYZ[\\]``abcdeeeffffffffffffffgijkmnoqsux{}~|{zzzzzz{{{{|||}}~ǽ}{yxyxxwwvvvvvvvvvuuuuuuuuutttttttssssssssssssssssssssssssssssssssssrrrrrrrrrrrrrqqqppppooonmmllkjiiedb`^][XVTROMKJJJIHHHHHGGHHIJKKMNNOPQRSTVXYZ[]^``abdefgikkllmmnnoooooooopqqqqqqqqqqqqqrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqppoonnnlkihgeca`^\YVUSRPNLKJIHHIIIIIJKLLMNNOPQSSTVWXXYYYXWVSQOLKIGEDCCCDDCBAA@@???????>>>>>???@@?>>=<:87643210//.-,,+**))))('&%%$$##" "!  !"#$$&')*+,-.03345567776655433322110/-,,+***))))))***++++++++++,,,,---.......--------,+++***))))))))))))))))*+,-//0124555430-,+*****,-../.-++-./01369=?ADFGHFCA>;9777:;=>><;:865554444333334466666547;AGLTWZ^^__``aabbbccddeefeeeeeee`[TLD>:9:@IU_eijjjjjkkklllllllllllllllllllkkkkkkjkllkhfdbejoohS911000////////////00013568;?CHOUY^adgjlnqtuvwwxxxwwvuuuvvwxxyz{{{zxwvvvvvvwwxxxxy___``aaba`_][YXWSQMJHEA?=<;:876566666677:;=?ADEHKMPRRRSTSTTUVVWXXXYZ[\\]``aabcdddeeeeeeeeeeeeeefghjlmoprtwz|~~~}{zzzzzzzzz{{||}}}Ǽ}{yxxxwwvvuuuuuuuuuuuuuuuuutttttttsssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqppooonnnmmlkkjiiedb`^][XVTRPNLKJJJIIIIIIIIJJKLLNNOPQRSTUWXY[\]^``abdefgikkllmmnnoooooooopqqqqqqqqqqqqrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqppoonnmmljigfeca`^\YWUTRPNMLLKJJJJJJJKLLLMNNOPQSSTVWXXYYYXWTRPNKJGECCBBCDDDCBBA@@??????===>>>??@??>>==;8764321/..--,,+)))))))(''&&&%%#"#"!!  !"#%&()*+--/1234567776655545433211/-,++*))))))))))**********++++++,........--------,,++**)))))))))))))))**+,,-//0124555430-,+*****,-../.-++-./01369>ACEFGFC@=;97778:;====<;987766554433345577777646;@FKSWZ^^__``aabbbccddeefeeeeeee`[TLD>:88=GR\dhjijjjjkkkkkkkkkkllllllllllkkkkkkkjkllkhfdbdjoohU;111000000000000///00122358<@DJOUZ^adgjlpstuvwwxxxwwvvvvwwxxyyzzyxxwvuvuuuvwwwwww__``abbba`_][YXWSQMJHEA?=<;:8755455666679<>?ACEHKMPRRRSTSTTUUVWWXXYYZ[\\__`abccdcdeeeeeedddddddeghikmnpqswz{}~~}{zzzzzzzzzz{{||}~ǽ}{yxxxwwvvuuuuuuuuuttttttttsssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqppppppooonnnmmlkkjiiedb`^][XVUSQOMLKJJJJIIIIIJJKLLMNOOPQRSTUWXY[\]^``abdefgikkllmmnnnooooooopqqqqqqqqqqqqrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqpooonnnmmlkjigfdcb`^\ZXVTRPNMLLKJJJJJJJKLLLMNNOPQSSTVWXXYYXWVSQOMKIFDBBBBCCDCBBAAA@@@??>>===>>>>????>===;8764321/..--,,+******)(''''&&%$#$#""!!  !#%&'(*+,-/013467766655545433211/-,++*))(((((((()))))))****+++++,-...............-,++*))))))))))())))**+,,-./00134555430-,+*****+--,,,,+*+-/13569>ACDFFEB?=:86667:;<<<<<;::9877665544456799887646:@EKSVY]^__``aabbbccddeefeeeeeee`[TLE?;89=DO[cgiijjjkkkkkkkkkkkkkkkkkkkkkkkkkkkkjkllkhfdbdinoiW=2111000000000000//000112369=AFKPV[^adgknqrsuwwxyxxxwwwwwxxxyyyyyxxwvvvuuuvwwwxxx```abbcca`_][YXWSQMJHEA?==;9865434456667:<>@ACFIKMPQRRSSSSTTUUVWWWXYZ[[\^__`abbcbcdddddddddddddefgijlmopruxz|~~}}|{zzzzzzzzzz{{||}}Ž|zyxxxwwvvuttttttttttttttttsssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqppppppooonnnmmlkkjiiedb`^][XWUSQONMLKKJJJIJJJJKKLLMNOOPQRSTUWXY[\]^``abdefgikkllmmnnnooooooopqqqqqqqqqqqqrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqponnnmmmmlkjigfdcb`^\ZXVTRPNMLLKJJJJJJJKLLLMNNOPQSSTVWXXYYXWUSQOMKIFCBAABBCCCBBAAAAA@@?>><===>>>>??>>=<<:8764321/..--,,+*******)((('''&%$$##""!! !#$%&()*+-.02467666555545433211/-,++*))(((((((())))))))****++++,,----../........-,++*))))))))))))))***+,--./00134555430-,+*****+--,,,,+*+-/13569>@BCDDC@=;9755579:;;;;;:9998776655545668::8876469>DIQUX\^__``aabbbccddeefeeeeeee_ZTLE@<:9;CMXafiijjjkkkkkkkkkkkkkkkkkkkkkkkkkkkkjkllkhfdcdhmnkYA2221111111111110//000011147:>CGLSW[^beilopruwxyzyyyxxxxxxyyyyzzyxxwwvvuuuvwwxxxx`aabbccca`_][YXWSQMJHEA?>=;9754323445668;=?@BDFIJLNPPQRSRSTTUUUVVWWXYZ[[]^^_`aabaccccccccccccccdefhiklnoqtwy{}~~}}||{zzzzzzzzzz{{{||}~|zyxxxwwvvuttttttttttttttttsssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqppppppooonnnmmlkkjiiedb`^][YWUTRPNMLKKKKJJJJJKKLMMNOOPQRSTUUWXY[\]^``abdefgikkllmmnnnooooooopqqqqqqqqqqqqrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqponnnmmmmlkjigfdcb`^\ZXVTRPNMLLKJJJJJJJKLLLMNNOPQSSTVWXXXXXWUSQOMJHECAAAABCCBBBBAAAA@@??><<===>>>>>>=<<;:8764321/..--,,+*******))))((('&%%$$##""!! !#$%&()*,-/0246555555545433211/-,++*))((((((((((((((()))***+++,+,,--../........-,++*)))))))))))))***++,-../01234555441.-+*****+,,++++**+-/13569=@ABCBA>;975444589:::::98888776555555679;:9876468>CIQTW[^__``aabbbbcccddeeeeeeee`[TMGB><:;BKV_dhijjjkkkkkkkkkkkkkkkkkkkkkkkkkkkkjkllkhfdbchlol[D4333222222222210000000001258;?CIOTX[_cfjmpruwyyzzzzzyyyyyyzzzzzyyxxwvvuuuvwwxyyyaaabccdda`_][YXWSQMJHEA?>=;9743212345679<>@ABDFIIJMNOPRSRSSTTUUUVVWXYYZZ\]]^_`aaabccccccccccccccdegijkmnpsvxz|}~~~}}||{{zzzzzzzzzzz{{||}~~|zyxxxwwvvuttttttttttttttttsssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqppppppooonnnmmlkkjiiedb`^][YWVTRPOMLLLKKKKKKKKLMMNNOPPQRSTUUWXY[\]^``abdefgikkllmmnnnnooooooopqqqqqqqqqqqrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqponnnmmmmlkjigfdcb`^\ZXVTRPNMLLKJJJJJJJKLMMNOOPQRTTUWXYYYXWVUSPOLJHEBA@@AABBBBBBBAAAA@@?><<<====>>==<<;;:8764321/..--,,+*********+****)('&%%$$##""! !"#%&'(*,-/124555555545433211/-,++*)(((((((('((((((())))***++,+,,--../........-,++*))))))))))))***++,--./001235565542/.,***+*+,,++++**+-/13569<964211134678:=>@ABDFIHIKLNOQSRRSTTUUUUVVWXYZZ\\]^__```abbbbbbbbbbbbbcdefhjklmpsuwy{}~~~}||{{zzzzzzzzzzzzz{{||~~¿{zyxxxwwvvtttttttttssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqppppppooonnnmmlkkjiiedb`^][YWVTRPONMMLLLKKKLLLMMNNOPPPQRSTUVWXY[\]^``abdefgikkllmmnnnnnnnnnnopppqqqqqqqqqrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqponnnmmmmlkjigfdcb`^\ZXVTSQONMLKKKKKKKKLLMMNOOPQRTTUWXYYYXWVTRPNLIGDB@@@@ABBBBBBBBBAA@@??<<<<======<<;;::8764321/..--,,+++++++++++++***)('&&&%$$##"  !"#%&')*+-/12444455545433211/-,++*)('''''''''''''''())))***+,+,,--../........-,++*)))))))))))***+++,-../011235565542/.,***+**,,++++**+-/13568<>??@?=:76421123567777776666666555556789;;9877567=CHPSW[^__``aabbbbcccddeeeeeeeeb\VOIECB>?CKT[afijjjkkkkkkkkkkkkkkkkkkkkkkkkkkkjjkllkhfdbcglon^H644433222222222010000//012357;>BHLPTX]aeiloswyz|||}}}}}|{{{zzzzzyyxxwwvvwxyz||||bcceddddb`^\[YWUSQMJHEA?@><853101235689;>?ABCDFHHIJKLMOQRRRSTTUUUVVWWXXY[\\\]^__`aaaaaaaaabbbbbbcdeghijlnqsvxz{|}}||{{zzyyyyyyyyzzzzz{||}}~|zzyxxwwvvtssssssssssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqppppppooonnnmllkjjihecb`^][YXWUSQPONMLLLKKKLLLMNNNOPPPQRTUUVWXY[\]^``abdeefhjjkkllmnnnnnnnnnopppqqqqqqqqqrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqponnnmmmmkkkigeddb`^\ZXVTSRPOMLLKKKKKKKLMMMNOOPQRSSTVWXXXWWVTRPNKIFDA@??@@AABBBBCBBBAA@@?;<<<<===<<;::9988764332/..--,,++++++++++******)(('''&%%$$#"  !"$%&')*,./1334445545433211/-,+*))(''''''&&'''''''(()))***+,+,,--../........-,++*)))))))))))**++++,-../0122366766530.,***+**+++++++++-/13568;>>>>=;85420001245666666666666655556789:;;9987568@ABCEFHHHIJKKLOPQQRRSTTTTUUVVWWZ[[[\\\]^____``````````aabcdeghklortvxzz{{{{{zzyxxxxxxxxzzzzz{{||}~~{yyyxwwvvutsssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqppppppooonnnmlkjjihgecb`_][YXVUSQPONMLLKKLLLMMNNOOPQRRSTUVWWWXY[\^_`abceffgijjkkllmnnnnnnnnnopppqqqqqqqqqqrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqponnnmmmmkkkigeddb`^\ZXVUSRPOMLLKKKKKKKLMMMNOOPQRSSTVWXXXWWVTRPNKHFCA????@@AAABBCBBBBA@@?<<<<<<<<<;;::98877654430//.-,,++****++*********))((''&&%%$#!  !#$%&'(*,./333445544322100.-,+)((('''&&&%%&&&&&&&'((())**+++,,--../........-,++*)))))))))))+++,,,-.//00122366766530.,***+**********+-/13568:<===<97531000013456665555666665555678:;<;:987668;@FMSXZ]]^__`aabbbcccddeeeeeeeeb\VPKIIIDEGLS\bdijjjjkkkkkkkkkkkkkkkkkkkkkkkkkkjijjkjigfecelnofQ943334212222222111100////012369<@DHMRW[`fjmqtwy|~|||{{zzzzyyxxwwxyz|~bcceddddb`^\ZXVTRPMJHEB@>=:754544568:<=?ABCDEFGHGHHIJJKMOPPQQRRSSSTTUUVVZZZZ[[[\\\]]]^_________`aaabdegjkmpruwxyzzzzzzyywwwwwwwxyyzzzz{{{|}}}~~~~}~}zxxxwwvvuutsssssssssrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqrrrrrrrrrrrrrrrqqqqqqqrqqqppppppoooonnnmlkjjihgdcb`^]\YXWVTRQOOMMLLLLMMMNNOOPPRSSTUUVWXXYZ\]_``abceffgijjkkllmnnnnnnnnnopppqqqqqqqqpqqqqqqqqrrrrrrrrrrrrrrrrrrrqqqqqqpoonnnmmmmkkkigeddb`^\ZXVUSRPOMLLKKKKKKKLMMMNOOPQRSSTVWXXXXWVURPNLIFC@?>>???@@@AABBBBBAA@@==<<<<;;;:998777776655410/.-,,++****++********+***))((''&%%$#""!  !!"#$%&(*,-,.0234544322100.-,+)((((''&&%%%&&&&&&&&''(())**++,,--../........--,++*))))))****,,--../00111223466766530.,***+**********+-/135688888765310/..../23455555555555555556799:<<:987668;?FLRWZ]]^__`aabbbcccddeeeeeeeea\VPLJJKIJKNU\bdijjjijjjjjjjjjjkkkkkkkkkkkkkkkkjijjkjigfecekmofS:53333212222222211100/////012469=AEINSX]cgjosvx{~~}}||{zzzzyyxxy{|}bcceddddb`][YWUSRPMJHEC@?=;87555678:<>?ACDEFGHHHGHHIIJKMNOOPQQQRRRSSTUUUYYYYZZZZ[[\\]]^^^^^^^^^___`acdfijlnqsuwwyyyyyyyxwwwwwwwwxyyyyzzzz{{{||}~~~~~~~~~~|}}zxxxwwvvuutsssssssssssssssrqqqqqqrrrrrrrrrrrrrrrrrrrqrrrrrrrrrrrrrrrppppppqqqqpppppppooonnnmmlkjjihgdcb`^]\ZYXVUSRQPNMLLLMMMNNOOPPQRSSTUUVWXXYZ\]_``abceffgijjkkllmnnnnnnnnnopppqqqqqqqqpqqqqqqqqqrrrrrrrrrrrrrrrrrqqpppppooonnnmmmmkkkhgeedb`^\ZXVUSRPOMLLKKKKKKKLMMMNOOPQRSSTVWXXXXWVURPNLIFB@>>>>>???@@AAABBAAA@@===<<<<;;:998777776655410//.-,,,+**++++++++++++++++**))('&&&%$$#"! ! !"$%&()+,.0134444322100.-,+*)(((''&&%%$%%%%%%%&&''(())*++,,--../.........-,,+**))****+++,--.//011122223466766530.,***+**********+,.135678887655310...../12345665555555555667899:<<;:98668;?ELRVX]]^__`aabbbcccddeeeeeeeeb]WQMKKLLNORW]acikkjiijjjjjjjjjjjjjjjjjjjjjjjjjjijjkjigfeceknohU;533332222222222111000////012346:=AEINSY_cglptwz~~}||{zzzzz{{|bcceddddb`][YVTRROMJHFCA?=<:977778:;=??ADDEFGGHHGGHHIIJLMNNOOPPQQQRRSSTTWWWXXXYYZZZ[[\\]]]]]]]]]^^_`acdfgiloqstvwwwwwwwwvvvvvvvvxxyyyyzzzz{{||}}~~~~~~~~}}}}}}}}|}|yxwwwvvuuttsssssssssssssssrqqqqqqrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqrqqqppppoooonnnmmllkjjihgecb`_^\ZYXWUTSRPNMLLLMMMNNOOPPQRSSTUUVWXXYZ\]_``abceffgijjkkllmnnnnnnnnnopppqqqqpqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqpppooooonnnmmmmkkkhgeedb`^\ZXVUSRPOMLLKKKKKKKLMMMNOOPQRSSTVWXXXXWVURPNLIFB@>>>>>???@@@@AAAAAA@@====<<<;;:9987777766554210/.--,,++++,,++++++++,+++++*))(('&&%%$$#"!!! ""#%&()+,.012333322110.-,+*)(((''&&%%$%%%%%%%%&&&'(()**++,,-.//.........-,,++******+++,--..//011222333466766530.,***+**********+,.13567777765420/.----.01235555555555556677899:<<;;98669;?DKPVX]]^__`aabbbcccddeeeeeeeeb]WQMKKMNQSUY]achjkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjijjkjigfdacinqkX=53444323333332211100000//0123457;>AEJOV\`ejnrvy}~}}|||||}}}bcceddddb`]ZXVSQQOLJHFCA?>=<;:999:;=?@ABDDEFFGHHFGHHIIJKLMMNNOOPPPQQRRSSUUVVVWWWXXYYZZ[[[[[[[[[\\\]^_abdegiloprsuuuuuuuutttttttuwwxyyyyzzzz{{||}}}}~~~~}|||}~~~~~|||||||||}zwvvvvvutttsssssssssrssssssrqqqqqqrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrqqqqqqqrqqqppppooonnnmmmllkjjihgecb`_]\YYXWUTSRPNMLLLMMMNNOOPPQRSSTUUVWXXYZ\]_``abceffgijjkkllmnnnnnnnnnopppqqqqpppppqqqqqqqqqqqqqqqqqqqqqqqqqqqqpppooooonnnmmmmkkkhgeedb`^\ZXVUSRPOMLLKKKKKKKLMMMNOOPQRSSTVWXXXXWVURPNLIFB@>>>>>>???@@@@AAAA@@@>>===<<;;:99877777665543110/.---,++,-,,++++++,,,,,++**))('''&%%$$""""!!!!!"#$%&')+,./0123322111/-,+*)(((''&&%%$$$$$$$$$%%&&''()**++,-.//........--,,+++****++++,-../00112233334467766530.,+++,+*********+,.0345666765431/.-,,,,-01234455555555556678899;==<<:9769;?DJPUW]]^__`aabbbcccddeeeeeeeeb^XRMKKMPRTUX\`bhjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjijjkjigfdbcgmpl[?635554344444432211100000000123458;>BFLRX^bglptw|~~~~~~bcceddddb_\ZXURPQOLJHFDB???>=<;;<<=?@ABCDEEFFGGGFGGHHIIJKLLMMNNOOOPPQQRRTTTTUUUUVWWWXXYYZZZZZZZZZ[[\^_`bcegjlnpqrrrrrrrsrrrrrrrsuvwwwxxxyyyzzz{{{{{|}~~~~}}}}}}|{{{|}~~~~~~}}}{{{{{{{{}~}xuuttuttsrrrrrrrrrrrrrrrrrrrqqqqqqrrrrrrrrrrrrrrqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqppppoonnnnmmmmllkjjihgfdb`^][YYXWUTSRPNMLLLMMMNNOOPPQRSSTUUVWXXYZ\]_``abceffgijjkkllmnnnnnnnnnoppppqqqpppppqqqqqqqqqqqqqqqqqqqqqqqqqqqqpppooooonnnmmmmkkkhgeedb`^\ZXVUSRPOMLLKKKKKKKLMMMNOOPQRTTUVWXXXXWVURPNLIFB@>>>>>>>???@@@AAA@@@?>>>===<<;:99877777665543210//...-,,---,++++++,,-,,,,+**))((''&&%$####""""!!"#$%&()*,-/023222211/.-,*))((''&&%%$$$$$$$$$$$%%&&')**++,-../........--,,,++***+++,,-../001123333444467765420.,,+,,,*********+,.0245556654320.-,+++,-/012334555555555567899:=<:879;>CJOTV]]^__`aabbbcccddeeeeeeeec^YSNKKMPRSUVY]ahjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjijjkjigfdbcglpm_A6455554444444422211100000001233469==>>?ABCDDEEEFFGGGFFGHHIIJJKKLLMNNNNOOPPQQSSSSTTTTUUVVVWXXXXXXXXXYYZZ[]^_`acehkmnopppppppqppppppqqstuuvvvwwwxxxyyzzyy{|}~~~~}}}}||||||{{{{{|}}~~~}}}}|||{{{{{{{{}~{vtsssssrqqqqqqqqqqqqqqqqqqqqppppppqqqqqqqqqqqqqqppppppppppppppppppppppppppppqpppooooonnnmmmmlllkjjihgfdb`^][YYXWUTSRPNMLLLMMMNNOOPPQRSSTUUVWXXYZ\]_``abceffgijjkkllmnmnnnnnnnopppppppoooppqqqqqqqqqqqqqqqqqqqqqqqqqqqqpppooooonnnmmmmkkkhgeedb`^\ZXVUSRPONMLLKKKKKLLMMNNOPQQSTTUVWXYYXWVURPNLIFB@>>>>>>>>???@@@A@@@??>>>>===<;:998777776655442210/...----..,,,,,,,,-----,,+***))((''&%%$$$###""!"##$%''(*,-/122222220.-,+*))(''&&%%###########$$%&&()**+,--..........,,,,,,++*+++,,,-//001123334445446777642/-,,+,,,*********+,.023454554321/-,+++++,./0123455555555556789:;=@@?>=<:89;>CJOTV]]^__`aaaabbbccdddddddddc_YSNKKLOQSSUW\`hjjjjkkjjjjjjjjjjjjjjjjjjjjjjjjjijjkjigfdbcglpo`B7455554444444422221111110001222357:<@EJPV[`fjnsx{~cccddddb_]ZXURPPPNLJHFDCA@@????@@ABCDEEEDEEEEFFFFFFGHHIIJJKKLLMNNOOPPQRRRRSSSTTTTUUUVWWXWWWWWWWYXYYZ[\^_`acfikmonnnnnnnoooppppqrqqssssttttuuvvwwxxyz|}~~~~}}}|{||||{{{{{zzyyzz{}~~~}}}}|||{{{{{{{{|}~xtrqrrqqqqqppopppppppppppqppppppppppppppppppppooooooooooonnnnnnnnnnnnnooooonnnnnnnnooonnmmmmllkjjihhfffdb`^][YXWVTSRQONMLLLLMMMNNOOPPQSTUUVWWXYZ\]_`ababbcdeeghhijklllllmnnnnnnnnooooooppqqqqqqqppppppqqqqqqqqpppqpppopppooooonnnmmmmllkihgfdba_][YXVTSRPONNMMKKKKLLLLMNOOPQRSTUVWXXYXXWVURPNLIFB@>>>=>>>>>????@@@@???>>>>===<<;::988766555432221000/..-----.-,,,,,,,-.--,,++++**)((('&&%%%$$$#""#####%''(*,.012222221/.-,+**)('&&%%$###########$$%%&(**+,--.........-.--,,+++***+++,,.//011123444455556665320.-,++,,+*******+,-.023344444321/-,+****+-./012345555555555689;<<@AA@?>=<9;>DIOUW[\]__```aabbbccdddddddddda\VPLJKNPPRSV[`dfijkjkkjjjjjjjjjjjjjjjjjjjjjjjjiijjjigfdbcekoocH94554464444444322221111110001232469;>CGMSX^chmpvz}ddcccbba^\ZWTQPONMKIGFDCBAAAAAABABCDDEEEEEEEEEEFFGGHHIHIIJJKKLMNMNNOOPPQRRRRSSSSSSTTUVVVVVVVVVVWWXXXYYZ[]_abdfhjjkkllmmmmmnnnnoonpppqqqrrrrsssttvwxy{|}~}}}|||{z||||{{{{zyyxxyyz}~~~~~}|||{{{{{yzz{{|}}wsqqqqqppppoppppooooppppppppppppppoooooooooonnmmnnnnnnnnnnnnnnnnnnnnnnooooooooonnnnnnnnmmmllllkjjihhfedca`^][YXWUTSRPOONMLLLMMMNNOOPPQSSTUUVVWZ[\]_`aaaabceffgijjkkllmmmnoooooonnnnnnnnnoppppppppqqqqqppppppppqqppppooppooooonnnmmmmlkkjhggfdcb`^\ZYWUSRPOMMLLKKKKKKMNNNOPQQRTUUVWXYYYXWVURPNLIFCA???>=>>>>???@AA@@@@?>>>>>>>=<<;:99877666654222211110///.....-------./.--,,++++***)))((('&&&&%%%$####$&&'(*,./0111221//.-+**)(('&&%$###"""""!""##$%%()*+,,-.........---,,,,++**+++,,-/00112234444555556665420.-,,+,,+*******+,-.023344444321/-,+****+-..01234555555555679:;<>BBBA@?>=<=@DINRWZ[]^___`aabbbccddddddddcda\VPLJKLOPPRUY^cfijkkkljjjjjjjjjjjjjjjjjjjjjjjjiijjjihgdbddkopgI:55644644444443332221111100012113579;@DJOTZ`ejmtx{~ddcccbba]\YVSQONMLJIGEEDBBBBBBBCDDDDDDDEEEEEEEEEEFFFGGIIIJJJJKKKKKKLLLLMOOPPPQQRRRSSTTTTTTTTTTTUUVVWWWXXZ\^_abdefghhijkkkjkkllmmlmmmnnnoooopppqqrsuwxy{}~~~}|zzzzzzzxzzzzzzzzxxwwwxyz}~~~~}}|{{{{{zzzyyz{||}¿xrpoopooonnnnmmnnnooonnnnnnnnnnmmmmllllmmmmmmmmllmmmmlllllllllllllllllkmlllllllllllllllllllllkiihgfeedccba`_^ZWWVUTSRQOONMLLLMMNNOPPQQQRSSTUUUWZZ[\^__`aabbcdeeghhijklllmmmmmnnnmnooooooooooooooonnnnnnoooooooooonnnnmmnnmmmmmmmmlllkkjjhgffeca`^\ZYXVTSQPONNLLKKKKLLLMMNOPQQRTUUVWXXYYXWVTRPNKHFCA@@@?>>>>????@@@@@@@?>>>>>>>>=<<;:99877666643222211110000////.......///.--,,+++****))))(('''''&&%$$$$$%&&'()+,-./001100/.,+**)((''&%$$##"!! !!"##$&'()*+,,,,--------,,,,,,,++,,,---/01123445555555556665420.-,+++,+********+,-/01122333210.,+*))))*--./012444455555578:<=>@CCCCBA@?=>@DINRVZ[]^__`aaabbbccddddddddcca]WRNKKLNNOPRW]dfijkklljjjjjjjjjjjjjjjjjjjjjjjjiijjjihgdbcckopgL;556546444444433333222100000110024579=@FJPV\agjpty|~ddcccbb`][YVSPNMLKJIGEDCBBBBBBBCDDDDDDDDDDDDDDDEEFFFGGHHIIIJJJJJJJKKKLLMNOOOOPPQQQQRSSSSSSSSSSSTTUUVVVWWXZ\]_`abcdeefghhhghhiijjillllmmmmmnnnnoopqsuwxz|~~~}}{zzzzzzzyzzzzzzzzyxxxxyzz|~~~~}}}||{zzzzzzyyyyzz{|}}~vpnmlmlllklkkjjkkkllllllllllllllmmmllllllllllllkkllllkkkkkkkkkkkkkkkkkjlkkkkkkkkkkkkkkkjjjjjjiihgfeedcaa`_^]\ZWWVUTSRPOONMLLLMMNNOOPQQQRSSTTUUVXYZ[\]]_`aabcddeffghiijjjkklllmmmmmnnnnnnnnooooooonnnnnnnnnnnnnnnnnnnmmmnmmmmmmlllkkkjihhgfedcb``^\ZXWUSRQONMMLLKKKKLLLMMNNOPQRSTUUVWXXXXWVTRPNKHFCA@@@????????????????????????>==<;:998777666533322211111000/////////////..--,,+++*******))((((((('&&&%%%&&'(()++,-/000110/-,++))((''&%%$#"!!  !!""#%&'()*++++,,,,----,,,,,,,,---....01123455555555555666532/-,+++++++++****)*+,-//000122210.+*)((())++,-./0222344555579;=?@BDDDCBAA?=>@DINRVY[^_```aaabbbccdddddddddca]XTPLKMNMMMOSZbeijjikkjjjjjjjjjjiiihhhhiiiiiiiiijjjihgeabckopiO=556556444444434333322100000001013468:;CFKQW]cgmqvy|ddcccbb`\[XURPNMLKJHGEDCBBBBBBBCDDDDDDDDDDDDDDDEEFFFGGGGHHHIIIIIIIJJJKKLMMMNNNNOOOPPQRRRRRRRRRRSSTTTTUUUVXZ[\]^__`abbcddddeffghhgiijjjkkkkkkklllmoqsuvxz|~~~}}{zzzzzzzxyyyyyyyyxxwwxxyz}~~~~~}|||{{{zyyyyyyyyzz{|}~~zrljiijijjiiiihhhhhiiiiiiiiiiiiijkkkjjjjiiiiiiiihhiiiiiiiiiiiiiiiiiiiiiijjjjjjjjjjjjjjjjgggggggggfedcca__^]\[ZXWVUTSRQPOONMLLLMMNNOOPPQQRRSSTTUUVWXYZ[\^_``abccdddeffghhhiijkkkklkklllllllmnnnnnnnnnnnnnmmmmmmmmmmmmmlllmllllkkkjjjjihhggedccb`_^\ZYWVTSQPOMMLLLKKKKLLLMMMNOPPQRSTTUVWWWWWVTROMKHFCA@@@????????????????????????>>=<<;:988777665433332222111100000000000000/..--,,+++*******))))))))(&&&&&&&''(()*+,-./00111/.-,+**)((''&%$$#"!! !!!""$%&'()*+++++,,,-,-,,,,,,,---...//01234455666666655666431/,,+***++++***)))*+,-.//00111110-+*)(((()**+,-./022333455579;>@ACEEEDCBB@>?ADINRVY[^__``aaabbbccdddddddddca]YTPLKMNMLLNRY`dhihhijjjjjjjjjjjiiihhhhiiiiiiihhiiihgfecdbhnqkT?55665644444444444333221111111111246789@BFLRX_cjotwz~ddcccbb`\ZXUROMLLKJHFEDCBBBBBBBCDDDDDDDCCCCCCCDEEFFFGGFGGGHHHHHIHIIIJJJKLLLLMMMMMNNOPPPQQQQQQQQRRRRSSSSTUWWXYZ[\\]^^_`aabbcddeffegghhhiiihhhiiijkloqrtvx{~~~}}}zyyyyyyyxyyyyyyyyxxwwwxyz~~~~~~}}}||{{{{zzyyyyyyyyz{||}~woigfefgffgfeffffffgggfffffffffffgggffffggggggggffggggfffffffffggggggggfhgggggggggggggggeeeeeeeeddcbaa_]]\[ZYXWVUTSRQPONNMMLKLLLMMNNOOPPQQQRRSSTUVWXYZ[\^^__`abbbccdeffggghhhiiijiijjjjjjjkmmmmmmmmmmmmmmmmmmmmmllkkkkjjkkjjjiiihhhggggffdcba`^]\[YWVUTRQONMLLLLKJJKLLKLLLMNOPPQRRSTUVVVVVUSQOMKHFCA@@@?????????????????????????>>=<;::988777654443333222211110000000000100/..--,,+++*******)))))))(''''''''(())**+,-.//1110/.-,+**))('&&%$#""!!!!!!!!"#$%&'()***++++,,,-,,,,----...////12234566666666665665420.,+*****+***))))))*+,-.///00000/-+*((''(()*+,-./022333445579;>ABDFFFEDCCA?@ADIMQUY[^___``aabbbccdddddddddcb^YUQNLMNLKKMPW_chihhijjjjjjjjjjjiiihhhhiiiiiiihhiiihgffccbhnqlYB54565544444444544443321111111112345678<>BGMSZ_glqux|ddcccbb_[ZWTQNMLKJIHFDCCBBBBBBBCDDDDDDDCCCCCCCDEEFFFGGFFGGGGHHHHHHIIIIJJKKKLLLLMMMNNOOPPPPPPPPPQQQQRRRSSTUVWXYYZZZ[\]^^_``abcceedeefffgggfffggghijlnpruwz}~~~}||zyyyyyyyxyyyyyyyyxxxwxxyz~~~~}}}}|||{{zzzzzyyyyyyyyz{{|}~~tlfdccdddddcccdddddeeedddddddddddeeeddddccccccdeddeeeecbbbbbbbbcccccccccddddddddddddddddcccccccbba`_^^]\\[ZYXWUTSSQPONNMMLKKJJKKLLLMMNNOOOPPQQRSTUVWXYZ[[\\]^_``abbcdeefffffffggggghhhhhhhijjjjjjjjjjjjjjjjjjjjjjiiiiihhiihhhggfffeeeeeddca``_][ZYWVUTSRONMLKKJJJJJJKKJKKLMNNOPPQQRSTUUUUUTSQOMKHFCA@@@??????????????????????????>>=<;;9888777655444333332221111111111111100//.-,,,++++++**********)((((((((())))**+,--.00100/.-,++**)('&&%$#""!!!!!!!!#$$%&'())***++++,,,,,----.////00012345666666666665665310-++*)))***))))(())*+,,-.../////.,*)(''''())*+-..022333444579CHNUZdinsvz~ddcccbb_[YWTQNMLKJIGFDCCBBBBBBBCDDDDDDDDDDDDDDDEEFFFGGFFGGGHHHHHHHIIIJJJKKKKLLLLLMMNNOOPPPPPPPPPPQQQRRRSTUUVWXXYXXXYZ[\\]^``abddcddeeefffddeeeffghkmoqtvz|}~~}}||{{{{{{{{zzzzzzzzzzzyyyz{|~~~}}}||||{{yyzzzzyyyyyyyyyz{||}~¿|qica`_`a``a`_`aaaaabbbaaaaaaaaaabcccbbbbaaaaaaaa``aaaa`________`````````aaaaaaaaaaaaaaaaaaaaaa`__^]\[[[ZZYXWVUTRQPONMLLKKKJIIIIJJJJKLLMMMMNOOPPQSTUVWXXYYYZ[\\]]``abbcdddeddddeeedeffffffffggggggggggggghhhhhhhhggggfffefffeeeddddcccccbba_^^]ZYYWVTSRRQNMLKJJIHIHHIIJJKKKLMNOOOPPQRSTTTTTSRPOMKHFCA@@@??????????>>>>>>>@@@@@@@@??>=<<;:888777655544443333222211122222221110//.-,,,,++++++***********)))))))))))))**+,,,//000//.-,,+**)('&%$$##""!!! "##$%'(()))***++,,,,---../00001112344567766666666566531/-++*))))*)))(((())**++,---.///.-+)(''&&&'(()*+,-/11233444579>>>>>>@@@@@@@@??>>=<<;9998877666554443333222222222222221100//.-,,,+++++++++++++++++***)))))))))))**+++--./...---,,++*(''&%$###""!!  !""#$&''()))****+,,---..//01111223345677777777776666430.,**)(())))(((('()))**++,---...--+)('&&&&&''()*+,-//123344579>===<;:99888877655544333222222223333321100//..-,+++++++++++++++++++++****)))))))***++,,-......--,,,+))('&%%$$#""! !!!"#$%&'((())***,,--..//01112223345567887777777677642/,*(((((())(((('''()))***+,,--.--,*('&&%%&&''()*+,,-./1234468:=@CEHJJJJIHGFCCDFHLOTX[]^__`a``aabbcddddddddedc_[WTQNLMLIGHJRZ`ehhhhiiiiiiiiijjiiihhhghhhhhhhiihhhgggedbagnqpbM:5764655555555777655543333222223333333457:>DKPX]chmquz}~cccba_^\ZWUSQNLJHGFDCBBBCCCDEEFFFGGHHHHHIIIIIIIIHHHHHHHIIIJJJJJJJJJJJJJJIIJJKKLLLLMNNOOOPPPPPPPQQQQQRRRSTUUUUUUUVWWWWWWWYZ[]^`abbbbbbccccdddddddgilnprtvx{{||}}~}}~~~~~~~~~~~~~~~~~~}}}||||{zyyyyyyxxxxyyyyyyzzzz{|~ÿuh_ZYXXXVWWUWWXUUUWWVVVWWWWWWXXXXWXXXWWWWXXWVVVVVVUUUTTVXXWWWVVVTUUUVVVVVVVVWWWWWWVVVVVVVWWVVVVUTTTTTTTTTSRPNMLKJJJIHGGECCCCCCCCCDDEEEEFFGGHHIIJJKLLMNNOPSTTTUUUVYZZZZ[[[[[\]]]]]]]^________________`````````___^```__________^]^]]]]][ZZ[ZYXXWVUTRPNLKIHHGGGFFDDDDDDDDFFFFFGHHIJKKKLMMNOOOOONMLJFCA@@???????????>>>>>>>???@@@AA@??>>>==<;::9988877665544433222112233344322100///.-,++++++++++++++++++++++***************+,,---......---+,++)('&%$$#"!!!!!!""##$%&&&'((()+,--.//001112223345678887666666687541.+)'(((()))(((''&'((())))*++,,-,++)'&&%%$%%&'()*+,,,,-/134468;?BEFIJKKKJIGFDDEFILOTX[]^___```aabbcddddddddeeda]ZVROLKKJIIJPZ`ehhhghiiiiiiiijjiiihhhghhhhhhhiihhhgggddbafnqpcN;68646666666668877665544443333333333332358>????????>======>??@@@AA@??>>>==<;;::9988877655444332222223333443221100//.-,++,,++++++++++++++++++***************++,,,--........-..-,*('&&%$#"!!!!!!"""#$%%%&&'')*+,,-./001111222345678877666666676531-*('(((()))((''&&&'(((((()**++,+**(&%%%$$$$%'()*++++++-/23468>>????????>======>??@@@AA@??>>>===<<;::9988776655443332222333344443321100/.-,,,,,+++,++++++++++++++++++++*********+++,,,,-........//.-+*)('&%$#"!!!!!!""""#$$%%&&()*+,-./011111222345678877666666676430-*('(((()))(('&&%&''(((()*++++++*)'%%%%$$$$%&'()******,.13479<@DGIKLLMNMKJIGGGHJMPUXZ]^____``aabbcddddddddeddb_[VRMHGHIHHHOW]bfghhhiiiiiiiiiihhhgggfgggggggiihhhgggecbbekpsiT@787666666666699987766555444333333333322358=ADJOTY^dinruy}bba`_]\ZXUSRPNLJHGFDCBBBCDDEEFFGIIJKKLMMNNNNNNNMLLLLLLKKLLLMMMMMMMMMMMMMNNNNNNOQRRRSSTTTTTTTTTTUUUUVVVWWXYYYYYYYZZZZZZZZ[\]^_`abbcccddddeeffghijlmprsuwyy{{||}}~~~}}||{{{{{zzzzyxxxxxxxyyyyyyyyzz{{||}~~{tonljkiiighhiffffffffeeddddbbbb_^^^____]\\\[[ZYYYXXXXVUTTSRRRQOPPPQPPPPPPPPPPPOOOOOOOOONMMMLLLKLKKJJJIKJIIHGGFEDDCBA@@@@@@@@@@@??@@@@AAAAABCCDDEFFFGGGHKKLMMNOOQQQQRRRRRRSTTTTTTTTUUUUUUUUVVVVVUUVWWWWWXXXWWWVVVVVUUUUUUUUUUTSTSSSSSQPPQPPONMMLKJIGFEEDCCCBBBAAAAAAAABBBBCCDDDEEFGHIJKLLMMMLKJHEB@?>>>>????????>======>??@@@AA@??>>>====<<;::9988776655443332223334445443321100/.-,,,,,+,,+++++++++++++++++++++********++++++,,--...////0//-,+)('&%$#"!!!!!!!!!""##$%%')*+,-.//0111112234567887766666666542/,)('(((()))(''&&%&'''(())*+++++*)('%%%$$$#$%&'()**)))*+.1348:=AEHJLMNOONMKKIHIJKNQUXZ]^____``aabbcdddddddddddb_[WRLFEFHGHINW]bfghhhhhhhhhhihihhhgggffffffffiihhhgggfcabejptkWA8886666666666::998877655554443333333333347;>AFJOTY_djnrvz}aaa`^]\YWUSRPNLJHGFDCBBBDDEEFFGHJJKLMNOOPPPPPPPPOOOOOONNNOOOPPPPPPPPPPPPPPOOOOQSTTTUVVWVVVVVVVVWWWWXXXYYZ[[[[[[[\\\\\\\\]^_`abbcccddddeefghhjklmoprtuvxzz{{||}}~~}}}|{{zzzzzzyyyxxxxxxxxyyyyyyyyzz{{||}~~|zxwwvuuttstrsqrrrrrppoooonnnnlkkkjjjjihhhggecccbbbbaa``_^^]]ZZZYZYYYWYWWVVUUUTSSSSSSSQPPPPOONPONNMMMNLJIGFEECBA@@?>>>>>>>>>>>==>>>>??>>?@@AABCCDDEFFFGHHIJKLLNNNOOOPPPPQRRRRRRRRRRRRRRRRRRRRRQQQRRRRRSSSRRRRQRRRQQQQQQQQQQPOPOOOOONMMNMMLKJJIIHFEDCCBAAA@@@????????AA@@AABBCCCDEGHIJKLLLLKJIGEB@>>>=>????????>======>??@@@AA@??>>>=====<;;;:998776655544333333444455544332210/.-,,-,,,,,+++++++++++++++++++++********+++++++,,-../00/01100/-+*)'&$##"!!!!!!!!!!!"##$%()*+,-.//000111224567887766666665531/+)''(((())(''&&%%%&''(())*++,++*('&%%$$$##$&'())))))))+-0248:>BFILNOOPPONMMKJKKLNQUXZ]^____``aabbcdddddddddddb`\WQJEEFHGHINV\bfghgghhhhhhhhhihhhgggffffffffiihhhgggfcbaejptmYC9997677777777;;::99876665555444444444433358;>CGKOTZ`fjnrvy}aa``^]\YVTSRPNLJHGFDCBBBDDEEFGGIKKLNOPQQRRRRRRRSRRRRRRRRRRSSSSSSSSSSSSSSSSSRRRSUVVVWXXXXXXXXXXXYYYZZZ[[[\^^^^^^^^^^^^^^^_`aabccdddddeeefghhiklnoqrsuwxz|z{{||}}~~~}}||{{zzzzzzyyyxxxxxxxxxyyyyyyyyzz{{||}~~}}~~|}||||{{zzzzyyyywvvvuuuutsttssqpqqqpppoommljjiighhgihhgfgfeeddccb```````\\\[[[ZYYXWWVVUSPNLIGEDB@@?>=<<;;;;;;;;;::;;;;<<<<=>>??@@ABCDFFGFFGHIJKLKKLLLMMMMMNOOOOOOOOOOOOOOOOOOOOOOOOPPPPPPPOOONNNNNNMMMMMMMMMMLKLKKKKKKKKLKJIHHGFEEDCBBA@@???>>>>>>>>>>?????@AAAAACDFGHIJKKKKJIHFDA?>>>=>????????>======>??@@@AA@??>>>=====<<;;:998877655544433334444555544432220/.-----,,,,,++++++++++++++++++++********+++****+,,-/0010122221/-+*)'%$#"!!!  !!""#'()*+,-..//0011124567887766666665431/+(''(((())(''&&%$%&&'(()*+,,,+*)'&%$$$$###$&'())))((()*-0249;?CHKMOPQRRQPOOMLLLMORUXZ]^____``aabbcdddddddddddb`]WQJDDEGGHIMT[afgggghhhhhhhhhihhhgggffffffffiihhhgggfdbaejpto\D:9:9677777777<<;;:9987766655544444444543347:;@DGKPV[afjnruy|cba_][ZXVTSQOMLJFFEDCBAABCDEGHIJKLMNOQQSTTTTTTTUVVVVVVWWWWXXXYYYVVWWWWWWXXXWWWVVVWWXXYYYZ[[[[[[[[\\\\]]^________`````````abbcddeddeeeeffhiiklnopqsuwxyz{||||}}}}~}}|||{{zzyzyyyxxxxxxxxxxxxyyyyyyyyzz{{||}~~~}}}||{zyyxvvvvuutuutstssrqqppnnnnnnnmkkkjjjjhgfedcba`\WSOLHB>>=<;:97::::::::::::::::;;<<==>>>>>@BDFIMNOPRSSSSSSSTTTTTUUUUUUUTSSSSSSSUUTTTTTTSSTTTTTTTTTTSSTTSSSSRQQQPQQPPPOOQPPPOOOOPONMLKJGFECA@?>=>>>>===<=======?>>>?@@A@AABCEGHIIIIIHGFEC@>==<<=>>>>>>>=======>????@@@AA@??>>>==>==<<;;:99887765544443333444555544432220/.-----,--,++++++++++++,,,,,,,,+++++****+++***)*+,,./0113333320.,+*(&%$"!!  !!"##$%&'()*,-./011213456787766666666520.*'&'(((())('%%$$##$&&'())*+,,,+)'&%$$$$###$''())(((''(),.14:<@DILOQQRSSRQOONMLLLNPRVY\^____``aaabcdddddddddddcb_YQIBABCDEEIR[cfggffggggggghhhgggfffeeeeegggfffffffffdb_agmppbJ:9;:9:998899::<<;;:9987766655555555555433469:>ADGKQV\aejnrvy||~ba`_][YXVSRQOMKJFFEDBAAABCDEGHIJKLMNPQQRTTTUUVVWXXYYYYYZZ[[\\]]][[[[[\\\\\[[ZZZZYXXYYZ[[[\]]]]]]]^^^^__`aaaaaaa`bbbbbbbbbbccddeeeefffgggiijlmoqrsuxyz{|}}|||||||~~~}}}||{{{zzyyyyyxxxxxxxxxxxxyyyyyyyyzz{{||}~~~}}}|{zzyyxwyxwwvvvuuusrqonmkgc^YTOIDA>;98767777777766677779:999:;==<>@CGJMSWXZ[\]^``aaaabbbbba````aaaaaaaaaaaaaaaaabbba````````____^^^^]\\\\\\\ZZZYYYYYZZZZ\[ZYVTROMKHEB?===<<<;;::;;;;;;;=<==>?@@@BAAADGHJJJJIIHFEB@><;;;<>>>>>>>========>???@@@AA@?>>>===>==<<;;:998877655555544445556676554433210.-----,,,,++++++++++++,-------,,,,+++**++***))*++,./01234344420.-+)(&%#"!  !"#$%&'()+,-./0121345678776666666541/,)'&(((((()(&%%$$###%&')**+,-,+)'%$$$#"""""%''())((''((),.15:<@DILOQRRSTSQPQPONMMOQSVY\^____``aaabcdddddddddccba_YQIB@BABABFOZcfggffgggggggggggggfffeeeeeffffffffffffdb_agmppdL=:=<:;::99::;;<<<;::987776666666666666543469:;>ADGLRX^afkosvyx{~a`_]\ZXWURQONLJIFEDCBA@ABCDEGHIJKLMOQQQRSTUVWWXXYYZZ[[[\]]^^____^^^^_______^^]]^^________`aaaaaa```````bbbbbbbbbcdeeeeeedddeeffffggghiiijjkmoqrstvxyz{|}}|||||||}~~~~}}}|{{{{zzzxxxxxxxxyyyyyyyyyyyyyyyyzz{{||}~~~}zwuqmgb\WQLFA=96556555556333445558877779;<=@DHNSX\aehjmooopqqrrrqqpponnnnoooooooopooooooooqqqpoooooooonnnnnnnnmllllllljjjjjjjjljiikjifc`]ZWTOJEA>==;9998789999999:;<<=>???@A@@CGIKKJIIIHGDA@=;:::;===============>>??@@@AA@?>>>===>==<<;;:998877765555555555677887665443310/....-,,,,,,,,++++++++---------,,,+++*******))**++,./01344455421/-+)'%$#"  !#$%&'*+,-/0121334556666666666430-+)''(((((((''&%$$#$#%&(*+,,--,*('%$##"!!!"#%'()))((''()*,/25:=BFJMPSSSTTTSRSQPONOPRTWY\^____``aaabcdddddddddcdcb`[SKB@>?><>BNZcfffffgggggggggggggfffeeeeeeeegggggggfecb_agmppeO>;==<;;::::;;<=<<;::988877766666666666544579::<>ADINSZ]bgkoruux{~__^\ZXWUSQPNLJIGEEDCBA@ABCDEGHIJKLNOQQQRSTTUVWWYZZZ[\\]^__``aaaa````aaabcccbbbbbaaaaaaaaabcccccccccccccccccccccdeffffffffeefffggghhhijjjkklnprstuwxyz{||||||||||}~~~~~}}}||{{{zzzyxxxxxxxxyyyyyyyyzzzzzzzzzz{{{||}}~~}yupjd^WQKE?:6545443445222334446666568:=@DKPW]bfkoruxzz{|}~~~~~}}}}~}}}}}}}~~~~~~~~~~~~}||||||||||||||||{yyyyyyyxxxxxxxxwvvvwvuroliea]WQKFB?=:88876677788889:;<==>>>?@?@DHKLKJIIIHGEB@=;:::;<<<<<<<<=======>>??@@@AA@??>>===>==<;;:988877776555555555667788777654432100//...----,,,,,,,,,,,-......--,,+++**))))))))*****,-.02345554320.,*(&%#"! "#%&(*+,-/11123344555666666642/,*(&'((((((((''%$$$$$&')+,----+)'&%$#"!!!"#$%()))))(''()*,/26:?DILORUUUUUUUUUSRPPPQRUWY[]^^^_``aaabcddddddddecddcb]VND><:878>NYcfffffgggggggggggggfffeeeeeeeegggggggfecb_ahnppgQ@;==:<<;::;<<<=<<;;:99988887666666666666666669:=@CFKNVY]aeimprvy|~^^][YWVTRPOMKIHFEEDCBA@@BCDEGHIJKLNPQQRRSSTUUVWXZZ[\\]^_`aabbcccbbbccccdfffeeeefeeeeeeeeefffffffeeeeeeeddddddddffgggggggffffggggghhiijkkllmoqstuvxyz{|||||||||||}~~~~~~}}}||{{{zzzyyxxxxxxxxyyyyyyyyzzzzzzzzzz{{{{|}}~~~ysmg_WQJB;7434322222100122333454568:@DKRY_fkouy}~zwtpkgaZSMGB=9776555666777899:;<==>>>?@AEKMNLKJJIHGEB@=;::::;;;;;;;;<<<<===>>??@@AAAA@??>>====<;::9877777776555555555677888887654432110///....---,,--------........-,,+++**)))))))))))**+-./2334554321/-+)(&$#!  "#$'()+,.00112234445555555542/,*(&'(((((((('&%$$$$%')*,-..-,*('%$###""#$%&')****)(''()+-036:?EJMPSVWWWWWWWVUSRQQRRTWXZ\^^_```aaabcddddddddecddcb^WND>:7545;=<<;;<<===<<;;:999988887666666666666666689<>ADHKRUY]aeikosvy{~^]\ZXVUTQONLJHGFEEDCA@@@BCDEGHIJKLNPQRRRSSSTUUVWYZ[\]]^`aabccccdcdddeeefggggggghggggggggghhhhhhhgggggggffffffffghhhhhhhhggggghhhhiijjkklmmnprtvwxyyz{|||{{||||||}~~}~}~~~}}}|||{{zzzyyyxxxxxxxxyyyyyyyyzzzzzzzzzzz{{{{|}~~zunf_WOF>8422100000/.//0111134346:=DKQZahosx|}ytoic\UOH?:7654334556677899:;<===>@BDINQPLKJJJIGFC@>;:99:;;;;;;;:;;;<<==>>??@@AAAA@@??>>===<;:998666666665555555666778899877654422100////....-----------........-,,,+++*)((((((())))*+,-/12245543320.,+*'%$"!  !"%'()+,./001123344555555532.,*(&'((((((((&&%$$$$&)*,-...-+)(&%$#$$##$%&')+,,,+)('')*+.147:@FKNRUXXXXXXXXWVTSRRRSUWXZ\^^_```aaabcddddddddecdddc^YOF@:7212;MXbefgffgggggggggggggfffeeeeeeeefffffffeecb``djoqjVC=@@===<<<<====<<;;::99998887777777776666666678;=?BFHNQUY]aegkosux{}]][ZXVTSPNMKIHFEDDCBA@@@ABCEGHIJLMOPRRRRRSSSTTUVXYZ[\\^_`aabcccddeeefffghhhhhiiihiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhihhhhhhhhhhhhiiiijjkkllmnoprtvwxyyzz{||{{{||||||}~}|}}}}~}}}||||{zzzyyyyxxxxxxxxyyyyyyyyzzzzzzzyyzzz{{{|}~~{unh_VLB;6321/.......//001133357?BEINRTRLKJJJIHFCA>;9999:::::::9::;;<<=>>??@@AAAA@@??>>==<;::98766666665555555566778899998765543221000////....---------........---,,,+*)(((((((())))*+,.012455444310.,+)'%$#"! #%&')*,-.//012334555555531.,)'&'((((((('&%%$$$%&*+,....,*)'&$##$$#$%'()+...-+*((()*,.258;AGLPTWYXXXXXXXXWUTSSSSUWXZ\^^_```aaabcddddddddecdddd`ZRJB;51.0;LXaefgffgggggggggggggfffeeeeeeeeeeeeeeeeecc`_ciorlYF?A@>>>=<<=>>>=<<;;::::999887777777776666666668:<=@CFJMQUY]achlorux{}~\\ZXVTRPNLKJHGEDCCBAA@@@ABCEGHIJLMOQRRRRRRSSSTTUVWXYZ[]^_``aabcdeeeefffgijjjkkklkjjjjjjjjkkkkkkkjjjjjjjjjjjjjjjijiiiiiiihhiiiijjjkkklmmnopqsuwxyzzzz{||{{{||||||}~}|{|}}}~}}||||{zzzyyyyxxxxxxxxxyyyyyyyyzzzzzzzyyyzzz{{|}~~}vog\QG?8420/.---...//000134459?FOW_jrw}~yuog`WJB<8521112345567::;;<=>>@CHMRVXTMKKKJIHFCA>;9988999999999::;<<=>>??@@AAAA@@??>>=<<;:988766666665555555566778999:99876654322110000///............///////...---,+*((((((()))(()*+-/12355445431/.-+('&$#" !#$%')*+,-./01224555555531.+)'&'((((((('&%%$$%%'+,--.--+)('&%$$$$$%')*,.00/.,*((()+,/269?>=;;=>?>=<<;;:::::99988777777776666666678:;?AELRW[[WNMLLKJIHEB?<:99999999999999:<<=>>??@@AAAA@@??>>=;;:9887666666665555555567788999::98776643322110000////./////////////////.....--+)))))))))(((()*,./13555555320/.,*('&$$"  !"$%'(**,-./0122334455520.+)''((((((((''&%%&&')+,,-,,+*('&%$#$%&'()+-/0210.,*(((*+-/36;>DINQTWZZZZZZZZZYXVUTTUVWXZ\^^_```aaabcddddddddeccccda\UOG@80+,6LWadfgffgggggggggggggffffffffffffffffffeeeca`chmqn]I>>>=??=<<===>=<<;;::::::99987777777766666666789:;=@BEHLPSW\^cgknrtwz{|}~[ZYWTRPNLJIHGFEDCBBAA@@@ABCDFGHJJLNPRRSRRRRRRRRRTTUVWWY[]]]^^_`accdeeffgiiijklllllllllllmnnnnnnnmmmmmmmkkkkkkkkjkjjjjjjjiiijkkkllllmnnooprstvwyzzz{{{{{{{{{{{{{{}~}|{{{{||||||||||{zyyyyyyxxxxxxxxxyyyyyyyyzzzzzzzyyyyyzz{||}}~~{si^SH=62/.-,,,--.//00012356>JT_hpy}umeYNE<520/00123457889;=>?@BGOX_b_[RONNLKJHFC?<:99998877777888:;==>>?@@AABBA@@??>>=;::9877655555555555555567789:::;:987665443321110000/////////////0000000///////..)))))))))(((())+-/124556654310/.,+)(&%$"!  !#$&'((+,-./012334455520.+)'''(((((((''&%%&&')+++,++**(('&&%%')*+-/122321/-*)()+,.047<@EJNQTW[\\\\\\\\[ZXVUUUVWXZ\^^_```aaabcddddddddedddddb]WQIB92-/8LXaefffffffffffffffffeeeeeeeeeeefffffffeedca`chmqo_K@??=??======>=<<;;:;;:::9998888888886666666667889:=?BEIMPTY[_chlpsvxyz|}~ZYXVSQOMKIHGFEDCCBBAA@@@ABCDEGGIJLOQSSSSSSRRRRRRSSSTUVWYZ[\\]]^`abcdeffghiijklllllmmmmmmmmmmnnnnnnnnnnnmmllllllklkkkkkkkjijjkkkklmmnoppqstuvwxyzzz{{{{{{{{{{{{||}~}{{{{{{{{{{{{{{{{zyyyyyyxxxxxxxxxyyyyyyyyzzzzzzzyxyyyzz{|{||}~xodXK>730.-,----./00112356:BNZfow{skaVK?63100002345678:;=>?ACJS]efb]UQPPNMLJGC?<;:99988766677789;<=>>@@AABBBA@@??>>=;:98776555555555555555567789::;;:9887665443322111110000////////000000000000000//,,++++**)(((())+,.01346666543210.-+*('&%$#"! !"$&''))+,-./0233445520.+)(''''(((((''&&&&'')++++****))((''(+-./023333320-+)(),./259=AFKORUX[]]]^^]]^\ZXVUUVVWXZ\^^_```aaabcdddddddddddcdcb`ZTMF=3.08MXaeffeefffffffffffffeeeeeeeeeeeffffffffedb`_bgmpoaNA@@>@?=====>=<<;;;:;;;:::9988888888866666666567789;<@CFJMQUX\aejmqtwxz{|~YXWURPNLJHGFFEDCCBBAA@@@ABCDEFGIKLNQRSTTTTSSSRRRRRSSTTUWXXYZ[\]^``abcdefgghijkkkllllmmmmmmmmnnnonnnnnnnnnmmmlllklkkkkkkkkjjjkkklmnnopqqrtuvwxyzzzz{{{{{{{{{{{|||~}|{{{{{{{{{{{{{{{zyxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyxxxyyyzz{zz{}~|ri]NA730.-,----./01124579=FS`lt|zpg\OB84100001245689:<>?@BEMWaijf`URPPOMLJGC?=;:99988776677789:;=>>?@@AABBA@??>>==:998776544444445444444466789::;;;:998776544332222211110000000000000000000000000/..-,,++*)))))))*,.01245666654321/.-+*)('&%$#"! "$&&'')*+,-/123345520.,)(''''((())'('&&&'')+++++***))))))+.001234443320-+)(*,/136:?CGLPTWY[\\]^_^^^^\ZYWWWWWXZ\]^_```aaabcddddddddddcccbbb]VQI?4/1:MXaeffeefffffffffffffeeeeeeeeeeefffffffffeb__bgmrpfQB?@??>=<<=<==<;;;;:;;;::::98888888888887776666677899=@CFIMRUY^cgkoruwyz|}XWVTQOMLIGFFEDDCCBBAA@@@ABCDEEFIKLNPRSTUUUUTTSSRRRRRSSSTUUWXYZ[\^^_`abcdeefghiijkllllmmmmmmnnnoonnnnnnnnnmmmlllklkkkkkkkkjjjkllmnoopqrrstvvwxyzzz{{{{{{{{zz{{|}}~~|{zzzzzzzzzzzzzzzzyxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyxxxyyyzz{zz{}~wm`QB830.-,---../0112568;?IWdpyuj_RD9420000124579:;=>@ACHPZfmnibWSRQONMKHD@=<;:99988766677789;<=>>??@@AA@??>>==<99887665444444444444444667789::;::9987765543332222211110000000000000000011111100..--,,++*******+-.0123566666543210.-,+*)('&%$#! !#$%&&()*,,.012334410.+)(''''((())(('&&&'')++++++++)**+++-/11234443210.,*)(*-/147;@DHMQTWY[\]^_`____^][ZXWXXYZ\]__```aaabcddddddddddcccbbb^YTMB823<<;;;;;;;;::::9888888888887777666677888<>ADGKORW[`dimpsuwz{|~WWVSQNLKIFFEEDCCCBBAA@@@ABCDEEFHJKMOQRTUVVUUUTTRRRRRRRRRRSUVWXYZ[\]^_`abccdeffghijjkkllmmmmnnnoonnnnnnnnnmmmlllklkkkkkkkkjjklmmnoppqrsttvwwxyyzzz{{{{{{{{zz{{|}~}{{zzzzzzzzzzzzzzzyyxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyxxxyyyzz{zz{}~zpcSC930.-,--..//0122568;AL[iu}zocVF;52000012457:;<>?ABEKS_jqqkdYUSSPNMKHD@><;;:9988776666678:;==>>?@@@@@?>>>==<998776643333333433333335567899::::9988766544333332221110000000000000000011111100/..--,,+*******+-/01235667766543210/.-,+*)(''&$#! !"#$%&()*+.0/012221/-+)(''''((())(('''''()++++,,-,+,,--./13445555310/-+*()+-0258=<<<;;;;;;;;:::9999999998888777766777788;=@BEIMPTX]bgkoqsvyz{}~~VVUSPNLKHFFEEDCCBBAA@@?@ABCCDEEHJJKMORSUVVUUUUUSSSRRRQQQQRSTUVWXYYZ[\]^_aabbcdefghiijjkllmmmmnnnnnnnnnnnnmmmlllklkkkkkkkkkkkmnnopqrsstuvwxxyyzzz{{{{{{{{{zz{{|}~}{zzyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyxxxyyyzz{zz{}~}reTE930.-,--../01122458;CN]lz}tgYH<52000012458;;=>@BCFMWcnutnfZVTTQONLIEA>=<;::9988776666789:;=>>??@@@??>>>==<:987665433333333333333345567899::99988876654443333222110000000000000000000000000///.--,,+++++++,-/112345667666543210//.-,+**))(&%$"!  !!"#$&'(),.//00110/-+)(''''((())()('''())+++,--..-./00123555555420/-+*)()+.1369=BGKORUWXZ[\^_abbba`_]\[ZZZ[\]^^_```aaabcddddddddddcccbbba^YSJA:>>====<<<;;<<<;;;::9999999999988877777777777:<>@CFJLQUY^diloruxyz|}}UTSQOMKJHFEEDDCCBAA@@??@ABCCDEEGIIJLNPRTUUUUUVVTTSSRRQQQQQRSTTUVWWXYZ[\]__`abbcdeffghijjkllllmmmnnnnnnnnnmmmlllklkkkkkkkkkklmoopqsstuuvwxyyyzzz{{{{{{{{{{zz{{|}~|zzyyyyyyyyyyyyyyyyyxwwwwwwwxxxxxxxxyyyyyyyyyyyyyyyxxxyyyzz{zz{}~tfTE930.-,--.//01223458>?????>>>==<:987654333333333222222244567788999999987766554433332222000000000000000000000000000//.--,+++++++,./112345556665443321000/..--,,+)('&$"!  !!#$&'(*,--//01/.,+)(''''((()))))((())**+,--./0/112234566554431/-+*)(()+.147;?CGKORSUVXY[^_abccba`_^]\\\\]]^^^_``aaabcddddddddddcccbbbb`[WOE@BIW^cdeffffffffffffffffeeeeeeeeeeeeeeeeeeefgda_aciopk\I@BCBA@??>=<===<<<<<<<<;;;::9999999999988877777777779:<>@CFHMQUZ`ejmptwxz{||TSQOMKJHEDEDDCDCBAA@@??@ABCCDEEGHHIKMOQSTUUVWWWVTTSSRQQQQQQRSSSTUVWXYZ[\]^_`aabccdefghhijjkkkllmlnnnnnnnnnnmmmlllkkkkkkkkkllmopprsstuvwxxyyyzzzzzzzzzzzzzz{{|}~|zyyxxxxxxxxxxxxxxxwwwwwwwwxxxxxxxxxyyyyyyyyyyyyyyyyxxyyyzzz{{|~vgUF:2/-,+,,-//01233469=<;::9988766666789;;<<=>>>>?>>>===;:976433222222222111111134456778999999999877665544333322100000000////////0000000000//..-,,,,,,,--/0112334455554432100//0/00//..,++*)(&$#"!  !"$%&(*+,./00//.-+)('''''((())))(())**++,-./01334445666544321/.,*)(((*-0259=ADFJMPQSTVY[]`bcdeddcbaa`__^^^^^^_```aaabcddddddddedddccccc_ZSJFGOabcdeeefeeeeeeeefffffeeeeeeeeeeeeeeeeeeeeefb__bfnqn_KACDA@@??>>====<<<<=<<<;;;::::::::::999988887766655578:;=?BELOSX]chlorvxy{||SRPNLKIGEDEDCCDCAA@@??@AABCCDEEFFGHIKLNQSTUVWWWVUTSSRQQQQQQRRSSTTTUVVWXZ\]^^_`abbcdefgghiiijjkklkmmmmmmmmmmmlllkkkkkkkkklmnoqrstuuuvwxxyzzzz{{{{{{{{{{{{||}}~}{zyyxxxxxxxxwvvvvvvvwwwwwwwwxxxxxxyyyyyyyyyyyyyyyyyyxxyyyzzz{{|}wgUF:2/-,+,,-/001233669=ESdrÿyl\K;40-.--02568:=<;;;:988766556789:;;<<==>>?>>>===;9865432222222111111111123445678899999999887766554333222100000000///..//.//////0100//..---------/001122334443333311100001111111/--,+)'&%$#"!  !"#$&()+,.//..-,+)(''((()))****))))***+,./124655666666543210.,+)(''(*-0369=AEGILNOPQTWY[^acdeeddcbbb`___^^^^_```aaabcddddddddddccccccda^XROPVabddeeefeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeefb__bfmpo`LBBCA@@??>>=>===<<<<=<<<;;;::::::::::999888877766555789:;>@CHKOTY_dimptvxz{{RQOMKJHFDDDDCCCB@@@@??@AABCCDEEEEFGIJJLPRSTUVVVUUTTSSRRQQQQQQQQRSSSTTTTWZ[[\]^^`aabcdeefgghhijkkkkkkkkllllllllkkkjjjkkklmnoprtvwwwwxxyyz{{{{||||||||||||}~~|zzzyxxxxxxxxvvvvvvvvvwwwwwwwwwwwwxyyyyyyyyyyyyyyyyyyxxyyyzzz{{|}ÿvgUD92/-,+,,-/012233569>FUft¿zlZI931.--.02568:=@ACEHLQ\jvwj^ZXWUSQNJGDA??>=>==<;:976666666799::;<==>======;8764322332221111111111123345677888999999998877665544333211110000//...//...//////---,,,,-------./0001122332221122111111001111111//.-+*(('%%$""!!  !"$&'()+,-,,+*)('&&'(())**++++***))*+-/1346877766655421//.+*(''''(),036:>CFHIKLMMNPTVY\`cdeeeeedccba``_____```aaabcdddddddddddddddccb`\YXW]acdedeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedca__`dmppbNCACA@@??>>=>>===========<<<;;;;;;;;:::99998777665556789:<>AEHLQV\afknqtvx{{QONLJHGFDCDCCBCB@@@????AABCCDEEEEFGIJJLOPQRSTUUUUTTSRRRQPPPPPPPQRRRSSSTUXYYZ[\]^_``abcddeffghijjjjjjjjkkkkkkkkkjjijjjkklmopqsuwxxxxyyyzz{{{|||||||||||||}~~{zyyxwwwwwwwwvvvvvvvvwwwwwwwxxxxxxxyyyyyyyyyyyyyyyyyyxxyyyzz{{{|}wgUD82/-,+,,-0012334569>HWhvzm[H92.--../2567:=@ACFILR\kxwk_ZYXVTRNJHEB@@@>>>=<;:976666655677889:;;<<<<<<<:8764322332221111111111112234567788889999:::998877665544322221110/..--...-.......--,,,++,,,,,,,-/0000112221111111000000001111111110/-,**)(''%$#"!  #$%'()++++*)(''&&'(())**+++++****+,.0245688776664321/.,+)'&&&&'(*-148<@DGIIJKKKLMQSVZ^aceefffeedccba`___````aaabcdddddddddddddddccba^\]]`bcdddeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedca__`cmqqdPCACA@@?>>>>>>>>>>>>>>>>>>>=<<<<<<<;;;;;:::97776554466778:<>BFJNTY^chknqtvyyONLJHGFECCCBBBBA@@???>?@ABCCDEEEEFGIJJKMNOPQSSTTTTSSRRQPPPPPPPPQQQQRRRSTVWWXYZZ\]^_`abbcddefghhhiiiiiiiiiiiiiiiiihijjjklnpqstvxyyyyzzzz{{{||}}}}}}}}}}}}~}zyyxwwwwwwwwwvwwwwwwwwxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyxxyyyzz{{{|}wgTD82/-,+,,-001234457:?IWiwzm[H90.+-.-/2567;>@BCFIMT^lywj_ZYXVTRNJHEB@@@?>>=<;;:877665556677899::;;;;;;;97765433332221111111111112234566778889999::::99987766554433222110/..---.---..----,,+++++,,,,,,,-//000011000//////......0122222222220/-,+*))('%$$"! !#$%&()****)('&&&'(())**,,+++****+,.035678877665421/.,+)'&%%%&'),047;?BEGHHIIIIJKOQTX\_addffgffedcba`__`````aaabcdddddddddddddddccbb``aaccdddeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedca`_`clqqfSDBDAA@??>>>>>>>>>>>>>>>>>>=======<<<<<;;;:987765544666789;=@CGLQVZ_dgkoqtvvMLJIGEDDCBBBAABA@???>>?@ABCCDEEEEFGIJJKLMNOPQRSSTSSRQQQPOOOOOOOPPPPQQQQRTTUVWWXZ[\]^__`abbcdeeffggggggghhhhhhhhhhghijjkloqrtuwyzzzz{{{{{||||}}}}}}}}}}}}~|zyxwvwwwwwwwwvwwwwwwwwxxxxxxxxxxxxxyyyyyyyyyyyxxxxxxxxxyyyzz{{{|}vfTC82/-,+,,-/01234558;?IWiwÿykZG81/,---02679;>ABDFINU_nyvj_ZYXVTRNJHEB@@@???>=<;;9887655677777889:;;;;;;;976554333322211111111111122334556778889999::::9998877665544433321/..-----,-------,+++***+++++++-/.///0///...---..------/122222232221/.-,+++*)'&&$"! !"#%&'())(('&&&&'(())**,,+++****+-/13567887665421/.,*)'&%$$%'(*.37:=ADFHHHHHGGGILNRVY\_bcefggggecba````````aaabcdddddddddddddddccbbaaccdddeeeeeeeeeeeeeeeeeeeeeeeeedddddeeeeeeeddedb`__bkqrhUECDBAA@?>>>>>>????????????>=======>===<<<;:87766555666789:>>?@ABCCDEEEEFGIJJKLLMNOPQRRSSRQQPPOOOOOOOOOOOOPPPPQRSTTUVVXYZ[[\]^_``aabcddeeeeeeffffffffffffghijkmprstvxz{{{{{||||||{|||||}}}}}}}}~~{yxwvuwwwwwwwwvwwwwwwwwxxxxxxxxxxxxyyyyyyyyyyyyxxxxxxxxxyyyzz{{{|}vfTC82/-,+,,,/01345659>>>>?????@@@@@@@@?>>>>>>????>>==<:98876655777789:;>@CGKPSY^aeilnqrKJHFDCBCBABAA@A@??>>>>>@ABCCDEEEEFGIJJKKLMNOPQQRRRQQPPPONNNNNNNNNNNOOOOPQRSSTUUVWXYYZ[\]^^__`abbccccccdeeeeeeeeeeefgijkmprsuwxz{{{{|||||||{{||||||||||||~~|zxxwutvvvvvvvvvwwwwwwwxxxxxxxyyyyyyyyyyyyyyyyyxxxxxxxxxxyyyzz{{{|}vfTC72/-,+,,-/02345668;?IWiwylZG8/-*-..03689==<;:::99889988777778888888766554443322211111111111112223335667788899::::::::::988777776664322110//.--..----,,++++********+,+,,,-,,,++++**++******+.//////02211000///.-,+*)'%$#"!  "#$%&&&&&&%%%%'(())**+++***))*+,.035666654321/-,+)('&%$$%'*,049=@CEGHIJJHFEDCDHJMQUXZ]_bcefggfdcba``a````aaabcdddddddddddddddddbbabcbceeddeeeeeeeeeeeeeeeeeeeeeeedddddeeeeeeeddddca__ajprjXHDFCCBA@?>>>>??@@@@@@@@@@@@@@@@@@@AA@@?>>=;:98876667777789:<>AEHLPUZ^bfilnoIHFDCBAAAAAA@@@??>>>>>>@BBCDDEFFFFHIJKKLLMNOPPQQRRQPPOONNNNNNNNNMNNNOOOORRSTUUVWVVWXYZZ[\\]^_``aabbbbbcddddddddeedegiklnprsuvxz{{{{||||{zz{zzzzz|||||||||zxwvvtsvvvvvvvvvxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxyyyzz{{{|}veSC730.-,--./02356668;AJYjxxjYE6.,)*,./148;===<;;;;;::::99877667777777766554443322211111111111111222224567778889::::;;;;;;:99888887776432110/..-......---,,,+***********+++,+++))))(())))))))*,------/110000///0/.-,+*(&%$#"!  !"#$%%&%%%%%%%'(())**+**)))(()+,.02455543210/-**)('&%$%%'),/37=ADGHJKKKJHFECBBEGJNRUWZ\_aceffedcba`aaa```aaabcddddddddddddddddcbbcddccddddeeeeeeeeeeeeeeeeeeeeeeedddddeeeeeeedcddca__aiorm[HFGFFECA@>>>>??@AAAAAAAAAAABBBBBBBBBBA@??><;::99888877778999;>ADHLQVZ_cfilmFEDCBA@@@@@@@@@?>>>>>>>@BBCDEEFFFGHIJKLLMMNNOPPPRQQPPOONMMMMMMMMLMMMMMMNPQQQRSSTTUVWWXXYZ[[\]^^_``````abbaaaaabcccdegiklnrtvwyz|{{z{{{{zyz{zzzyyzzzzzz{{~ywvuttssuuvvvvvvwxxxxxxxyyyyyyyyyyyyyyyyyyxxxxxxwwwwwwwxxxyyyzz{{||}udSB730.-,--./01345568;AJYjyĿxjYE6.,)+,.0158;>@CEFILOV`q}{od_ZYXVTRNJHEB@@@@@??>>==<<<<<<<<;;:9887766666666655544443332222221111111111122224457777889:::;;;<<<;;::9::::999874432200///00///.---,,,*)))))))))******))(('''''&''''((()***+++,//////////.-,+**('&%$#"!  !""#%%%%%$$%&''(())))))(((((*+-.02434321/.-+))((''&$%&(+.25:@DGJKLMMLJHFDCAABDFJMPSVY]`bdeeedcbaaaaa```aaabcddddddddddddddddccbcddccddddeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddca`_`horn]JFGGGFCB@>>>>??@ABBCCCCCCCCCCCCCCCCCCBA@?>=<:::98888877789:9:<>BEINRW[_bfjlEDCBA@@@@@@@@@@?>>>>>>?@ABCDEEEFFGHIJKLMMMNNOOOPOONNMMMMMLLLKKKKLLLLLLLMOOPPPQQRSSTUUVVWXYYYZ[[\\^^^^^_``_____`aaabcefhkmqtvwxz{zzyzzyyyxxyyyyxxxyxxxyz{~~{vutsssstuuvvvwwwwyyyyyyyyyyyyyyyyyyyyyyyyxxxxwwwwwwwwwwxxxyyyzz{{||}~ÿtcQA730.-,--//01345669ACEGJLPVaq}ymc^ZYWUTRNJGDB@@??=======<=>?@@@??=<;:977666665555444444433333333211111001111111134566777889:::;;<<;<<<<<<<<<<;;;:76554322222221000//.--,+***))))***))))((''&&&&%$%%&&&&''((()))*------------,,++)('&%$#"!  !""""""#$%&&&'''(((((''''()*+-.01110/.--,**))((('&&(+.269>DGKMNOONLJHFCBAAABCFHKNQTY\_acdcbba````````aaabcdddddddddddddddcccccccccddddeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddcba`^`gmqn^KGGIHGDCA>>????@ABBDDDDDDDDDDDDDEEEEDCBA@>==;;::998888778899::>>>>>?@ABBCDEEEEFGHJJKLMMNNOOPPPOONMMMMLLLLKKKKKKKKKKKLOOOOPPPQQRSSTTUUVWXXXYYZY]]]]]^__^^^^^_````adegimpsuwxyzzyyyxxwwvwwwwwwvwwvvvwxy|}~|zuutsssttuvvvvwwwwyyyyyyyyyyyyyyyyyyyyyyyxxxwwwwwvvwwwxxxxxyyzz{{{{||}}~~ÿtbPA730.-,--//0235667:=CL[lzÿwiUA5.,++-/136:=?ADFHJLPWbq}wlb\ZYWUSQNIFCA@?>=========>?@@@@@??>=;98766555444444444444333333332111100011111112345556667899:::;;;<===>>>>>==<<;987654444444322210/..-,++***))*****)))((''&&&%$$$%%%%%&&'''(((*********++++++++*)('&%$#!  !"!!!""#%&&&''''(('''&&&'))*,-.///.--,,+*))(('''')+.169=BGJMOPPONLJGECAAA@@ACEHJLOTX[^aba```___`````aaabcdddddddddddddddcccccccccddddeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddcba`^`elpoaNHHKJIEDB??@???@@ACEEEEEEEEDDDEFGGGFFEDB@?>>=<;::9999888889999;>BDHKOTX[`ehDCCBA@@@@@@@@@@?>>>>>>>?@ABCDDEEEFGHIJKLLMNNOPPPPOONNMMLLLLKKKKKKKKKKKKLNNNOOOOPPQRRSTTTUVWWWXXXXZZZZZ\]]\\\\\]^^^^_bcegkortuwxyxxxwvvvutuuuuuututtttuvwz{|~}yxtttsssttvvvvwwwwxyyyyyyyyyyyyyyyyyyyyyxxxwwwwvvvvvwwwwxxxxyyzz{{zz{{||}}ÿtbPA630.-,--/01235667:=CL[l{ÿviUA6/,+,-0137:=?ADFHJMQXcq|~vkb\ZYWUSQNIFCA@?>========>?@ABBAA@?>=<:8766555444444444444444444432221110111111113334455556788999::;<<==>>>>>>===<:9887655555554332210//-,+++****++++**))(''&&&%$$$$$$$$%%&&&''')*********++++++++*)('&&$"!  !!!!!!"#%&&&&''''''''&&&'()*+,-.---,+++*))))(((((*,/38<@EJLOQQQPNLJGECAA@???@BDFHKPTW[]_______``````aaabcdddddddddddddddcccccccccddddeeeeeeeeeeddeeeeeeeeeeedddddddddddddddcba`^`dkopeRJIMMLHED@@@@?@@ABCEEEEEEEFEEEEFGHGGFFDCA@??>=<;::999988889999;>ACFIMQUX\adDCCBAA@@@@@@@@@?>>>>>>>?@@ABCDDDDEFGHIJKLMNNOPPPPPOONMMLLLKKKJJJKKKKKKKLMMMNNNNOOPQQRRSSTUUVVVVWWXXXXXY[[ZZZZZZ[[[\]_abeimoqsuvwuuuutttsrrsrrrqqrqqqqrstxyz|~~zwvttttttttvvvwwwxxxyyyyyyyyyyyyyyyyyyyyxxwwwwvvvvuuvvwwwwwxxyyzz{{zzz{{||}ÿtbPA620-,,--//1245678:=CL[m{ÿwfT@60.,+-01369=?BDFHKMQYdr{|tjb\ZYWUSQNIFCA@?>=======>?@ABCCBBA@?><;98765554444444444444444444432222112222222233344455567888899::;;<<=>>>>>>==<;:99887777777654433211/--,,,+++,-,,,++*)(''&&%$$$$$$$$$$%%%&&&'))))))))********+**)('&%"!  ! !!"$%%&&&''''''&&&%&'()*+,,+,++***)))))))))*,/26;?DHMOQSSRQOLJGECAA@>>>>?ABEGLOSVY[\]]^^_``````aaabcdddddddddddddddcccccccccddddeeeeeeeeeeddeddeeeeeeeedddddddddddddddcba`^_djnpfTKKOOMIGEA@@@@@@ABCFFFFFFFFEEEFGHHHHGFEDB@??>==<;::99998889999;>@ADGJORTY]`DDCCBBA@@@@@@@@?>>>>>>>??@ABBCCCDEFGHIJKKLMOPPQQPPOONNNMLKKKJJJJKKKKKKKKLLMMMMNONOPPQQRRSTTTTUUUVVVVVVXYYXXXXXXYYYYZ]^`bfjlnpqstrrrrrqqqpppooonnonnnnopquvxz|~zxutssttttttvvwwwxxxxyyyyyyyyyyyyyyyyyyyyxwwwvvvuuuuuvvvwwwwxxyyzz{{yyz{{|||ÿtbPA620-,,-.//1245779;>DM\m{ÿwfS@6/-,+-01369=@BEGIKMQYerzzsjb\ZYWUSQNIFCA@?>=======>?ABCCCCCBA@?=<:976555444444444444444444443333222222222222333444556677788899::;;<=>>>>>>><;;::9999999987766554331//...---./...--,+)((''&%%$$$$$$$$%%%&&&'((((((((()))))))**)(''&%#"  !"#%%%&&&''''&&&%%&''()******))))))**++,,-./25:>CGKPRTTTSROLJGECAA@?>=<<>?ADHKNRUWXZ[\]^_`````aabbcdddddddddddddddcccccccccddddeeeeeeeeeeddeddeeeeeeeedddddddddddddddcba`^_cinpgULLPPNJHFBAA@@@AABDFFFFFFFGFFFFGHIIIHGFECA@??>=<;;:::999889999;=?@BEHLOQUZ\DDDCCCBA@@@@@@@?>>>>>>>>??@ABCCCCDEFGHIKKLMOPQQQQPPOONNLKKKJJJJJJJJJJJJJKLLLMMMNNNOOPPQQRRRSSSTTTUUUUUVWWVVVVVVWWWWXZ\^`cfikmnopoooooonnmmmllllkllkkklmnrtvx{~~{xvtsrsttuuttwwwwxxxxxyyyyyyyyyyyyyyyyyyyyxwvvvuuuuuttuvvvwwwxxyyzz{zyyyz{{||ÿtbPA620-,,-.//1245779;>DM\m{ÿwfS@5/-,+-0136:>@BEGIKNRZerzyrib\ZYWUSQNIFCA@?>=======>?ABCCCCCCBA@><;:865554444444444444444445544443332222222222333444556666777889:::;;<<<<<==<;;;::9999999999887665531110000000000//-,+*)(('&&%%%%$$$$%%%&&&&&&&&&&&&''''''''(((''&&$#"   !#%%%%&&&&&&&&%%%%&&'(()))))((((()*+,-./01259=BEIMRTUUUSROLJGECAA@?><;:;<>@CFJMQSUWXY[]^_````aabbcdddddddddddddddcccccccccdddddeeeeeeeeeeeeddeeeeeeeedddddddddddddddcba`^^chmqiWNMRQPLJGCBAA@@ABCDFFFFFFFGGGGGHIJJJIHGEDB@@??>=<;;:::99989899;=>>@BFILNRVYDDDCCCCCBA@@@@@@@?>>>><=>??@AABBCCDEFGHJKKLNOPQQQQPOONNLKKJJJJIIJJJJJJJJJJJKKKKLNNOOPPQQQRRRSSUUUUUUUUUUTTTTTUUTUUVWXZ\^bdgijlmmkkkkjjjjjjjjiiiiiiiiijklprtvy|~{xuttssstuvvvuvwwwxxxxxyyyyyyyyyyyyyyxwwvvvuuutttttssttuuvvvwwwxyyzz{zyyyyzz{{þtcPB831.,--./0124578:=======>?@ABCCCCCDCA@><<9655544443333333345555555555444432222222223333444445556668899:;;:;;;<<<<;;;;;;::::::::;:;::98766433322222221110/.,++*)('''''&&%%%$$$%%%$$&&&&&&&''''''''(''&&%%$"!  !"$%%%%&&&&&%%%%$$%%&&'''(((((((()*+-.012468;>CGJNQSTUTRPOJHECA@@?><:988:<=?BFJMOSUVXZ\^_````aaabcdddddddddddddcccccccccddcdddddddeeeeeeeeeeeeeeeddddddddcccccccccdddca_^^bhpriVMPSRQNKHECAAAABCDEFFFFFFGGHGGGHIJKKKJIGEDBB@?=<=<<;:::999899:;<=>?ABEFIMPTCCCCCCCCCBBBBAAA@?==>><=????@@@AAABCDDEFIKKMNPQRRRRQQQQOMLLKKJJJJJJJJJIIIJJJJKKKLMMMMNOPPPQQQQSSSSSSSSSTSSSSSSSSSSTUUVWZ^_bdeghhggggggggffffffeeffffghijpqsvy|~|{xusssssstuvvvuvvwwwxxxxxxxxxxxxxxwwwwvuuutttttttssssttsttuuuuuvwxxxxyyxxxxyy{|þsbOA730.---/00235679:??@BCCCDDDCB@>=<97666555533333333555555555555555444444444422333444444555577888899:::;;;;;;;;;;::::::::::;;:9987654433322244333211/.-,+)(((('''&&&%%%%%%$$%%%%%%%&&&&&&&&'&&&%%%$"!  !!$$%%%&&&''&&&%%%%%%&&'''((((((()+,.014579;>@DHKNQSTTSRPNJHFDBA@>=;9876799:=?CGIMPRUWZ\^````aaabcdddddddddddccccccccccccccdddddddeeeeeeeeeeeeeeeddddddddcccccccccdddca_^^aforjYOQUTSOLJGDBAAABCDFFFFFFFFGGGGGHJKKKKJIHFDCA@?=<=<<;:::9998899:;<==?@CDFJNQBBBBBBBBBCCBBBAA@?==>>=>???????@@AABCCDDGIJKMOPRSSSRRRRQONMLKKJJJJJJIIIHIJJJJJJJLLLLLLNOOOOOOOQQRRRRRRRRRRRRRRRQQQRRRSSVZ[\^`bccccccdddcbbbbbbba````acdejlpsvy}~}yxusrrrsssttuuuuuvwwwwwwwwwwwwwwwvvuuuutttssssssssssssssstttttttuwwwwwwwvvwwwx{}½rbOA720.---/01345689;ACFHJLOS\grurjc_\[YXVSQMJGCA@?==<;::;<=>>?ABCDDDEDBA?=<;9876544433333322445555555555666666666666776665555666666666666778899999::999999:::::::::::9988776555554445666543310/.,+*))(('''&&%%%%%%%%%%%%%%%%%%%%%%%&%%$$$$#"!! !!########%$####$$##$%%&'''''(())*,.13479====<;;;::999999:;<<>?@BDGJNBBBBBBBBBCCCBBBA@?==>>=>?????????@@ABBCCFHIJLNOPRRSSRRRQONNMLKKJJJJJIIIIIJJJJJJJKKKKKKMNNNNNNNPPPPPPPPPPPPPPPPPPPPQQQRRTVWYZ\^_____````````````_____`accilosvy}~|zvusrqqrsstttttttuvwwwwwwvvvvvvvvvuuutttttsssrrrrrrrrrrrrrssssssstvvvvvvvuuvvwx{}½qaM?520.---/0134568:;<>DO]lz¾seS>3/-,..0369;>ACFHJLOT]grsmf`_\[YXVSQMJGCA@?=<<;::;<=>>?@BCCCCDCBA?==;:987554432222211234445556666777888887778877766665666666666666677888888888888889999999999988877766666666667776654221/.-,++*)(''''&&&&&%%%&&%%%%%$$$$$$$$$$$####"!!  !""""""""$#####$$##$%%&''''''()*,.0467;=@@BCDFHJMPQRSRQPMIHFDBA?<;:87654544579<>BGJNSX[^_```aabbcdddddddddddcccccccccccccccccccddddddddeeeeeeeeedddddddccccccccccdddca_]]_elqm_RPVVTROLIGDBAAABCEEEEEEEDEEEEFHJKKKJIIHGDCCBA?===>=<<;;;:::9999::;=>>?ADFIBBBBBBBBBBCBBBAA@?==>>=>???????????@@AAADFGHJLMNQRRRSSSROONMLLKKKKJJJIIIIJJJJJJJKKKKKKLMMMMMMMNNOOOOOOOONNNNNNNNOOOOPPPQSTUVXYZ[ZZ[[[\\\]]]]]]]]]]]]^`abhkorvy}}zxwssrqqrrssstttttttuvvvvvvuuuuuuuuttsssrrrrrrqqqqqqqqqqqqqqrrrrrrrsttttttttstuvx{~½q`M?520.---/0134568:<<>DO^lz¾sdQ=3/--./147:?@ABBBBCBA@?>=<:98765543222111023344556666777888888888998888777777777776666677777777777777777899999999888887776666666668888877644310/.--+*)(((('''''&&%&&&%%%%$#########"""!!!  !!!!!!!!#"""""#$##$%%&''(''()*+-02679<;:875444333579;@EHLQV[]__``aabbcdddddddddddcccccccccccccccccccddddddddeeeeeeeeddddddddcbcccccccccdcba_^]^dlqnaTPWWUSPMJHDBAAABCDDDDDDDDDDDEFHJKKKJJIIGEDCBA?===>=<<<;;;:::99899:<===?BDFBBBBBBBBABBBBAAA@?==>>=>?????????>>???@@BDEFHIJKPPQQRRSRPPONMLLKKKKJJJIIIJJJJJJJKKKKKKKKKKKKKKMMNNNNNNNNMMMMMMMMMMNNNNOPPQRTUVWXXXYYYZZZ[[[[[[[[[[[[\^_`gjnquy}}zwutrrqqqrrsssssssssstuuuuuusssssssssrrqqqppqppppoooooooooppopppppppqrrrrrrrrrstvx|p`M?620.---/0235678:<=?DO^lz¾rcO<2/.-./147:=@BDGIKMOT]gooic_^\[YXVSQMJGCA@?=<;:99:;<==>?@AAAABBA@?>=<;:9876653222111123344556666777888999::::;:::99988888888877777777777777666666667888888888888777666666666789::998765432100/-,+**))((((('&&&&&&%%%%$#######!!!!  !!!!!""###$%%&''((()*,-/258:;>ACCDDDDFHJLMNONMLJGFDB@?=;:98654432112467==<<<<<;::988889;;<<=@BDAAAAAAAA@AAAA@@@@?==>>=>????????>>>>>>??ACCDFGHIMNOOPQRRQPONNMLLLKKKJJJJJJJJJJJJKKKKKKKKKKKKKKLLMMMMMMMMMMMMMMMMLLMMMNNOOPQRTUUVXXXXYYYYZZZZZZZZZZZZ[]^_fhmquy}~zwtsrqqqqqrrrrrrrrrrrrsssssssrrrrrrrrqpppooooooonnnnnnnnnnnnnnooooooooppppppppprtvy}|m^L>520.---/0245679;<=?EP^m{¾rcO<3/../0248;=@BDGIKMPU^fmlgb^^\[YXVSQMJGCA@?=<:9889:;<==>??@@@AA@??>>=<;:987653322211123344556666777889:::;;<<<<<;;;::::::::::8888888777777766666666788888888888777666666666679::;;::9765432210/.-,+++*****(''''&&&%%%$$####""   !"###$%%&''(()*,-.257:<=@BDCDCCCDEGIJKLKJIHFDB@>=<::986543210001348>BGMSY\^_``aabbcdddddddddddcccccccccccccccccccddddddddeedddddddddcccbbbbccccccbbbbba`__^]clrqfZQVWVURNJGECBAAAAAAAAAAABCCCDEGHJKJJJJJHGDCBA?===>=======<;:98877899::;=?AAAAAAAAA@@@@@???@?==>>=>?????????>>>>>>>@ABCDEFFJKLMNOPQQQPONMMLLLKKKJJJJJJJJJJJJJJJJJKKKKKKKKKKLLLLLLLMMMMMMMMLLLLMMMNNOOPQSSTVVVVWWWWXZZZZZZZYYYYYZ\]^dgkpty}}xurqqoooppppprrrrrrrrrrrrrrrrpppppppppoonnnnnnnnmmmmmmmmmmmmmmnnnnnnnnooooooooortwz|m^M?531/.--/1134568:<=?EP_m{¾rbO<30../0258;=@BEGIKNPU^fkida^^\[YXVSQMJGCA@?=;:9889:;<<==>>>??@@@???>==<;:9876433222112334455667778889::;;<===>====<<<<;;;;;;;9999999888887776666666777777777777776666666666689:;;;;:977654432110//-++*****(''(''&&&%%$$$##""!  !###$%%&'(()*+-/0479<=?ACECCBA@@BDGHIJIHGFDCA?=<;::976433200/./015;?DKRX[^_``aabbcdddddddddddddddddddcccccccccccdddddddddddddddddddcccbbbbbbbbbbbbbbba`_^]\blqqgZRVVVTRNJHFDBAAA@@@@@@@@ABBCCEFHIJJJJJJIGECBA?===>=====>>=<:98877788889;=?@@@@@@@@?????>>>>===>>>?????????>>>=====@@ABCDDEHIJLMNOQQQPONNMLLLKKKKJJJJJJJJJJJJJJJJJKJJJJJJJJLLLLLLLLLLLLLLLLLLLMMMNNNOPQRRSUWWWWXXXYXXXXXXXXYYYYZ[\]ehmqv{{vspoonnnoooooopppppppppppppppooooooooonnmmmmmmmmlllllkkkkkkkklmmmmmmmmnnnnnnnnprux|¾zl]K=431/.--/1134568:<>?EP`n|ÿraM<2/.//0258;>@CEHJLNPU_fhea_^^^\ZXVTQNJHECA?>;987789:<<<==>>>??>>>>>>===<:988754443333233445567778889::;;<==>>?>>>>====<;;;;;;:::::::999988776777777777777777777766666666666689:;<<<;:8776554322110.,,,,,,,*))('''&&&%%$$##""!  "##$%%&'())+,.0258:=>@BDEBA@?>>?BEFGHGFEEDCA?=<;:9876433210/../038===<<>>>>@?=<:98777777789;>>>==<<;;;;<=>>????>>>>========?@@AABBBEFGIJKLNPPPOOOOMMLKKKKKKKKKKKKKKKKKKKKLLKKKKKKKKLLLLLLMLLLLLLLMMMMMNNOOOOOPQRRSTVVWWXXYYXXXXXXXXYXXYZ[\]fimrw|yspnnmnnnnnnnnnooooooooooooooonnnnnnnnmlllkkkkkkkkkjjkjjjjjjiijjjjjkkklmmllllmmorvy}zk\K=4310/./01135678:ACEHJLNQV_egc`^^^^\ZYVSPMIHFDA?>;988889:<<==>>>>>>>>>>>>=>=<;:987644443333334455666778889:;<==>>??????>>>=<<<<<<<<<<<<<;::::9998777777776666666777766665566666667:;;<<<<;88876554332220.------+**)''''&&&%%$##"!!  "##$%%&'(**,.02479;=>?ABBAA@><<=@CDEFEDCCBA?=<;:98775433210/.-..16:@HPV[^_``aabbcdddddddddddddddddddcccccccccccdddddddddddddddddddcccbbbabbbbbbbbbbba`_^\^agoqj]TUVVVTPMKHECBBA@?>>>>>>?AABBDEGIJLLLLLKJGBBA@>===<=>>??@@>=<:8777766678:;===========<<;;:88889:;=>>?>>===========????????CDEFGHIKMMMMNNNMLKKKKKKKKKKKKKKKKKKKKKLLKKKKKKJJKKKLLLMMMMMMMMNNNNNOPPQQPQQRRSSTUUVWWXYYYYYYYYYYYYYYZ[]]dglqv{~xrommlmmmmmmmmmnnnnnnnnnnnnnnnmmmmmmmmlkjjjjjiiiiiiiijiiiiiihgihhhhiiikllkkkklmnrw{Ŀyk[J<4210//011135679;ADFIKMORW_dea_^^^^\ZXVSPMIHFDA?><988889:<==>>?????>>>====>>=<;;;:755554333333445555667789:<<==>>?>>>>>>>>>=============<;;;:::98777777766666666666665555555555557:;;;<<<;99876554333221//....-,+**((('''&%%$$#""!""""""! "$%&)+,.02458;<==>?@?;;:::;;=????@@@???=<;:987765432110.-,,,/39?GOV[_``aabbcddddddddddddddddddddcccccccccccdddddddddddddddddddcccbbbabbbbbbbbbbba`_^\\_fork^SUVVVTQNLJGEDBBA@>>===>>@?ABCEGIKMMMNMLKIEDBA?>>>>>???@A@?><;98777666689:;;;;;;;;:::998875555679;<==>====<<<<<<<<>>>>>>>>ABCDEFGHJJJKKKKLKKKKKKKKKJJJJJJJJJJJJJKKJJJJJJIIJJJKKKLLLMMMMNNNNNOPPQQQQRRSTTTTTUUVWWXXYYYYYYYXXXXXYZ[\dglqv{}wqnllkmmmmmmmmlmmmmmmmmmmmmmmmmmmmmmmmljjjjjjihhhhhhhihhhhhhgfhghhhhiijkkjjkklnptx|ľxj[J;4210//012135679;=?@FRbo{ÿq`M<30/001369<>AEHJLNPSX_bb`___^^\ZXVSPMIHFDA?><998899:<==>>>>>>>>>>====>=<<;::9877665443333334445567778:;<<==>>>>>>>>>>>=======>>>>>><;;;:::98777777766666666666655544444444447::::;;;:887655433322210///...,++*)))((('&%%%$$#"######"!  "$%&),.135669;;<<<==<:98889:<>>>>???>>=<;:998766543200/-,,++-28>GOU[_`aabbccdeeeeeeeddddddddddddcccccccccccdddddddddddddddddddcccbbbaaaaaaaabbbba`_^\[^eosm_STUVUSROLJIGECBA@?>=====>>@BCEGIKMNNNMLKJGFDB@?????@@@@A@?>=<;98777766788777777776666554421123469:;<<=<<<<<<<<<<<========??@ABCDEGGGHHHHJIIJJJJJJJIIIIIIIHHHHHHJJIIIIIIHHIIIJJJKKLMMMNNNOOOPPQQRRRSSTUUUUVVWWXXYYYYYYYYYXWWVWWXYZbejoty~~~~~~~~~~~}wqnllkllllllllllmmmmmmllllllllkkkkkkkkjiiiiiihhhhhhhhhggggggffgggghhhiijjjjjklorvzþxjZI;4210//01224568:<=?@FRbo{ÿp`M<30/001369<:9999:;<=======>>>>====<<<;;:::99877665443333333455667789:;;<=========================<;;;:::98777777766666666655554444444444447888999:87665433332222100///..,+++*)))(((&&&&%%%$$$$$$$$"! !$%&),.035679::;;;;<:7666667:<<<<====<<;:998776554320/.-,+++-17>FNU[_`aabbccdeeeeeeeddddddddddddcccccccccccddddddddddddddddddcccbbbaaaaaaaaaabbaa`_^\\^dnrn`TRUVUSRPMKJHFDCBA?>====<==@BCEGIKMNNNMLLJGFDB@@?@??@@@@AA??>=;:98887767784444444433332110/../0135789:;:;;;;;;;;;;<<<<<<<<==>?@@ABDDDEEEEGEFGGGGGGGGGGGGGGGGGGGGHHGGGGGGGGHHHIIIJKLLMMNNOOPPPQRRSSSTTUVVVVWWWWXXYXXXXXXXXXVVUUVWXX_bglpuz}~}}||{{{{{{{{{|~}wqnllkllllllllkkkkkkkkkkkkkkkkkjjjjjjjjiiiiiihggggggggffffffeeffgggghhhhhiiklmpuy}whYI;3210//01224678:<=@AGSbo{ÿp_L;2/.00147:=?CEHKLNQTX^``____^][YWUROLIHFDA?>=::99::;============<<<<;;:::999887776654433333333445667789::;;<<<========<<<<<<<<<<<<<;::::999877777776666666654444433333333333666777887554322111110000///...,+++*)))(((''''&&&%&&&&&&&$"""!! !#&'),.13678999999998544444579::::;;;::98877765543210/-,+***,06=EMTZ^`aabbccdeeeeeeeddddddddddddcccccccccccddddddddddddddddddcccbbbaaaaaaaaaaaaaa`_^\]]cmqobUPTVVTSQNMKIGEDCA@?>=<<<<=?ACDFHJMMNNMMLKHFDBA@@@@@AAAABA@?>=<:988877677800000000/00//..-+++,-./235678899:;;;;;;;;;;;;;;;;;<==>>?@@AAABBCBCCCCCCCCDEEEEEEEEEEEEFEDDDDDDFFFFFGGGHIJJKLMMNNOOPQQRRQRSSTTUUUVVVVWWWVVVVVVVVVUUTTTUVW\_dimrvy}~~}|{{zzyyxxxxxxxxwwxz|~}wqnllkkkkkkkkkjjjjjjjjjjjjjjjjkjjjjjjjjiiiiiihggggggffeeeeeeeeffffggggggghjlnosx{ufWH;3210//01224678:<>@AGScp|ÿp_K:1..00147:=@CFIKMNQTX^``____^][YWUROLIHFDA?>=;;::;;<=====<<<<<<;;;::9998888888776665543333333334456677899::;;;;;;;;;;;;;;;;;;;;;;;;:9999888777777776666666653333322222222222455556665443321010///.....---,++++*)))((((((('''&))))))(&$$#""!  #&')-/146788887776664322234577788888887766655443210/.,+*)))+/4;DLRX^`aabbccdeeeeeeeddddddddddddcccccccccccdddddddddddddddddddcccbbbaaaaaaaa``aaa`_^\]]blpocVOSVVUSQONMKIGFDB@?=<;;;<<>@BCEGJLMMNNMMKHGECBA@A@@AAAABA@??=<:9888776778,,,,,,,,+,,++**)((())*,/023556668888888899999999899::;;;<<===>>?>????????@AAAAAABBBBBBCBAAAAAACCCCCDDDDFGGHIJKKLMMNNOOPOPQQRRSSSTTTTTTTSTTTTTTTTTSSRRSSTY\aejnsuwxyzzyxwwvvuutttsssssssssuvxz|}|vpmkkjkkkkkkkkjjjjjjjjjjjjjjjjkjjjjjjjjiiiiiigfffffffeddddddddeeefffggggghjmopuz~tfWG;310/./0124568:<=>@AGScp|ÿ¾p_K:1//00137:=@CFILMOQTX^``____^\[YWTQOLIHFDA?>=<;;;;<<====<<<;:::99998777777777776666554333333333344556778899::999999999:::::::::::::9888777666555555544444444332222111000000023444455433210///.----,,,,,+++***+*)))(((()(((''')))))))(&%%$#"! #&(+./24677766554433210001234555666666655554332100/--,*)(((*.3:CKQW]`bbccddeeeeeeeeeeeeddddddddcccccccccccccccccccccdddddddddcccbbbaaaaaaaa``aaa`_^\\\bkpodWPSVVUTRPONLJHGECA?=<;;;;<>@ACEGILLMNNNMLIGEDBBAA@@AAAABA@??><;:988776778*******+**))((''&%&&'(*,/02445554444444577777777666778778999::;<:::::::;<<<<<<;<=<=======???????@AAABBBCBCDEFGGHKLLMMNNNNOOPQQQQQQQQPPPPPPPPPPPQONNMNNOPTW\`dimptuvvuttsrqpnnonppppppppqqqsuwyz}~xrnkjjjjjjjjjjjjjkkkkkkkkllllljjjjjjjjiiiiiigfffffffeeeeeeeeeeeeeeefffggghjmoqz|seVF:4310//01245689;=>?AHTboz¾}n]I:10/.0137:=@CGJMNPRUY[^____^][YWUROMJIGEC@?>=<;;;;<<===<<<;:9888777766666666666666666222222223233455566778899888888888888888899999997766655544333333343333333111000//.......0233334442110/.--,,,++++****)))))))((('''())(((''((((((('&&%$#" !$'),0135666655432110/..../02333444443333332221110/.-,+*(((()-3:BJPV]`aabbcddeeeeeeddeddddddddcccccccccccccccccccccccddddddddddcccbbbaaaaaa```aa`_^]Z[Z`iopgZOSUUUTSQQOMKJIGDA?=<;::;;=?ABDFIKLMNNNMLIGEDCBBA@@@@@@@@@?=<:99877667788&&&&&&&&&%%%$$$$#"##$$&(+,-/01123333334444444445444454434445567:8888898888889:89;9::::;;;;;;;;;;<==>>>???@ABCEEFIIJJJKKKLMMMNNNMMMMMMMMMNNNNOONMKKKJKKKLMPTX\`djopqqpoooonmkklljljjkkkklmopqtvxz}|vqmlkjjjjjjjjkjkkkkkkkkklllllljjjjjjjjiiiiiihgfffffffeeeeeeeeeeeeeffffhhijmoqs|sdUF:4310//0124568:;=?ACJUcp{ÿ}m]I;410/2358;>@CFILMORUX\_````_][YWUSPMJIHFCA?>=<<;;;<<====<<;;:8666555554444455566666665433322232233445556677887777777777777777888888776665554433333333433333330000///......../222333210/.--,,,,,,,++++)))((((''()(((''((((((''((((((((('&%$##!$')-02345555543210/.--,,--/12223333423332220110/.--,+*)('''(+08@IPV]aabbccdeeeeeeeededdddddddddcccccccccccccccccccccddddddddddcccbbbaaaaaaaaaaa`_^]ZZZainoh[RRTVVUTTQPNLJIGDB@=<;::;;=?@BDFHJKLMMMLKJHFDCBBBAAAAAAA@@?><;:9877667788$$$$$$########""!!!!!!#%()*+-./01111112222222222222222212222223411112433333334455566667776666666688999::<<=?@ABCEFFFFGGHIIIIIIIIHHHHIIIIJJJJIHHGGGGGGGHIIKNRVY\`egijkkhffeeddeededdeeeefhjkloqsvx|}wqnlkkllkkkkklklkkkkkkkklllllljjjjjjjjjjjjjjihggggggfeeeeeeeeeeeefffgghijlmpsu~|qdSE:42000/0123468:;=@BELVcq{ÿ}l\I;410/2358;>ADHKNOPRUY\]^_```_[YVTRPMJHFEDDBA?======<=>=<;:988887654432222222245555555433332223212223334556677666666666666666655555567765544221100000011111000...---,++++++++,//////-++*))((''''''''''''''''''&&&&&&&&''''''''())))((''&%$#""!%),02333332110/.-,++++++++,-.///00000000000...---,,***)('''((.5=GOV\aabbccdeeeeeeeedeeeddddddddcccccccccccccccccccccddddddddddcccbbbaaaaaaaaaaa`_^]ZZZ_gmni]SQSUTSSSRPOMLKHDB@>=<;<==<<=?ABEGHJKLMMKIHFECBAA@@@@@@?===<;::9777655444"""""""!!!!!!!!! !$&'(*+,-/000000111111111100000000111111110///021111111211211111222322222235566678::;=>?@ABBBCCCDEFFFFFFFFEEEEFFFFGGGGFEEDDDDDDDEFFGJMPSV[^`bcddb`baa``aa_a___```abdegjmorux}}wrnmllnnmmmllllllllllllllllllljjjjjjjjjjjjjjiihhhhhhfeeeeeeffffffggghhijlmnquw~{pbSE:4310000135679:<=@BELVcq{~{xutpnmovÿ}m\H92//02469>>====>>=<;:9876543321100000000122222222222111121111122344556675555555555555555555555555443332211000000110000//-,,,+++********+------+**))(('&&''''''''&&&&&&&&%%&&&&&&'&&&&&&&())))(('&&%$#"""&*-02333321/.-,+*)()))))))*+,,,,-----------,,,++++*)((''&&&&',3;ENU[aabbccdeeeeeeeeeeeeddddddddcccccccccccccccccccccccccccccccbbbaaaaaaaaa``aaa`_^]ZZY_gmnj^TQQSRQQRRQONMLHDB@>=<<<=<<<<>@ACEGHJKKKJIHFECBA@??????>===<;:::877665544######""""""""""""""""#$%&'()*+-....../000000000////////0000000/.---./...............////0000000234445557899:;<>??@@@@BBBBBBBBBBBBBBBBBBCCCCCBBBAAAAAABCDEGJMORVXY[\]][Z\[[ZZ[[Y[ZZZ[[[\^_`behjmruz~}wronmlnnnnnmmmmmllllllllllllllkkkkkkkkkkkkkkjjiiiiiigffffffghhhhhiiijjklmnorux~¿ynaRD94421111235689;<=@BELVdq{vpnmnorqpoonkhfeedcdhp|ÿ}m\H9100/1358>>>======<;:986532110//.........0000000000///..////000123344556333333334444444333333333322110//......../////...++***)))********++++++*)(('&&&$#%$$$$$$$#$$$$$$$##$$$$$$%%%%%%%%'())(''%%$$#"!!#'*-0222221/,+*)('&&&&&&&&&'((()))**+*++++++*))))(((''&&%%$$$%*19CMT[abcccddeeeeeeeeeeeeddddddddcccccccccccccccccccccccccccccccbbbaaaaaaaaa``aaa`_^\ZYY_flnl`TPOPPOOQQPNMLKHDB@?=<<<<;:::<>?ACEFGHIIHHGFDCBA@>>>>>>>==<<;;::887765555%%%%%%$$$$$$$$$$$$$$$$$%%&'())*,------.////////0////////000000//....--,,+++++,,,,,,---...///////123334445567789;==>>>??@??????>>>>>>@AAABBBBBAAA@@@@@@ABCDEGIKNQUVWXYYXXXWWVVWVUVVVWWXXY[\]_begjosx{}|xsponnppppooonnnmmmmmmmmmmmmmmlllllllllllllllkjjjjjjihhhhhhijjjjjkkkllmnoopsvy~xnbTF<65333234689;<>??ABEMXer|zld_]_bdgijkkihebccdeffinwÿ|m]I:31112359>==<<<<;987753210/..--,,,,,,,.0000000..---,,,----.../012233442222222222222222222222221100//..--------...---,,**)))((()))))))())))))(''&%%$$#"#""""""""#######""######$$$$$$$$&''''&&$##"!!  #'*,/01100/-*(''&%$$$$$$$$$%&&&&'''((((((((((('''&&&%%%$$##""$)08ALSZacddddeeeeeeeeeeeeeddddddddcccccccccccccccccccccccccccccccbbbaaaaaaaaa``aaa`_^\ZYY^ekomaTNMNNNNOONMLKJGDB@>=<<;::9889;>========<<<<>???@@@@AAAA??????@ACDEFHJLNPQRSTTTTUTTSSTSSTSTUVVWXZ\]^`cehmpuxz||wtrqpprrrqqqppppnnnnnnnnnnnnnnllllllmnnnnnnnmmllllllkjjjjjjklllllmmmmnnoppqtwz}~xocVJA<<:::99:<>>?ABCDEGJQ[ht}s`WWUWY]adgjijjifeeeeefeegn}¾|m\I;4335679==<<;;:876643110/.---,,,,,,,,-------++++****++++,,,-.//0012211111111111111100000000100//..--,,++++++,,,,+++*))))((('''''''''))))))'&%%$$##""""""""""!""""""""!!!!!!!""""""""%%&&%%$"!!!  $'*,....-,+)'&%$$#"""""""""#$%%%&&&&&''''''''&&%%%$$$$$##""!!#(/7AKSZacddddeeeeeeeeeeeeeddddddddcccccccccccccccccccccccccccccccbbbaaaaaaaaa``aaa`_^\ZXX^cjoocTMJKKLLMNMLKJIGDB@>=<;:98766689======<;;;:::::776655444******)))))))))***********+,,,-......./////////0///////0111110/...----,,,,,,,-..,---...//10000001222333566788999;<<<==>><<<<<<<<<<<<=>>>????@@@AAAAAAABDEFGHIKMNPRSTTTTUTSSRRSRSSSTUVWWY[]]^`bdgknruwx{~{xutsrrsssssrrrrrppppppppnnnnnnmmmmmmnooooooonnmmmmmmmllllllmmmmmmnnnooopqqrtxz|}xqg\RJFFEEDDEEEFGHIJKLNORX`kv~tZFBHJMRV[`cefghfeddfcbbbdgmt~~m^M@;99;<<>@BEGJORUVWY\_`aabbaa_[YVTRPMLKJIHGFDBAA@@?>=<;;:98765310//.---,,,,,,,*++++++++**))))(**)***++,---..///////////////////////////..--,++********+****))))((('''&&&&&&&&&(((((('%%$$##"""!!!!!!!!!!!!!!!!! !!!!!!!!##$$##"  $')*,,++*('%#"!!  !""###$$$#$$$$$$$%$$###""#""!! !&-6?JSZacddddeeeeeeeeeeeeeddddddddcccccccccccccccccccccccccccccccbbbaaaaaaaaa``aaa`_^\ZXX]ckppdTKGGGHHJLLKJIHFCA?=<;:9876444569<=?@ABBBBA@??>>=;;;;;;:99999999665544333........-------........-,--.../01111111100000001111111122222221000000100/////00111233344343333335566677899:::;;;;<<<============>>>>?@@@@@@@ABBCCCCCCCDFHIJKMOPPQRSTTSTUUUUUUUUUUVWXYZZ\^__`bcegkmprtvxz|||}}~~~~|ywvuuuuutttsssssqqqqqqqqoooooonnnnnnnooooooooooooooonmnnnnnooonnnooopppqqrrtwz{|~yrjbZUSQPOOOPPPQQRSTUVWXZ^enw}aB..9?EKPW]abcefedcddcbbbcfhlsÿsfXLFEEFGHHJLOPSVXZ[\^`cdeefeddc`^[YWURQNMLKJIGFEDCBA@@>>><;:9755332100/////////.-,,,,++***)))(())(())))(()**,-----------------,,,,,,,,,,++**)(((((((((())))(((((('''&&&&&&&&&&&''''''&%%$$##"!!   !!  #&()*))(&%#"  !!!!"""""""""#   #*3>JRZacddddeeeeeeeeeeeeeddddddddcccccccccccccccccccccccccccccccbbbaaaaaaaaa``aaa`_^\ZYY]bjoqeSGBBCDDEHGFFEDC@?=<;:987653223468:;=>>>?@?>>=<<;999999989999999654432222222222221111111111111111011222222222222322222223333334444444444343333322111124455567778878777777:;;;<<<===>>>???@@@@@@@?>>>>>>?@???>@BBBCCCCDEEFFGGGHHIJJKMNPRSRRSTUUUUWWWWWWWWYXZ[\]^^_abbcdeghlnoqsuvwwwwxxxxyzzzz{{||~~~}}}||}|ywvvvvuutttssssrqqqqqqqqppppppooooooooooooooooooooooonnnnnnnnonoooppppopqqqsvxyz{~|vpic`^\[[[[[\\]]^^__`acdfkrypU:,+07@FIOV[_acdddccaa```abceks¿{obWRPPRSSSUWYZ[\^```beggghhggfeca^\ZXUTSQPOOMKIIHHGFEDBAA?>=<:88765443321111111/.----,,*)))))))*)(()))*())**,,-,,,,,,,------,,,,,,,,,+,++*))))()((((((())((('''&&&%%%%%&&&&&&&&&&&&&&%%%$$##"!   #&'((''&$#! !"(2=HPXacddddeeeeeeeeeeeeeddddddddcccccccccccccccccccccccccccccccbbbaaaaaaaaa``aaa`_^\[ZZ[ahnriVF=;>@A@BA@@?????=;:9875431111235689:;;<==<;;:987776665566666663322110005555555544444444444444445556666655555555555555566666678888888877777777666777899:::;;;<<<;=======?@@@AAABCCCCDDDDCCCCCCCCBBBBBBCCDDDCEFEFGGHHHHHIIJKKLLMMNOPRTUVXYYZZZ[[\\\\\\\\^]^_``aabcdefgghinopqstuttttttttuuuuuvvvvxxyyyzzzzzyxwwwwvuuutttttsrrrrrrrrppppppppppppppppppppppoppppppppppppqqqqqqqqqqqqqrrstuwxyz{}ztolihffffffffgggggghikkmquzyeM9.+-18@HOSW]_`abbaa__^^^__acejt¿zrjc`_^^aaabbcceghhijkknoonmlkihgeca`^\ZWWVUTSSRQPONMLLJJJHFEDB@@>>==<<;;::998887754331100/.-,+*+++++****+,,----......./////...-------,,,++**)))))))))((((((((('&&&%%%%$$$$$$$$$$$$$$$####""""!!!"#$$"!  '0>>????@@@?AAAAAAACDDDDEEGGGGHHHIHHHHHHHHIHHHHHHHHHHGGHIIIJJJJKKKLMNNOPPQQRSTVXYZ\\\]]]^^________a`bbccddeeffghiijoppqrsssssssssssrrrsssttwwwwxxxxxxxxxxxxwvvuuuutttttttssssqqqqqqqqqqqqqqqqqqqqppppqqqrrrqqqqqqrrssssssssssttuuuvxxyz|}}~~zvsqommmmmmmmnnnnnnnopprtx}r_L;20038>ELPU[]_`aaa`^]]\]]^_abdkw¿~wpjhgghjjjjkkkmnnoopqqrssrqpnmljhfeca_][ZZYXWVVVUTTSRQONNLKIHFFFEEDDCCBA@@??>>=<<::98665543210/////....-..///002222222211110000//////..--,+++******)))))(((((''''&&&%%%%%%%%%%%%%%%%%$$###"""""  !! &/;FNVacdddeeeeeeeeeeeeeeddddddddccccccccccccccccbbbbbbbbbbbbbbbaaa```______________][ZYY^ekpjWD52577688888887776554310/-,+**+-.024566655544320000///01111111-.--,++**999999::999999999999999:::;;;;<<<<<<<<<<<<<<<<<<=============>>>>>>>>?>???@@ABACCBDDDEEEDEEEEEEEGHHHIIIJKKKKLLLLLLLLLLLLKKKKKKLMLLLLLMMMNNNNOOPQQRRSTTUUVWXZ\]^``aaabbbccccccccedeffggghhhiijklmppqqqrrrrrrrrrrrqqqrrrssuuuvvvwwxxxxxxxxwvvvuuuuuuutttssssrrrrrrqqqqqqqqqqqqqqqppqqqrrrrqqqqqrrrssssssssttttuuvvwxxyz{{{}~~~{ywvtttttttstttttttuuuvx{~yk[LA:7458=CHLRXZ\^_``_\[[Z[[\\^``dly}xsrsrsttttuuuutttuuvvwxxwvusrqomkjhfdb``_^]\\[[[ZYYXWVUUSRPONMMLLKKJJIIIHHGGFFEDBBA@>>=<;:987666554433223334445555555544443333222222000//..-,+,++***)))))))((((('''&&&&&&&&&&&&&&&&&%$$####"""!!!!  %.:FNV`cddeeeeffffffeeeeeddddddddccccccccccccccccbbbbbbbbbbbbbbbaaa```______________][ZYY^ekpkXC3/1222333333445544331/.-+*)))*++-/01232222211000///..////////+,++**)))<<<<<<==<<<<<<;:::::::;<<<====>????????>>>>>>>>?@@@@@@@@@@@@@AAAAAAAAA@@AAABDFEFGFGHHHIIHIIIIIIIKKKLLLMMMMNNNOOOOOOOOONMMMMMMMNOOOOOOOOPQQQQRSSTTUUVWWXXYZ\]_`acdddeeeefffffffghhhhhiiijjjkklmnoqqqqqqqrrrrrrrrrqqqrrrssuuuvvvwwxxxxxxxxwvvvvuuuuuuuutttssrrrrrrrrrrrrrrrrrrrrqqqqrrrssssssssttttttttttttuuuuuvvwwxxxyyy}}~~}|zzzzzzzyyyyyyyzz{{{|}ugYOHC@;;=@CHLQUWZ]^^^]ZYYXYYZZ\^^`enz|zz{{z{{{{{{{{zzz{{{{|}}|{zxwvsqonljhfddccba``___^]]\\\\ZYXWUUUTSSRRQQRQQPPOONMLJJIHFFDCBA@?>>><;;;::998889999::::::::::::9999666666444322110/0/.--,,++,,,++***)))((((((((((('''''''&%&%%%$$$#$####"""!!!!!!!!"!!!!!!!  ! %.:FNV`cddeeeffffffffeeeeddddddddccccccccccccccccbbbbbbbbbbbbbbbaaa```______________][ZYY]cjplYD2.01012222223444433310/.-+++++++,./01111111111111000/////////,,,++****>>>>>>??>>>>>>=<<<<<<<==>>>???@@???????>??????@ABBBBBBBBBBBBBCDDCCCCDEDDEFFFGIHIJIJKKKLLKLLLLLLLMMNNNOOPPPQQQRRRRRRRRRQOOOOOOOPQPPPOPQQRSSTTUUVVVWWXYYZ[[\^`acdefffggggiiiiiiiiiiiiijjjkklmmnoopqqqqqqqqrrrrrrrrqqqrrrrsuvvvwwwwxxxxxxxxwwwvvvuuuuvuuuutttssssssrrrrrrrrrrrrrrrrrrrrssttttttttuuuuuuuuuuvvvvvvvvwwxxxyyy||}~{pdZTOLHCBACFJNQTVY[\]][YXWWWXYY[]]^agp{~}{yvtrqomkjhhgfeeddeeddccbbbb``_^\\]\\[[ZZZZYYXWWVVUTRQPPNNMKKJHGFFEDCBBA@@@?>????@BBBBBBBBBBBBAAA@======;;::99886666544322122100//--,,,,+++++++++*******)(((('''''((((''''%%%%%%%%$$$$$$$$$#######""""""""!!!!!!!""""""""#"!  !!""""! !!!!!!!!  &/;GNW`cddeeeffffffffeeeeddddddddccccccccccccccccbbbbbbbbbbbbbbbaaa```______________][ZYX\cjpn[D2.0222222222345544443210/....--../012222222222222211111111111///......??????@@??????>=======>??@@@AAAA@@@@@@@@AAAAAABCDDDDDEEEEEEEEFGGFFFFGHGGIIIJKLKLMLMMNNNNNOOOOOOOPOPPPQQSSSSTTTTTRRRRRRQPRRRRRRSTSSRRSTTUUVVVWWXXXYYZZ[\]^_`bcefgghhhiiijjjjjjjjkkkkkkkkllmnnopppqqqqqqqrrrrrrrrrqqqrrrrsuuvvvwwwxxxxxxxxwwwwvvvvuvvvvuuuutssssssssssssssssssssrrrrssstttuuuuuvvvuuuuuuuuwwvvvvvvwxxxyyzz||}~}wnf_YURPLKIIKNQSUWY[\]\ZXWVVVWXXZ[[\_bhq{}zxvusqonmllkjihiiihhhggghhffeeccccbbaa``aa``_^^^]\ZYXXVVUTSRQPONMMLKKJIHHGFGGGHHKKKKKKKJJJJJIIIHEEEEEECCBBA@@@>>>>=<;::989987655332221100000000/......-----,,,,,++***)))*+++++++*))))))))(''''''&&&&&&&&%%%%%%%&&&&&&&''''&%%$$$$$$$$$$%&&&&&&&&%$$##""########"!!!!!!!!"""""""""""""""$$$$$$$$$#######"!$)1>HOW`cddeeeffffffffeeeeddddddddccccccccccccccccbbbbbbbbbbbbbbbaaa```______________][ZXX[bipo]E3/36655555556777777776654433444445667666777777766665679999999877777766>>>>>??@@@@@@??>????@@@@AABBBCCCCCCCCCCCCCCCCCDEDEEEEEFFGGGGGGGHIIIIIIJKJJJKKKLNMNNNNOPPPPPPPPPOPPPPQQRSUUUVVVUTTSSSSSSRRSSSSSSTUUVVVVWWXXYYYYYYZZ[[\\]^_`acdeghghjjkkklkkkkkkkklllllllmmmnoppqqqqqrrrrrrrrrrrrrrrrrsssttuuuvvvwxxxxxxxxxxxxxxxxvvvuuuutssssssssssssssrrrrrrrrsssssttuuvvvvvvvvvvvvvvvwwwwwwwxxxxyyzz{{|}}xsnid]YWXSQONNPRSVXZ[\\\ZWVVVVWWXYYY[]`cinsz}{yxvtrrqqponnmnnmnnnnnnmmlllkkihhggfffgffeeddcccba``_^\[ZYXWVVVVVUTSRQRQQQQQQQRRRRRRRRQPPPOOONNNMMMMMMKJJIIHHGFDCBBA@?@@@@?>=<:99988766666666555555455333221223333222100000000////.-,,...//...--------,,,,,,,-,,,,------./..--++++,--------,,,,,,++***********((((((''((((((''(((((()*))))))))+++++++*)-4;EMSZaceefggfffffffffgfffeeeeedddcccbcbbbbbbbbbaaaaaaaaaaaaaaaaa```______________^^^\ZYXZ`jqrbJ5489;;<<<<<<>?======>>??>=======>>??????@@@AAA???>?ABBCCCCCCCCCCCCCCCC>>>>>>?@AAAAAA@@@@@@AABBBCDDDDDDDDDDDDEFFFFFFFFGFGHHHHHHIIIJJJJJJJKKKKKKLMMNNOOOOPPPQQQRRRRRRQQQRRRRSSSTUUUVVVUUTTTTTTSSSSSSTTTUUUVVWWXXXYZZZZZZ[\\\]]^__abcefghghjjkkklkklllllllllllllllmnoopqrrrrrrrrrrrrrrrrrrrrrsssttuuuvvvwxxxxxxxxxxxxxxxxwwvvvuuttttttttsssssssrrrrrrrrssssstuuvvvvvvvvvvvvvvvwwwwwwwwxxxxyyzz{{|}{xtpmhb^\][ZXVVVVVYZ[\\\[ZWVVVVVVWXWXZ\]_bfkrw}~|zyxvttsrrqqqqqqqppqqqqqqqqonlmnmmlkkkkkjjihhhhggfedcaa`_^]\\[\\[ZYXWXVVVVVVVWYZZZZZZZYXXXWWWWWVUTSSSRRRQONNMLKJIHGFEFEDDCBA@????>>>==<<<<<<<;;::::::99888888999888898888876776566654555544445544444455555555333322222222223422222333333323333333222101111111000000//000000//000000000000000/1111110/.17>GOUZaceffggggggggggggfffeeeeedddcccbcbbbbbbbbbaaaaaaaaaaaaaaaaa```____________^^^]]\[ZXY^gorcM:;BBBCDDDDEEFFEDCCCDDDEEEEFFFFFFGGHHHIFHHHHHGGHIIIIJJJKMMMNNNNNNNNNNNN@@@@@@@ABBBBBBAAAAAABCCCDEEEEEEEEEEEEEEFFGGGHHIIHIJJJJJJJJKLLLLLKKLLLMMMNOOPPQQRQRRRRSSSSSSSSSSSTTTTTTTUVVVVVVVVUUUUUTTTTSSTTTUUVVVWWXXYYZ[[[[[[[\]]^^__`bcdefghhijkkkklkkllllllmmmmmmllmnnopqqrrrrrrrrrrrrrrrrrrrrrsssttuuuvvvwxxxxxxxxxxxxxxxxwwwvvvvuttttttttttttttssssssssssssttuuvvwwwwwwwwwwwwwwwwwwwwwxxxxyyzz{{|}|yvsplheb`^]\\[ZZZ\\]]]\\[XWVUUTTVWVWXZZ[^aflrw|}|{yxvuuutttttttuuuttttttttsssrqqqpppooooooommmmmlllkiiihhggffecccbbab```````abbbcccccbaaaaaaaa`_^]]][[[[ZYXXVTSSRQQPPOOONMMLKKJIHGGFFFEEEDDDEEDDDDDDCCBBBBBABBBBBCCCBBBBBAAAA@@@@@?>@@@@@@??@@??????>>>>>>>=>>>><<=>===<==<;======<;;;;;;<>><>>>>>>>::::::::<<<<<;::::::9899999:::::;;;;;;;;;;;;;;;<; + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../.. +include $(DIR_TO_ROOT)/build.param + +#CFLAGS += +LIBS += -lhal_common -lhal_platform +OUTPUT_DIR := $(DIR_TO_ROOT)/output/examples/vdec +#PREPARE := lib_camera + +TARGET_1 := vdec_demo1 +SRCS_1 = vdec_demo1.c + +TARGET_ALL := $(TARGET_1) + +all: $(TARGET_ALL) + +clean: + rm -rf .obj + rm -f $(TARGET_1) $(OUTPUT_DIR)/$(TARGET_1) + +include $(DIR_TO_ROOT)/common_target.mk + +.PHONY: clean all diff --git a/examples/vdec/vdec_demo1.c b/examples/vdec/vdec_demo1.c new file mode 100644 index 0000000..6225cfa --- /dev/null +++ b/examples/vdec/vdec_demo1.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "vdec_demo1" +#include + +#include +#include + +#define TEST_MODULE_NAME "simulator_vdec0" + +int main(int argc, char *argv[]) +{ + int ret; + csi_vdec_info_t vdec_info; + csi_vdec_dev_t decoder; + csi_vdec_chn_t channel; + + // 打印HAL接口版本号 + csi_api_version_u version; + csi_vdec_get_version(&version); + printf("Video Decoder HAL version: %d.%d\n", version.major, version.minor); + + // 获取设备中,所有的Video Decoder + struct csi_vdec_infos vdec_infos; + csi_vdec_query_list(&vdec_infos); + + // 打印所有设备所支持的Video Decoder + bool found_module = false; + for (int i = 0; i < vdec_infos.count; i++) { + csi_vdec_info_t *info; + info = &(vdec_infos.info[i]); + printf("decoder[%d]: module_name='%s', device_name='%s', capabilities=0x%lx\n", + i, info->module_name, info->device_name, info->capabilities); + + if (strcmp(info->module_name, TEST_MODULE_NAME) == 0) { + vdec_info = *info; + found_module = true; + } + } + + if (!found_module) { + LOG_E("Can't find module_name:'%s'\n", TEST_MODULE_NAME); + exit(-1); + } + + // 打开设备获取句柄,作为后续操对象 + csi_vdec_open(&decoder, vdec_info.device_name); + + // 创建视频解码通道 + csi_vdec_config_s dec_cfg; + dec_cfg.input_mode = CSI_VDEC_INPUT_MODE_FRAME; + dec_cfg.input_stream_buf_size = 1*1024*1024; + dec_cfg.output_format = CSI_PIX_FMT_I420; + dec_cfg.output_width = 1920; + dec_cfg.output_height = 1080; + dec_cfg.output_order = CSI_VDEC_OUTPUT_ORDER_DISP; + dec_cfg.dec_vcodec_id = CSI_VCODEC_ID_H264; + dec_cfg.output_img_type = CSI_VDEC_MODE_IPB; + dec_cfg.dec_frame_buf_cnt = 8; + csi_vdec_create_channel(&channel, decoder, &dec_cfg); + + // 配置内存分配器 + if (1) { + //csi_allocator_s *allocator = csi_allocator_get(CSI_ALLOCATOR_TYPE_DMA); + //csi_vdec_set_memory_allocator(channel, allocator); + } else { + csi_frame_s *allocated_frames[8]; // TODO: allocate frames first + csi_vdec_register_frames(channel, allocated_frames, 8); + } + + // 配置解码通道模式 + csi_vdec_mode_s vdec_mode; + vdec_mode.fb_source = CSI_FB_SOURCE_DMABUF; + vdec_mode.low_latency_mode = false; + vdec_mode.mini_buf_mode = false; + csi_vdec_set_mode(channel, &vdec_mode); + + // 获取和重新配置解码通道属性 + csi_vdec_get_chn_config(channel, &dec_cfg); + dec_cfg.dec_frame_buf_cnt = 16; + csi_vdec_set_chn_config(channel, &dec_cfg); + + // 订阅事件 + csi_vdec_event_handle_t event_handle; + csi_vdec_create_event_handle(&event_handle, decoder); + csi_vdec_event_subscription_t subscribe_dec, subscribe_chn; + + subscribe_dec.type = CSI_VDEC_EVENT_TYPE_DECODER; // 订阅Decoder事件 + subscribe_dec.id = CSI_VDEC_EVENT_ID_ERROR; + csi_vdec_subscribe_event(event_handle, &subscribe_dec); + + subscribe_chn.type = CSI_VDEC_EVENT_TYPE_CHANNEL; // 订阅Channel事件 + subscribe_chn.id = CSI_VDEC_CHANNEL_EVENT_ID_FRAME_READY; + csi_vdec_subscribe_event(event_handle, &subscribe_chn); + + // 传送视频码流 + csi_vdec_start(channel); + + const int send_count = 25; + const int stream_packet_len = 1024; + + csi_vdec_stream_s stream; + stream.data = malloc(stream_packet_len); + + for (int i = 0; i < send_count; i++) { + stream.length = stream_packet_len; + stream.pts = 40 * i; + stream.eos = (i < (send_count - 1)) ? false : true; + + if (i == 10) { + // Just for testing + csi_vdec_reset(channel); + } + csi_vdec_send_stream_buf(channel, &stream, 1000); + } + free(stream.data); + stream.data = NULL; + + // 收取解码结果 + csi_frame_s *frame; + struct csi_vdec_event event; + + int get_count = 0; + csi_vdec_chn_status_s status; + + while (get_count < send_count) { + // timeout unit: ms, -1 means wait forever, or until error occurs + csi_vdec_get_event(event_handle, &event, -1); + + switch (event.type) { + case CSI_VDEC_EVENT_TYPE_DECODER: + csi_vdec_query_status(channel, &status); + // Dump Status + break; + case CSI_VDEC_EVENT_TYPE_CHANNEL: + switch (event.id) { + case CSI_VDEC_CHANNEL_EVENT_ID_FRAME_READY: + csi_vdec_get_frame(channel, &frame, 1000); + get_count++; + // display frame + // release frame: frame.release(); + } + break; + default: + break; + } + + + } + + // 关闭与回收资源 + csi_vdec_stop(channel); + csi_vdec_unsubscribe_event(event_handle, &subscribe_dec); + csi_vdec_unsubscribe_event(event_handle, &subscribe_chn); + csi_vdec_destory_channel(channel); + csi_vdec_close(decoder); + + return 0; +} + diff --git a/examples/venc/.gitignore b/examples/venc/.gitignore new file mode 100644 index 0000000..6ea1e5f --- /dev/null +++ b/examples/venc/.gitignore @@ -0,0 +1 @@ +venc_demo1 diff --git a/examples/venc/Makefile b/examples/venc/Makefile new file mode 100644 index 0000000..c5f7e4d --- /dev/null +++ b/examples/venc/Makefile @@ -0,0 +1,30 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../.. +include $(DIR_TO_ROOT)/build.param + +#CFLAGS += +LIBS += -lhal_common -lhal_platform +OUTPUT_DIR := $(DIR_TO_ROOT)/output/examples/venc +#PREPARE := lib_camera + +TARGET_1 := venc_demo1 +SRCS_1 = venc_demo1.c + +TARGET_ALL := $(TARGET_1) + +all: $(TARGET_ALL) + +clean: + rm -rf .obj + rm -f $(TARGET_1) $(OUTPUT_DIR)/$(TARGET_1) + +include $(DIR_TO_ROOT)/common_target.mk + +.PHONY: clean all diff --git a/examples/venc/venc_demo1.c b/examples/venc/venc_demo1.c new file mode 100644 index 0000000..f54ad71 --- /dev/null +++ b/examples/venc/venc_demo1.c @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "venc_demo1" +#include + +#include + +#define TEST_MODULE_NAME "simulator_venc0" + +int main(int argc, char *argv[]) +{ + int ret; + csi_venc_info_s venc_info; + csi_venc_dev_t encoder; + csi_venc_chn_t channel; + + // 打印HAL接口版本号 + csi_api_version_u version; + csi_venc_get_version(&version); + printf("Video Encoder HAL version: %d.%d\n", version.major, version.minor); + + // 获取设备中,所有的Video Encoder + struct csi_venc_infos venc_infos; + csi_venc_query_list(&venc_infos); + + // 打印所有设备所支持的Video Encoder + bool found_module = false; + for (int i = 0; i < venc_infos.count; i++) { + csi_venc_info_s *info; + info = &(venc_infos.info[i]); + printf("decoder[%d]: module_name='%s', device_name='%s', capabilities=0x%lx\n", + i, info->module_name, info->device_name, info->capabilities); + + if (strcmp(info->module_name, TEST_MODULE_NAME) == 0) { + venc_info = *info; + found_module = true; + } + } + + if (!found_module) { + LOG_E("Can't find module_name:'%s'\n", TEST_MODULE_NAME); + exit(-1); + } + + // 打开设备获取句柄,作为后续操对象 + csi_venc_open(&encoder, venc_info.device_name); + + // 创建视频解码通道 + csi_venc_chn_cfg_s chn_cfg; + chn_cfg.attr.enc_vcodec_id = CSI_VCODEC_ID_H264; + chn_cfg.attr.max_pic_width = 3840; + chn_cfg.attr.max_pic_height = 2160; + chn_cfg.attr.input_format = CSI_PIX_FMT_I420; + chn_cfg.attr.pic_width = 1920; + chn_cfg.attr.pic_height = 1080; + chn_cfg.attr.buf_size = 1*1024*1024; + + chn_cfg.attr.h264_attr.profile = CSI_VENC_H264_PROFILE_HIGH; + chn_cfg.attr.h264_attr.level = CSI_VENC_H264_LEVEL_4; + chn_cfg.attr.h264_attr.frame_type = CSI_VCODEC_I_FRAME | + CSI_VCODEC_P_FRAME | + CSI_VCODEC_B_FRAME; + chn_cfg.attr.h264_attr.share_buf = true; + + chn_cfg.gop.gop_num = 5; + chn_cfg.gop.gop_mode = CSI_VENC_GOPMODE_NORMALP; + chn_cfg.gop.normalp.ip_qp_delta = 10; + + chn_cfg.rc.rc_mode = CSI_VENC_RC_MODE_H264CBR; + chn_cfg.rc.h264_vbr.stat_time = 10; + chn_cfg.rc.h264_vbr.framerate_numer = 25; + chn_cfg.rc.h264_vbr.framerate_denom = 1; + chn_cfg.rc.h264_vbr.max_bit_rate = 16 * 1024; // kbps + csi_venc_create_channel(&channel, encoder, &chn_cfg); + + // 配置内存分配器 + //csi_allocator_s *allocator = csi_allocator_get(CSI_ALLOCATOR_TYPE_DMA); + //csi_venc_set_memory_allocator(channel, allocator); + + // 设置解码通道扩展属性 + csi_venc_chn_ext_property_s ext_property; + ext_property.prop_id = CSI_VENC_EXT_PROPERTY_ROI; + ext_property.roi_prop.index = 0; + ext_property.roi_prop.enable = true; + ext_property.roi_prop.abs_qp = 1; + ext_property.roi_prop.qp = 51; + csi_venc_set_ext_property(channel, &ext_property); + + // 订阅事件 + csi_venc_event_handle_t event_handle; + csi_venc_create_event_handle(&event_handle, encoder); + csi_venc_event_subscription_s subscribe_dec, subscribe_chn; + + subscribe_dec.type = CSI_VENC_EVENT_TYPE_DECODER; // 订阅Encoder事件 + subscribe_dec.id = CSI_VENC_EVENT_ID_ERROR; + csi_venc_subscribe_event(event_handle, &subscribe_dec); + + subscribe_chn.type = CSI_VENC_EVENT_TYPE_CHANNEL; // 订阅Channel事件 + subscribe_chn.id = CSI_VENC_CHANNEL_EVENT_ID_FRAME_READY; + csi_venc_subscribe_event(event_handle, &subscribe_chn); + + // 传送视频码流 + csi_venc_start(channel); + + csi_frame_s frame; + int send_count = 25; + for (int i = 0; i < send_count; i++) { + if (i == 10) { + // Just for testing + csi_venc_reset(channel); + } + + if (i %5 != 0) { + // Send normal frame + csi_venc_send_frame(channel, &frame, 1000); + } + else { + // Send frame with extended properties + csi_venc_frame_prop_s frame_prop[2]; + frame_prop[0].type = CSI_VENC_FRAME_PROP_FORCE_IDR; + frame_prop[0].force_idr = true; + frame_prop[1].type = CSI_VENC_FRAME_PROP_FORCE_SKIP; + frame_prop[1].force_skip = false; + csi_venc_send_frame_ex(channel, &frame, 1000, + frame_prop, ARRAY_SIZE(frame_prop)); + } + } + + // 收取解码结果 + csi_stream_s stream; + struct csi_venc_event event; + + int get_count = 0; + csi_venc_chn_status_s status; + + while (get_count < send_count) { + int timeout = -1; // unit: ms, -1 means wait forever, or until error occurs + csi_venc_get_event(event_handle, &event, timeout); + + switch (event.type) { + case CSI_VENC_EVENT_TYPE_DECODER: + csi_venc_query_status(channel, &status); + // Dump Status + break; + case CSI_VENC_EVENT_TYPE_CHANNEL: + switch (event.id) { + case CSI_VENC_CHANNEL_EVENT_ID_FRAME_READY: + csi_venc_get_stream(channel, &stream, 1000); + // save stream ... + // release stream: stream.release(); + } + break; + default: + break; + } + } + + // 关闭与回收资源 + csi_venc_stop(channel); + csi_venc_unsubscribe_event(event_handle, &subscribe_dec); + csi_venc_unsubscribe_event(event_handle, &subscribe_chn); + csi_venc_destory_channel(channel); + csi_venc_close(encoder); + + return 0; +} + diff --git a/include/common/camera_string.h b/include/common/camera_string.h new file mode 100644 index 0000000..43fea81 --- /dev/null +++ b/include/common/camera_string.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CAMERA_STRING_H__ +#define __CAMERA_STRING_H__ + +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +const char *camera_string_enum_name(int property_id, int enum_id); +const char *camera_string_bitmask_name(int property_id, int enum_id); +const char *camera_string_capture_type(csi_camera_channel_capture_type_e type, bool accept_unknown); +const char *camera_string_chn_capture_types(int fields, char *fields_string); +const char *camera_string_pixel_format(csi_pixel_fmt_e pix_fmt); +const char *camera_string_img_type(csi_img_type_e img_type); +const char *camera_string_meta_field(csi_camera_meta_id_e type, bool accept_unknown); +const char *camera_string_chn_meta_fields(int fields, char *fields_string); +const char *camera_string_chn_status(csi_img_type_e img_type); + +const char *camera_string_camera_event_type(csi_camera_event_id_e event_id); +const char *camera_string_channel_event_type(csi_camera_channel_event_id_e event_id); +const char *camera_string_channel_action(camera_channel_action_e action); +const char *camera_string_camera_action(camera_action_e action); + +#ifdef __cplusplus +} +#endif + +#endif /* __CAMERA_STRING_H__ */ diff --git a/include/common/common.h b/include/common/common.h new file mode 100644 index 0000000..226dd89 --- /dev/null +++ b/include/common/common.h @@ -0,0 +1,65 @@ +/* + * Copyright 2018 C-SKY Microsystems Co., Ltd. + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * 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. + * + */ +#ifndef __COMMON_H__ +#define __COMMON_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common_errno.h" +#include "common_datatype.h" +#include "syslog.h" +#include "list.h" + + +#define CHECK_RET_RETURN(ret) \ + if (ret != 0) { \ + LOG_E("failed, ret=%d\n", ret); \ + return ret; \ + } + +#define CHECK_RET_EXIT(ret) \ + if (ret != 0) { \ + LOG_E("failed, ret=%d\n", ret); \ + exit(ret); \ + } + +#define CHECK_RET_WARNING(ret) \ + if (ret != 0) { \ + LOG_W("Warning, ret=%d\n", ret); \ + } + +#define CHECK_POINTER_RETURN(pointer) \ + if (pointer == NULL) { \ + LOG_E("pointer is NULL\n"); \ + return ERRNO_INVALID_PARAMETER; \ + } + +#endif /* __COMMON_H__ */ + diff --git a/include/common/common_datatype.h b/include/common/common_datatype.h new file mode 100644 index 0000000..9d523b1 --- /dev/null +++ b/include/common/common_datatype.h @@ -0,0 +1,58 @@ +/* + * Copyright 2018 C-SKY Microsystems Co., Ltd. + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * 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. + * + */ +#ifndef __COMMON_DATATYPE_H__ +#define __COMMON_DATATYPE_H__ + +#ifndef NULL +#define NULL 0x00 +#endif + +#ifndef TRUE +#define TRUE 0x01 +#endif +#ifndef FALSE +#define FALSE 0x00 +#endif + +#ifndef true +#define true 0x01 +#endif +#ifndef false +#define false 0x00 +#endif + + +#ifndef bool +typedef unsigned char bool; +#endif + +#if 0 /* below types are defined in /usr/include/stdint.h */ +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; + +typedef signed char int8_t; +typedef signed short int16_t; +typedef signed int int32_t; + +typedef unsigned long long uint64_t; +#endif + +#endif /* __COMMON_DATATYPE_H__ */ + diff --git a/include/common/common_errno.h b/include/common/common_errno.h new file mode 100644 index 0000000..e38c812 --- /dev/null +++ b/include/common/common_errno.h @@ -0,0 +1,34 @@ +/* + * Copyright 2018 C-SKY Microsystems Co., Ltd. + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * 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. + * + */ +#ifndef __COMMON_ERRNO_H__ +#define __COMMON_ERRNO_H__ +#include + +typedef enum { + FAILURE = -1, + SUCCESS = 0, + + ERRNO_INVALID_PARAMETER, + ERRNO_INVALID_STATE, + + ERRNO_INVALID_CLIENT_PACKAGE, +} common_errno_e; + +#endif /* __COMMON_ERRNO_H__ */ + diff --git a/include/common/dump_utils.h b/include/common/dump_utils.h new file mode 100644 index 0000000..02d1742 --- /dev/null +++ b/include/common/dump_utils.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __DUMP_UTILS_H__ +#define __DUMP_UTILS_H__ + +#include +#include + +#include + +void csi_dump_hex(char *data, int length, char *name); +void csi_dump_img_info(csi_img_s *img); + +#endif // __DUMP_UTILS_H__ diff --git a/include/common/ipc_fd_transfer.h b/include/common/ipc_fd_transfer.h new file mode 100644 index 0000000..d56ad91 --- /dev/null +++ b/include/common/ipc_fd_transfer.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include + +union { + struct cmsghdr cm; // control msg + char ctl[CMSG_SPACE(sizeof(int))]; // the pointer of char +} ctl_un; + diff --git a/include/common/list.h b/include/common/list.h new file mode 100644 index 0000000..5b94fce --- /dev/null +++ b/include/common/list.h @@ -0,0 +1,154 @@ +/* + * Copyright (c) HighPoint Technologies, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/sys/dev/hptrr/list.h,v 1.2.2.1.4.1 2010/06/14 02:09:06 kensmith Exp $ + */ +/* + * $Id: list.h,v 1.6 2006/10/31 06:25:28 gmm Exp $ + * Copyright (C) 2004-2005 HighPoint Technologies, Inc. All rights reserved. + */ +#ifndef _HPT_LIST_H_ +#define _HPT_LIST_H_ + +#ifndef _LINUX_LIST_H + +#ifndef _LINUX_TYPES_H + +#ifndef HPT_INLINE +#define HPT_INLINE __inline +#endif + +typedef unsigned long HPT_UPTR; + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define INIT_LIST_HEAD(ptr) do { (ptr)->next = (ptr); (ptr)->prev = (ptr); } while (0) + +static HPT_INLINE void __list_add(struct list_head * _new, struct list_head * prev, struct list_head * next) +{ + next->prev = _new; + _new->next = next; + _new->prev = prev; + prev->next = _new; +} + +static HPT_INLINE void list_add(struct list_head *_new, struct list_head *head) +{ + __list_add(_new, head, head->next); +} + +static HPT_INLINE void list_add_tail(struct list_head *_new, struct list_head *head) +{ + __list_add(_new, head->prev, head); +} + +static HPT_INLINE void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +static HPT_INLINE void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); +} + +static HPT_INLINE void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add(list, head); +} + +static inline void list_move_tail(struct list_head *list, + struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add_tail(list, head); +} + +static HPT_INLINE int list_empty(struct list_head *head) +{ + return head->next == head; +} + +static HPT_INLINE void __list_splice(struct list_head *list, + struct list_head *head) +{ + struct list_head *first = list->next; + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; +} + +static HPT_INLINE void list_splice(struct list_head *list, struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head); +} + +static HPT_INLINE void list_splice_init(struct list_head *list, struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head); + INIT_LIST_HEAD(list); + } +} + +/*#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(HPT_UPTR)(&((type *)0)->member))) */ +#define list_entry(ptr, type, member) \ + ((type *)((unsigned long)(ptr)-((unsigned long)(&((type *)1)->member) - 1))) + +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +#define get_first_item(attached, type, member) \ + ((type *)((char *)((attached)->next)-(HPT_UPTR)(&((type *)0)->member))) + +#endif + +#endif + +#endif + diff --git a/include/common/llist.h b/include/common/llist.h new file mode 100644 index 0000000..08afdb5 --- /dev/null +++ b/include/common/llist.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: zhuxinran + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LLIST_H__ +#define __LLIST_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define LLIST_FORWARD 1 +#define LLIST_BACKWARD 2 + +typedef void llist_op(const void *); +typedef int llist_cmp(const void *, const void *); + +struct llist_node_st { + struct llist_node_st *prev; + struct llist_node_st *next; + char data[1]; +}; + +typedef struct llist_head_st { + int size; + struct llist_node_st head; + int (*insert)(struct llist_head_st *, const void *data, int mode); + void *(*find)(struct llist_head_st *, const void *data, llist_cmp *cmp); + int (*delete)(struct llist_head_st *, const void *data, llist_cmp *cmp); + int (*fetch)(struct llist_head_st *, const void *data, llist_cmp *cmp, + void *rdata); + void (*travel)(struct llist_head_st *, llist_op *op); +} LLIST; + +LLIST *llist_create(int size); + +void llist_destroy(LLIST *); + +#ifdef __cplusplus +} +#endif + +#endif + + diff --git a/include/common/producer_consumer_async.h b/include/common/producer_consumer_async.h new file mode 100644 index 0000000..6fd6e5c --- /dev/null +++ b/include/common/producer_consumer_async.h @@ -0,0 +1,104 @@ +/* + * Copyright 2018-2019 C-SKY Microsystems Co., Ltd. + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * 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. + * + */ +#ifndef __PRODUCER_CONSUMER_ASYNC_H__ +#define __PRODUCER_CONSUMER_ASYNC_H__ + +#include "common.h" + +struct pca_data_info; +typedef int32_t (*pca_consumed_callback)(struct pca_data_info* data_info); + +typedef struct pca_data_info { + void* data; /* data pointer */ + uint32_t length; /* data length */ + uint32_t type; /* data type */ + pca_consumed_callback consumed_callback;/* data need free by dispatcher */ +} pca_data_info_t; + +typedef struct pca_data_statistics { + uint32_t total_delivered; /* Data feeded in */ + uint32_t total_discarded; /* Data discarded because busy */ + uint32_t total_updated; /* New data updated in before consume */ + uint32_t total_consumed; /* Data consumed */ +} pca_data_statistics_t; + +typedef int32_t (*pca_consumer_process)(pca_data_info_t* data_info); + +typedef struct pca_thread_info { + pthread_t tid; /* thread ID */ + char name[32]; /* thread name */ + sem_t sem_wakeup; /* semphore to wakeup thread */ + bool is_running; + + pca_data_info_t data_info; /* the data delivered from parent */ + bool has_new_data; /* indicate there is new data to handle */ + bool need_unlock_data_mutex; /* Just for mark flag when need unlock */ + pthread_mutex_t mutex_data; /* mutex to protect handling data */ + pca_data_statistics_t stats; /* the statistics of data */ +} pca_thread_info_t; + +typedef struct pca_consumer_processor_info { + pca_consumer_process process; /* consumer processor function */ + int data_type; /* the data type to be consume */ + char name[16]; /* consumer name for debug */ +} pca_consumer_processor_info_t; + +struct pca_dispatcher_info; /* Declare here but defined below */ + +typedef struct pca_consumer_list_info { + pca_thread_info_t thread_info; /* thread info */ + pca_consumer_processor_info_t processor;/* post processor info */ + struct list_head list; /* to build list */ + struct pca_dispatcher_info* dispatcher; /* dispatcher's pointer */ +} pca_consumer_list_info_t; + +typedef struct { + pca_data_info_t data_info; /* data_info */ + unsigned int reference_count; /* data using count */ + struct list_head list; /* to build list */ +} pca_data_free_list_info_t; + +typedef struct pca_dispatcher_info { + pca_thread_info_t thread_info; /* thread info */ + + pthread_mutex_t consumer_mutex; /* mutex to protect consumer_list */ + pca_consumer_list_info_t consumer_list; /* consumers list */ + + pthread_mutex_t data_free_mutex; /* mutex to protect data_free_list */ + pca_data_free_list_info_t data_free_list;/* data list need to free */ +} pca_dispatcher_info_t; + +int32_t pca_dispatcher_create(pca_dispatcher_info_t** dispatcher, char* name); +int32_t pca_dispatcher_run(pca_dispatcher_info_t* dispatcher); +int32_t pca_dispatcher_feed(pca_dispatcher_info_t* dispatcher, + void *data, int32_t data_type); +int32_t pca_dispatcher_stop(pca_dispatcher_info_t* dispatcher); +int32_t pca_dispatcher_destory(pca_dispatcher_info_t* dispatcher); + +int32_t pca_add_consumer_processor(pca_dispatcher_info_t* dispatcher, + pca_consumer_processor_info_t *processor); +int32_t pca_remove_consumer_processor(pca_dispatcher_info_t* dispatcher, + pca_consumer_processor_info_t *processor); +int32_t pca_feed_consumers(pca_dispatcher_info_t* dispatcher, + pca_data_info_t* data_info); +int32_t pca_feed_dispatcher(pca_dispatcher_info_t* dispatcher, + pca_data_info_t* data_info); + +#endif /* __PRODUCER_CONSUMER_ASYNC_H__ */ + diff --git a/include/common/ringbuffer.h b/include/common/ringbuffer.h new file mode 100644 index 0000000..05868a6 --- /dev/null +++ b/include/common/ringbuffer.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: zhuxinran + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +/* + * Copyright (C) 2017-2020 Alibaba Group Holding Limited + */ + +#ifndef _DRV_RINGBUFFER_H_ +#define _DRV_RINGBUFFER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "stdint.h" +#include + +typedef struct ringbuffer { + uint8_t *buffer; + uint32_t size; + uint32_t write; + uint32_t read; + uint32_t data_len; +} ringbuffer_t; + +void ringbuffer_reset(ringbuffer_t *fifo); +uint32_t ringbuffer_len(ringbuffer_t *fifo); +uint32_t ringbuffer_avail(ringbuffer_t *fifo); +bool ringbuffer_is_empty(ringbuffer_t *fifo); +bool ringbuffer_is_full(ringbuffer_t *fifo); + +/*write to ringbuffer*/ +uint32_t ringbuffer_in(ringbuffer_t *fifo, const void *in, uint32_t len); + +/*read to ringbuffer*/ +uint32_t ringbuffer_out(ringbuffer_t *fifo, void *out, uint32_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* _DRV_RINGBUFFER_H_ */ diff --git a/include/common/syslog.h b/include/common/syslog.h new file mode 100644 index 0000000..1642558 --- /dev/null +++ b/include/common/syslog.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#ifndef SYSLOG_H +#define SYSLOG_H + +#define LOG_ENABLE + +#ifdef LOG_LEVEL +#if (LOG_LEVEL >= 3) +#define LOG_ENABLE_D +#endif + +#if (LOG_LEVEL >= 2) +#define LOG_ENABLE_I +#endif + +#if (LOG_LEVEL >= 1) +#define LOG_ENABLE_W +#endif + +#if (LOG_LEVEL >= 0) +#define LOG_ENABLE_E +#endif +#else /* #ifdef LOG_LEVEL */ +#define LOG_ENABLE_E /* Default level if LOG_LEVEL not defiend */ +#endif /* #ifdef LOG_LEVEL */ + +/* [LogLevel:FileName:Function:Line] */ +extern const char *PFORMAT_D; +extern const char *PFORMAT_I; +extern const char *PFORMAT_W; +extern const char *PFORMAT_E; +extern const char *PFORMAT_O; + +#ifdef LOG_PREFIX +#define LOG_PREF LOG_PREFIX"-" +#else +#define LOG_PREF "" +#endif + +#define LOG_E_BASE_ARGS LOG_PREF, __FUNCTION__, __LINE__ +#define LOG_W_BASE_ARGS LOG_PREF, __FUNCTION__, __LINE__ +#define LOG_I_BASE_ARGS LOG_PREF, __FUNCTION__, __LINE__ +#define LOG_D_BASE_ARGS LOG_PREF, __FUNCTION__, __LINE__ +#define LOG_O_BASE_ARGS LOG_PREF, __FUNCTION__, __LINE__ + +/* Log in freely format without prefix */ +#ifdef LOG_ENABLE +#define LOG_F(fmt, args...) printf(fmt,##args) +#else +#define LOG_F(fmt, args...) +#endif + +// OK, Green color +#define LOG_O(fmt, args...) \ + do {printf(PFORMAT_O,LOG_O_BASE_ARGS); printf(fmt,##args);} while(0) + +#define LOGPRINT_E printf +#define LOGPRINT_W printf +#define LOGPRINT_I printf +#define LOGPRINT_D printf + +/* Log debug */ +#if defined(LOG_ENABLE_D) && defined(LOG_ENABLE) +#define LOG_D(fmt, args...) \ + do {LOGPRINT_D(PFORMAT_D,LOG_D_BASE_ARGS); LOGPRINT_D(fmt,##args);} while(0) +#else +#define LOG_D(fmt, args...) +#endif + +/* Log information */ +#if defined(LOG_ENABLE_I) && defined(LOG_ENABLE) +#define LOG_I(fmt, args...) \ + do {LOGPRINT_I(PFORMAT_I ,LOG_I_BASE_ARGS); LOGPRINT_I(fmt,##args);} while(0) +#else +#define LOG_I(fmt, args...) +#endif + +/* Log warning */ +#if defined(LOG_ENABLE_W) && defined(LOG_ENABLE) +#define LOG_W(fmt, args...) \ + do {LOGPRINT_W(PFORMAT_W,LOG_W_BASE_ARGS); LOGPRINT_W(fmt,##args);} while(0) +#else +#define LOG_W(fmt, args...) +#endif + +/* Log error */ +#if defined(LOG_ENABLE_E) && defined(LOG_ENABLE) +#define LOG_E(fmt, args...) \ + do{LOGPRINT_E(PFORMAT_E,LOG_E_BASE_ARGS); LOGPRINT_E(fmt,##args);} while(0) +#else +#define LOG_E(fmt, args...) +#endif + +#define ENTER_VOID() LOG_D("Enter\n") +#define EXIT_VOID() do { LOG_D("Exit\n"); return;} while(0) +#define EXIT_INT(val) do { LOG_D("Exit, return val=%d\n", (int)val); return val;} while(0) +#define EXIT_PTR(ptr) do { LOG_D("Exit, return ptr=%p\n", (void*)ptr); return ptr;} while(0) + +#endif // SYSLOG_H diff --git a/include/hal/csi_allocator.h b/include/hal/csi_allocator.h new file mode 100644 index 0000000..30a7eea --- /dev/null +++ b/include/hal/csi_allocator.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CSI_ALLOCATOR_H__ +#define __CSI_ALLOCATOR_H__ + +#include +#include +#include +#include + +typedef enum csi_allocator_type { + CSI_ALLOCATOR_TYPE_SYSTEM, + CSI_ALLOCATOR_TYPE_SYSTEM_CONTIG, + CSI_ALLOCATOR_TYPE_CARVEOUT, + CSI_ALLOCATOR_TYPE_DMA, + CSI_ALLOCATOR_TYPE_CUSTOM, +} csi_allocator_type_e; // refer from ion_heap_type + +typedef struct csi_mem { + void *usr_addr; + union { + int64_t phy_addr; /* 0 means invalid */ + int fd; /* -1 means invalid */ + }; + + size_t size; + void *opaque; /* not for user, DO NOT use or modify it */ +} csi_mem_s; + + +typedef struct csi_allocator { + //int (*init)(char *args); + csi_mem_s *(*alloc)(csi_allocator_type_e type, size_t size, uint32_t align); + void (*free)(csi_mem_s *mem); + void *(*map)(csi_mem_s *mem); + void (*unmap)(csi_mem_s *mem); +} csi_allocator_s; + +//csi_allocator_s *csi_allocator_get(csi_allocator_type_e type); +int32_t csi_hal_set_allocator(csi_allocator_s allocator); +int32_t csi_hal_get_allocator(csi_allocator_s *allocator); + +#endif /* __CSI_ALLOCATOR_H__ */ diff --git a/include/hal/csi_audio_ai.h b/include/hal/csi_audio_ai.h new file mode 100644 index 0000000..9d09f54 --- /dev/null +++ b/include/hal/csi_audio_ai.h @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: fuqian.zxr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __CSI_AUDIO_AI_H__ +#define __CSI_AUDIO_AI_H__ + +#include "csi_audio_common.h" + +#define CSI_AUDIO_AI_VERSION_MAJOR 0 +#define CSI_AUDIO_AI_VERSION_MINOR 1 + +#define VQE_DRC_SECNUM 5 + +#define CSI_AI_MAX_COUNT 16 +#define CSI_AI_NAME_MAX_LEN 32 + +#define CSI_AI_CAP_SPDIF 0x00000001 /*Is a spdif device*/ +#define CSI_AI_CAP_HDMI 0x00000002 /*IS a hdmi audio*/ +#define CSI_AI_CAP_CODEC 0x00000003 /*Is a codec audio*/ + +typedef struct csi_ai_info { + char ai_name[CSI_AI_NAME_MAX_LEN]; + unsigned int capbilities; +}csi_ai_info_s; + +typedef struct csi_ai_infos { + unsigned int count; + csi_ai_info_s info[CSI_AI_MAX_COUNT]; +}csi_ai_infos_s; + +typedef struct csi_aec_frame { + csi_audio_frame_s refframe; + bool bvalid; + bool bsysbind; +}csi_aec_frame_s; + +typedef struct csi_aichn_frame{ + uint32_t usr_frm_depth; +}csi_aichn_frame_s; + +typedef struct csi_ai_aec_config { + bool usrmode; + int8_t cngmode; + int8_t nearallpassenergy; + int8_t nearcleansupenergy; + int16_t dthnl_sort_qth; + int16_t echobandlow; + int16_t echobandhigh; + int16_t echobandlow2; + int16_t echobandhigh2; + int16_t erl_band[6]; + int16_t erl[7]; + int16_t voice_protect_freql; + int16_t voice_protect_freql1; + int32_t reserved; +}csi_ai_aec_config_s; + +typedef struct csi_ai_rnr_config { + bool usrmode; + int32_t nrmode; + int32_t maxnrlevel; + int32_t noisethresh; +}csi_ai_rnr_config_s; + +typedef struct csi_ai_hdr_config { + bool usrmode; + int32_t mingaindb; + int32_t maxgaindb; + int32_t micgaindb; + int32_t micgainstepdb; + void *callback; +}csi_ai_hdr_config_s; + +typedef struct csi_ai_drc_config { + bool usrmode; + int16_t attacktime; + int16_t releasetime; + int16_t oldlevdb[VQE_DRC_SECNUM]; + int16_t newlevdb[VQE_DRC_SECNUM]; +}csi_ai_drc_config_s; + + +int32_t csi_ai_get_version(csi_api_version_u *version); + +int32_t csi_ai_query_list(csi_ai_infos_s *infos); + +int32_t csi_ai_setpubattr(int32_t aidevid,csi_aio_attr_s *aiattr); +int32_t csi_ai_getpubattr(int32_t aidevid,csi_aio_attr_s *aiattr); + +int32_t csi_ai_enable(int32_t aidevid); +int32_t csi_ai_disable(int32_t aidevid); + +int32_t csi_ai_enablechn(int32_t aidevid,int32_t aichn); +int32_t csi_ai_disablechn(int32_t aidevid,int32_t aichn); + +int32_t csi_ai_getframe(int32_t aidev,int32_t aichn,csi_audio_frame_s *frm,csi_aec_frame_s *aecfrm,int32_t timeout); +int32_t csi_ai_releaseframe(int32_t aidev,int32_t aichn,csi_audio_frame_s *frm,csi_aec_frame_s *aecfrm); + +int32_t csi_ai_setchnparam(int32_t aidev,int32_t aichn,csi_aichn_frame_s *chnparam); +int32_t csi_ai_getchnparam(int32_t aidev,int32_t aichn,csi_aichn_frame_s *chnparam); + +int32_t csi_ai_enableresmp(int32_t aidev,int32_t aichn,csi_audio_sample_rate_e out_sample_rate); +int32_t csi_ai_disableresmp(int32_t aidev,int32_t aichn); + +int32_t csi_ai_setvqeattr(int32_t aidev,int32_t aichn,int32_t aochn,int *level); +int32_t csi_ai_getvqeattr(int32_t aidev,int32_t aichn,int *level); + +int32_t csi_ai_settalkvqeattr(int32_t aidev,int32_t aichn,int32_t aodev,int32_t aochn,int *level); +int32_t csi_ai_gettalkvqeattr(int32_t aidev,int32_t aichn,int *level); + +int32_t csi_ai_setrecordvqeattr(int32_t aidev,int32_t aichn,int *level); +int32_t csi_ai_getrecordvqeattr(int32_t aidev,int32_t aichn,int *level); + +int32_t csi_ai_enablevqe(int32_t aidev,int32_t aichn); +int32_t csi_ai_disablevqe(int32_t aidev,int32_t aichn); + +int32_t csi_ai_settrackmode(int32_t aidev,csi_audio_track_mode_e trackmode); +int32_t csi_ai_gettrackmode(int32_t aidev,csi_audio_track_mode_e *trackmode); + +int32_t csi_ai_reset(int32_t aidevid); + +int32_t csi_ai_queryfilestatus(int32_t aidev,int32_t aichn,csi_audio_file_status_s *filestatus); + +int32_t csi_ai_setvqevolume(int32_t aidev,int32_t aichn,int32_t volumedb); +int32_t csi_ai_getvqevolume(int32_t aidev,int32_t aichn,int32_t *volumedb); + +int32_t csi_ai_enableaecrefframe(int32_t aidev,int32_t aichn,int32_t aodev,int32_t aochn); +int32_t csi_ai_disableaecrefframe(int32_t aidev,int32_t aichn); + +#endif \ No newline at end of file diff --git a/include/hal/csi_audio_ao.h b/include/hal/csi_audio_ao.h new file mode 100644 index 0000000..dedfde9 --- /dev/null +++ b/include/hal/csi_audio_ao.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: fuqian.zxr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __CSI_AUDIO_AO_H__ +#define __CSI_AUDIO_AO_H__ + +#include "csi_audio_common.h" + +#define CSI_AO_MAX_COUNT 16 +#define CSI_AO_NAME_MAX_LEN 32 + +#define CSI_AO_CAP_SPDIF 0x00000001 /*Is a spdif device*/ +#define CSI_AO_CAP_HDMI 0x00000002 /*IS a hdmi audio*/ +#define CSI_AO_CAP_CODEC 0x00000003 /*Is a codec audio*/ + +typedef struct csi_ao_info { + char ai_name[CSI_AO_NAME_MAX_LEN]; + unsigned int capbilities; +}csi_ao_info_s; + +typedef struct csi_ao_infos { + unsigned int count; + csi_ao_info_s info[CSI_AO_MAX_COUNT]; +}csi_ao_infos_s; + +#define CSI_AUDIO_AO_VERSION_MAJOR 0 +#define CSI_AUDIO_AO_VERSION_MINOR 1 + +typedef struct csi_ao_chn_state { + uint32_t chntotalnum; + uint32_t chnfreenum; + uint32_t chnbusynum; +}csi_ao_chn_state_s; + +typedef enum csi_audio_fade_rate { + AUIDO_FADE_RATE_1 = 0, + AUIDO_FADE_RATE_2 = 1, + AUIDO_FADE_RATE_4 = 2, + AUIDO_FADE_RATE_8 = 3, + AUIDO_FADE_RATE_16 = 4, + AUIDO_FADE_RATE_32 = 5, + AUIDO_FADE_RATE_64 = 6, + AUIDO_FADE_RATE_128 = 7, + AUIDO_FADE_RATE_BUTT +}csi_audio_fade_rate_e; + +typedef struct csi_audio_fade { + bool fade; + csi_audio_fade_rate_e fadeinrate; + csi_audio_fade_rate_e fadeoutrate; +}csi_audio_fade_s; + +typedef struct csi_ao_vqe_config { + int32_t hpfopen; + int32_t anropen; + int32_t agcopen; + int32_t eqopen; + int32_t worksamplerate; + int32_t framesample; + csi_vqe_workstate_e workstate; + csi_audio_hpf_config_s hpfcfg; + csi_audio_anr_config_s anrcfg; + csi_audio_agc_config_s agccfg; + csi_audio_eq_config_s eqcfg; +}csi_ao_vqe_config_s; + +int32_t csi_ao_get_version(csi_api_version_u *version); + +int32_t csi_ao_query_list(csi_ao_infos_s *infos); +int32_t csi_ao_setpubattr(int32_t aodev,csi_aio_attr_s *attr); +int32_t csi_ao_getpubattr(int32_t aodev,csi_aio_attr_s *attr); + +int32_t csi_ao_enable(int32_t aodev); +int32_t csi_ao_disable(int32_t aodev); + +int32_t csi_ao_enablechn(int32_t aodev,int32_t aochn); +int32_t csi_ao_disablechn(int32_t aodev,int32_t aochn); + + +int32_t csi_ao_sendframe(int32_t aodev,int32_t aochn,const csi_audio_frame_s *data,int32_t timeout); +int32_t csi_ao_enableresmp(int32_t aodev,int32_t aochn,csi_audio_sample_rate_e insamplerate); + +int32_t csi_ao_disableresmp(int32_t aodev,int32_t aochn); +int32_t csi_ao_pausechn(int32_t aodev,int32_t aochn); + +int32_t csi_ao_resumechn(int32_t aodev,int32_t aochn); +int32_t csi_ao_clearchnbuf(int32_t aodev,int32_t aochn); + +int32_t csi_ao_querychnstat(int32_t aodev,int32_t aochn,csi_ao_chn_state_s *status); + +int32_t csi_ao_settrackmode(int32_t aodev,csi_audio_track_mode_e trackmode); +int32_t csi_ao_gettrackmode(int32_t aodev,csi_audio_track_mode_e *trackmode); + +int32_t csi_ao_setvolume(int32_t aodev,int32_t volume); +int32_t csi_ao_getvolume(int32_t aodev,int32_t *volume); + +int32_t csi_ao_setmute(int32_t aodev,bool enable,csi_audio_fade_s *fade); +int32_t csi_ao_getmute(int32_t aodev,bool enable,csi_audio_fade_s *fade); + +int32_t csi_ao_reset(int32_t aodev); + +int32_t csi_ao_setvqeattr(int32_t aodev,int32_t aochn,csi_ao_vqe_config_s *vqeconfig); +int32_t csi_ao_getvqeattr(int32_t aodev,int32_t aochn,csi_ao_vqe_config_s *vqeconfig); + +int32_t csi_ao_enablevqe(int32_t aodev,int32_t aochn); +int32_t csi_ao_disablevqe(int32_t aodev,int32_t aochn); + +#endif diff --git a/include/hal/csi_audio_common.h b/include/hal/csi_audio_common.h new file mode 100644 index 0000000..52503d8 --- /dev/null +++ b/include/hal/csi_audio_common.h @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: fuqian.zxr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __CSI_AUDIO_COMMON_H__ +#define __CSI_AUDIO_COMMON_H__ + +#include +#include +#include +#include "csi_common.h" + +#define MAX_AUDIO_FILE_PATH_LEN 256 +#define VQE_EQ_BAND_NUM 10 + +typedef enum csi_audio_sample_rate { + AUDIO_SAMPLE_RATE_8000 = 8000, /*8K samplerate*/ + AUDIO_SAMPLE_RATE_12000 = 12000, /*12K samplerate*/ + AUDIO_SAMPLE_RATE_11025 = 11025, /*11.025K samplerate*/ + AUDIO_SAMPLE_RATE_16000 = 16000, /*16K samplerate*/ + AUDIO_SAMPLE_RATE_22050 = 22050, /*22.05k samplerate*/ + AUDIO_SAMPLE_RATE_24000 = 24000, /*24K samplerate*/ + AUDIO_SAMPLE_RATE_32000 = 32000, /*32K samplerate*/ + AUDIO_SAMPLE_RATE_44100 = 44100, /*44.1K samplerate*/ + AUDIO_SAMPLE_RATE_48000 = 48000, /*48K samplerate*/ + AUDIO_SAMPLE_RATE_64000 = 64000, /*64K samplerate*/ + AUDIO_SAMPLE_RATE_96000 = 96000, /*96K samplerate*/ + AUDIO_SAMPLE_RATE_BUTT, +}csi_audio_sample_rate_e; + +typedef enum csi_audio_bit_width { + AUDIO_BIT_WIDTH_8 = 0, /*8bit width*/ + AUDIO_BIT_WIDTH_16 = 1, /*16bit width*/ + AUDIO_BIT_WIDTH_24 = 2, /*24bit width*/ + AUDIO_BIT_WIDTH_BUTT, +}csi_audio_bit_width_e; + +typedef enum csi_audio_mode { + AIO_MODE_I2S_MASTER = 0, /* AIO I2S master mode */ + AIO_MODE_I2S_SLAVE, /* AIO I2S slave mode */ + AIO_MODE_PCM_SLAVE_STD, /* AIO PCM slave standard mode */ + AIO_MODE_PCM_SLAVE_NSTD, /* AIO PCM slave non-standard mode */ + AIO_MODE_PCM_MASTER_STD, /* AIO PCM master standard mode */ + AIO_MODE_PCM_MASTER_NSTD, /* AIO PCM master non-standard mode */ + AIO_MODE_BUTT, +}csi_audio_mode_e; + +typedef enum csi_audio_sound_mode { + AUDIO_SOUND_MODE_MONO = 0, /*mono*/ + AUDIO_SOUND_MODE_STEREO = 1, /*stereo*/ + AUDIO_SOUND_MODE_BUTT +}csi_audio_sound_mode_e; + +typedef enum csi_auido_i2stype { + AIO_I2STYPE_INNERCODEC = 0, /*AIO I2S connect inner audio CODEC*/ + AIO_I2STYPE_INNERHDMI, /*AIO I2S connect inner HDMI*/ + AIO_I2STYPE_EXTERN, /*AIO I2S connect extern hardware*/ +}csi_auido_i2stype_e; + +typedef struct csi_aio_attr { + csi_audio_sample_rate_e samplerate; /*sample rate*/ + csi_audio_bit_width_e bitwidth; /*bitwidth*/ + csi_audio_mode_e workmode; /*master or slave mode*/ + csi_audio_sound_mode_e soundmode; /*mono or steror*/ + uint32_t exflag; + uint32_t frmnum; + uint32_t pt_num_per_frm; + uint32_t chncnt; + csi_auido_i2stype_e i2stype; +}csi_aio_attr_s; + +typedef struct csi_audio_frame { + csi_audio_bit_width_e bitwidth; + csi_audio_sound_mode_e soundmode; + void *viraddr[2]; + uint32_t phyaddr[2]; + uint64_t timestamp; /*audio frame timestamp*/ + uint32_t seq; /*audio frame seq*/ + uint32_t len; /*data length per channel in frame*/ + uint32_t poolid[2]; +}csi_audio_frame_s; + +typedef enum csi_audio_track_mode { + AUDIO_TRACK_NORMAL = 0, + AUDIO_TRACK_BOTH_LEFT = 1, + AUDIO_TRACK_BOTH_RIGHT = 2, + AUDIO_TRACK_EXCHANGE = 3, + AUDIO_TRACK_MIX = 4, + AUDIO_TRACK_LEFT_MUTE = 5, + AUDIO_TRACK_RIGHT_MUTE = 6, + AUDIO_TRACK_BOTH_MUTE = 7, + AUDIO_TRACK_BUTT, +}csi_audio_track_mode_e; + +typedef struct csi_audio_save_file_info { + bool cfg; + char filepath[MAX_AUDIO_FILE_PATH_LEN]; + char filename[MAX_AUDIO_FILE_PATH_LEN]; + uint32_t filesize; +}csi_audio_save_file_info_s; + +typedef struct csi_audio_file_status { + bool saving; +}csi_audio_file_status_s; + +typedef enum csi_vqe_workstate { + VQE_WORKSTATE_COMMON = 0, + VQE_WORKSTATE_MUSIC = 1, + VQE_WORKSTATE_NOISY = 2 +}csi_vqe_workstate_e; + +typedef enum csi_audio_hpf_freq { + AUDIO_HPF_FREQ_80 = 80, /*80HZ*/ + AUDIO_HPF_FREQ_120 = 120, /*120Hz*/ + AUDIO_HPF_FREQ_150 = 150, /*150Hz*/ + AUDIO_HPF_FREQ_BUTT, +}csi_audio_hpf_freq_e; + +typedef struct csi_audio_hpf_config { + bool usrmode; + csi_audio_hpf_freq_e hpffreq; +}csi_audio_hpf_config_s; + +typedef struct csi_audio_anr_config { + bool usrmode; + int16_t nrintensity; + int16_t noisedbthr; + int8_t sp_pro_switch; + int32_t reserved; +}csi_audio_anr_config_s; + +typedef struct csi_audio_agc_config { + bool usrmode; + int8_t target_level; + int8_t noise_floor; + int8_t maxgain; + int8_t adjustspeed; + int8_t improvesnr; + int8_t usehighpassfilt; + int8_t outputmode; + int16_t noisesupswitch; + int32_t reserved; +}csi_audio_agc_config_s; + +typedef struct csi_audio_eq_config { + int8_t gaindb[VQE_EQ_BAND_NUM]; + int32_t reserved; +}csi_audio_eq_config_s; + +typedef enum { + PT_PCMU = 0, + PT_1016 = 1, + PT_BUTT +}PAYLOAD_TYPE_E; + +typedef struct csi_audio_stream { + uint8_t *stream; /*the virtual address of stream*/ + uint32_t phyaddr; /*the physics address of stream*/ + uint32_t len; /*stream length,by bytes*/ + uint64_t timestamp; /*frame time stamp*/ + uint32_t seq; /*frame seq,if stream is not a valid frame 0*/ +}csi_audio_stream_s; + +#endif \ No newline at end of file diff --git a/include/hal/csi_audio_dec.h b/include/hal/csi_audio_dec.h new file mode 100644 index 0000000..9683171 --- /dev/null +++ b/include/hal/csi_audio_dec.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: fuqian.zxr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __CSI_AUDIO_DEC_H__ +#define __CSI_AUDIO_DEC_H__ + +#include "csi_audio_common.h" + +#define CSI_AUDIO_DEC_VERSION_MAJOR 0 +#define CSI_AUDIO_DEC_VERSION_MINOR 1 + +typedef enum csi_adec_mode { + ADEC_MODE_PACK = 0, + ADEC_MODE_STREAM, + ADEC_MODE_BUTT +}csi_adec_mode_e; + +typedef struct csi_adec_ch_attr { + PAYLOAD_TYPE_E type; + uint32_t bufsize; + csi_adec_mode_e mode; + void *value; +}csi_adec_ch_attr_s; + +typedef struct csi_adec_decoder { + PAYLOAD_TYPE_E type; + char name[16]; + int32_t (*openendecoder)(void *decoderattr,void **decoder); + int32_t (*decoderfrm)(void *decoder,uint8_t **inbuf,int32_t leftbyte,uint16_t *outbuf,uint32_t *outlen,uint32_t *chns); + int32_t (*getfrminfo)(void *decoder,void *info); + int32_t (*closedecoder)(void *decoder); + int32_t (*resetdecoder)(void *decoder); +}csi_adec_decoder_s; + +typedef struct csi_audio_frame_info { + csi_audio_frame_s *frame; + uint32_t id; +}csi_audio_frame_info_s; + +int32_t csi_adec_get_version(csi_api_version_u *version); + +int32_t csi_adec_create(int32_t adchn,csi_adec_ch_attr_s); +int32_t csi_adec_destroy(int32_t adchn); + +int32_t csi_adec_sendstream(int32_t adchn,const csi_audio_stream_s *stream, bool block); +int32_t csi_adec_clearchnbuf(int32_t adchn); + +int32_t csi_adec_registerdecoder(int32_t handle,csi_adec_decoder_s *decoder); +int32_t csi_adec_unregisterdecoder(int32_t hanbdle); + +int32_t csi_adec_getframe(int32_t adchn,csi_audio_frame_info_s *frameinfo,bool block); +int32_t csi_adec_releaseframe(int32_t adchn,csi_audio_frame_info_s *frameinfo); + +int32_t csi_adec_sendendofstream(int adchn,bool instant); + +#endif \ No newline at end of file diff --git a/include/hal/csi_audio_enc.h b/include/hal/csi_audio_enc.h new file mode 100644 index 0000000..d6ea0ce --- /dev/null +++ b/include/hal/csi_audio_enc.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: fuqian.zxr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CSI_AUDIO_ENC_H__ +#define __CSI_AUDIO_ENC_H__ + +#include +#include +#include + +#include "csi_audio_common.h" + +#define CSI_AUDIO_ENC_VERSION_MAJOR 0 +#define CSI_AUDIO_ENC_VERSION_MINOR 1 + +typedef struct csi_aenc_chn_attr +{ + PAYLOAD_TYPE_E type; + uint32_t numperfrm; + uint32_t buffersize; +}csi_aenc_chn_attr_s; + +typedef struct csi_aec_frame { + csi_audio_frame_s refframe; + bool valid; + bool sysbind; +}csi_aec_frame_s; + +typedef struct csi_aenc_encode { + PAYLOAD_TYPE_E type; + uint32_t maxfrmlen; + char name[16]; + int32_t (*openencoder)(void *encoderattr,void **encoder); + int32_t (*encoderfrm)(void *encoder,const csi_audio_frame_s *data,uint8_t *outbuf,uint32_t *outlen); + int32_t (*closeencoder)(void *encoder); +}csi_aenc_encode_s; + +int32_t csi_adec_get_version(csi_api_version_u *version); + +int32_t csi_aenc_createchn(int32_t aechn,const csi_aenc_chn_attr_s attr); +int32_t csi_aenc_destroychn(int32_t aechn); + +int32_t csi_aenc_sendframe(int32_t aechn,const csi_audio_frame_s *frm,const csi_aec_frame_s *aecfrm); +int32_t csi_aenc_getstream(int32_t aechn,csi_audio_stream_s *stream,int32_t timeout); + +int32_t csi_aenc_releasestream(int32_t aechn,const csi_audio_stream_s *stream); + +int32_t csi_aenc_getfd(int32_t aechn); +int32_t csi_aenc_registerencoder(int32_t handle,csi_aenc_encode_s *encoder); +int32_t csi_aenc_unregisterencoder(int32_t handle); + +int32_t csi_aenc_savefile(int32_t aechn,csi_audio_save_file_info_s *savefileinfo); +int32_t csi_aenc_queryfilestatus(int32_t aechn,csi_audio_file_status_s *filestatus); + +int32_t csi_aec_getstreambufinfo(int32_t aechn,uint32_t *physaddr,uint32_t *size); + +#endif \ No newline at end of file diff --git a/include/hal/csi_camera.h b/include/hal/csi_camera.h new file mode 100644 index 0000000..a59ef70 --- /dev/null +++ b/include/hal/csi_camera.h @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CSI_CAMERA_H__ +#define __CSI_CAMERA_H__ + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Camera logical parts are below, including: input sensor and output channel + * @------------------------------@ + * | Camera Settings _____|______ Channel Settings + * | =================== | | ===================== + * | Exposure | channel[0] | configuration: width/height, pix_fmt + * | R/G/B Gain |_____ ______| ---> Channel Events + * | HDR mode _____|______ + * | ...... | | + * | | channel[1] | configuration: width/height, pix_fmt + * | |_____ ______| ---> Channel Events + * | _____|______ + * | | | + * | | channel[n] | configuration: width/height, pix_fmt + * | |_____ ______| ---> Channel Events + * | CAMERA | + * @------------------------------@ ===> Camera Events (sensor, error, warning...) + */ + +#define CSI_CAMERA_VERSION_MAJOR 0 +#define CSI_CAMERA_VERSION_MINOR 2 + +#define CSI_CAMERA_NAME_MAX_LEN 32 +typedef void *csi_cam_handle_t; +typedef void *csi_cam_event_handle_t; +typedef struct csi_cam_handle_info { + int idx; +} csi_cam_handle_info_t; + +#define MAX_FRAME_COUNT 1024 +typedef enum csi_frame_status_type { + CSI_FRAME_IDLE = 1, + CSI_FRAME_WORKING = 2, + CSI_FRAME_READY = 3, + CSI_FRAME_DISPATCHED = 4, +} csi_frame_status_type_e; + +typedef struct frame_channel_info { + + unsigned char *frame_bufs[MAX_FRAME_COUNT]; + int frame_status[MAX_FRAME_COUNT]; + int refcount[MAX_FRAME_COUNT]; + + int frame_cnt; + csi_frame_s frame[MAX_FRAME_COUNT]; +} frame_channel_info_s; + +typedef struct camera_frame_info { + unsigned char *frame_bufs; + int frame_status; +} camera_frame_info_s; + +#define CSI_CAMERA_CAP_VIDEO_CAPTURE 0x00000001 /* Is a video capture device */ +#define CSI_CAMERA_CAP_META_CAPTURE 0x00800000 /* Is a metadata capture device */ + +typedef struct csi_camera_info { + char camera_name[CSI_CAMERA_NAME_MAX_LEN]; + char device_name[CSI_CAMERA_NAME_MAX_LEN]; + char bus_info[32]; /* e.g. "MIPI-CSI" */ + unsigned int capabilities; /* bit mask of CSI_CAMERA_CAP_xx */ +} csi_camera_info_s; + +#define CSI_CAMERA_MAX_COUNT 16 +typedef struct csi_camera_infos { + unsigned int count; + csi_camera_info_s info[CSI_CAMERA_MAX_COUNT]; +} csi_camera_infos_s; + +#define CSI_CAMERA_MODE_MAX_COUNT 16 +typedef struct csi_camera_modes { + int count; + struct { + int mode_id; + char description[128]; + } modes[CSI_CAMERA_MODE_MAX_COUNT]; +} csi_camera_modes_s; + +typedef struct csi_camera_mode_cfg { + int mode_id; + char *calibriation; /* set NULL to use default in system */ + char *lib3a; /* set NULL to use default in system */ +} csi_camera_mode_cfg_s; + +typedef enum csi_camera_property_type { + CSI_CAMERA_PROPERTY_TYPE_INTEGER = 1, + CSI_CAMERA_PROPERTY_TYPE_BOOLEAN = 2, + CSI_CAMERA_PROPERTY_TYPE_ENUM = 3, + CSI_CAMERA_PROPERTY_TYPE_STRING = 7, + CSI_CAMERA_PROPERTY_TYPE_BITMASK = 8, +} csi_camera_property_type_e; + +typedef union csi_camera_property_data { + bool bool_value; + int int_value; + int enum_value; + uint32_t bitmask_value; + char str_value[32]; +} csi_camera_property_data_u; + +typedef struct csi_camera_property_description { + unsigned int id; /* CSI_CAMERA_PID_xx */ + csi_camera_property_type_e type; /* data type */ + char name[32]; /* Whatever */ + int minimum; /* Note signedness */ + int maximum; + int step; + csi_camera_property_data_u default_value; + csi_camera_property_data_u value; /*current value*/ + unsigned int flags; + unsigned int reserved[2]; +} csi_camera_property_description_s; + +typedef struct csi_camera_property { + unsigned int id; + csi_camera_property_type_e type; /* data type */ + csi_camera_property_data_u value; +} csi_camera_property_s; + +typedef struct csi_camera_properties { + unsigned int count; + csi_camera_property_s *property; +} csi_camera_properties_s; + +typedef enum csi_camera_channel_id { + CSI_CAMERA_CHANNEL_0 = 0, + CSI_CAMERA_CHANNEL_1, + CSI_CAMERA_CHANNEL_2, + CSI_CAMERA_CHANNEL_3, + CSI_CAMERA_CHANNEL_4, + CSI_CAMERA_CHANNEL_5, + CSI_CAMERA_CHANNEL_6, + CSI_CAMERA_CHANNEL_7, + CSI_CAMERA_CHANNEL_MAX_COUNT +} csi_camera_channel_id_e; + +typedef enum csi_camera_channel_status { + CSI_CAMERA_CHANNEL_INVALID = -1, /* channel can't be openned */ + CSI_CAMERA_CHANNEL_CLOSED, + CSI_CAMERA_CHANNEL_OPENED, + CSI_CAMERA_CHANNEL_RUNNING, + CSI_CAMERA_CHANNEL_EXCEPTION, +} csi_camera_channel_status_e; + +typedef enum csi_camera_channel_capture_type { + CSI_CAMERA_CHANNEL_CAPTURE_VIDEO = (1 << 0), + CSI_CAMERA_CHANNEL_CAPTURE_META = (1 << 1), +} csi_camera_channel_capture_type_e; + +typedef struct csi_camera_channel_cfg { + csi_camera_channel_id_e chn_id; + unsigned int capture_type; /* bitmask of: csi_camera_channel_capture_type_e */ + unsigned int frm_cnt; + csi_img_format_t img_fmt; + csi_img_type_e img_type; + unsigned int meta_fields; /* bitmask of: csi_camera_meta_id_e */ + csi_camera_channel_status_e status; +} csi_camera_channel_cfg_s; + +typedef enum csi_camera_event_type { + CSI_CAMERA_EVENT_TYPE_INVALID = -1, + CSI_CAMERA_EVENT_TYPE_CAMERA, + CSI_CAMERA_EVENT_TYPE_CHANNEL0, + CSI_CAMERA_EVENT_TYPE_CHANNEL1, + CSI_CAMERA_EVENT_TYPE_CHANNEL2, + CSI_CAMERA_EVENT_TYPE_CHANNEL3, + CSI_CAMERA_EVENT_TYPE_CHANNEL4, + CSI_CAMERA_EVENT_TYPE_CHANNEL5, + CSI_CAMERA_EVENT_TYPE_CHANNEL6, + CSI_CAMERA_EVENT_TYPE_CHANNEL7, +} csi_camera_event_type_e; + +typedef enum csi_camera_event_id { + CSI_CAMERA_EVENT_WARNING = 1 << 0, + CSI_CAMERA_EVENT_ERROR = 1 << 1, + CSI_CAMERA_EVENT_SENSOR_FIRST_IMAGE_ARRIVE = 1 << 2, + CSI_CAMERA_EVENT_ISP_3A_ADJUST_READY = 1 << 3, + + CSI_CAMERA_EVENT_MAX_COUNT = 32 +} csi_camera_event_id_e; + +typedef enum csi_camera_channel_event_id { + CSI_CAMERA_CHANNEL_EVENT_FRAME_READY = 1 << 0, + CSI_CAMERA_CHANNEL_EVENT_FRAME_PUT = 1 << 1, + CSI_CAMERA_CHANNEL_EVENT_OVERFLOW = 1 << 2, + + CSI_CAMERA_CHANNEL_EVENT_MAX_COUNT = 32 +} csi_camera_channel_event_id_e; + +typedef struct csi_camera_event_subscription { + csi_camera_event_type_e type; + unsigned int id; /* bitmasks */ +} csi_camera_event_subscription_s; + +typedef struct csi_camera_event { + csi_camera_event_type_e type; + unsigned int id; + struct timespec timestamp; + union { + char bin[128]; + }; +} csi_camera_event_s; + +int csi_camera_get_version(csi_api_version_u *version); + +int csi_camera_query_list(csi_camera_infos_s *infos); +int csi_camera_open(csi_cam_handle_t *cam_handle, const char *device_name); +int csi_camera_close(csi_cam_handle_t cam_handle); + +int csi_camera_get_modes(csi_cam_handle_t cam_handle, + csi_camera_modes_s *modes); + +int csi_camera_set_mode(csi_cam_handle_t cam_handle, + csi_camera_mode_cfg_s *cfg); + +int csi_camera_get_io_pattern(csi_cam_handle_t cam_handle, int *pattern); +int csi_camera_get_frame_config(csi_cam_handle_t cam_handle, csi_img_format_t *img_fmt, csi_frame_config_s *frm_cfg); +int csi_camera_set_frame_config(csi_cam_handle_t cam_handle, csi_img_format_t *img_fmt, csi_frame_config_s *frm_cfg); + +int csi_sensor_enable_colobar(csi_cam_handle_t cam_handle, int en); +int csi_camera_query_property(csi_cam_handle_t cam_handle, + csi_camera_property_description_s *desc); +int csi_camera_get_property(csi_cam_handle_t cam_handle, + csi_camera_properties_s *properties); +int csi_camera_set_property(csi_cam_handle_t cam_handle, + csi_camera_properties_s *properties); + +int csi_camera_channel_open(csi_cam_handle_t cam_handle, + csi_camera_channel_cfg_s *cfg); +int csi_camera_channel_close(csi_cam_handle_t cam_handle, + csi_camera_channel_id_e chn_id); +int csi_camera_channel_query(csi_cam_handle_t cam_handle, + csi_camera_channel_cfg_s *cfg); + +int csi_camera_get_frame_count(csi_cam_handle_t cam_handle, + csi_camera_channel_id_e chn_id); +int csi_camera_get_frame(csi_cam_handle_t cam_handle, + csi_camera_channel_id_e chn_id, + csi_frame_s *frame, int timeout); + +int csi_camera_put_frame(csi_frame_s *frame); + + +int csi_camera_dequeue_frame(csi_cam_handle_t cam_handle, + csi_camera_channel_id_e chn_id, + csi_frame_ex_s **frame, + int timeout); + +int csi_camera_enqueue_frame(csi_cam_handle_t cam_handle, + csi_camera_channel_id_e chn_id, + csi_frame_ex_s *frame); + + +int csi_camera_create_event(csi_cam_event_handle_t *event_handle, + csi_cam_handle_t cam_handle); +int csi_camera_destory_event(csi_cam_event_handle_t event_handle); + +int csi_camera_subscribe_event(csi_cam_event_handle_t event_handle, + csi_camera_event_subscription_s *subscribe); +int csi_camera_unsubscribe_event(csi_cam_event_handle_t event_handle, + csi_camera_event_subscription_s *subscribe); +int csi_camera_get_event(csi_cam_event_handle_t event_handle, + csi_camera_event_s *event, + int timeout); + +int csi_camera_channel_start(csi_cam_handle_t cam_handle, + csi_camera_channel_id_e chn_id); +int csi_camera_channel_stop(csi_cam_handle_t cam_handle, + csi_camera_channel_id_e chn_id); + +#ifdef __cplusplus +} +#endif + +#endif /* __CSI_CAMERA_H__ */ diff --git a/include/hal/csi_camera_frame.h b/include/hal/csi_camera_frame.h new file mode 100644 index 0000000..ba3f242 --- /dev/null +++ b/include/hal/csi_camera_frame.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CSI_CAMERA_FRAME_H__ +#define __CSI_CAMERA_FRAME_H__ + +#include +#include +#include + +#include + +#define CSI_CAMERA_META_MAX_LEN 1024 + +typedef enum csi_camera_meta_id { + CSI_CAMERA_META_ID_CAMERA_NAME = (1<<0), /* str_value */ + CSI_CAMERA_META_ID_CHANNEL_ID = (1<<1), /* uint_value */ + CSI_CAMERA_META_ID_FRAME_ID = (1<<2), /* uint_value: Re-Count from zero when start() */ + CSI_CAMERA_META_ID_TIMESTAMP = (1<<3), /* time_value: Get from gettimeofday() */ + CSI_CAMERA_META_ID_HDR = (1<<4), /* bool_value */ +} csi_camera_meta_id_e; + +#define CSI_CAMERA_META_NO_FIELD 0 +#define CSI_CAMERA_META_DEFAULT_FIELDS (CSI_CAMERA_META_ID_CAMERA_NAME | \ + CSI_CAMERA_META_ID_CHANNEL_ID | \ + CSI_CAMERA_META_ID_FRAME_ID | \ + CSI_CAMERA_META_ID_TIMESTAMP) + +typedef struct csi_camrea_meta_unit { + csi_camera_meta_id_e id; + csi_meta_value_type_e type; + union { + bool bool_value; + int int_value; + unsigned int uint_value; + char str_value[32]; + struct timeval time_value; + }; +} csi_camrea_meta_unit_s; + +typedef struct csi_camera_meta { + unsigned int count; + size_t size; + csi_camrea_meta_unit_s *units; // Is meta_unit array head +} csi_camera_meta_s; + +int csi_camera_frame_alloc_meta(csi_camera_meta_s **meta, int meta_count, size_t *meta_data_size); +int csi_camera_frame_free_meta(csi_camera_meta_s *meta); + +int csi_camera_frame_get_meta_unit(csi_camrea_meta_unit_s *meta_unit, + csi_camera_meta_s *meta_data, + csi_camera_meta_id_e meta_field); + + +#endif /* __CSI_CAMERA_FRAME_H__ */ + diff --git a/include/hal/csi_camera_platform_spec.h b/include/hal/csi_camera_platform_spec.h new file mode 100644 index 0000000..db5dcda --- /dev/null +++ b/include/hal/csi_camera_platform_spec.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CSI_CAMERA_PLATFORM_SPEC_H__ +#define __CSI_CAMERA_PLATFORM_SPEC_H__ +#include +#include + +/******************************************************************************/ +/*********** Platform Spec in Enum ********************************************/ +/******************************************************************************/ + +#define CAMERA_PROPERTY_MAX_ENUM_COUNT 16 + +typedef struct camera_support_enums { + int count; + int enums[CAMERA_PROPERTY_MAX_ENUM_COUNT]; +} camera_spec_enums_s; + +typedef enum camera_sepc_enums { + CAMERA_SPEC_ENUM_CHANNEL_PIX_FMT, + CAMERA_SPEC_ENUM_CHANNEL_IMG_TYPE, + CAMERA_SPEC_ENUM_CAMERA_EVENT_TYPES, + CAMERA_SPEC_ENUM_CHANNEL_EVENT_TYPES, + + /* Camera Properties below */ + CAMERA_SPEC_ENUM_CAMERA_EXPOSURE_MODES = CSI_CAMERA_PID_EXPOSURE_MODE, +} camera_sepc_enums_e; + +const camera_spec_enums_s *camera_spec_get_enum_array(int property_id); + + +/******************************************************************************/ +/*********** Platform Spec in Bitmask *****************************************/ +/******************************************************************************/ + +#define CAMERA_PROPERTY_MAX_BITMASK_COUNT 32 + +typedef struct camera_support_bitmasks { + int count; + int bitmask[CAMERA_PROPERTY_MAX_BITMASK_COUNT]; +} camera_spec_bitmasks_t; + + +typedef enum camera_sepc_bitmasks { + CAMERA_SPEC_BITMAKS_CHANNEL_CAPTURE_TYPE, + CAMERA_SPEC_BITMAKS_CHANNEL_META_TYPE, + + /* Camera Properties below */ + CAMERA_SPEC_BITMAKS_CAMERA_3A_LOCK = CSI_CAMERA_PID_3A_LOCK, +} camera_sepc_bitmasks_e; + +const camera_spec_bitmasks_t *camera_spec_get_bitmask_array(int property_id); + +#endif /* __CSI_CAMERA_PLATFORM_SPEC_H__ */ + diff --git a/include/hal/csi_camera_property.h b/include/hal/csi_camera_property.h new file mode 100644 index 0000000..6594967 --- /dev/null +++ b/include/hal/csi_camera_property.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CSI_CAMERA_PROPERTY_H__ +#define __CSI_CAMERA_PROPERTY_H__ + +/* Camera Property ID */ +#define CSI_CAMERA_PID_BASE (0x009a0000 | 0x900) + +#define CSI_CAMERA_FLAG_NEXT_CTRL 0x80000000 + +/* image processing releated */ +#define CSI_CAMERA_PID_HFLIP (CSI_CAMERA_PID_BASE + 0x1) +#define CSI_CAMERA_PID_VFLIP (CSI_CAMERA_PID_BASE + 0x2) +#define CSI_CAMERA_PID_ROTATE (CSI_CAMERA_PID_BASE + 0x3) + +/* exposure related */ +#define CSI_CAMERA_PID_EXPOSURE_MODE (CSI_CAMERA_PID_BASE + 0x11) +enum CSI_CAMERA_EXPOSURE_MODE { + CSI_CAMERA_EXPOSURE_MODE_AUTO = 0, + CSI_CAMERA_EXPOSURE_MANUAL = 1, + CSI_CAMERA_EXPOSURE_SHUTTER_PRIORITY = 2, + CSI_CAMERA_EXPOSURE_APERTURE_PRIORITY = 3, +}; +#define CSI_CAMERA_PID_EXPOSURE_ABSOLUTE (CSI_CAMERA_PID_BASE + 0x12) +#define CSI_CAMERA_PID_EXPOSURE_AUTO_PRIORITY (CSI_CAMERA_PID_BASE + 0x13) +#define CSI_CAMERA_PID_EXPOSURE_BIAS (CSI_CAMERA_PID_BASE + 0x14) +#define CSI_CAMERA_PID_EXPOSURE_METERING (CSI_CAMERA_PID_BASE + 0x15) +enum CSI_CAMERA_PID_EXPOSURE_METERING_MODE { + CSI_CAMERA_EXPOSURE_METERING_AVERAGE = 0, + CSI_CAMERA_EXPOSURE_METERING_CENTER_WEIGHTED = 1, + CSI_CAMERA_EXPOSURE_METERING_SPOT = 2, + CSI_CAMERA_EXPOSURE_METERING_MATRIX = 3, +}; + +/* Focus related */ +#define CSI_CAMERA_PID_FOCUS_ABSOLUTE (CSI_CAMERA_PID_BASE + 0x21) +#define CSI_CAMERA_PID_FOCUS_RELATIVE (CSI_CAMERA_PID_BASE + 0x22) +#define CSI_CAMERA_PID_FOCUS_AUTO (CSI_CAMERA_PID_BASE + 0x23) + +#define CSI_CAMERA_PID_AUTO_FOCUS_START (CSI_CAMERA_PID_BASE + 0x24) +#define CSI_CAMERA_PID_AUTO_FOCUS_STOP (CSI_CAMERA_PID_BASE + 0x25) + +#define CSI_CAMERA_PID_AUTO_FOCUS_STATUS (CSI_CAMERA_PID_BASE + 0x26) +#define CSI_CAMERA_AUTO_FOCUS_STATUS_IDLE (1<<0) +#define CSI_CAMERA_AUTO_FOCUS_STATUS_BUSY (1<<1) +#define CSI_CAMERA_AUTO_FOCUS_STATUS_REACHED (1<<2) +#define CSI_CAMERA_AUTO_FOCUS_STATUS_FAILED (1<<3) + +/* 3A misc */ +#define CSI_CAMERA_PID_AUTO_PRESET_WHITE_BALANCE (CSI_CAMERA_PID_BASE + 0x31) +enum CSI_CAMERA_AUTO_PRESET_WHITE_BALANCE_MODE { + CSI_CAMERA_WHITE_BALANCE_MANUAL = 0, + CSI_CAMERA_WHITE_BALANCE_AUTO = 1, + CSI_CAMERA_WHITE_BALANCE_CUSTOM1 = 2, + CSI_CAMERA_WHITE_BALANCE_CUSTOM2 = 3, +}; + +#define CSI_CAMERA_PID_3A_LOCK (CSI_CAMERA_PID_BASE + 0x32) +#define CSI_CAMERA_LOCK_EXPOSURE (1 << 0) +#define CSI_CAMERA_LOCK_WHITE_BALANCE (1 << 1) +#define CSI_CAMERA_LOCK_FOCUS (1 << 2) + +/* Gain */ +#define CSI_CAMERA_PID_RED_GAIN (CSI_CAMERA_PID_BASE + 0x41) +#define CSI_CAMERA_PID_GREEN_GAIN (CSI_CAMERA_PID_BASE + 0x42) +#define CSI_CAMERA_PID_BLUE_GAIN (CSI_CAMERA_PID_BASE + 0x43) +#define CSI_CAMERA_PID_WIDE_DYNAMIC_RANGE (CSI_CAMERA_PID_BASE + 0x44) +#define CSI_CAMERA_PID_ZOOM_ABSOLUTE (CSI_CAMERA_PID_BASE + 0x45) +#define CSI_CAMERA_PID_ZOOM_RELATIVE (CSI_CAMERA_PID_BASE + 0x46) +#define CSI_CAMERA_PID_ZOOM_CONTINOUS (CSI_CAMERA_PID_BASE + 0x47) + +/* PAN & TILT */ +#define CSI_CAMERA_PID_PAN_RELATIVE (CSI_CAMERA_PID_BASE + 0x51) +#define CSI_CAMERA_PID_TILT_RELATIVE (CSI_CAMERA_PID_BASE + 0x52) +#define CSI_CAMERA_PID_PAN_ABSOLUTE (CSI_CAMERA_PID_BASE + 0x53) +#define CSI_CAMERA_PID_TILT_ABSOLUTE (CSI_CAMERA_PID_BASE + 0x54) +#define CSI_CAMERA_PID_PAN_RESET (CSI_CAMERA_PID_BASE + 0x55) +#define CSI_CAMERA_PID_TILT_RESET (CSI_CAMERA_PID_BASE + 0x56) + +/* misc enhance */ +#define CSI_CAMERA_PID_HDR_MODE (CSI_CAMERA_PID_BASE + 0x61) +#define CSI_CAMERA_PID_BAND_STOP_FILTER (CSI_CAMERA_PID_BASE + 0x62) +#define CSI_CAMERA_PID_IMAGE_STABILIZATION (CSI_CAMERA_PID_BASE + 0x63) + +#endif /* __CSI_CAMERA_PROPERTY_H__ */ + diff --git a/include/hal/csi_common.h b/include/hal/csi_common.h new file mode 100644 index 0000000..52948dc --- /dev/null +++ b/include/hal/csi_common.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CSI_COMMON_H__ +#define __CSI_COMMON_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) + +typedef union csi_api_version { + struct { + uint16_t minor; + uint16_t major; + }; + uint32_t version; +} csi_api_version_u; + +typedef struct csi_size { + uint32_t width; + uint32_t height; +} csi_size_s; + +typedef struct csi_rect { + uint32_t pos_x; + uint32_t pos_y; + uint32_t width; + uint32_t height; +} csi_rect_s; + + +enum { + CSI_IO_PATTERN_INTERNAL_MEMORY = 0x01, /* Internal preparation IO memory mode */ + CSI_IO_PATTERN_EXTERNAL_MEMORY = 0x02 /* External preparation IO memory mode */ +}; + +typedef enum csi_common_err_code { + CSI_SUCCESS, + CSI_ERR_BAD_PARAM, + CSI_ERR_NOT_SUPPORT, + CSI_ERR_BUSY, + CSI_ERR_OUT_OF_MEMORY, + CSI_ERR_NEED_MORE_DATA, + CSI_ERR_NEED_MORE_IO_FRAME, + CSI_ERR_TIMEOUT, + CSI_ERR_WAIT +} csi_common_err_code_e; + + +int32_t csi_hal_init(); +int32_t csi_hal_deinit(); + + +#ifdef __cplusplus +} +#endif + +#endif /* __CSI_COMMON_H__ */ diff --git a/include/hal/csi_fce.h b/include/hal/csi_fce.h new file mode 100644 index 0000000..568da90 --- /dev/null +++ b/include/hal/csi_fce.h @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright(C) 2021 Alibaba Communications Inc. + * Author: David Li + */ + +#ifndef _FCE_HAL_H_ +#define _FCE_HAL_H_ + +#ifdef __cplusplus +extern "C"{ +#endif + +#include "fce_cfg.h" + +enum FCE_EVENT { + FCE_EVENT_COMPLETE = 0, + FCE_EVENT_PAUSE, + FCE_EVENT_BUSERR, + FCE_EVENT_TIMEOUT, + FCE_EVENT_UNDEFMSG, + FCE_EVENT_SOCKERR, + FCE_EVENT_OTHER +}; + +typedef void *csi_fce_handle_t; +typedef void *csi_fce_sock_handle_t; + +int csi_fce_open(csi_fce_handle_t *fce_handle, char *name); +int csi_fce_close(csi_fce_handle_t fce_handle); +int csi_fce_query_result_cnt(csi_fce_handle_t fce_handle, unsigned int *cnt); +int csi_fce_set_result_cnt(csi_fce_handle_t fce_handle, unsigned int *cnt); +int csi_fce_set_attr(csi_fce_handle_t fce_handle, struct fce_cfg *cfg); +int csi_fce_get_attr(csi_fce_handle_t fce_handle, struct fce_cfg *cfg); +int csi_fce_do_compare(csi_fce_handle_t fce_handle, struct target_vector *target_vect); +int csi_fce_get_result(csi_fce_handle_t fce_handle, struct top_result *top_buf); +int csi_fce_suspend(csi_fce_handle_t fce_handle); +int csi_fce_resume(csi_fce_handle_t fce_handle); + +unsigned long csi_fce_create_featurelib(csi_fce_handle_t fce_handle, unsigned int size); +int csi_fce_release_featurelib(csi_fce_handle_t fce_handle, unsigned long offset); + +int csi_fce_config_init(struct fce_cfg *cfg); +int csi_fce_check_result(struct top_result *res1, struct top_result *res2); + +int csi_fce_init_sock(csi_fce_sock_handle_t *sock_handle, unsigned long timeout_ms); +int csi_fce_release_sock(csi_fce_sock_handle_t sock_handle); +enum FCE_EVENT csi_fce_waitevent(csi_fce_sock_handle_t sock_handle); + +#ifdef __cplusplus +} +#endif + +#endif // #ifndef _FCE_HAL_H_ \ No newline at end of file diff --git a/include/hal/csi_fce_cfg.h b/include/hal/csi_fce_cfg.h new file mode 100644 index 0000000..fbade60 --- /dev/null +++ b/include/hal/csi_fce_cfg.h @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright(C) 2021 Alibaba Communications Inc. + * Author: David Li + */ + +#ifndef FCE_CFG_H +#define FCE_CFG_H + +#ifdef __cplusplus +extern "C"{ +#endif + +#define MAX_BASE_NUM 1000000 +#define TOPREG_CNT 8 +#define TARGET_VECTOR_CNT 64 +#define PREPRO_VECTOR_CNT 64 +#define BITS_PER_BYTE 8 + +enum FCE_BASE_NUM { + BASENUM_1 = 0, + BASENUM_10, + BASENUM_1w, + BASENUM_5w, + BASENUM_10w, + BASENUM_20w, + BASENUM_50w, + BASENUM_100w, + BASENUM_MAX = BASENUM_100w, +}; + +enum FCE_DIMENSION { + DIM_128 = 0, + DIM_160, + DIM_192, + DIM_224, + DIM_256, + DIM_MAX = DIM_256, +}; + +enum FCE_INT_WIDTH { + INT_WIDTH_8 = 0, + INT_WIDTH_4, + INT_WIDTH_MAX = INT_WIDTH_4, +}; + +enum FCE_SIGN_SEL { + UNSIGN = 0, + SIGN, + SIGSEL_MAX = SIGN, +}; + +struct target_vector { + int value; +}; + +struct prepro_vector { + int value; +}; + +enum FCE_ENDIAN_SEL { + FCE_LIT_ENDIAN = 0, + FCE_BIG_ENDIAN, + FCE_ENDIAN_MAX = FCE_BIG_ENDIAN, +}; + +struct fce_cfg { + unsigned int base_num; // feature num in library, < MAX_BASE_NUM + // comparasion start address in feature library buffer, default is 0 + unsigned long offset_base; + unsigned long saddr; // fill in kernel driver + enum FCE_DIMENSION dim; // dimension + enum FCE_INT_WIDTH width; + enum FCE_SIGN_SEL sigsel; // sign or unsign + unsigned int readnum; // number of target vector, unit byte + // data endian format of feature library + enum FCE_ENDIAN_SEL base_endian; + // data endian format of target feature vector for comparasion + enum FCE_ENDIAN_SEL target_endian; + // data endian format of xor operation on the feature library + enum FCE_ENDIAN_SEL prepro_endian; + struct prepro_vector prepro_vect[PREPRO_VECTOR_CNT]; // xor vector +}; + +struct top_result { + int match_value; + unsigned int match_index; +}; + +#ifdef __cplusplus +} +#endif + +#endif // #ifndef FCE_CFG_H \ No newline at end of file diff --git a/include/hal/csi_frame.h b/include/hal/csi_frame.h new file mode 100644 index 0000000..17b9d33 --- /dev/null +++ b/include/hal/csi_frame.h @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CSI_FRAME_H__ +#define __CSI_FRAME_H__ + +#include +#include +#include +#include + +/* + * CSI frame defination is below, including + * + * @-- [frm_id] ----------------@ + * | __________ ___________ | + * | | | | | | + * | | img_info | | meta_info | | + * | |__________| |___________| | + * | | + * @------- csi_frame_s --------@ + */ + +#define CSI_IMAGE_MAX_PLANES 3 +#define CSI_IMAGE_I420_PLANES 2 + +typedef enum csi_pixel_fmt { + CSI_PIX_FMT_I420, // YYYYYYYY UU VV, alias: YUV420P/YU12/IYUV + CSI_PIX_FMT_NV12, // YYYYYYYY UV UV, alias: YUV420SP + CSI_PIX_FMT_BGR, + CSI_PIX_FMT_RAW_8BIT,/*default no align*/ + CSI_PIX_FMT_RAW_10BIT, + CSI_PIX_FMT_RAW_12BIT, + CSI_PIX_FMT_RAW_14BIT, + CSI_PIX_FMT_RAW_16BIT, + CSI_PIX_FMT_RGB_PLANAR_888, + CSI_PIX_FMT_RGB_INTEVLEAVED_888, + CSI_PIX_FMT_YUV_PLANAR_422, + CSI_PIX_FMT_YUV_PLANAR_420, + CSI_PIX_FMT_YUV_PLANAR_444, + CSI_PIX_FMT_YUV_SEMIPLANAR_422, + CSI_PIX_FMT_YUV_SEMIPLANAR_420, + CSI_PIX_FMT_YUV_SEMIPLANAR_444, + CSI_PIX_FMT_YUV_TEVLEAVED_422, + CSI_PIX_FMT_YUV_TEVLEAVED_420, + CSI_PIX_FMT_YUV_TEVLEAVED_444, +} csi_pixel_fmt_e; + +typedef enum csi_color_gamut { + CSI_COLOR_GAMUT_DEFAULT = 0, + CSI_COLOR_GAMUT_BT601, + CSI_COLOR_GAMUT_BT709, + CSI_COLOR_GAMUT_BT2020, +} csi_color_gamut_e; + +typedef enum csi_meta_type { + CSI_META_TYPE_SYSTEM, + CSI_META_TYPE_CAMERA, + CSI_META_TYPE_VDEC, + CSI_META_TYPE_GPU, + CSI_META_TYPE_G2D, +} csi_meta_type_e; + +typedef enum csi_img_type { + CSI_IMG_TYPE_DMA_BUF, // memory allocated via dma-buf + CSI_IMG_TYPE_SYSTEM_CONTIG, // memory allocated via kmalloc + CSI_IMG_TYPE_CARVEOUT, // memory allocated from reserved memory + CSI_IMG_TYPE_UMALLOC, // memory allocated from user mode malloc + CSI_IMG_TYPE_SHM, // memory allocated from share memory() +} csi_img_type_e; + +typedef enum csi_meta_value_type { + CSI_META_VALUE_TYPE_BOOL, + CSI_META_VALUE_TYPE_INT, + CSI_META_VALUE_TYPE_UINT, + CSI_META_VALUE_TYPE_STR, + CSI_META_VALUE_TYPE_TIMEVAL, +} csi_meta_value_type_e; + + +/* + * CSI frame config design + * + * line_stride -->| + * img_width -->| | + * _____________|___| __ start addr of frame/image, alignment (such as 4096) + * | | | + * | img_content | | + * |_____________|___| __ end addr of image, No alignment requirement + * | extra_data | + * |_________________| __ end addr of frame, No alignment requirement + */ + +typedef struct csi_img_format { + uint32_t width; + uint32_t height; + csi_pixel_fmt_e pix_fmt; +} csi_img_format_t; + + +typedef struct { + int32_t stride_alignment; // should >= to the width of the image according to IP design + int32_t addr_alignment; // physical begin address alignment according to IP design + int32_t extra_size; // storaging platform private data according to IP design (meta data storage is suggested) + int32_t min_buffers_count; // minimum number of buffers required +} csi_frame_config_s; // New defined structure + +typedef struct { + int fds; // stores in dma_buf memory(s) + unsigned long offset; +} cam_frame_dmabuf_t; + +typedef struct csi_img { + csi_img_type_e type; + size_t size; + uint32_t width; + uint32_t height; + csi_pixel_fmt_e pix_format; + csi_color_gamut_e color_gamut; + uint32_t num_planes; + union { + cam_frame_dmabuf_t dmabuf[CSI_IMAGE_MAX_PLANES]; // stores in dma_buf memory(s) + int fds[CSI_IMAGE_MAX_PLANES]; // stores in dma_buf memory(s) + void *phy_addr[CSI_IMAGE_MAX_PLANES]; // stores in phy contigous memory(s) + void *usr_addr[CSI_IMAGE_MAX_PLANES]; // stores in usr contigous memory(s) + }; + uint32_t strides[CSI_IMAGE_MAX_PLANES]; + uint32_t offsets[CSI_IMAGE_MAX_PLANES]; + uint64_t modifier; + void *priv; +} csi_img_s; + +typedef struct csi_meta { + csi_meta_type_e type; + size_t size; + void *data; +} csi_meta_s; + +typedef struct csi_frame { + csi_img_s img; + csi_meta_s meta; +} csi_frame_s; + + +int csi_frame_reference(csi_frame_s *frame_dest, csi_frame_s *frame_src); +int csi_frame_release(csi_frame_s *frame); +void *csi_frame_mmap(csi_frame_s *frame); +int csi_frame_munmap(csi_frame_s *frame); + +#endif /* __CSI_FRAME_H__ */ diff --git a/include/hal/csi_frame_ex.h b/include/hal/csi_frame_ex.h new file mode 100644 index 0000000..335af5b --- /dev/null +++ b/include/hal/csi_frame_ex.h @@ -0,0 +1,176 @@ +/** +* Copyright (C) 2021 Alibaba Group Holding Limited +**/ +#ifndef _CSI_FRAME_EX_H +#define _CSI_FRAME_EX_H + +#include + +#define MAX_PLANE_COUNT 3 + +typedef enum { + CSI_PIXEL_FORMAT_RGB_444 = 0, + CSI_PIXEL_FORMAT_RGB_555, + CSI_PIXEL_FORMAT_RGB_565, + CSI_PIXEL_FORMAT_RGB_888, + + CSI_PIXEL_FORMAT_BGR_444, + CSI_PIXEL_FORMAT_BGR_555, + CSI_PIXEL_FORMAT_BGR_565, + CSI_PIXEL_FORMAT_BGR_888, + + CSI_PIXEL_FORMAT_ARGB_1555, + CSI_PIXEL_FORMAT_ARGB_4444, + CSI_PIXEL_FORMAT_ARGB_8565, + CSI_PIXEL_FORMAT_ARGB_8888, + CSI_PIXEL_FORMAT_ARGB_2BPP, + + CSI_PIXEL_FORMAT_ABGR_1555, + CSI_PIXEL_FORMAT_ABGR_4444, + CSI_PIXEL_FORMAT_ABGR_8565, + CSI_PIXEL_FORMAT_ABGR_8888, + + CSI_PIXEL_FORMAT_RGB_BAYER_8BPP, + CSI_PIXEL_FORMAT_RGB_BAYER_10BPP, + CSI_PIXEL_FORMAT_RGB_BAYER_12BPP, + CSI_PIXEL_FORMAT_RGB_BAYER_14BPP, + CSI_PIXEL_FORMAT_RGB_BAYER_16BPP, + + CSI_PIXEL_FORMAT_YVU_PLANAR_422, + CSI_PIXEL_FORMAT_YVU_PLANAR_420, + CSI_PIXEL_FORMAT_YVU_PLANAR_444, + + CSI_PIXEL_FORMAT_YVU_SEMIPLANAR_422, + CSI_PIXEL_FORMAT_YVU_SEMIPLANAR_420, + CSI_PIXEL_FORMAT_YVU_SEMIPLANAR_444, + + CSI_PIXEL_FORMAT_YUV_SEMIPLANAR_422, + CSI_PIXEL_FORMAT_YUV_SEMIPLANAR_420, + CSI_PIXEL_FORMAT_YUV_SEMIPLANAR_444, + + CSI_PIXEL_FORMAT_YUYV_PACKAGE_422, + CSI_PIXEL_FORMAT_YVYU_PACKAGE_422, + CSI_PIXEL_FORMAT_UYVY_PACKAGE_422, + CSI_PIXEL_FORMAT_VYUY_PACKAGE_422, + CSI_PIXEL_FORMAT_YYUV_PACKAGE_422, + CSI_PIXEL_FORMAT_YYVU_PACKAGE_422, + CSI_PIXEL_FORMAT_UVYY_PACKAGE_422, + CSI_PIXEL_FORMAT_VUYY_PACKAGE_422, + CSI_PIXEL_FORMAT_VY1UY0_PACKAGE_422, + + CSI_PIXEL_FORMAT_YUV_400, + CSI_PIXEL_FORMAT_UV_420, + + CSI_PIXEL_FORMAT_MAX +} csi_pixel_format_e; + +#if 0 +typedef enum { + CSI_PICTURE_TYPE_NONE = 0, /* undefined */ + CSI_PICTURE_TYPE_I, /* intra */ + CSI_PICTURE_TYPE_P, /* predicted */ + CSI_PICTURE_TYPE_B, /* BI-dir predictd */ + CSI_PICTURE_TYPE_S, /* S(GMC)-VOP MPEG-4 */ + CSI_PICTURE_TYPE_SI, /* Switching intra */ + CSI_PICTURE_TYPE_SP, /* Switching Predicted */ + CSI_PICTURE_TYPE_BI, /* BI Type */ +} csi_picture_type_e; +#endif + + +typedef enum { + CSI_CHROMA_LOCATION_LEFT, + CSI_CHROMA_LOCATION_CENTER, + CSI_CHROMA_LOCATION_TOPLEFT, + CSI_CHROMA_LOCATION_TOP, + CSI_CHROMA_LOCATION_BOTTOMLEFT, + CSI_CHROMA_LOCATION_BOTTOM, + CSI_CHROMA_LOCATION_DV420 +} csi_chroma_location_e; + +#if 0 +typedef enum { + CSI_FRAME_COMPRESS_MODE_NONE = 0, /* no compress */ + CSI_FRAME_COMPRESS_MODE_SEQ, /* compress unit is 256x1 bytes as a segment. */ + CSI_FRAME_COMPRESS_MODE_TILE, /* compress unit is a tile. */ + CSI_FRAME_COMPRESS_MODE_LINE, /* compress unit is the whole line. raw for VI */ + CSI_FRAME_COMPRESS_MODE_FRAME, /* compress unit is the whole frame. YUV for VI(3DNR), RGB for TDE(write)/VO(read) */ + CSI_FRAME_COMPRESS_MAX +} csi_frame_compress_mode; +#endif + +typedef enum { + CSI_COLORSPACE_ACES, /* RGB color space ACES standardized as SMPTE ST 2065-1:2012. */ + CSI_COLORSPACE_ACESCG, /* RGB color space ACEScg standardized as Academy S-2014-004. */ + CSI_COLORSPACE_RGB, /* RGB color space Adobe RGB (1998). */ + CSI_COLORSPACE_BT2020, /* RGB color space BT.2020 standardized as Rec. */ + CSI_COLORSPACE_BT709, /* RGB color space BT.709 standardized as Rec. */ + CSI_COLORSPACE_CIE_LAB, /* Lab color space CIE L*a*b*. */ + CSI_COLORSPACE_CIE_XYZ, /* XYZ color space CIE XYZ. */ + CSI_COLORSPACE_DCI_P3, /* RGB color space DCI-P3 standardized as SMPTE RP 431-2-2007. */ + CSI_COLORSPACE_DISPLAY_p3, /* RGB color space Display P3 based on SMPTE RP 431-2-2007 and IEC 61966-2.1:1999. */ + CSI_COLORSPACE_EXTENDED_SRGB, /* RGB color space scRGB-nl standardized as IEC 61966-2-2:2003. */ + CSI_COLORSPACE_LINEAR_EXTENDED_SRGB, /* RGB color space scRGB standardized as IEC 61966-2-2:2003. */ + CSI_COLORSPACE_LINEAR_SRGB, /* RGB color space sRGB standardized as IEC 61966-2.1:1999. */ + CSI_COLORSPACE_NTSC_1953, /* RGB color space NTSC, 1953 standard. */ + CSI_COLORSPACE_PRO_PHOTO_RGB, /* RGB color space NTSC, 1953 standard. */ + CSI_COLORSPACE_SMPTE_C, /* RGB color space SMPTE C. */ + CSI_COLORSPACE_SRGB /* RGB color space sRGB standardized as IEC 61966-2.1:1999. */ +} csi_color_space_e; + +typedef enum { + CSI_VIDEO_FORMAT_UNDEFINED, + CSI_VIDEO_FORMAT_COMPONENT, + CSI_VIDEO_FORMAT_PAL, + CSI_VIDEO_FORMAT_NTSC, + CSI_VIDEO_FORMAT_SECAM, + CSI_VIDEO_FORMAT_MAC +} csi_video_format_e; + + +typedef struct csi_frame_info { + int32_t width; + int32_t height; + /* the region of interest of the frame */ + //csi_rect_s roi; + csi_pixel_format_e pixel_format; + //csi_frame_compress_mode compress_mode; + csi_color_space_e color_space; + csi_chroma_location_e chroma_location; +} csi_frame_info_s; + +typedef struct csi_frame_data { + int64_t pts; + int64_t dts; + int64_t frame_num; + int8_t is_key_frame; + int8_t is_eos; /* last frame */ + + int32_t offset[MAX_PLANE_COUNT]; + int32_t stride[MAX_PLANE_COUNT]; + int32_t num_plane; + + void *vir_addr[MAX_PLANE_COUNT]; + union { + int fd[MAX_PLANE_COUNT]; /* -1 means invalid */ + int64_t phy_addr[MAX_PLANE_COUNT]; /* 0 means invalid */ + }; +} csi_frame_data_s; + + +typedef struct csi_frame_ex { + csi_frame_info_s frame_info; + csi_frame_data_s frame_data; + void *opaque; /* not for user, DO NOT use or modify it */ +} csi_frame_ex_s; + +#if 0 +typedef struct csi_bitstream { + void *buf; + uint32_t len; + uint8_t is_eos; //is the end of stream + uint8_t is_frame; //is whole compress frame data +} csi_bitstream_s; +#endif + +#endif \ No newline at end of file diff --git a/include/hal/csi_g2d.h b/include/hal/csi_g2d.h new file mode 100644 index 0000000..14b075f --- /dev/null +++ b/include/hal/csi_g2d.h @@ -0,0 +1,204 @@ +#ifndef __CSI_G2D_H__ +#define __CSI_G2D_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct _g2d_surface; +typedef struct _g2d_surface g2d_surface; + +struct _g2d_context; +typedef struct _g2d_context g2d_context; + +typedef enum _csi_g2d_tiling { + CSI_G2D_LINEAR, +} csi_g2d_tiling; + +typedef enum _csi_g2d_rotation { + CSI_G2D_ROTATION_0_DEGREE, + CSI_G2D_ROTATION_90_DEGREE, + CSI_G2D_ROTATION_180_DEGREE, + CSI_G2D_ROTATION_270_DEGREE, + CSI_G2D_ROTATION_FLIP_X = 0x10000000, + CSI_G2D_ROTATION_FLIP_Y = 0x20000000, +} csi_g2d_rotation; + +/* TODO: maximum 16 different modes */ +typedef enum _csi_g2d_blend_mode { + CSI_G2D_BLEND_MODE_CLEAR, + CSI_G2D_BLEND_MODE_SRC, + CSI_G2D_BLEND_MODE_DST, + CSI_G2D_BLEND_MODE_SRC_OVER, + CSI_G2D_BLEND_MODE_DST_OVER, + CSI_G2D_BLEND_MODE_SRC_IN, + CSI_G2D_BLEND_MODE_DST_IN, + CSI_G2D_BLEND_MODE_SRC_OUT, + CSI_G2D_BLEND_MODE_DST_OUT, + CSI_G2D_BLEND_MODE_SRC_ATOP, + CSI_G2D_BLEND_MODE_DST_ATOP, + CSI_G2D_BLEND_MODE_ADD, + CSI_G2D_BLEND_MODE_XOR, +} csi_g2d_blend_mode; + +typedef enum _csi_g2d_alpha_mode { + CSI_G2D_ALPHA_MODE_STRAIGHT, + CSI_G2D_ALPHA_MODE_INVERSED +} csi_g2d_alpha_mode; + +typedef enum _csi_g2d_global_alpha_mode { + CSI_G2D_GLOBAL_ALPHA_MODE_OFF, + CSI_G2D_GLOBAL_ALPHA_MODE_ON, + CSI_G2D_GLOBAL_ALPHA_MODE_SCALE +} csi_g2d_global_alpha_mode; + +typedef enum _csi_g2d_global_multiply_mode { + CSI_G2D_GLOBAL_COLOR_MULTIPLY_DISABLE, + CSI_G2D_GLOBAL_COLOR_MULTIPLY_ALPHA, + CSI_G2D_GLOBAL_COLOR_MULTIPLY_COLOR +} csi_g2d_global_multiply_mode; + +typedef enum _csi_g2d_yuv_mode { + CSI_G2D_YUV_MODE_BT601, + CSI_G2D_YUV_MODE_BT709, + CSI_G2D_YUV_MODE_BT2020, + CSI_G2D_YUV_MODE_USER_DEFINED, + CSI_G2D_YUV_MODE_USER_DEFINED_CLAMP, + CSI_G2D_YUV_MODE_NUM, +} csi_g2d_yuv_mode; + +typedef enum _csi_g2d_filter_tap { + CSI_G2D_FILTER_TAP_1, + CSI_G2D_FILTER_TAP_3, + CSI_G2D_FILTER_TAP_5 +} csi_g2d_filter_tap; + +typedef struct _csi_g2d_point { + int x; + int y; +} csi_g2d_point; + +typedef struct _csi_g2d_line { + csi_g2d_point start; + csi_g2d_point end; +} csi_g2d_line; + +typedef struct _csi_g2d_triangle { + csi_g2d_line line[3]; +} csi_g2d_triangle; + +typedef struct _csi_g2d_rectangle { + csi_g2d_line line[4]; +} csi_g2d_rectangle; + +typedef struct _csi_g2d_region { + unsigned int left; + unsigned int top; + unsigned int right; + unsigned int bottom; +} csi_g2d_region; + +typedef struct _csi_g2d_surface { + g2d_surface *priv; /* point to g2d_surface */ + int width; /* in pixels */ + int height; /* in pixels */ + int cpp[3]; /* bytes per pixel (per plane) */ + int stride[3]; /* in bytes */ + int nplanes; /* planes number */ + int alignment; /* in pixels */ + unsigned int format; /* in fourcc */ + csi_g2d_tiling tiling; + void *lgcaddr[3]; + unsigned int flags; +} csi_g2d_surface; + +int csi_g2d_open(void); +int csi_g2d_close(void); +int csi_g2d_flush(void); + +/* csi_g2d_surface APIs */ +int csi_g2d_surface_create(csi_g2d_surface *csi_surface); +int csi_g2d_surface_destroy(csi_g2d_surface *csi_surface); +int csi_g2d_surface_select_source(unsigned int id); +int csi_g2d_surface_set_source(csi_g2d_surface *surface); +int csi_g2d_surface_set_target(csi_g2d_surface *surface); +int csi_g2d_surface_set_source_clipping(csi_g2d_region *region); +int csi_g2d_surface_set_target_clipping(csi_g2d_region *region); +int csi_g2d_surface_set_source_yuv_mode(csi_g2d_yuv_mode yuv_mode); +int csi_g2d_surface_set_target_yuv_mode(csi_g2d_yuv_mode yuv_mode); + +int csi_g2d_surface_set_source_alpha_mode( + csi_g2d_alpha_mode alpha_mode, + csi_g2d_blend_mode blend_mode +); +int csi_g2d_surface_set_source_global_alpha_mode( + csi_g2d_global_alpha_mode global_mode, + unsigned int global_color +); +int csi_g2d_surface_set_source_multiply_mode( + bool multiply, + csi_g2d_global_multiply_mode global_mode +); + +int csi_g2d_surface_set_target_alpha_mode( + csi_g2d_alpha_mode alpha_mode, + csi_g2d_blend_mode blend_mode +); +int csi_g2d_surface_set_target_global_alpha_mode( + csi_g2d_global_alpha_mode global_mode, + unsigned int global_color +); +int csi_g2d_surface_set_target_multiply_mode( + bool multiply, + bool demultiply +); + +int csi_g2d_surface_enable_disable_alpha_blend(bool enable); + +/* csi_g2d_brush APIs */ +int csi_g2d_brush_create(unsigned int argb_color, bool convert); +int csi_g2d_brush_destroy(void); + +int csi_g2d_palette_load(unsigned int *table, + unsigned int count, + bool convert); + +/* csi_g2d_line APIs: draw line(s) in target */ +int csi_g2d_line_draw_line(csi_g2d_line *lines, int nlines); +int csi_g2d_line_draw_triangle(csi_g2d_triangle *triangles, + int ntriangles); +int csi_g2d_line_draw_rectangle(csi_g2d_rectangle *rectangles, + int nrectangles); + +/* blit properties config APIs */ +int csi_g2d_blit_set_rotation(csi_g2d_rotation rotation); +int csi_g2d_blit_set_mirror(bool hmirror, bool vmirror); +int csi_g2d_blit_set_filter_tap(csi_g2d_filter_tap htap, + csi_g2d_filter_tap vtap); + +/* clear target: clear color should be ARGB8888 */ +int csi_g2d_fill(csi_g2d_region *regions, + int nregions, + unsigned int argb_color); + +/* blit without resize, support alpha blending */ +int csi_g2d_blit_bitblit(csi_g2d_region *target_regions, int nregions); + +/* scaling up or down APIs */ +int csi_g2d_blit_stretchblit(csi_g2d_region *target_regions, int nregions); +int csi_g2d_blit_filterblit(csi_g2d_region *target_regions, int nregions); + +/* multisrc blit API */ +int csi_g2d_blit_multisrc_blit( + unsigned int src_mask, + csi_g2d_region *target_regions, + int nregions +); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/include/hal/csi_g2d_types.h b/include/hal/csi_g2d_types.h new file mode 100644 index 0000000..4eaa43e --- /dev/null +++ b/include/hal/csi_g2d_types.h @@ -0,0 +1,111 @@ +#ifndef __CSI_G2D_TYPES_H__ +#define __CSI_G2D_TYPES_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define swap(a, b) \ + do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0) + +#define __cmp(x, y, op) ((x) op (y) ? (x) : (y)) + +#define __cmp_once(x, y, op) ({ \ + typeof(x) __x = (x); \ + typeof(y) __y = (y); \ + __cmp(__x, __y, op); }) + +#define min(x, y) __cmp_once(x, y, <) +#define max(x, y) __cmp_once(x, y, >) + +#define csi_g2d_fourcc(a, b, c, d) \ + ((unsigned int )(a) | ((unsigned int)(b) << 8) | \ + ((unsigned int )(c) << 16) | ((unsigned int)(d) << 24)) + +/* 32 bpp RGB */ +#define CSI_G2D_FMT_XRGB8888 csi_g2d_fourcc('X', 'R', '2', '4') +#define CSI_G2D_FMT_ARGB8888 csi_g2d_fourcc('A', 'R', '2', '4') +#define CSI_G2D_FMT_RGBX8888 csi_g2d_fourcc('R', 'A', '2', '4') +#define CSI_G2D_FMT_RGBA8888 csi_g2d_fourcc('R', 'X', '2', '4') +#define CSI_G2D_FMT_XBGR8888 csi_g2d_fourcc('X', 'B', '2', '4') +#define CSI_G2D_FMT_ABGR8888 csi_g2d_fourcc('A', 'B', '2', '4') +#define CSI_G2D_FMT_BGRX8888 csi_g2d_fourcc('B', 'X', '2', '4') +#define CSI_G2D_FMT_BGRA8888 csi_g2d_fourcc('B', 'G', '2', '4') + +/* 24 bpp RGB */ +#define CSI_G2D_FMT_RGB888 csi_g2d_fourcc('R', 'G', '2', '4') +#define CSI_G2D_FMT_BGR888 csi_g2d_fourcc('B', 'G', '2', '4') +#define CSI_G2D_FMT_BGR888_PLANAR csi_g2d_fourcc('B', 'R', '8', 'P') +#define CSI_G2D_FMT_RGB888_PLANAR csi_g2d_fourcc('R', 'B', '8', 'P') + +/* 16 bpp RGB */ +#define CSI_G2D_FMT_RGB565 csi_g2d_fourcc('R', 'G', '1', '6') +#define CSI_G2D_FMT_BGR565 csi_g2d_fourcc('B', 'G', '1', '6') +#define CSI_G2D_FMT_XRGB1555 csi_g2d_fourcc('X', 'R', '1', '5') +#define CSI_G2D_FMT_ARGB1555 csi_g2d_fourcc('A', 'R', '1', '5') +#define CSI_G2D_FMT_RGBX5551 csi_g2d_fourcc('R', 'X', '1', '5') +#define CSI_G2D_FMT_RGBA5551 csi_g2d_fourcc('R', 'A', '1', '5') +#define CSI_G2D_FMT_XBGR1555 csi_g2d_fourcc('X', 'B', '1', '5') +#define CSI_G2D_FMT_ABGR1555 csi_g2d_fourcc('A', 'B', '1', '5') +#define CSI_G2D_FMT_BGRX5551 csi_g2d_fourcc('B', 'X', '1', '5') +#define CSI_G2D_FMT_BGRA5551 csi_g2d_fourcc('B', 'A', '1', '5') +#define CSI_G2D_FMT_XRGB4444 csi_g2d_fourcc('X', 'R', '1', '2') +#define CSI_G2D_FMT_ARGB4444 csi_g2d_fourcc('A', 'R', '1', '2') +#define CSI_G2D_FMT_RGBX4444 csi_g2d_fourcc('R', 'X', '1', '2') +#define CSI_G2D_FMT_RGBA4444 csi_g2d_fourcc('R', 'A', '1', '2') +#define CSI_G2D_FMT_XBGR4444 csi_g2d_fourcc('X', 'B', '1', '2') +#define CSI_G2D_FMT_ABGR4444 csi_g2d_fourcc('A', 'B', '1', '2') +#define CSI_G2D_FMT_BGRX4444 csi_g2d_fourcc('B', 'X', '1', '2') +#define CSI_G2D_FMT_BGRA4444 csi_g2d_fourcc('B', 'A', '1', '2') + +/* 10Bit RGB */ +#define CSI_G2D_FMT_XRGB2101010 csi_g2d_fourcc('X', 'R', '3', '0') +#define CSI_G2D_FMT_ARGB2101010 csi_g2d_fourcc('A', 'R', '3', '0') +#define CSI_G2D_FMT_RGBX1010102 csi_g2d_fourcc('R', 'X', '3', '0') +#define CSI_G2D_FMT_RGBA1010102 csi_g2d_fourcc('R', 'A', '3', '0') +#define CSI_G2D_FMT_XBGR2101010 csi_g2d_fourcc('X', 'B', '3', '0') +#define CSI_G2D_FMT_ABGR2101010 csi_g2d_fourcc('A', 'B', '3', '0') +#define CSI_G2D_FMT_BGRX1010102 csi_g2d_fourcc('B', 'X', '3', '0') +#define CSI_G2D_FMT_BGRA1010102 csi_g2d_fourcc('B', 'A', '3', '0') + +/* 8 bpp RGB */ +#define CSI_G2D_FMT_A8 csi_g2d_fourcc('A', '8', ' ', ' ') +#define CSI_G2D_FMT_INDEX8 csi_g2d_fourcc('I', '8', ' ', ' ') + +/* packed YUV formats */ +#define CSI_G2D_FMT_YUY2 csi_g2d_fourcc('Y', 'U', 'Y', 'V') +#define CSI_G2D_FMT_YVYU csi_g2d_fourcc('Y', 'V', 'Y', 'U') +#define CSI_G2D_FMT_UYVY csi_g2d_fourcc('U', 'Y', 'V', 'Y') +#define CSI_G2D_FMT_VYUY csi_g2d_fourcc('V', 'Y', 'U', 'Y') + +/* planar YUV formats */ +#define CSI_G2D_FMT_I420 csi_g2d_fourcc('Y', 'U', '1', '2') +#define CSI_G2D_FMT_YV12 csi_g2d_fourcc('Y', 'V', '1', '2') +#define CSI_G2D_FMT_YUV422 csi_g2d_fourcc('Y', 'U', '1', '6') +#define CSI_G2D_FMT_YVU422 csi_g2d_fourcc('Y', 'V', '1', '6') +#define CSI_G2D_FMT_YUV444 csi_g2d_fourcc('Y', 'U', '2', '4') +#define CSI_G2D_FMT_YVU444 csi_g2d_fourcc('Y', 'V', '2', '4') + +/* semi-planar YUV formats */ +#define CSI_G2D_FMT_NV16 csi_g2d_fourcc('N', 'V', '1', '6') +#define CSI_G2D_FMT_NV61 csi_g2d_fourcc('N', 'V', '6', '1') +#define CSI_G2D_FMT_NV12 csi_g2d_fourcc('N', 'V', '1', '2') +#define CSI_G2D_FMT_NV21 csi_g2d_fourcc('N', 'V', '2', '1') +#define CSI_G2D_FMT_P010 csi_g2d_fourcc('P', '0', '1', '0') + +/* TODO: verify below formats supported or not: + gcvSURF_P010_LSB gcvSURF_I010 gcvSURF_I010_LSB + */ +#define CSI_G2D_FMT_NV16_10BIT csi_g2d_fourcc('N', '1', '6', 'X') +#define CSI_G2D_FMT_NV61_10BIT csi_g2d_fourcc('N', '6', '1', 'X') +#define CSI_G2D_FMT_NV12_10BIT csi_g2d_fourcc('N', '1', '2', 'X') +#define CSI_G2D_FMT_NV21_10BIT csi_g2d_fourcc('N', '2', '1', 'X') + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/include/hal/csi_vcodec_common.h b/include/hal/csi_vcodec_common.h new file mode 100644 index 0000000..9b0cdef --- /dev/null +++ b/include/hal/csi_vcodec_common.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CSI_VIDEO_COMMON__ +#define __CSI_VIDEO_COMMON__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Refer ffmpeg: libavcodec/codec_id.h */ +typedef enum csi_vcodec_id { + CSI_VCODEC_ID_UNKNOWN = 0, /* unknown decoder */ + + CSI_VCODEC_ID_JPEG = (1 << 0), + CSI_VCODEC_ID_MJPEG = (1 << 1), + + CSI_VCODEC_ID_MPEG1 = (1 << 2), + CSI_VCODEC_ID_MPEG2 = (1 << 3), /* MPEG-2 decoder */ + CSI_VCODEC_ID_MPEG4 = (1 << 4), /* MPEG-4 decoder */ + + CSI_VCODEC_ID_H263P = (1 << 5), /* H.263 progressive */ + CSI_VCODEC_ID_H263I = (1 << 6), /* H.263 interleave */ + CSI_VCODEC_ID_H264 = (1 << 7), /* H.264 AVC/SVC */ + CSI_VCODEC_ID_H265 = (1 << 8), /* H.265 */ + CSI_VCODEC_ID_H266 = (1 << 9), /* H.266 */ + + CSI_VCODEC_ID_RV30 = (1 << 10), /* RealVideo 8 */ + CSI_VCODEC_ID_RV40 = (1 << 11), /* RealVideo 9/10 */ + + CSI_VCODEC_ID_WMV1 = (1 << 12), + CSI_VCODEC_ID_WMV2 = (1 << 13), + CSI_VCODEC_ID_WMV3 = (1 << 14), + + CSI_VCODEC_ID_MSMPEG4V1 = (1 << 15), + CSI_VCODEC_ID_MSMPEG4V2 = (1 << 16), + CSI_VCODEC_ID_MSMPEG4V3 = (1 << 17), + + // Alias names + CSI_VCODEC_ID_HEVC = CSI_VCODEC_ID_H265, + CSI_VCODEC_ID_DIVX = CSI_VCODEC_ID_MSMPEG4V2, +} csi_vcodec_id_e; + +typedef enum csi_vcodec_frame_type { + CSI_VCODEC_I_FRAME = 1 << 0, + CSI_VCODEC_P_FRAME = 1 << 1, + CSI_VCODEC_B_FRAME = 1 << 2, +} csi_vcodec_frame_type_e; + +#ifdef __cplusplus +} +#endif + +#endif /* __CSI_VIDEO_COMMON__ */ diff --git a/include/hal/csi_vdec.h b/include/hal/csi_vdec.h new file mode 100644 index 0000000..cde2524 --- /dev/null +++ b/include/hal/csi_vdec.h @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CSI_VDEC_H__ +#define __CSI_VDEC_H__ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define CSI_VDEC_VERSION_MAJOR 0 +#define CSI_VDEC_VERSION_MINOR 2 + +typedef void *csi_vdec_dev_t; +typedef void *csi_vdec_chn_t; +typedef void *csi_vdec_event_handle_t; + +typedef enum csi_vdec_status { + CSI_VDEC_STATUS_OK = 0, + CSI_VDEC_STATUS_MORE_FRAME_BUFFER = 1, + CSI_VDEC_STATUS_MORE_BITSTREAM = 2, + CSI_VDEC_STATUS_HAS_INFO = 3, + CSI_VDEC_STATUS_PIC_DECODED = 4, + CSI_VDEC_STATUS_NO_FRAME_BUFFER = 5, + CSI_VDEC_STATUS_EOS = 6, + CSI_VDEC_STATUS_TIMEOUT = 7, + CSI_VDEC_STATUS_SKIPPED = 8, + CSI_VDEC_STATUS_PIC_OUTPUT = 9, + CSI_VDEC_STATUS_FLUSHED = 10, + + /* error codes < 0 */ + CSI_VDEC_ERR_UNKOWN = -1, + CSI_VDEC_ERR_UNSUPPORTED = -2, + CSI_VDEC_ERR_INSUFFICIENT_RESOURCES = -3, + CSI_VDEC_ERR_WRONG_PARAM = -4, + CSI_VDEC_ERR_BAD_STREAM = -5, + CSI_VDEC_ERR_NOT_INTIALIZED = -6, + CSI_VDEC_ERR_MEMORY = -7, + /* add more ... */ + + /* warnings > 0 */ + CSI_VDEC_WRN_DEVICE_BUSY = 256, + CSI_VDEC_WRN_INCOMPATIBLE_PARAM = 257, + CSI_VDEC_WRN_NOT_IMPLEMENTED = 258, + /* add more ... */ +} csi_vdec_status_e; + +typedef enum csi_vdec_event_type { + CSI_VDEC_EVENT_TYPE_DECODER, + CSI_VDEC_EVENT_TYPE_CHANNEL, +} csi_vdec_event_type_e; + +typedef enum csi_vdec_event_id { + CSI_VDEC_EVENT_ID_ERROR = 1 << 0, +} csi_vdec_event_id_e; + +typedef enum csi_vdec_channel_event_id { + CSI_VDEC_CHANNEL_EVENT_ID_ERROR = 1 << 0, + CSI_VDEC_CHANNEL_EVENT_ID_FRAME_READY = 1 << 1, +} csi_vdec_channel_event_id_e; + +typedef enum csi_framebuf_source { + CSI_FB_SOURCE_DMABUF, + CSI_FB_SOURCE_CARVEOUT, +} csi_framebuf_source_e; + +typedef enum csi_vdec_input_mode { + CSI_VDEC_INPUT_MODE_STREAM, + CSI_VDEC_INPUT_MODE_FRAME, +} csi_vdec_input_mode_e; + +typedef enum csi_vdec_img_type { + CSI_VDEC_MODE_I = CSI_VCODEC_I_FRAME, + CSI_VDEC_MODE_IP = (CSI_VCODEC_I_FRAME | CSI_VCODEC_P_FRAME), + CSI_VDEC_MODE_IPB = (CSI_VCODEC_I_FRAME | CSI_VCODEC_P_FRAME | CSI_VCODEC_B_FRAME), +} csi_vdec_img_type_e; + +typedef enum csi_vdec_output_order { + CSI_VDEC_OUTPUT_ORDER_DISP = 0, + CSI_VDEC_OUTPUT_ORDER_DEC, +} csi_vdec_output_order_e; + +typedef enum csi_vdec_pp_rotate { + CSI_VDEC_PP_ROTATE_0, + CSI_VDEC_PP_ROTATE_90, + CSI_VDEC_PP_ROTATE_180, + CSI_VDEC_PP_ROTATE_270 +} csi_vdec_pp_rotate_t; + +#define CSI_VDEC_NAME_MAX_LEN 32 +typedef struct csi_vdec_info { + char module_name[CSI_VDEC_NAME_MAX_LEN]; + char device_name[CSI_VDEC_NAME_MAX_LEN]; + uint64_t capabilities; /* bitmask of 1<<(csi_vcodec_id_e) */ +} csi_vdec_info_t; + +#define CSI_VDEC_MAX_COUNT 2 +typedef struct csi_vdec_infos { + uint32_t count; + csi_vdec_info_t info[CSI_VDEC_MAX_COUNT]; +} csi_vdec_infos_s; + +typedef struct csi_vdec_stream { + uint32_t length; + uint64_t pts; + bool eos; + char *data; +} csi_vdec_stream_s; + +typedef struct csi_vdec_mode { + csi_framebuf_source_e fb_source; + bool low_latency_mode; + bool mini_buf_mode; +} csi_vdec_mode_s; + + +typedef struct csi_vdec_video_264 { +} csi_vdec_video_264_s; + +typedef struct csi_vdec_video_265 { +} csi_vdec_video_265_s; + +typedef struct csi_vdec_picture_jpeg { +} csi_vdec_picture_jpeg_s; + +typedef struct csi_vdec_config { + csi_vcodec_id_e dec_vcodec_id; + uint32_t dec_frame_buf_cnt; + uint32_t dec_frame_buf_size; + union { + csi_vdec_video_264_s video_264_param; + csi_vdec_video_265_s video_265_param; + csi_vdec_picture_jpeg_s picture_jpeg_param; + }; + + csi_vdec_input_mode_e input_mode; + uint32_t input_stream_buf_size; + + csi_vdec_img_type_e output_img_type; + csi_pixel_fmt_e output_format; + uint32_t output_width; + uint32_t output_height; + csi_vdec_output_order_e output_order; +} csi_vdec_config_s; + +typedef struct csi_vdec_pp_config { + csi_vdec_pp_rotate_t rotate; + bool h_flip; + bool v_flip; + csi_rect_s crop; /* width or height to be zero means no crop */ +} csi_vdec_pp_config_s; + +typedef struct csi_vdec_error_stats { + int32_t format_err; /* R; format error. eg: do not support filed */ + int32_t pic_size_err_set; /* R; picture width or height is larger than chnnel width or height*/ + int32_t stream_unsupprt; /* R; unsupport the stream specification */ + int32_t pack_err; /* R; stream package error */ + int32_t prtcl_num_err_set; /* R; protocol num is not enough. eg: slice, pps, sps */ + int32_t ref_err_set; /* R; refrence num is not enough */ + int32_t pic_buf_size_err_set; /* R; the buffer size of picture is not enough */ + int32_t stream_size_over; /* R; the stream size is too big and and force discard stream */ + int32_t vdec_stream_not_release;/* R; the stream not released for too long time */ +} csi_vdec_error_stats_s; + +typedef struct csi_vdec_chn_status { + csi_vcodec_id_e dec_vcodec_id; /* R; video type to be decoded */ + uint32_t left_stream_bytes; /* R; left stream bytes waiting for decode */ + uint32_t left_stream_frames; /* R; left frames waiting for decode,only valid for VIDEO_MODE_FRAME*/ + uint32_t left_pics; /* R; pics waiting for output */ + bool recv_stream_started; /* R; had started recv stream? */ + uint32_t recv_stream_frames; /* R; how many frames of stream has been received. valid when send by frame. */ + uint32_t decode_stream_frames; /* R; how many frames of stream has been decoded. valid when send by frame. */ + csi_vdec_error_stats_s stVdecDecErr; /* R; information about decode error */ +} csi_vdec_chn_status_s; + +typedef struct csi_vdec_event_subscription { + csi_vdec_event_type_e type; + unsigned int id; /* bitmasks */ +} csi_vdec_event_subscription_t; + +typedef struct csi_vdec_event { + csi_vdec_event_type_e type; + unsigned int id; + struct timespec timestamp; + union { + char bin[128]; + }; +} csi_vdec_event_s; + +int csi_vdec_get_version(csi_api_version_u *version); +int csi_vdec_query_list(csi_vdec_infos_s *infos); + +int csi_vdec_open(csi_vdec_dev_t *dec, const char *device_name); +int csi_vdec_close(csi_vdec_dev_t dec); + +int csi_vdec_get_io_pattern(csi_vdec_dev_t dec, int *pattern); + +int csi_vdec_create_channel(csi_vdec_chn_t *chn, csi_vdec_dev_t dec, csi_vdec_config_s *cfg); +int csi_vdec_destory_channel(csi_vdec_chn_t chn); + +int csi_vdec_get_frame_config(csi_vdec_chn_t chn, csi_img_format_t *img_fmt, csi_frame_config_s *frm_cfg); +int csi_vdec_set_frame_config(csi_vdec_chn_t chn, csi_img_format_t *img_fmt, csi_frame_config_s *frm_cfg); + +//int csi_vdec_set_memory_allocator(csi_vdec_chn_t chn, csi_allocator_s *allocator); + +int csi_vdec_set_mode(csi_vdec_chn_t chn, csi_vdec_mode_s *mode); +int csi_vdec_get_mode(csi_vdec_chn_t chn, csi_vdec_mode_s *mode); + +int csi_vdec_set_chn_config(csi_vdec_chn_t chn, csi_vdec_config_s *cfg); +int csi_vdec_get_chn_config(csi_vdec_chn_t chn, csi_vdec_config_s *cfg); + +int csi_vdec_set_pp_config(csi_vdec_chn_t chn, csi_vdec_pp_config_s *cfg); +int csi_vdec_get_pp_config(csi_vdec_chn_t chn, csi_vdec_pp_config_s *cfg); + +int csi_vdec_start(csi_vdec_chn_t chn); +int csi_vdec_stop(csi_vdec_chn_t chn); +int csi_vdec_reset(csi_vdec_chn_t chn); + +//int csi_vdec_get_frame_info(csi_vdec_chn_t chn, csi_vdec_frame_info *stream_info, csi_vdec_stream_s *stream); +int csi_vdec_send_stream_buf(csi_vdec_chn_t chn, csi_vdec_stream_s *stream, int32_t timeout); + +int csi_vdec_register_frames(csi_vdec_chn_t chn, csi_frame_s *frame[], int count); +int csi_vdec_put_frame(csi_vdec_chn_t chn, csi_frame_s *frame); +int csi_vdec_get_frame(csi_vdec_chn_t chn, csi_frame_s **frame, int32_t timeout);// Release by frame.release() + + +int csi_vdec_enqueue_frame(csi_vdec_chn_t chn, csi_frame_ex_s *frame); +int csi_vdec_dequeue_frame(csi_vdec_chn_t chn, csi_frame_ex_s **frame, int32_t timeout); + + +int csi_vdec_query_status(csi_vdec_chn_t chn, csi_vdec_chn_status_s *pstStatus); + +int csi_vdec_create_event_handle(csi_vdec_event_handle_t *chn, csi_vdec_dev_t event_handle); +int csi_vdec_destory_event(csi_vdec_event_handle_t event_handle); + +int csi_vdec_subscribe_event(csi_vdec_event_handle_t event_handle, + csi_vdec_event_subscription_t *subscribe); +int csi_vdec_unsubscribe_event(csi_vdec_event_handle_t event_handle, + csi_vdec_event_subscription_t *subscribe); +int csi_vdec_get_event(csi_vdec_event_handle_t event_handle, + csi_vdec_event_s *event, int timeout); + +#ifdef __cplusplus +} +#endif + +#endif /* __CSI_VDEC_H__ */ diff --git a/include/hal/csi_venc.h b/include/hal/csi_venc.h new file mode 100644 index 0000000..7b1578f --- /dev/null +++ b/include/hal/csi_venc.h @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CSI_VENC_H__ +#define __CSI_VENC_H__ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define CSI_VENC_VERSION_MAJOR 0 +#define CSI_VENC_VERSION_MINOR 3 + +typedef void *csi_venc_dev_t; +typedef void *csi_venc_chn_t; +typedef void *csi_venc_event_handle_t; + +#define CSI_VENC_NAME_MAX_LEN 32 +typedef struct csi_venc_info { + char module_name[CSI_VENC_NAME_MAX_LEN]; + char device_name[CSI_VENC_NAME_MAX_LEN]; + uint64_t capabilities; /* bitmask of 1<<(csi_vcodec_id_e) */ +} csi_venc_info_s; + +#define CSI_VENC_MAX_COUNT 2 +typedef struct csi_venc_infos { + uint32_t count; + csi_venc_info_s info[CSI_VENC_MAX_COUNT]; +} csi_venc_infos_s; + +typedef struct csi_venc_chn_cfg { + csi_venc_attr_s attr; + csi_venc_gop_property_s gop; + csi_venc_rc_property_s rc; +} csi_venc_chn_cfg_s; + +typedef enum csi_venc_event_type { + CSI_VENC_EVENT_TYPE_DECODER, + CSI_VENC_EVENT_TYPE_CHANNEL, +} csi_venc_event_type_e; + +/* the attribute of the roi */ +typedef struct csi_venc_chn_roi_prop { + uint32_t index; /* Range:[0, 7]; Index of an ROI. The system supports indexes ranging from 0 to 7 */ + bool enable; /* Range:[0, 1]; Whether to enable this ROI */ + bool abs_qp; /* Range:[0, 1]; QP mode of an ROI.HI_FALSE: relative QP.HI_TURE: absolute QP.*/ + int32_t qp; /* Range:[-51, 51]; QP value,only relative mode can QP value less than 0. */ + csi_rect_s rect; /* Region of an ROI*/ +} csi_venc_chn_roi_prop_s; + +typedef enum csi_venc_ext_property_id { + CSI_VENC_EXT_PROPERTY_ROI, +} csi_venc_ext_property_id_e; + +typedef struct csi_venc_chn_ext_property { + csi_venc_ext_property_id_e prop_id; + union { + csi_venc_chn_roi_prop_s roi_prop; // CSI_VENC_EXT_PROPERTY_ROI + }; +} csi_venc_chn_ext_property_s; + +typedef enum csi_venc_event_id { + CSI_VENC_EVENT_ID_ERROR = 1 << 0, +} csi_venc_event_id_e; + +typedef enum csi_venc_chn_event_id { + CSI_VENC_CHANNEL_EVENT_ID_ERROR = 1 << 0, + CSI_VENC_CHANNEL_EVENT_ID_FRAME_READY = 1 << 1, +} csi_venc_chn_event_id_e; + +typedef struct csi_venc_event_subscription { + csi_venc_event_type_e type; + unsigned int id; /* bitmasks */ +} csi_venc_event_subscription_s; + +typedef struct csi_venc_event { + csi_venc_event_type_e type; + unsigned int id; + struct timespec timestamp; + union { + char bin[128]; + }; +} csi_venc_event_s; + +typedef union csi_venc_data_type { + csi_venc_h264_nalu_e h264_type; + csi_venc_h265_nalu_e h265_type; + csi_venc_jpeg_pack_e jpeg_type; +} csi_venc_data_type_u; + +typedef struct csi_stream { + size_t size; + union { + int buf_fd; // stores in dma_buf memory + void *phy_addr; // stores in phy contigous memory + void *usr_addr; // stores in usr contigous memory + }; + uint64_t pts; + bool frame_end; + csi_venc_data_type_u data_type; + uint32_t data_num; +} csi_stream_s; + +typedef enum csi_venc_prop_type { + CSI_VENC_FRAME_PROP_NONE = 0, + CSI_VENC_FRAME_PROP_FORCE_IDR, // Instantaneous Decoding Refresh + CSI_VENC_FRAME_PROP_FORCE_SKIP, +} csi_venc_prop_type_e; + +typedef struct csi_venc_prop { + csi_venc_prop_type_e type; + union { + bool force_idr; // CSI_VENC_FRAME_PROP_FORCE_IDR + bool force_skip; // CSI_VENC_FRAME_PROP_FORCE_SKIP + }; +} csi_venc_frame_prop_s; + +/* the status of the venc chnl*/ +typedef struct csi_venc_chn_status { + uint32_t left_pics; /* R; left picture number */ + uint32_t left_stream_bytes; /* R; left stream bytes*/ + uint32_t left_stream_frames; /* R; left stream frames*/ + uint32_t cur_packs; /* R; pack number of current frame*/ + uint32_t left_recv_pics; /* R; Number of frames to be received. This member is valid after HI_MPI_VENC_StartRecvPicEx is called.*/ + uint32_t left_enc_pics; /* R; Number of frames to be encoded. This member is valid after HI_MPI_VENC_StartRecvPicEx is called.*/ + bool jpeg_snap_end; /* R; the end of Snap.*/ +} csi_venc_chn_status_s; + +int csi_venc_get_version(csi_api_version_u *version); +int csi_venc_query_list(csi_venc_infos_s *infos); + +int csi_venc_open(csi_venc_dev_t *enc, const char *device_name); +int csi_venc_close(csi_venc_dev_t enc); + +int csi_venc_get_io_pattern(csi_venc_dev_t enc, int *pattern); +int csi_venc_get_frame_config(csi_venc_dev_t enc, csi_img_format_t *img_fmt, csi_frame_config_s *frm_cfg); +int csi_venc_set_frame_config(csi_venc_dev_t enc, csi_img_format_t *img_fmt, csi_frame_config_s *frm_cfg); + + + +int csi_venc_create_channel(csi_venc_chn_t *chn, csi_venc_dev_t enc, csi_venc_chn_cfg_s *cfg); +int csi_venc_destory_channel(csi_venc_chn_t chn); + +//int csi_venc_set_memory_allocator(csi_venc_chn_t chn, csi_allocator_s *allocator); + +int csi_venc_set_ext_property(csi_venc_chn_t chn, csi_venc_chn_ext_property_s *prop); +int csi_venc_get_ext_property(csi_venc_chn_t chn, csi_venc_chn_ext_property_s *prop); + +int csi_venc_start(csi_venc_chn_t chn); +int csi_venc_stop(csi_venc_chn_t chn); +int csi_venc_reset(csi_venc_chn_t chn); + +int csi_venc_send_frame(csi_venc_chn_t chn, csi_frame_s *frame, int timeout); +int csi_venc_send_frame_ex(csi_venc_chn_t chn, csi_frame_s *frame, int timeout, + csi_venc_frame_prop_s *prop, int prop_count); + + +int csi_venc_enqueue_frame(csi_venc_chn_t chn, csi_frame_ex_s *frame); +int csi_venc_enqueue_frame_ex(csi_venc_chn_t chn, csi_frame_ex_s *frame, + csi_venc_frame_prop_s *prop, int prop_count); +int csi_venc_dequeue_frame(csi_venc_chn_t chn, csi_frame_ex_s **frame, int timeout); + + +int csi_venc_get_stream(csi_venc_chn_t chn, csi_stream_s *stream, int timeout);// Release by stream.release() + +int csi_venc_query_status(csi_venc_chn_t chn, csi_venc_chn_status_s *status); + +int csi_venc_create_event_handle(csi_venc_event_handle_t *chn, csi_venc_dev_t event_handle); +int csi_venc_destory_event(csi_venc_event_handle_t event_handle); + +int csi_venc_subscribe_event(csi_venc_event_handle_t event_handle, + csi_venc_event_subscription_s *subscribe); +int csi_venc_unsubscribe_event(csi_venc_event_handle_t event_handle, + csi_venc_event_subscription_s *subscribe); +int csi_venc_get_event(csi_venc_event_handle_t event_handle, + csi_venc_event_s *event, int timeout); + +#ifdef __cplusplus +} +#endif + +#endif /* __CSI_ENC_H__ */ diff --git a/include/hal/csi_venc_h264.h b/include/hal/csi_venc_h264.h new file mode 100644 index 0000000..3c98cd9 --- /dev/null +++ b/include/hal/csi_venc_h264.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CSI_VENC_H264_H__ +#define __CSI_VENC_H264_H__ + +typedef enum { + CSI_VENC_H264_PROFILE_BASELINE = 0, + CSI_VENC_H264_PROFILE_EXTENDED = 1, + CSI_VENC_H264_PROFILE_MAIN = 2, + CSI_VENC_H264_PROFILE_HIGH = 3, + CSI_VENC_H264_PROFILE_HIGH10 = 4, + CSI_VENC_H264_PROFILE_HIGH422 = 5, + CSI_VENC_H264_PROFILE_HIGH444 = 6, + + CSI_VENC_H264_PROFILE_SVC = 10, +} csi_venc_h264_profile_e; + +typedef enum { + CSI_VENC_H264_LEVEL_1 = 10, /* QCIF (176x144) 64k bps */ + CSI_VENC_H264_LEVEL_1_b = 99, + CSI_VENC_H264_LEVEL_1_1 = 11, + CSI_VENC_H264_LEVEL_1_2 = 12, + CSI_VENC_H264_LEVEL_1_3 = 13, + CSI_VENC_H264_LEVEL_2 = 20, /* CIF (352x288) 576k bps" */ + CSI_VENC_H264_LEVEL_2_1 = 21, + CSI_VENC_H264_LEVEL_2_2 = 22, + CSI_VENC_H264_LEVEL_3 = 30, /* SD (720x576) 2M bps */ + CSI_VENC_H264_LEVEL_3_1 = 31, + CSI_VENC_H264_LEVEL_3_2 = 32, + CSI_VENC_H264_LEVEL_4 = 40, /* 1080p (1920x1080) 25M bps */ + CSI_VENC_H264_LEVEL_4_1 = 41, + CSI_VENC_H264_LEVEL_4_2 = 42, + CSI_VENC_H264_LEVEL_5 = 50, /* 2k (2560x1920) 168M bps */ + CSI_VENC_H264_LEVEL_5_1 = 51, + CSI_VENC_H264_LEVEL_5_2 = 52, +} csi_venc_h264_level_e; + +typedef struct csi_venc_h264_attr { + csi_venc_h264_profile_e profile; + csi_venc_h264_level_e level; + uint32_t frame_type; // bitmask of csi_vcodec_frame_type_e + bool frame_mode; // encode by frame or slice mode + bool share_buf; //Whether to enable the Share Buf of Rcn and Ref +} csi_venc_h264_attr_s; + +typedef struct csi_venc_h264_cbr { + uint32_t stat_time; /* Range:[1, 60]; the rate statistic time, the unit is senconds(s) */ + uint32_t framerate_numer; + uint32_t framerate_denom; + uint32_t bit_rate; /* Range:[2, 409600]; average bitrate, units:kbps */ +} csi_venc_h264_cbr_s; + +typedef struct csi_venc_h264_vbr { + uint32_t stat_time; /* Range:[1, 60]; the rate statistic time, the unit is senconds(s) */ + uint32_t framerate_numer; + uint32_t framerate_denom; + uint32_t max_bit_rate; /* Range:[2, 409600]; average bitrate, units:kbps */ +} csi_venc_h264_vbr_s; + +typedef struct csi_venc_h264_avbr { + uint32_t stat_time; /* Range:[1, 60]; the rate statistic time, the unit is senconds(s) */ + uint32_t framerate_numer; + uint32_t framerate_denom; + uint32_t max_bit_rate; /* Range:[2, 409600]; average bitrate, units:kbps */ +} csi_venc_h264_avbr_s; + +typedef struct csi_venc_h264_fixqp { + uint32_t framerate_numer; + uint32_t framerate_denom; + uint32_t i_qp; /* Range:[0, 51]; qp of the I frame */ + uint32_t p_qp; /* Range:[0, 51]; qp of the P frame */ + uint32_t b_qp; /* Range:[0, 51]; qp of the B frame */ +} csi_venc_h264_fixqp_s; + +typedef struct csi_venc_h264_qpmap { + uint32_t stat_time; /* Range:[1, 60]; the rate statistic time, the unit is senconds(s) */ + uint32_t framerate_numer; + uint32_t framerate_denom; +} csi_venc_h264_qpmap_s; + +typedef enum csi_venc_h264_nalu { + CSI_VENC_H264_NALU_BSLICE, /*B SLICE types*/ + CSI_VENC_H264_NALU_PSLICE, /*P SLICE types*/ + CSI_VENC_H264_NALU_ISLICE, /*I SLICE types*/ + CSI_VENC_H264_NALU_IDRSLICE, /*IDR SLICE types*/ + CSI_VENC_H264_NALU_SEI, /*SEI types*/ + CSI_VENC_H264_NALU_SPS, /*SPS types*/ + CSI_VENC_H264_NALU_PPS, /*PPS types*/ +} csi_venc_h264_nalu_e; + +#endif /* __CSI_VENC_H264_H__ */ diff --git a/include/hal/csi_venc_h265.h b/include/hal/csi_venc_h265.h new file mode 100644 index 0000000..056091a --- /dev/null +++ b/include/hal/csi_venc_h265.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CSI_VENC_H265_H__ +#define __CSI_VENC_H265_H__ + +#include + +typedef enum { + CSI_VENC_H265_PROFILE_MAIN = 0, + CSI_VENC_H265_PROFILE_MAIN10, + CSI_VENC_H265_PROFILE_MAINSTILL, +} csi_venc_h265_profile_e; + +typedef enum { + CSI_VENC_H265_LEVEL_1 = 10, /* QCIF (176x144) 64k bps */ + CSI_VENC_H265_LEVEL_2 = 20, /* CIF (352x288) 122k bps" */ + CSI_VENC_H265_LEVEL_2_1 = 21, + CSI_VENC_H265_LEVEL_3 = 30, /* SD (720x576) 6M bps */ + CSI_VENC_H265_LEVEL_3_1 = 31, + CSI_VENC_H265_LEVEL_4 = 40, /* 1080p (1920x1080) 12M bps */ + CSI_VENC_H265_LEVEL_4_1 = 41, + CSI_VENC_H265_LEVEL_5 = 50, /* 4k (3840x2160) 25M bps */ + CSI_VENC_H265_LEVEL_5_1 = 51, + CSI_VENC_H265_LEVEL_5_2 = 52, + CSI_VENC_H265_LEVEL_6 = 60, /* 8k (7680x4320) 60M bps */ + CSI_VENC_H265_LEVEL_6_1 = 61, + CSI_VENC_H265_LEVEL_6_2 = 62, +} csi_venc_h265_level_e; + +typedef struct csi_venc_h265_attr { + csi_venc_h265_profile_e profile; + csi_venc_h265_level_e level; + + uint32_t frame_type; // bitmask of csi_vcodec_frame_type_e + bool frame_mode; // encode by frame or slice mode + bool share_buf; //Whether to enable the Share Buf of Rcn and Ref +} csi_venc_h265_attr_s; + +typedef enum csi_venc_h265_nalu { + CSI_VENC_H265_NALU_BSLICE, /*B SLICE types*/ + CSI_VENC_H265_NALU_PSLICE, /*P SLICE types*/ + CSI_VENC_H265_NALU_ISLICE, /*I SLICE types*/ + CSI_VENC_H265_NALU_IDRSLICE, /*IDR SLICE types*/ + CSI_VENC_H265_NALU_VPS, /*VPS types*/ + CSI_VENC_H265_NALU_SEI, /*SEI types*/ + CSI_VENC_H265_NALU_SPS, /*SPS types*/ + CSI_VENC_H265_NALU_PPS, /*PPS types*/ +} csi_venc_h265_nalu_e; + +typedef csi_venc_h264_cbr_s csi_venc_h265_cbr_s; +typedef csi_venc_h264_vbr_s csi_venc_h265_vbr_s; +typedef csi_venc_h264_avbr_s csi_venc_h265_avbr_s; +typedef csi_venc_h264_fixqp_s csi_venc_h265_fixqp_s; + +#endif /* __CSI_VENC_H265_H__ */ diff --git a/include/hal/csi_venc_mjpeg.h b/include/hal/csi_venc_mjpeg.h new file mode 100644 index 0000000..04613dd --- /dev/null +++ b/include/hal/csi_venc_mjpeg.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CSI_VENC_MJPEG_H__ +#define __CSI_VENC_MJPEG_H__ + +typedef struct csi_venc_mpf_cfg +{ + uint8_t large_thumbnail_count; /* Range:[0,2]; the large thumbnail pic num of the MPF */ + csi_size_s large_thumbnail_size[2]; /* The resolution of large ThumbNail*/ +} csi_venc_mpf_cfg_s; + +/*the attribute of jpeg */ +typedef struct csi_venc_jpeg_attr { + bool support_dcf; /* Range:[0,1]; support dcf */ + csi_venc_mpf_cfg_s mpf_cfg; /* Range:[0,1]; config of Mpf*/ +} csi_venc_jpeg_attr_s; + +/*the attribute of mjpeg */ +typedef struct csi_venc_mjpeg_attr { + // reserve +} csi_venc_mjpeg_attr_s; + +typedef struct csi_venc_mjpeg_cbr { + uint32_t stat_time; /* Range:[1, 60]; the rate statistic time, the unit is senconds(s) */ + uint32_t framerate_numer; + uint32_t framerate_denom; + uint32_t bit_rate; /* Range:[2, 409600]; average bitrate, units:kbps */ +} csi_venc_mjpeg_cbr_s; + +typedef struct csi_venc_mjpeg_vbr { + uint32_t stat_time; /* Range:[1, 60]; the rate statistic time, the unit is senconds(s) */ + uint32_t framerate_numer; + uint32_t framerate_denom; + uint32_t max_bit_rate; /* Range:[2, 409600]; average bitrate, units:kbps */ +} csi_venc_mjpeg_vbr_s; + +typedef struct csi_venc_mjpeg_fixqp { + uint32_t framerate_numer; + uint32_t framerate_denom; + uint32_t q_factor; /* Range:[1,99];image quality. */ +} csi_venc_mjpeg_fixqp_s; + +typedef enum csi_venc_jpeg_pack { + CSI_VENC_JPEG_PACK_ECS, /*ECS types*/ + CSI_VENC_JPEG_PACK_APP, /*APP types*/ + CSI_VENC_JPEG_PACK_VDO, /*VDO types*/ + CSI_VENC_JPEG_PACK_PIC, /*PIC types*/ +} csi_venc_jpeg_pack_e; + +#endif /* __CSI_VENC_MJPEG_H__ */ diff --git a/include/hal/csi_venc_property.h b/include/hal/csi_venc_property.h new file mode 100644 index 0000000..3c9d4bc --- /dev/null +++ b/include/hal/csi_venc_property.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CSI_VENC_PROPERTY_H__ +#define __CSI_VENC_PROPERTY_H__ + +#include + +typedef struct csi_venc_attr { + csi_vcodec_id_e enc_vcodec_id; /* RW; the type of payload*/ + csi_pixel_fmt_e input_format; + uint32_t max_pic_width; /* RW; maximum width of a picture to be encoded, in pixel*/ + uint32_t max_pic_height; /* RW; maximum height of a picture to be encoded, in pixel*/ + uint32_t buf_size; /* RW; stream buffer size*/ + uint32_t pic_width; /* RW; maximum width of a picture to be encoded, in pixel*/ + uint32_t pic_height; /* RW; maximum height of a picture to be encoded, in pixel*/ + union { + csi_venc_h264_attr_s h264_attr; + csi_venc_h265_attr_s h265_attr; + csi_venc_jpeg_attr_s jpeg_attr; + csi_venc_mjpeg_attr_s mjpeg_attr; + }; +} csi_venc_attr_s; + +/* VENC GOP Property */ +typedef enum csi_venc_gop_mode { + CSI_VENC_GOPMODE_NORMALP = 0, + CSI_VENC_GOPMODE_DUALP = 1, + CSI_VENC_GOPMODE_SMARTP = 2, + CSI_VENC_GOPMODE_ADVSMARTP = 3, + CSI_VENC_GOPMODE_BIPREDB = 4, + CSI_VENC_GOPMODE_LOWDELAYB = 5, +} csi_venc_gop_mode_e; + +typedef struct csi_venc_gop_normalp { + int32_t ip_qp_delta; /* Range:[-10,30]; QP variance between P frame and I frame */ +} csi_venc_gop_normalp_s; + +typedef struct csi_venc_gop_dualp { + uint32_t sp_interval; /* Range:[0, 1)U(1, Gop -1]; Interval of the special P frames */ + int32_t sp_qp_delta; /* Range:[-10,30]; QP variance between P frame and special P frame */ + int32_t ip_qp_delta; /* Range:[-10,30]; QP variance between P frame and I frame */ +} csi_venc_gop_dualp_s; + +typedef struct csi_venc_gop_smartp { + uint32_t bg_interval; /* Range:[0, 1)U(1, Gop -1]; Interval of the special P frames */ + int32_t sp_qp_delta; /* Range:[-10,30]; QP variance between P frame and special P frame */ + int32_t ip_qp_delta; /* Range:[-10,30]; QP variance between P frame and I frame */ +} csi_venc_gop_smartp_s; + +typedef struct csi_venc_gop_advsmartp { + uint32_t bg_interval; /* Range:[Gop,4294967295] ;Interval of the long-term reference frame */ + int32_t bg_qp_delta; /* Range:[-10,30]; QP variance between P frame and Bg frame */ + int32_t vi_qp_delta; /* Range:[-10,30]; QP variance between P frame and virtual I frame */ +} csi_venc_gop_advsmartp_s; + +typedef struct csi_venc_gop_bipredb { + uint32_t b_frm_num; /* Range:[1,3]; Number of B frames */ + int32_t b_qp_delta; /* Range:[-10,30]; QP variance between P frame and B frame */ + int32_t ip_qp_delta; /* Range:[-10,30]; QP variance between P frame and I frame */ +} csi_venc_gop_bipredb_s; + +typedef struct csi_venc_gop_property { + uint32_t gop_num; + csi_venc_gop_mode_e gop_mode; + union { + csi_venc_gop_normalp_s normalp; /* attributes of normal P */ + csi_venc_gop_dualp_s dualp; /* attributes of dual P */ + csi_venc_gop_smartp_s smartp; /* attributes of Smart P */ + csi_venc_gop_advsmartp_s advsmartp; /* attributes of AdvSmart P */ + csi_venc_gop_bipredb_s bipredb; /* attributes of B */ + }; +} csi_venc_gop_property_s; + +/* VENC Rate Control Property */ +typedef enum csi_venc_rc_mode { + CSI_VENC_RC_MODE_NONE = 0, /* JPEG use it */ + + CSI_VENC_RC_MODE_H264CBR = 1, + CSI_VENC_RC_MODE_H264VBR, + CSI_VENC_RC_MODE_H264AVBR, + CSI_VENC_RC_MODE_H264FIXQP, + CSI_VENC_RC_MODE_H264QPMAP, + + CSI_VENC_RC_MODE_H265CBR, + CSI_VENC_RC_MODE_H265VBR, + CSI_VENC_RC_MODE_H265AVBR, + CSI_VENC_RC_MODE_H265FIXQP, + CSI_VENC_RC_MODE_H265QPMAP, + + CSI_VENC_RC_MODE_MJPEGCBR, + CSI_VENC_RC_MODE_MJPEGVBR, + CSI_VENC_RC_MODE_MJPEGFIXQP, +} csi_venc_rc_mode_e; + +typedef struct csi_venc_rc_property { + csi_venc_rc_mode_e rc_mode; + union { + csi_venc_h264_cbr_s h264_cbr; + csi_venc_h264_vbr_s h264_vbr; + csi_venc_h264_avbr_s h264_avbr; + csi_venc_h264_fixqp_s h264_fixqp; + + csi_venc_h265_cbr_s h265_cbr; + csi_venc_h265_vbr_s h265_vbr; + csi_venc_h265_avbr_s h265_avbr; + csi_venc_h265_fixqp_s h265_fixqp; + + csi_venc_mjpeg_cbr_s mjpeg_cbr; + csi_venc_mjpeg_vbr_s mjpeg_vbr; + csi_venc_mjpeg_fixqp_s mjpeg_fixqp; + }; +} csi_venc_rc_property_s; + +#endif /* __CSI_VENC_PROPERTY_H__ */ diff --git a/include/lib_camera/camera_manager.h b/include/lib_camera/camera_manager.h new file mode 100644 index 0000000..4ff87ff --- /dev/null +++ b/include/lib_camera/camera_manager.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CAMERA_MANAGER_H__ +#define __CAMERA_MANAGER_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum manage_target { + MANAGE_TARGET_INVALID = -1, + MANAGE_TARGET_CAMERA = 0, + MANAGE_TARGET_CHANNEL, +} manage_target_e; + +typedef enum camera_state { + CAMERA_STATE_CLOSED = (1 << 0), + CAMERA_STATE_OPENED = (1 << 1), + CAMERA_STATE_MODE_SET = (1 << 2), + CAMERA_STATE_RUNNING = (1 << 3), +} camera_state_e; + +typedef enum camera_action { + CAMERA_ACTION_NONE = 0, + CAMERA_ACTION_LOG_PRINT = (1 << 0), + CAMERA_ACTION_CAPTURE_FRAME = (1 << 1), + + CAMERA_ACTION_MAX_COUNT = 2, +} camera_action_e; + +typedef struct camera_event_action { + csi_camera_event_id_e event; + bool supported; + bool subscribed; + camera_action_e action; +} camera_event_action_t; + +typedef enum camera_channel_action { + CAMERA_CHANNEL_ACTION_NONE = 0, + CAMERA_CHANNEL_ACTION_LOG_PRINT = (1 << 0), + CAMERA_CHANNEL_ACTION_CAPTURE_FRAME = (1 << 1), + CAMERA_CHANNEL_ACTION_DISPLAY_FRAME = (1 << 2), + + CAMERA_CHANNEL_ACTION_MAX_COUNT = 3, +} camera_channel_action_e; + +typedef struct camera_channel_event_action { + csi_camera_channel_event_id_e event; + bool supported; + bool subscribed; + camera_channel_action_e action; +} camera_channel_event_action_t; + +typedef struct camera_event_action_union { + manage_target_e target; + int camera_id; + int channel_id; + union { + camera_event_action_t camera; + camera_channel_event_action_t channel; + }; +} camera_event_action_union_t; + +struct camera_session; +typedef int (*camera_action_fun_t)(struct camera_session *, csi_camera_event_s *); + +typedef struct camera_session { + camera_state_e state; /* camera current state */ + uint32_t info_status; /* bitmask of camera_info_status_e */ + + int camera_id; /* from csi_camera_infos_s.info[id] */ + csi_cam_handle_t camera_handle; /* from csi_camera_open() */ + csi_camera_infos_s camera_infos; /* from csi_camera_query_list() */ + csi_camera_modes_s camera_modes; /* from csi_camera_get_modes() */ + int camera_mode_id; /* from csi_camera_modes_s.modes[].mode_id */ + + csi_camera_channel_cfg_s chn_cfg[CSI_CAMERA_CHANNEL_MAX_COUNT]; + csi_camera_channel_cfg_s chn_cfg_tmp; /* store temp params when configuring */ + + csi_cam_event_handle_t event_handle; + camera_event_action_union_t camera_event_action[CSI_CAMERA_EVENT_MAX_COUNT]; + camera_event_action_union_t channel_event_action[CSI_CAMERA_CHANNEL_MAX_COUNT][CSI_CAMERA_CHANNEL_EVENT_MAX_COUNT]; + + camera_action_fun_t camera_action_fun; + camera_action_fun_t channel_action_fun; + + pthread_t event_action_thread_id; +} cams_t; + +int camera_create_session(cams_t **session); +int camera_destory_session(cams_t **session); + +int camera_query_list(cams_t *session); +int camera_get_caps(cams_t *session); +int camera_open(cams_t *session, int cam_id); +int camera_close(cams_t *session); + +int camera_get_modes(cams_t *session); +int camera_set_mode(cams_t *session, int mode_id); + +int camera_query_property(cams_t *session, + csi_camera_property_description_s *description); +int camera_set_property(cams_t *session, csi_camera_properties_s *properties); + +int camera_channel_query_list(cams_t *session); +int camera_channel_open(cams_t *session, csi_camera_channel_cfg_s *chn_cfg); +int camera_channel_close(cams_t *session, csi_camera_channel_id_e chn_id); + +int camera_register_event_action(cams_t *session, + camera_action_fun_t camera_action_fun, + camera_action_fun_t channel_action_fun); +int camera_subscribe_event(cams_t *session); +int camera_channel_start(cams_t *session, csi_camera_channel_id_e chn_id); +int camera_channel_stop(cams_t *session, csi_camera_channel_id_e chn_id); + +#ifdef __cplusplus +} +#endif + +#endif /* __CAMERA_MANAGER_H__ */ diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..72a4bf3 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,29 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## + +DIR_TO_ROOT=.. +include $(DIR_TO_ROOT)/build.param + +all: common platform camera + +common: + make -C common + +platform: common + make -C platform + +camera: platform + make -C lib_camera + +clean: + make -C common clean + make -C platform clean + +.PHONY: clean all common platform + diff --git a/src/common/Makefile b/src/common/Makefile new file mode 100644 index 0000000..2836a45 --- /dev/null +++ b/src/common/Makefile @@ -0,0 +1,45 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../.. +include $(DIR_TO_ROOT)/build.param + +TARGET := libhal_common.a +TARGET_DYNAMIC := libhal_common.so +OUTPUT_DIR := $(DIR_TO_ROOT)/output/hal + +SRCS = $(wildcard *.c) +OBJS = $(SRCS:.c=.o) + +all: $(TARGET) $(TARGET_DYNAMIC) + +$(TARGET): $(OBJS) + @mkdir -p $(OUTPUT_DIR) + @echo "Linking" $@ "..." + $(AR) -r -o $(OUTPUT_DIR)/$@ .obj/*.o + +$(TARGET_DYNAMIC): $(OBJS) + @mkdir -p $(OUTPUT_DIR)/$(SO_LIB_DIR) + @echo "Linking" $@ "..." + $(CC) -shared -fPIC -o $(OUTPUT_DIR)/$(SO_LIB_DIR)/$@ .obj/*.o $(CFLAGS) $(INCLUDE) + +$(OBJS): %.o:%.c + @mkdir -p .obj + @echo "Compiling" $< "..." +ifeq ($(PLATFORM),light) + $(CC) $(CFLAGS) $(INCLUDE) -fPIC -c -o .obj/$(notdir $@) $< +else + $(CC) $(CFLAGS) $(INCLUDE) -c -o .obj/$(notdir $@) $< +endif + +clean: + rm -rf .obj + rm -rf $(OUTPUT_DIR)/$(TARGET) + rm -rf $(OUTPUT_DIR)/$(SO_LIB_DIR)/$(TARGET_DYNAMIC) + +.PHONY: clean all diff --git a/src/common/camera_string.c b/src/common/camera_string.c new file mode 100644 index 0000000..7e89853 --- /dev/null +++ b/src/common/camera_string.c @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +const char *camera_string_enum_name(int property_id, int enum_id) +{ + switch(property_id) { + case CSI_CAMERA_PID_EXPOSURE_MODE: + switch (enum_id) { + case CSI_CAMERA_EXPOSURE_MODE_AUTO: + return "Auto"; + case CSI_CAMERA_EXPOSURE_MANUAL: + return "Manual"; + case CSI_CAMERA_EXPOSURE_SHUTTER_PRIORITY: + return "Shutter Priority"; + case CSI_CAMERA_EXPOSURE_APERTURE_PRIORITY: + return "Aperture Priority"; + default: + return "Unknown"; + } + default: + return "Unknown"; + } +} + +const char *camera_string_bitmask_name(int property_id, int bitmask) +{ + switch(property_id) { + case CSI_CAMERA_PID_3A_LOCK: + switch (bitmask) { + case CSI_CAMERA_LOCK_EXPOSURE: + return "Lock Exposure"; + case CSI_CAMERA_LOCK_WHITE_BALANCE: + return "Lock White Balance"; + case CSI_CAMERA_LOCK_FOCUS: + return "Lock Focus"; + default: + return "Unknown"; + } + default: + return "Unknown"; + } +} + +const char *camera_string_capture_type(csi_camera_channel_capture_type_e type, bool accept_unknown) +{ + switch (type) { + case CSI_CAMERA_CHANNEL_CAPTURE_VIDEO: + return "Video"; + case CSI_CAMERA_CHANNEL_CAPTURE_META: + return "Meta"; + default: + if (accept_unknown) + return "Unknown"; + else + return ""; + } +} + +const char *camera_string_chn_capture_types(int fields, char *fields_string) +{ + fields_string[0] = '\0'; + bool has_item = false; + + for (uint32_t i = 1; i < 0x80000000; i = i << 1) { + const char *item_string = camera_string_capture_type(i & fields, false); + if (strlen(item_string) > 0) { + if (has_item) + strcat(fields_string, "|"); + strcat(fields_string, item_string); + + has_item = true; + } + } + return fields_string; +} + +const char *camera_string_pixel_format(csi_pixel_fmt_e pix_fmt) +{ + switch (pix_fmt) { + case CSI_PIX_FMT_I420: + return "I420"; + case CSI_PIX_FMT_NV12: + return "NV12"; + case CSI_PIX_FMT_BGR: + return "BGR"; + default: + return "Unknown"; + } +} + +const char *camera_string_img_type(csi_img_type_e img_type) +{ + switch (img_type) { + case CSI_IMG_TYPE_DMA_BUF: + return "dma-buf"; + case CSI_IMG_TYPE_SYSTEM_CONTIG: + return "system-contig"; + case CSI_IMG_TYPE_CARVEOUT: + return "carveout"; + case CSI_IMG_TYPE_UMALLOC: + return "user-malloc"; + case CSI_IMG_TYPE_SHM: + return "shared-memory"; + default: + return "Unknown"; + } +} + +const char *camera_string_meta_field(csi_camera_meta_id_e type, bool accept_unknown) +{ + switch (type) { + case CSI_CAMERA_META_ID_CAMERA_NAME: + return "Cam_Name"; + case CSI_CAMERA_META_ID_CHANNEL_ID: + return "Chn_ID"; + case CSI_CAMERA_META_ID_FRAME_ID: + return "Frm_ID"; + case CSI_CAMERA_META_ID_TIMESTAMP: + return "Timestamp"; + case CSI_CAMERA_META_ID_HDR: + return "Is_HDR"; + default: + if (accept_unknown) + return "Unknown"; + else + return ""; + } +} + +const char *camera_string_chn_meta_fields(int fields, char *fields_string) +{ + fields_string[0] = '\0'; + bool has_item = false; + + for (uint32_t i = 1; i < 0x80000000; i = i << 1) { + const char *item_string = camera_string_meta_field(i & fields, false); + if (strlen(item_string) > 0) { + if (has_item) + strcat(fields_string, "|"); + strcat(fields_string, item_string); + + has_item = true; + } + } + return fields_string; +} + +const char *camera_string_chn_status(csi_img_type_e img_type) +{ + switch (img_type) { + case CSI_CAMERA_CHANNEL_INVALID: + return "invalid"; + case CSI_CAMERA_CHANNEL_CLOSED: + return "closed"; + case CSI_CAMERA_CHANNEL_OPENED: + return "opened"; + case CSI_CAMERA_CHANNEL_RUNNING: + return "running"; + default: + return "Unknown"; + } +} + +const char *camera_string_camera_event_type(csi_camera_event_id_e event_id) +{ + switch (event_id) { + case CSI_CAMERA_EVENT_WARNING: + return "Event Warning"; + case CSI_CAMERA_EVENT_ERROR: + return "Event Error"; + case CSI_CAMERA_EVENT_SENSOR_FIRST_IMAGE_ARRIVE: + return "Sensor 1st Frame"; + case CSI_CAMERA_EVENT_ISP_3A_ADJUST_READY: + return "3A Adjust Ready"; + default: + return "Unknown"; + } +} + +const char *camera_string_channel_event_type(csi_camera_channel_event_id_e event_id) +{ + switch (event_id) { + case CSI_CAMERA_CHANNEL_EVENT_FRAME_READY: + return "Frame Ready"; + case CSI_CAMERA_CHANNEL_EVENT_FRAME_PUT: + return "Frame put back"; + case CSI_CAMERA_CHANNEL_EVENT_OVERFLOW: + return "Frame overflow"; + default: + return "Unknown"; + } +} + +const char *camera_string_camera_action(camera_action_e action) +{ + switch (action) { + case CAMERA_ACTION_NONE: + return "Do nothing"; + case CAMERA_ACTION_LOG_PRINT: + return "Log print"; + case CAMERA_ACTION_CAPTURE_FRAME: + return "Capture Frame"; + default: + return "Unknown"; + } +} +const char *camera_string_channel_action(camera_channel_action_e action) +{ + switch (action) { + case CAMERA_CHANNEL_ACTION_NONE: + return "Do nothing"; + case CAMERA_CHANNEL_ACTION_LOG_PRINT: + return "Log print"; + case CAMERA_CHANNEL_ACTION_CAPTURE_FRAME: + return "Capture Frame"; + case CAMERA_CHANNEL_ACTION_DISPLAY_FRAME: + return "Display Frame"; + default: + return "Unknown"; + } +} + diff --git a/src/common/dump_utils.c b/src/common/dump_utils.c new file mode 100644 index 0000000..c479bef --- /dev/null +++ b/src/common/dump_utils.c @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#define LOG_LEVEL 3 +//#define LOG_PREFIX "" +#include +#include + +#define DUMP_HEX_MAX_LENGHT 1024 + +void csi_dump_hex(char *data, int length, char *name) +{ + if (data == NULL || length <=0) { + LOG_E("Input param invalid\n"); + return; + } + + int i; + int dump_len = (length <= DUMP_HEX_MAX_LENGHT) ? length : DUMP_HEX_MAX_LENGHT; + + LOG_F("%s (%d bytes) dump in hex mode:\n", (name != NULL) ? name :"", length); + LOG_F(" addr: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n"); + LOG_F(" ------------------------------------------------------\n"); + for (i = 0; i < dump_len; i++) { + if (i % 16 == 0) + LOG_F(" %04x: ", i); + LOG_F("%02x ", (unsigned char)data[i]); + + if (i % 16 == 7) + LOG_F(" "); + + if (i % 16== 15) + printf("\n"); + } + if (length != dump_len) + LOG_F("..."); + if (i % 16 != 0) + LOG_F("\n"); + LOG_F(" ------------------------------------------------------\n"); +} + +void csi_dump_img_info(csi_img_s *img) +{ + // dump img info + LOG_F("frame.img = {\n"); + LOG_F("\t type=%d size=%zu w=%u h=%u pix_format=%d num_planes=%u\n", + img->type, img->size, img->width, img->height, img->pix_format, img->num_planes); + LOG_F("\t fds[3] = {%d, %d, %d},\n", + img->fds[0], img->fds[1], img->fds[2]); + LOG_F("\t strides[3] = {%u, %u, %u},\n", + img->strides[0], img->strides[1], img->strides[2]); + LOG_F("\t offsets[3] = {%u, %u, %u},\n", + img->offsets[0], img->offsets[1], img->offsets[2]); + LOG_F("\t modifier = %lu\n", img->modifier); + LOG_F("}\n"); +} + diff --git a/src/common/llist.c b/src/common/llist.c new file mode 100644 index 0000000..0eab668 --- /dev/null +++ b/src/common/llist.c @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: zhuxinran + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#include "llist.h" + +int llist_insert(LLIST *, const void *data, int mode); +void *llist_find(LLIST *, const void *data, llist_cmp *cmp); +int llist_delete(LLIST *, const void *data, llist_cmp *cmp); +int llist_fetch(LLIST *, const void *data, llist_cmp *cmp, void *rdata); +void llist_travel(LLIST *, llist_op *op); + +LLIST *llist_create(int size) +{ + LLIST *me; + + me = malloc(sizeof(*me)); + if (me == NULL) + return NULL; + + me->size = size; + me->head.prev = &me->head; + me->head.next = &me->head; + + me->insert = llist_insert; + me->find = llist_find; + me->delete = llist_delete; + me->fetch = llist_fetch; + me->travel = llist_travel; + + return me; +} + + +int llist_insert(LLIST *me, const void *data, int mode) +{ + struct llist_node_st *newnode; + + newnode = malloc(sizeof(*newnode) + me->size); + if (newnode == NULL) + return -1; + + memcpy(newnode->data, data, me->size); + + if (mode == LLIST_FORWARD) { + newnode->prev = &me->head; + newnode->next = me->head.next; + } else if (mode == LLIST_BACKWARD) { + newnode->prev = me->head.prev; + newnode->next = &me->head; + } else // error + return -3; + + newnode->prev->next = newnode; + newnode->next->prev = newnode; + + return 0; +} + +static struct llist_node_st *find_(LLIST *me, const void *data, llist_cmp *cmp) +{ + struct llist_node_st *cur; + + for (cur = me->head.next ; cur != &me->head ; cur = cur->next) { + if (cmp(cur->data, data) == 0) + break; + } + + return cur; +} + +void *llist_find(LLIST *me, const void *data, llist_cmp *cmp) +{ + struct llist_node_st *cur; + + cur = find_(me, data, cmp); + if (cur == &me->head) + return NULL; + return cur->data; +} + +int llist_delete(LLIST *me, const void *data, llist_cmp *cmp) +{ + struct llist_node_st *node; + + node = find_(me, data, cmp); + if (node == &me->head) + return -1; + + node->prev->next = node->next; + node->next->prev = node->prev; + free(node); + + return 0; +} + +int llist_fetch(LLIST *me, const void *data, llist_cmp *cmp, void *rdata) +{ + struct llist_node_st *node; + + node = find_(me, data, cmp); + if (node == &me->head) + return -1; + + if (rdata != NULL) + memcpy(rdata, node->data, me->size); + + node->prev->next = node->next; + node->next->prev = node->prev; + free(node); + + return 0; +} + +void llist_travel(LLIST *me, llist_op *op) +{ + struct llist_node_st *cur; + + for (cur = me->head.next ; cur != &me->head ; cur = cur->next) { + op(cur->data); + } +} + +void llist_destroy(LLIST *me) +{ + struct llist_node_st *cur, *next; + + for (cur = me->head.next ; cur != &me->head ; cur = next) { + next = cur->next; + free(cur); + } + + free(me); + return ; +} diff --git a/src/common/producer_consumer_async.c b/src/common/producer_consumer_async.c new file mode 100644 index 0000000..cb99163 --- /dev/null +++ b/src/common/producer_consumer_async.c @@ -0,0 +1,589 @@ +/* + * Copyright 2018-2019 C-SKY Microsystems Co., Ltd. + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * 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. + * + */ +/* 0: Error; 1: Error|Warning; 2:Error|Warning|Info; 3:Error|Warning|Info|Debug */ +#define LOG_LEVEL 0 + +#include "common.h" +#include "producer_consumer_async.h" + +typedef enum { + _PCA_DATA_DISPOSE_DISCARDED, + _PCA_DATA_DISPOSE_DELIVERED, + _PCA_DATA_DISPOSE_UPDATED, + + _PCA_DATA_DISPOSE_COUNT +} _pca_data_dispose_e; + +static const char* _pca_data_dispose_string[_PCA_DATA_DISPOSE_COUNT] = { + "discarded(-)", "delivered(+)", "updated(.)" +}; +#define _PCA_DATA_DISPOSE_STRING(dispose) (_pca_data_dispose_string[dispose]) + +static int list_get_count(const struct list_head * head) +{ + uint32_t count = 0; + struct list_head *pos; + list_for_each(pos, head){ + count ++; + } + return count; +} + +static int32_t _pca_init_thread_info(pca_thread_info_t* info) +{ + CHECK_POINTER_RETURN(info); + + memset(info, 0, sizeof(pca_thread_info_t)); + info->tid = -1; + sem_init(&info->sem_wakeup, 0, 0); + pthread_mutex_init(&info->mutex_data, NULL); + + return SUCCESS; +} + +static inline int32_t _pca_data_free(pca_data_info_t* data_info) +{ + CHECK_POINTER_RETURN(data_info); + if (data_info->consumed_callback != NULL) { + data_info->consumed_callback(data_info); + } + return SUCCESS; +} + +static void _pca_data_free_list_dump(pca_data_free_list_info_t* data_free_list) +{ +#if (LOG_LEVEL >= 3) + pca_data_free_list_info_t* item; + struct list_head *pos; + uint32_t count = 0; + + LOG_D("Dumpping items in list begin\n"); + list_for_each(pos, &data_free_list->list) { + item = list_entry(pos, pca_data_free_list_info_t, list); + count++; + LOG_D("\t data(%p,%d,%d,%d), reference_count=%d\n", + item->data_info.data, + *((uint32_t*)item->data_info.data), + item->data_info.length, + item->data_info.type, + item->reference_count); + } + LOG_D("There are %d items in list\n", count); +#endif +} + +static void _pca_thread_dump_statistics(pca_data_statistics_t *stats, char *name) +{ + LOG_I("The statistics of '%s' are:\n" + " total_delivered = %d\n" + " total_discarded = %d\n" + " total_updated = %d\n" + " total_consumed = %d\n\n", + (name == NULL) ? "unknown" : name, + stats->total_delivered, + stats->total_discarded, + stats->total_updated, + stats->total_consumed); +} + +static inline int32_t _pca_add_data_free_item(pca_dispatcher_info_t* dispatcher, + pca_data_info_t* data_info) +{ + pca_data_free_list_info_t* data_free_list = &dispatcher->data_free_list; + struct list_head *pos; + pca_data_free_list_info_t* item; + bool found_in_list = false; + + if (data_info->consumed_callback == NULL) + return SUCCESS; + + LOG_D("Add data(%p,%d,%d,%d) to list\n", + data_info->data, *((uint32_t*)data_info->data), + data_info->length, data_info->type); + + pthread_mutex_lock(&dispatcher->data_free_mutex); + /* If data has been found in list, reference_count++ */ + list_for_each(pos, &data_free_list->list) { + item = list_entry(pos, pca_data_free_list_info_t, list); + if (item->data_info.data == data_info->data) { + found_in_list = true; + item->reference_count++; + break; + } + } + + /* If data has not been found in list, create & add into list */ + if(!found_in_list) { + pca_data_free_list_info_t* item = + malloc(sizeof(pca_data_free_list_info_t)); + memcpy(&item->data_info, data_info, sizeof(pca_data_info_t)); + item->reference_count = 1; + + list_add_tail(&item->list, &data_free_list->list); + } + _pca_data_free_list_dump(data_free_list); + pthread_mutex_unlock(&dispatcher->data_free_mutex); + + return SUCCESS; +} + +static inline int32_t _pca_remove_data_free_item(pca_dispatcher_info_t* dispatcher, + pca_data_info_t* data_info) +{ + pca_data_free_list_info_t* data_free_list = &dispatcher->data_free_list; + struct list_head *pos; + pca_data_free_list_info_t* item; + bool found_in_list = false; + + if (data_info->consumed_callback == NULL) + return SUCCESS; + + /* Search in the list */ + LOG_D("Remove data(%p,%d,%d,%d) from list\n", + data_info->data, *((uint32_t*)data_info->data), + data_info->length, data_info->type); + + pthread_testcancel(); + pthread_mutex_lock(&dispatcher->data_free_mutex); + list_for_each(pos, &data_free_list->list) { + item = list_entry(pos, pca_data_free_list_info_t, list); + if (item->data_info.data == data_info->data) { + found_in_list = true; + item->reference_count--; + /* If reference_count is 0, then remove from list */ + if (item->reference_count == 0) { + LOG_D("Free\n"); + _pca_data_free(&item->data_info); + list_del(&item->list); + free(item); + } + break; + } + } + _pca_data_free_list_dump(data_free_list); + pthread_mutex_unlock(&dispatcher->data_free_mutex); + + if (!found_in_list) { + LOG_W("Can't find the data(%p,%d,%d,%d) in list!\n", + data_info->data, *((uint32_t*)data_info->data), + data_info->length, data_info->type); + } + + return SUCCESS; +} + +static void _pca_flush_data_free_list(pca_dispatcher_info_t* dispatcher) +{ + pca_data_free_list_info_t* data_free_list = &dispatcher->data_free_list; + struct list_head *pos; + pca_data_free_list_info_t* item; + + pthread_mutex_lock(&dispatcher->data_free_mutex); + list_for_each(pos, &data_free_list->list) { + item = list_entry(pos, pca_data_free_list_info_t, list); + + list_del(&item->list); + LOG_D("Free\n"); + _pca_data_free(&item->data_info); + free(item); + } + _pca_data_free_list_dump(data_free_list); + pthread_mutex_unlock(&dispatcher->data_free_mutex); + LOG_I("Flush data free list finished\n"); +} + +static void* _pca_thread_consumer(void *arg) +{ + pca_consumer_list_info_t* consumer = (pca_consumer_list_info_t*)arg; + pca_dispatcher_info_t* dispatcher = consumer->dispatcher; + + LOG_D("Entering\n"); + consumer->thread_info.is_running = true; + + while (1) { + sem_wait(&consumer->thread_info.sem_wakeup); + LOG_D("sem_wait\n"); + if (!consumer->thread_info.has_new_data) + continue; + + /* Use mutex to protect data can't be modify while handling */ + pthread_mutex_lock(&consumer->thread_info.mutex_data); + LOG_D("pthread_mutex_lock\n"); + pca_consumer_process process = consumer->processor.process; + int process_data_type = consumer->processor.data_type; + + pca_data_info_t* data_info = &consumer->thread_info.data_info; + if (process_data_type == data_info->type) { + process(data_info); + consumer->thread_info.stats.total_consumed++; + LOG_D("Data(%p,%d,%d,%d) is consumed by %s.\n", + data_info->data, *((uint32_t*)(data_info->data)), + data_info->length, data_info->type, + consumer->thread_info.name); + } else { + LOG_W("This data_type(%d) should not dispatcher to %s\n", + data_info->type, consumer->processor.name); + } + _pca_remove_data_free_item(dispatcher, data_info); + consumer->thread_info.has_new_data = false; + pthread_mutex_unlock(&consumer->thread_info.mutex_data); + + } + return NULL; +} + +static void* _pca_thread_dispatcher(void *arg) +{ + pca_dispatcher_info_t* dispatcher = (pca_dispatcher_info_t*)arg; + int i, ret; + + LOG_I("Entering\n"); + dispatcher->thread_info.is_running = true; + + while (1) { + sem_wait(&dispatcher->thread_info.sem_wakeup); + + pthread_mutex_lock(&dispatcher->thread_info.mutex_data); + ret = pca_feed_consumers(dispatcher, &dispatcher->thread_info.data_info); + if (ret != SUCCESS) { /* No consumer accept the data */ + LOG_D("Free\n"); + _pca_data_free(&dispatcher->thread_info.data_info); + } + dispatcher->thread_info.has_new_data = false; + dispatcher->thread_info.stats.total_consumed++; + pthread_mutex_unlock(&dispatcher->thread_info.mutex_data); + } + return NULL; +} + +int32_t pca_dispatcher_create(pca_dispatcher_info_t** dispatcher, char* name) +{ + CHECK_POINTER_RETURN(dispatcher); + CHECK_POINTER_RETURN(name); + pca_dispatcher_info_t *info = malloc(sizeof(pca_dispatcher_info_t)); + CHECK_POINTER_RETURN(info); + + _pca_init_thread_info(&info->thread_info); + strncpy(info->thread_info.name, name, sizeof(info->thread_info.name)); + + pthread_mutex_init(&info->consumer_mutex, NULL); + INIT_LIST_HEAD(&info->consumer_list.list); + + int item_count = list_get_count(&(info->consumer_list.list)); + printf("item_count = %d\n",item_count); + + + pthread_mutex_init(&info->data_free_mutex, NULL); + INIT_LIST_HEAD(&info->data_free_list.list); + + *dispatcher = info; + + return SUCCESS; +} + +int32_t pca_dispatcher_run(pca_dispatcher_info_t* dispatcher) +{ + CHECK_POINTER_RETURN(dispatcher); + + int ret = pthread_create(&(dispatcher->thread_info.tid), NULL, + _pca_thread_dispatcher, (void*)(dispatcher)); + CHECK_RET_RETURN(ret); + + return SUCCESS; +} + +int32_t pca_dispatcher_stop(pca_dispatcher_info_t* dispatcher) +{ + CHECK_POINTER_RETURN(dispatcher); + + /* Stop consumers */ + pca_consumer_list_info_t* consumer_list = &dispatcher->consumer_list; + struct list_head *pos; + pca_consumer_list_info_t* consumer; + + pthread_mutex_lock(&dispatcher->consumer_mutex); + list_for_each(pos, &consumer_list->list) { + consumer = list_entry(pos, pca_consumer_list_info_t, list); + + /* Stop consumer's thread */ + pthread_cancel(consumer->thread_info.tid); + pthread_join(consumer->thread_info.tid, NULL); + consumer->thread_info.is_running = false; + + _pca_thread_dump_statistics(&consumer->thread_info.stats, + consumer->thread_info.name); + + /* Remove consuer from consumers' list */ + list_del(&consumer->list); + + /* Free consumer's memory */ + free(consumer); + } + pthread_mutex_unlock(&dispatcher->consumer_mutex); + + /* Flush "need free data list" */ + LOG_I("All consumers thread are stopped\n"); + _pca_flush_data_free_list(dispatcher); + + /* Dispatcher thread stop */ + pthread_cancel(dispatcher->thread_info.tid); + pthread_join(dispatcher->thread_info.tid, NULL); + dispatcher->thread_info.is_running = false; + LOG_I("Dispatcher thread is stopped\n"); + + _pca_thread_dump_statistics(&dispatcher->thread_info.stats, + dispatcher->thread_info.name); + + return SUCCESS; +} + +int32_t pca_dispatcher_destory(pca_dispatcher_info_t* dispatcher) +{ + CHECK_POINTER_RETURN(dispatcher); + + free(dispatcher); + return SUCCESS; +} + +int32_t pca_add_consumer_processor(pca_dispatcher_info_t* dispatcher, + pca_consumer_processor_info_t *processor) +{ + CHECK_POINTER_RETURN(dispatcher); + CHECK_POINTER_RETURN(processor); + CHECK_POINTER_RETURN(processor->process); + int32_t ret; + + /* Get dispatcher's consumer list head */ + pca_consumer_list_info_t* consumer_list = &dispatcher->consumer_list; + + /* malloc a new consumer item */ + pca_consumer_list_info_t* consumer; + consumer = (pca_consumer_list_info_t*)(malloc(sizeof(pca_consumer_list_info_t))); + CHECK_POINTER_RETURN(consumer); + + /* init the item members */ + ret = _pca_init_thread_info(&consumer->thread_info); + CHECK_RET_RETURN(ret); + memcpy(&consumer->processor, processor, + sizeof(pca_consumer_processor_info_t)); + snprintf(consumer->thread_info.name, + sizeof(consumer->thread_info.name), + "consumer_%s", processor->name); + consumer->dispatcher = dispatcher; + + /* Add consumer item to dispatcher's list */ + pthread_mutex_lock(&dispatcher->consumer_mutex); + list_add_tail(&consumer->list, &consumer_list->list); + + int item_count = list_get_count(&consumer_list->list); + pthread_mutex_unlock(&dispatcher->consumer_mutex); + LOG_I("There are %d consumer processor now\n", item_count); + + /* Create consumer thread */ + ret = pthread_create(&(consumer->thread_info.tid), NULL, + _pca_thread_consumer, (void*)(consumer)); + CHECK_RET_RETURN(ret); + + return SUCCESS; +} + +int32_t pca_remove_consumer_processor(pca_dispatcher_info_t* dispatcher, + pca_consumer_processor_info_t *processor) +{ + LOG_E("Not support yet, but stop can remove all automatically\n"); + pthread_mutex_lock(&dispatcher->consumer_mutex); + pthread_mutex_unlock(&dispatcher->consumer_mutex); + return FAILURE; +} + +int32_t pca_feed_dispatcher(pca_dispatcher_info_t* dispatcher, + pca_data_info_t* data_info) +{ + CHECK_POINTER_RETURN(dispatcher); + CHECK_POINTER_RETURN(data_info); + CHECK_POINTER_RETURN(data_info->data); + + while (dispatcher->thread_info.is_running == false) { + LOG_D("Waiting for dispatcher thread run...\n"); + usleep(10*1000); + } + + _pca_data_dispose_e dispose; + + LOG_D("Feed Data(%p,%d,%d,%d) to dispatcher.\n", + data_info->data, *((uint32_t*)(data_info->data)), + data_info->length, data_info->type); + + if (pthread_mutex_trylock(&dispatcher->thread_info.mutex_data) == 0) { + dispatcher->thread_info.stats.total_delivered++; + if (dispatcher->thread_info.has_new_data) { + /* Release old data */ + pca_data_info_t old_data_info; + memcpy(&old_data_info, &dispatcher->thread_info.data_info, + sizeof(pca_data_info_t)); + LOG_D("Free\n"); + _pca_data_free(&old_data_info); + + /* Copy new data */ + memcpy(&dispatcher->thread_info.data_info, data_info, + sizeof(pca_data_info_t)); + dispatcher->thread_info.stats.total_updated ++; + + dispose = _PCA_DATA_DISPOSE_UPDATED; + } else { + /* Copy new data */ + memcpy(&dispatcher->thread_info.data_info, data_info, + sizeof(pca_data_info_t)); + + int sem_value; + if (sem_getvalue(&dispatcher->thread_info.sem_wakeup, &sem_value) == 0) { + if (sem_value == 0) { + sem_post(&dispatcher->thread_info.sem_wakeup); + } + } + dispose = _PCA_DATA_DISPOSE_DELIVERED; + } + dispatcher->thread_info.has_new_data = true; + + LOG_D("Feed data(%p,%d,%d,%d) to dispatcher is %s\n\n", + data_info->data, *((uint32_t*)data_info->data), + data_info->length, data_info->type, + _PCA_DATA_DISPOSE_STRING(dispose)); + pthread_mutex_unlock(&dispatcher->thread_info.mutex_data); + } else { + dispose = _PCA_DATA_DISPOSE_DISCARDED; + LOG_D("Feed data(%p,%d,%d,%d) to dispatcher is %s\n\n", + data_info->data, *((uint32_t*)data_info->data), + data_info->length, data_info->type, + _PCA_DATA_DISPOSE_STRING(dispose)); + LOG_D("Free\n"); + _pca_data_free(data_info); + dispatcher->thread_info.stats.total_discarded++; + } + + return SUCCESS; +} + +int32_t pca_feed_consumers(pca_dispatcher_info_t* dispatcher, + pca_data_info_t* data_info) +{ + CHECK_POINTER_RETURN(dispatcher); + CHECK_POINTER_RETURN(data_info); + + pca_consumer_list_info_t* consumer_list = &dispatcher->consumer_list; + + struct list_head *pos; + pca_consumer_list_info_t* consumer; + + pthread_mutex_lock(&dispatcher->consumer_mutex); + bool data_feed = false; + + LOG_D("Feed Data(%p,%d,%d,%d) to consumers.\n", + data_info->data, *((uint32_t*)(data_info->data)), + data_info->length, data_info->type); + + int i = 0; + list_for_each(pos, &consumer_list->list) { + consumer = list_entry(pos, pca_consumer_list_info_t, list); + _pca_data_dispose_e dispose; + + /* If consumer's data_type != data's type, DO NOT dispatch */ + if (data_info->type != consumer->processor.data_type) { + continue; + } + + consumer->thread_info.stats.total_delivered++; + if (pthread_mutex_trylock(&consumer->thread_info.mutex_data) == 0) { + consumer->thread_info.need_unlock_data_mutex = true; + if (consumer->thread_info.has_new_data) { + /* Release old data */ + pca_data_info_t old_data_info; + memcpy(&old_data_info, &consumer->thread_info.data_info, + sizeof(pca_data_info_t)); + LOG_D("Remove\n"); + _pca_remove_data_free_item(dispatcher, &old_data_info); + + /* Copy new data */ + memcpy(&consumer->thread_info.data_info, data_info, + sizeof(pca_data_info_t)); + _pca_add_data_free_item(dispatcher, &consumer->thread_info.data_info); + consumer->thread_info.stats.total_updated ++; + + dispose = _PCA_DATA_DISPOSE_UPDATED; + } else { + /* Copy new data */ + memcpy(&consumer->thread_info.data_info, data_info, + sizeof(pca_data_info_t)); + _pca_add_data_free_item(dispatcher, &consumer->thread_info.data_info); + + dispose = _PCA_DATA_DISPOSE_DELIVERED; + } + consumer->thread_info.has_new_data = true; + data_feed = true; + LOG_D("Feed Data(%p,%d,%d,%d) to %s is %s (%d)\n", + data_info->data, *((uint32_t*)(data_info->data)), + data_info->length, data_info->type, + consumer->thread_info.name, + _PCA_DATA_DISPOSE_STRING(dispose), i); + } else { + dispose = _PCA_DATA_DISPOSE_DISCARDED; + LOG_D("Feed Data(%p,%d,%d,%d) to %s is %s (%d)\n", + data_info->data, *((uint32_t*)(data_info->data)), + data_info->length, data_info->type, + consumer->thread_info.name, + _PCA_DATA_DISPOSE_STRING(dispose), i); + consumer->thread_info.stats.total_discarded++; + } + i++; + } + + list_for_each(pos, &consumer_list->list) { + consumer = list_entry(pos, pca_consumer_list_info_t, list); + if (consumer->thread_info.need_unlock_data_mutex) { + consumer->thread_info.need_unlock_data_mutex = false; + pthread_mutex_unlock(&consumer->thread_info.mutex_data); + } + + if (consumer->thread_info.has_new_data) { + int sem_value; + if (sem_getvalue(&consumer->thread_info.sem_wakeup, &sem_value) == 0) { + if (sem_value == 0) { + LOG_D("sem_post to %s\n", consumer->thread_info.name); + sem_post(&consumer->thread_info.sem_wakeup); + } + } + } + } + + pthread_mutex_unlock(&dispatcher->consumer_mutex); + dispatcher->thread_info.has_new_data = false; + + if (!data_feed) { + LOG_D("Data(%p,%d,%d,%d) no consumer accepted.\n", + data_info->data, *((uint32_t*)(data_info->data)), + data_info->length, data_info->type); + return FAILURE; + } else { + LOG_D("Data(%p,%d,%d,%d) feed finished.\n", + data_info->data, *((uint32_t*)(data_info->data)), + data_info->length, data_info->type); + return SUCCESS; + } +} + diff --git a/src/common/ringbuffer.c b/src/common/ringbuffer.c new file mode 100644 index 0000000..e2cb841 --- /dev/null +++ b/src/common/ringbuffer.c @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: zhuxinran + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include "ringbuffer.h" + +#define min(a, b) (((a) < (b)) ? (a) : (b)) + +/** + * \brief Removes the entire FIFO contents. + * \param [in] fifo: The fifo to be emptied. + * \return None. + */ +void ringbuffer_reset(ringbuffer_t *fifo) +{ + fifo->write = 0U; + fifo->read = 0U; + fifo->data_len = 0U; +} + +/** + * \brief Returns the size of the FIFO in bytes. + * \param [in] fifo: The fifo to be used. + * \return The size of the FIFO. + */ +static inline uint32_t ringbuffer_size(ringbuffer_t *fifo) +{ + return fifo->size; +} + +/** + * \brief Returns the number of used bytes in the FIFO. + * \param [in] fifo: The fifo to be used. + * \return The number of used bytes. + */ +uint32_t ringbuffer_len(ringbuffer_t *fifo) +{ + return fifo->data_len; +} + +/** + * \brief Returns the number of bytes available in the FIFO. + * \param [in] fifo: The fifo to be used. + * \return The number of bytes available. + */ +uint32_t ringbuffer_avail(ringbuffer_t *fifo) +{ + return ringbuffer_size(fifo) - ringbuffer_len(fifo); +} + +/** + * \brief Is the FIFO empty? + * \param [in] fifo: The fifo to be used. + * \retval true: Yes. + * \retval false: No. + */ +bool ringbuffer_is_empty(ringbuffer_t *fifo) +{ + return (ringbuffer_len(fifo) == 0U); +} + +/** + * \brief Is the FIFO full? + * \param [in] fifo: The fifo to be used. + * \retval true: Yes. + * \retval false: No. + */ +bool ringbuffer_is_full(ringbuffer_t *fifo) +{ + return (ringbuffer_avail(fifo) == 0U); +} + +/** + * \brief Puts some data into the FIFO. + * \param [in] fifo: The fifo to be used. + * \param [in] in: The data to be added. + * \param [in] len: The length of the data to be added. + * \return The number of bytes copied. + * \note This function copies at most @len bytes from the @in into + * the FIFO depending on the free space, and returns the number + * of bytes copied. + */ +uint32_t ringbuffer_in(ringbuffer_t *fifo, const void *datptr, uint32_t len) +{ + uint32_t writelen = 0U, tmplen = 0U; + + if (ringbuffer_is_full(fifo)) { + writelen = 0U; + } else { + tmplen = fifo->size - fifo->data_len; + writelen = (tmplen > len) ? len : tmplen; + + if (fifo->write < fifo->read) { + memcpy((void *)&fifo->buffer[fifo->write], (void *)datptr, writelen); + } else { + tmplen = fifo->size - fifo->write; + + if (writelen <= tmplen) { + memcpy((void *)&fifo->buffer[fifo->write], (void *)datptr, writelen); + } else { + memcpy((void *)&fifo->buffer[fifo->write], (void *)datptr, tmplen); + memcpy((void *)fifo->buffer, (uint8_t *)datptr + tmplen, writelen - tmplen); + } + } + + fifo->write = (fifo->write + writelen) % fifo->size; + fifo->data_len += writelen; + } + + return writelen; +} + +/** + * \brief Gets some data from the FIFO. + * \param [in] fifo: The fifo to be used. + * \param [in] out: Where the data must be copied. + * \param [in] len: The size of the destination buffer. + * \return The number of copied bytes. + * \note This function copies at most @len bytes from the FIFO into + * the @out and returns the number of copied bytes. + */ +uint32_t ringbuffer_out(ringbuffer_t *fifo, void *outbuf, uint32_t len) +{ + uint32_t readlen = 0U, tmplen = 0U; + + if (ringbuffer_is_empty(fifo)) { + readlen = 0U; + } else { + readlen = (len > fifo->data_len) ? fifo->data_len : len; + tmplen = fifo->size - fifo->read; + + if (NULL != outbuf) { + if (readlen <= tmplen) { + memcpy((void *)outbuf, (void *)&fifo->buffer[fifo->read], readlen); + } else { + memcpy((void *)outbuf, (void *)&fifo->buffer[fifo->read], tmplen); + memcpy((uint8_t *)outbuf + tmplen, (void *)fifo->buffer, readlen - tmplen); + } + } + + fifo->read = (fifo->read + readlen) % fifo->size; + fifo->data_len -= readlen; + } + + return readlen; +} + diff --git a/src/common/syslog.c b/src/common/syslog.c new file mode 100644 index 0000000..3392521 --- /dev/null +++ b/src/common/syslog.c @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include + +#define LOG_WITH_COLOR + +#define LOG_COLOR_RED_YELLO_BACK "\033[1;31;43m" +#define LOG_COLOR_RED "\033[2;31;49m" +#define LOG_COLOR_YELLOW "\033[2;33;49m" +#define LOG_COLOR_GREEN "\033[2;32;49m" +#define LOG_COLOR_BLUE "\033[2;34;49m" +#define LOG_COLOR_GRAY "\033[1;30m" +#define LOG_COLOR_WHITE "\033[1;47;49m" +#define LOG_COLOR_RESET "\033[0m" + +#ifdef LOG_WITH_COLOR +const char *PFORMAT_D = LOG_COLOR_GRAY "[%sD][%s():%d] " LOG_COLOR_RESET; +const char *PFORMAT_I = LOG_COLOR_BLUE "[%sI][%s():%d] " LOG_COLOR_RESET; +const char *PFORMAT_W = LOG_COLOR_YELLOW "[%sW][%s():%d] " LOG_COLOR_RESET; +const char *PFORMAT_E = LOG_COLOR_RED "[%sE][%s():%d] " LOG_COLOR_RESET; +const char *PFORMAT_O = LOG_COLOR_GREEN "[%sO][%s():%d] " LOG_COLOR_RESET; +#else +const char *PFORMAT_D = "[%sD][%s():%d] "; +const char *PFORMAT_I = "[%sI][%s():%d] "; +const char *PFORMAT_W = "[%sW][%s():%d] "; +const char *PFORMAT_E = "[%sE][%s():%d] "; +const char *PFORMAT_O = "[%sO][%s():%d] "; +#endif diff --git a/src/lib_camera/Makefile b/src/lib_camera/Makefile new file mode 100644 index 0000000..c59772f --- /dev/null +++ b/src/lib_camera/Makefile @@ -0,0 +1,65 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../.. +include $(DIR_TO_ROOT)/build.param + +TARGET := libhal_camera.a +TARGET_DYNAMIC := libhal_camera.so +OUTPUT_DIR := $(DIR_TO_ROOT)/output/hal + +LIBS += -lcamera_action + +#CFLAGS = -Wall -g -O0 +INCLUDE += -I./camera_action/include + +SRCS = $(wildcard *.c) +OBJS = $(SRCS:.c=.o) + +ifeq ($(PLATFORM),simulator) + LIBOPENCV_INC = $(shell pkg-config --cflags opencv) + LIBOPENCV_LIBS = $(shell pkg-config --libs opencv) + LIBS += -lcamera_utilities $(LIBOPENCV_LIBS) + CFLAGS += $(LIBOPENCV_INC) +endif + + + +all: camera_action $(TARGET) $(TARGET_DYNAMIC) + +camera_action: + make -C camera_action + +$(TARGET_DYNAMIC): $(OBJS) camera_action + @mkdir -p $(OUTPUT_DIR)/$(SO_LIB_DIR) + @echo "Linking" $@ "..." + $(CC) -shared -fPIC -o $(OUTPUT_DIR)/$(SO_LIB_DIR)/$@ .obj/*.o $(CFLAGS) $(INCLUDE) \ + -L$(DIR_TO_ROOT)/output/common/ -L$(DIR_TO_ROOT)/output/hal/ \ + -lhal_common -lcamera_action $(LIBS) + +$(TARGET): $(OBJS) + @mkdir -p $(OUTPUT_DIR) + @echo "Linking" $@ "..." + $(AR) -r -o $(OUTPUT_DIR)/$@ .obj/*.o + +$(OBJS): %.o:%.c + @mkdir -p .obj + @echo "Compiling" $< "..." +ifeq ($(PLATFORM),light) + $(CC) $(CFLAGS) $(INCLUDE) -fPIC -c -o .obj/$(notdir $@) $< +else + $(CC) $(CFLAGS) $(INCLUDE) -c -o .obj/$(notdir $@) $< +endif + +clean: + rm -rf .obj + rm -f $(OUTPUT_DIR)/$(TARGET) + rm -f $(OUTPUT_DIR)/$(SO_LIB_DIR)/$(TARGET_DYNAMIC) + make -C camera_action clean + +.PHONY: clean all camera_action diff --git a/src/lib_camera/camera_action/Makefile b/src/lib_camera/camera_action/Makefile new file mode 100644 index 0000000..706dece --- /dev/null +++ b/src/lib_camera/camera_action/Makefile @@ -0,0 +1,22 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../../.. +include $(DIR_TO_ROOT)/build.param + +all: camera_action + +camera_action: + @echo $(BUILD_LOG_START) + make -C src/platform/$(PLATFORM) + @echo $(BUILD_LOG_END) + +clean: + make -C src/platform/$(PLATFORM) clean + +.PHONY: all camera_action clean diff --git a/src/lib_camera/camera_action/include/platform_action.h b/src/lib_camera/camera_action/include/platform_action.h new file mode 100644 index 0000000..bef23a9 --- /dev/null +++ b/src/lib_camera/camera_action/include/platform_action.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: fuqian.zxr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __PLATFORM_ACTION_H__ +#define __PLATFORM_ACTION_H__ + +#include +int camera_action_image_save(csi_frame_s *frame); +int camera_action_image_display(csi_frame_s *frame); + +#endif + + diff --git a/src/lib_camera/camera_action/src/platform/light/Makefile b/src/lib_camera/camera_action/src/platform/light/Makefile new file mode 100644 index 0000000..44b0d45 --- /dev/null +++ b/src/lib_camera/camera_action/src/platform/light/Makefile @@ -0,0 +1,46 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../../../../../.. +include $(DIR_TO_ROOT)/build.param + +TARGET := libcamera_action.a +TARGET_DYNAMIC := libcamera_action.so +OUTPUT_DIR := $(DIR_TO_ROOT)/output/hal + + +#CFLAGS = -Wall -g -O0 +INCLUDE += -I../../../include + +SRCS = $(wildcard *.c) +OBJS = $(SRCS:.c=.o) + +all: $(TARGET) $(TARGET_DYNAMIC) + + +$(TARGET_DYNAMIC): $(OBJS) + @mkdir -p $(OUTPUT_DIR)/$(SO_LIB_DIR) + @echo "Linking" $@ "..." + $(CC) -shared -fPIC -o $(OUTPUT_DIR)/$(SO_LIB_DIR)/$@ .obj/*.o $(CFLAGS) $(INCLUDE) + +$(TARGET): $(OBJS) + @mkdir -p $(OUTPUT_DIR) + @echo "Linking" $@ "..." + $(AR) -r -o $(OUTPUT_DIR)/$@ .obj/*.o + +$(OBJS): %.o:%.c + @mkdir -p .obj + @echo "Compiling" $< "..." + $(CC) $(CFLAGS) $(INCLUDE) -c -o .obj/$(notdir $@) $< + +clean: + rm -rf .obj + rm -f $(OUTPUT_DIR)/$(TARGET) + rm -f $(OUTPUT_DIR)/$(SO_LIB_DIR)/$(TARGET_DYNAMIC) + +.PHONY: clean all diff --git a/src/lib_camera/camera_action/src/platform/light/platform_action_light.c b/src/lib_camera/camera_action/src/platform/light/platform_action_light.c new file mode 100644 index 0000000..7dce90f --- /dev/null +++ b/src/lib_camera/camera_action/src/platform/light/platform_action_light.c @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: fuqian.zxr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define LOG_LEVEL 3 +#define LOG_PREFIX "platform_action" +#include +#include "platform_action.h" + +#include + +int camera_action_image_save(csi_frame_s *frame) +{ + //wait to complete + return 0; +} + +int camera_action_image_display(csi_frame_s *frame) +{ + //wait to complete + return 0; +} diff --git a/src/lib_camera/camera_action/src/platform/simulator/Makefile b/src/lib_camera/camera_action/src/platform/simulator/Makefile new file mode 100644 index 0000000..f6cf7f8 --- /dev/null +++ b/src/lib_camera/camera_action/src/platform/simulator/Makefile @@ -0,0 +1,49 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../../../../../.. +include $(DIR_TO_ROOT)/build.param + +TARGET := libcamera_action.a +TARGET_DYNAMIC := libcamera_action.so +OUTPUT_DIR := $(DIR_TO_ROOT)/output/hal + +#CFLAGS = -Wall -g -O0 +INCLUDE += -I./opencv -I../../../include + +SRCS = $(wildcard *.c) +OBJS = $(SRCS:.c=.o) + +all: opencv $(TARGET) $(TARGET_DYNAMIC) + +opencv: + make -C opencv + +$(TARGET_DYNAMIC): $(OBJS) opencv + @mkdir -p $(OUTPUT_DIR)/$(SO_LIB_DIR) + @echo "Linking" $@ "..." + $(CC) -shared -fPIC -o $(OUTPUT_DIR)/$(SO_LIB_DIR)/$@ .obj/*.o $(CFLAGS) $(INCLUDE) \ + -L$(DIR_TO_ROOT)/output/hal/ -lcamera_utilities $(LIBOPENCV_LIBS) + +$(TARGET): $(OBJS) + @mkdir -p $(OUTPUT_DIR) + @echo "Linking" $@ "..." + $(AR) -r -o $(OUTPUT_DIR)/$@ .obj/*.o + +$(OBJS): %.o:%.c + @mkdir -p .obj + @echo "Compiling" $< "..." + $(CC) $(CFLAGS) $(INCLUDE) -c -o .obj/$(notdir $@) $< + +clean: + rm -rf .obj + rm -f $(OUTPUT_DIR)/$(TARGET) + rm -f $(OUTPUT_DIR)/$(SO_LIB_DIR)/$(TARGET_DYNAMIC) + make -C opencv clean + +.PHONY: clean all opencv diff --git a/src/lib_camera/camera_action/src/platform/simulator/opencv/Makefile b/src/lib_camera/camera_action/src/platform/simulator/opencv/Makefile new file mode 100644 index 0000000..7e06716 --- /dev/null +++ b/src/lib_camera/camera_action/src/platform/simulator/opencv/Makefile @@ -0,0 +1,41 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../../../../../../.. + +include $(DIR_TO_ROOT)/build.param +LIBOPENCV_INC = $(shell pkg-config --cflags opencv) -I./ +LIBOPENCV_LIBS = $(shell pkg-config --libs opencv) + +TARGET := libcamera_utilities.a +OUTPUT_DIR := $(DIR_TO_ROOT)/output/hal/ + +#CFLAGS = -Wall -g -O0 + +SRCS = $(wildcard *.cpp) +OBJS = $(SRCS:.cpp=.o) + +all: $(TARGET) + +$(TARGET): $(OBJS) + @mkdir -p $(OUTPUT_DIR) + @echo "Linking" $@ "..." + $(AR) -r -o $(OUTPUT_DIR)/$@ .obj/*.o + +$(OBJS): %.o:%.cpp + @echo $(BUILD_LOG_START) + @mkdir -p .obj + @echo "Compiling" $< "..." + $(CXX) $(CFLAGS) $(INCLUDE) $(LIBOPENCV_INC) $(LIBOPENCV_LIBS) -c -o .obj/$(notdir $@) $< + @echo $(BUILD_LOG_END) + +clean: + rm -rf .obj + rm -f $(OUTPUT_DIR)/$(TARGET) + +.PHONY: clean all diff --git a/src/lib_camera/camera_action/src/platform/simulator/opencv/camera_utilities.cpp b/src/lib_camera/camera_action/src/platform/simulator/opencv/camera_utilities.cpp new file mode 100644 index 0000000..57984ad --- /dev/null +++ b/src/lib_camera/camera_action/src/platform/simulator/opencv/camera_utilities.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: fuqian.zxr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +using namespace cv; +using namespace std; + +#include "camera_utilities.h" + + +int image_save_opencv_i420(void *mat, int height, int width) +{ + static int save_mat_num = 0; + char output_filename[255]; + int buflen = height * width * 3 / 2; + sprintf(output_filename, "/tmp/%d_i420.yuv", save_mat_num); + FILE *pFileOut = fopen(output_filename, "wb"); + fwrite(mat, buflen * sizeof(unsigned char), 1, pFileOut); + save_mat_num++; + fclose(pFileOut); + return 0; +} + +int image_display_opencv_i420(void *mat, int height, int width) +{ + Mat dis_mat; + dis_mat.create(height * 3 / 2, width, CV_8UC1); + dis_mat.data = (uchar *)mat; + cvtColor(dis_mat, dis_mat, CV_YUV2BGRA_I420); + namedWindow("camera i420"); + imshow("camera i420", dis_mat); + waitKey(1); + dis_mat.release(); + return 0; +} + +int image_save_opencv_nv12(void *mat, int height, int width) +{ + static int save_mat_num = 0; + char output_filename[255]; + int buflen = height * width * 3 / 2; + sprintf(output_filename, "/tmp/%d_nv12.yuv", save_mat_num); + FILE *pFileOut = fopen(output_filename, "wb"); + fwrite(mat, buflen * sizeof(unsigned char), 1, pFileOut); + save_mat_num++; + fclose(pFileOut); + return 0; +} + +int image_display_opencv_nv12(void *mat, int height, int width) +{ + Mat dis_mat; + dis_mat.create(height * 3 / 2, width, CV_8UC1); + dis_mat.data = (uchar *)mat; + cvtColor(dis_mat, dis_mat, CV_YUV2BGRA_NV12); + namedWindow("camera nv12"); + imshow("camera nv12", dis_mat); + waitKey(1); + dis_mat.release(); + return 0; +} + +int image_save_opencv_bgr(void *mat, int height, int width) +{ + static int save_mat_num = 0; + char output_filename[255]; + int buflen = height * width * 3; + sprintf(output_filename, "/tmp/%d.bgr", save_mat_num); + FILE *pFileOut = fopen(output_filename, "wb"); + fwrite(mat, buflen * sizeof(unsigned char), 1, pFileOut); + save_mat_num++; + return 0; +} + +int image_display_opencv_bgr(void *mat, int height, int width) +{ + Mat dis_mat; + dis_mat.create(height, width, CV_8UC3); + dis_mat.data = (uchar *)mat; + namedWindow("camera"); + imshow("camera", dis_mat); + waitKey(1); + dis_mat.release(); + return 0; + +} diff --git a/src/lib_camera/camera_action/src/platform/simulator/opencv/camera_utilities.h b/src/lib_camera/camera_action/src/platform/simulator/opencv/camera_utilities.h new file mode 100644 index 0000000..1d5a297 --- /dev/null +++ b/src/lib_camera/camera_action/src/platform/simulator/opencv/camera_utilities.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: fuqian.zxr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __CAMERA_UTILITIES_H__ +#define __CAMERA_UTILITIES_H__ + +#ifdef __cplusplus +extern "C" { +#endif +#include + +//wait for complete +int image_save_opencv_i420(void *mat, int height, int width); +int image_display_opencv_i420(void *mat, int height, int width); + +int image_save_opencv_nv12(void *mat, int height, int width); +int image_display_opencv_nv12(void *mat, int height, int width); + +int image_save_opencv_bgr(void *mat, int height, int width); +int image_display_opencv_bgr(void *mat, int height, int width); + +#ifdef __cplusplus +}; +#endif + +#endif diff --git a/src/lib_camera/camera_action/src/platform/simulator/platform_action_simulator.c b/src/lib_camera/camera_action/src/platform/simulator/platform_action_simulator.c new file mode 100644 index 0000000..2e257de --- /dev/null +++ b/src/lib_camera/camera_action/src/platform/simulator/platform_action_simulator.c @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: fuqian.zxr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define LOG_LEVEL 3 +#define LOG_PREFIX "platform_action" +#include +#include "platform_action.h" +#include "camera_utilities.h" + +#include + +int camera_action_image_save(csi_frame_s *frame) +{ + int ret; + if (frame == NULL) { + LOG_E("pass error parameter!\n"); + ret = -1; + } + switch (frame->img.pix_format) { + case CSI_PIX_FMT_I420: + if (image_save_opencv_i420(frame->img.usr_addr[0], frame->img.height, + frame->img.width)) { + LOG_E("save picture error!\n"); + ret = -1; + } + break; + case CSI_PIX_FMT_NV12: + if (image_save_opencv_nv12(frame->img.usr_addr[0], frame->img.height, + frame->img.width)) { + LOG_E("save picture error!\n"); + ret = -1; + } + break; + case CSI_PIX_FMT_BGR: + if (image_save_opencv_bgr(frame->img.usr_addr[0], frame->img.height, + frame->img.width)) { + LOG_E("save picture error!\n"); + ret = -1; + } + break; + default: + LOG_E("Uknown format!\n"); + ret = -1; + } + return ret; +} +int camera_action_image_display(csi_frame_s *frame) +{ + int ret; + if (frame == NULL) { + LOG_E("pass error parameter!\n"); + ret = -1; + } + switch (frame->img.pix_format) { + case CSI_PIX_FMT_I420: + if (image_display_opencv_i420(frame->img.usr_addr[0], frame->img.height, + frame->img.width)) { + LOG_E("display picture error!\n"); + ret = -1; + } + break; + case CSI_PIX_FMT_NV12: + if (image_display_opencv_nv12(frame->img.usr_addr[0], frame->img.height, + frame->img.width)) { + LOG_E("display picture error!\n"); + ret = -1; + } + break; + case CSI_PIX_FMT_BGR: + if (image_display_opencv_bgr(frame->img.usr_addr[0], frame->img.height, + frame->img.width)) { + LOG_E("display picture error!\n"); + ret = -1; + } + break; + default: + LOG_E("Uknown format!\n"); + ret = -1; + } + return ret; +} diff --git a/src/lib_camera/camera_app_spec.c b/src/lib_camera/camera_app_spec.c new file mode 100644 index 0000000..398bfba --- /dev/null +++ b/src/lib_camera/camera_app_spec.c @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + + +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "app_spec" +#include +#include +#include + +#include "camera_app_spec.h" + +const camera_app_bitmasks_t camera_app_support_camera_event_action[] = { + 2, + { + CAMERA_ACTION_LOG_PRINT, + CAMERA_ACTION_CAPTURE_FRAME, + } +}; + +const camera_app_bitmasks_t camera_app_support_channel_event_action[] = { + 3, + { + CAMERA_CHANNEL_ACTION_LOG_PRINT, + CAMERA_CHANNEL_ACTION_CAPTURE_FRAME, + CAMERA_CHANNEL_ACTION_DISPLAY_FRAME, + } +}; + +const camera_app_bitmasks_t *camera_app_get_bitmask_array(int bitmask_id) +{ + switch(bitmask_id) { + case CAMERA_APP_BITMAKS_CAMERA_EVENT_ACTION: + return camera_app_support_camera_event_action; + case CAMERA_APP_BITMAKS_CHANNEL_EVENT_ACTION: + return camera_app_support_channel_event_action; + default: + LOG_E("Unknown bitmask_id:%d\n", bitmask_id); + return NULL; + } +} + diff --git a/src/lib_camera/camera_app_spec.h b/src/lib_camera/camera_app_spec.h new file mode 100644 index 0000000..fe0bc30 --- /dev/null +++ b/src/lib_camera/camera_app_spec.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CAMERA_APP_SPEC_H__ +#define __CAMERA_APP_SPEC_H__ + +#include +#include + +typedef enum camera_app_bitmask_id { + CAMERA_APP_BITMAKS_CAMERA_EVENT_ACTION, + CAMERA_APP_BITMAKS_CHANNEL_EVENT_ACTION, + + CAMERA_APP_BITMASKS_MAX_COUNT = 32 +} camera_app_bitmask_id_e; + +typedef struct camera_app_bitmasks { + int count; + int bitmask[CAMERA_APP_BITMASKS_MAX_COUNT]; +} camera_app_bitmasks_t; + +const camera_app_bitmasks_t *camera_app_get_bitmask_array(int bitmask_id); + +#endif /* __CAMERA_APP_SPEC_H__ */ diff --git a/src/lib_camera/camera_manager.c b/src/lib_camera/camera_manager.c new file mode 100644 index 0000000..68879c7 --- /dev/null +++ b/src/lib_camera/camera_manager.c @@ -0,0 +1,584 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "camera_manager" +#include +#include +#include + +#include "camera_manager.h" +#include "camera_manager_utils.h" +#include "platform_action.h" + +// ref: https://developer.android.google.cn/reference/android/hardware/camera2/CameraManager#inherited-methods + +#define _CHECK_SESSION_RETURN() \ + do { \ + if (session == NULL) { \ + LOG_E("session is NULL\n"); \ + return -EPERM; \ + } \ + } while (0) + +#define _CHECK_STATE_RETURN(expect) \ + do { \ + if ((session->state & expect) == 0) { \ + LOG_E("current state(%d) is not expected(%d)\n",\ + session->state, expect); \ + return -EPERM; \ + } \ + } while (0) +static bool camera_thread_flag = true; + +static int _camera_session_reset(cams_t *session) +{ + _CHECK_SESSION_RETURN(); + + memset(session, 0, sizeof(*session)); + session->state = CAMERA_STATE_CLOSED; + session->camera_id = -1; + session->camera_handle = NULL; + + session->event_handle = NULL; + for (int chn = 0; chn < CSI_CAMERA_CHANNEL_MAX_COUNT; chn++) { + session->chn_cfg[chn].chn_id = -1; + session->chn_cfg[chn].status = CSI_CAMERA_CHANNEL_INVALID; + } + + session->camera_action_fun = NULL; + session->channel_action_fun = NULL; + + return 0; +} + +typedef enum _info_field { + CAMERA_INFOS_READY = (1 << 0), // saved in: cams_t.camera_infos + CAMERA_MODES_READY = (1 << 1), // saved in: cams_t.camera_modes + CHANNEL_INIT_READY = (1 << 2), // saved in: cams_t.chn_cfg +} _info_field_e; + +/* set: true means set field to be 1, false means clear the field */ +static inline void _set_info_status(cams_t *session, _info_field_e field, + bool set) +{ + if (set) + session->info_status |= field; + else + session->info_status &= ~field; +} + +/* set: true means set field to be 1, false means clear the field */ +static inline bool _get_info_status(cams_t *session, _info_field_e field) +{ + return ((session->info_status & field) != 0); +} + +int camera_create_session(cams_t **session) +{ + if (session == NULL) { + LOG_E("input param error\n"); + return -EPERM; + } + *session = malloc(sizeof(cams_t)); + if (*session == NULL) { + LOG_E("Create manager failed, %s\n", strerror(errno)); + return -ENOMEM; + } + + _camera_session_reset(*session); + return 0; +} + +int camera_destory_session(cams_t **session) +{ + free(*session); + *session = NULL; + return 0; +} + +int camera_query_list(cams_t *session) +{ + if (_get_info_status(session, CAMERA_INFOS_READY)) { + LOG_D("camera list has been got before\n"); + return 0; + } + + int ret = csi_camera_query_list(&session->camera_infos); + if (ret != 0) { + LOG_E("csi_camera_query_list() failed\n"); + _set_info_status(session, CAMERA_INFOS_READY, false); + } + + _set_info_status(session, CAMERA_INFOS_READY, true); + return 0; +} + +int camera_get_caps(cams_t *session) +{ + int ret; + _CHECK_SESSION_RETURN(); + + return 0; +} + +int camera_open(cams_t *session, int cam_id) +{ + _CHECK_SESSION_RETURN(); + int ret; + + if (!_get_info_status(session, CAMERA_INFOS_READY) || + session->camera_infos.count <= cam_id) { + LOG_E("Can't open cam_id=%d\n", cam_id); + return -1; + } + + char *dev_name = session->camera_infos.info[cam_id].device_name; + csi_cam_handle_t handle; + ret = csi_camera_open(&handle, dev_name); + if (ret != 0) { + LOG_E("Open camera(%s) failed\n", dev_name); + return -1; + } + + session->camera_id = cam_id; + session->camera_handle = handle; + session->state = CAMERA_STATE_OPENED; + + camera_event_action_union_t *event_action; + const camera_spec_enums_s *enum_array; + + /* Camera Event/Action init */ + ret = csi_camera_create_event(&session->event_handle, session->camera_handle); + if (ret != 0) { + LOG_E("csi_camera_create_event failed, ret handle=%p\n", session->event_handle); + return -1; + } + + for (int i = 0; i < CSI_CAMERA_EVENT_MAX_COUNT; i++) { + event_action = &(session->camera_event_action[i]); + + event_action->target = MANAGE_TARGET_CAMERA; + event_action->camera_id = cam_id; + event_action->channel_id = -1; + event_action->camera.event = 1 << i; + //LOG_W("i=%d, event_action->camera.event=0x%08x\n", i, event_action->camera.event); + event_action->camera.supported = false; + event_action->camera.subscribed = false; + event_action->camera.action = CAMERA_ACTION_NONE; + } + + enum_array = camera_spec_get_enum_array(CAMERA_SPEC_ENUM_CAMERA_EVENT_TYPES); + for (int i = 0; (enum_array != NULL) && (i < enum_array->count); i++) { + for (int j = 0; j < CSI_CAMERA_EVENT_MAX_COUNT; j++) { + event_action = &(session->camera_event_action[j]); + if (enum_array->enums[i] == event_action->camera.event) { + event_action->camera.supported = true; + //cam_mng_dump_event_action_union(event_action); + } + } + } + + /* Camera Channel Event/Action init */ + for (int chn = 0; chn < CSI_CAMERA_CHANNEL_MAX_COUNT; chn++) { + for (int i = 0; i < CSI_CAMERA_CHANNEL_EVENT_MAX_COUNT; i++) { + event_action = &(session->channel_event_action[chn][i]); + + event_action->target = MANAGE_TARGET_CHANNEL; + event_action->camera_id = cam_id; + event_action->channel_id = chn; + event_action->channel.event = 1 << i; + event_action->channel.supported = false; + event_action->channel.subscribed = false; + event_action->channel.action = CAMERA_CHANNEL_ACTION_NONE; + } + } + + enum_array = camera_spec_get_enum_array(CAMERA_SPEC_ENUM_CHANNEL_EVENT_TYPES); + for (int i = 0; (enum_array != NULL) && (i < enum_array->count); i++) { + for (int j = 0; j < CSI_CAMERA_CHANNEL_MAX_COUNT; j++) { + for (int k = 0; k < CSI_CAMERA_CHANNEL_MAX_COUNT; k++) { + event_action = &(session->channel_event_action[j][k]); + if (enum_array->enums[i] == event_action->camera.event) { + event_action->camera.supported = true; + //cam_mng_dump_event_action_union(event_action); + } + } + } + } + + session->event_action_thread_id = -1; + return 0; +} + +int camera_close(cams_t *session) +{ + int ret; + _CHECK_SESSION_RETURN(); + + if (session->event_handle >= 0) { + csi_camera_destory_event(session->event_handle); + session->event_handle = NULL; + } + + if (csi_camera_close(session->camera_handle) != 0) { + LOG_E("Close camera() failed\n"); + return -1; + } + + _camera_session_reset(session); + session->state = CAMERA_STATE_CLOSED; + return 0; +} + +int camera_get_modes(cams_t *session) +{ + int ret; + _CHECK_SESSION_RETURN(); + _CHECK_STATE_RETURN(CAMERA_STATE_OPENED | CAMERA_STATE_MODE_SET); + + if (_get_info_status(session, CAMERA_MODES_READY)) + return 0; + + ret = csi_camera_get_modes(session->camera_handle, &(session->camera_modes)); + if (ret != 0) { + LOG_E("csi_camera_get_modes() failed, ret=%d\n", ret); + _set_info_status(session, CAMERA_MODES_READY, false); + return ret; + } + + _set_info_status(session, CAMERA_MODES_READY, true); + return 0; +} + +int camera_set_mode(cams_t *session, int mode_id) +{ + int ret; + _CHECK_SESSION_RETURN(); + _CHECK_STATE_RETURN(CAMERA_STATE_OPENED | CAMERA_STATE_MODE_SET); + + csi_camera_mode_cfg_s mode_cfg; + mode_cfg.mode_id = mode_id; + mode_cfg.calibriation = NULL; // TODO + mode_cfg.lib3a = NULL; // TODO + + ret = csi_camera_set_mode(session->camera_handle, &mode_cfg); + if (ret != 0) { + LOG_E("csi_camera_set_mode() failed, ret=%d\n", ret); + return ret; + } + + session->state = CAMERA_STATE_MODE_SET; + return 0; +} + +int camera_query_property(cams_t *session, + csi_camera_property_description_s *description) +{ + _CHECK_SESSION_RETURN(); + _CHECK_STATE_RETURN(CAMERA_STATE_OPENED | CAMERA_STATE_MODE_SET); + + return csi_camera_query_property(session->camera_handle, description); +} + +int camera_set_property(cams_t *session, csi_camera_properties_s *properties) +{ + _CHECK_SESSION_RETURN(); + _CHECK_STATE_RETURN(CAMERA_STATE_OPENED|CAMERA_STATE_MODE_SET|CAMERA_STATE_RUNNING); + + return csi_camera_set_property(session->camera_handle, properties); +} + +int camera_channel_query_list(cams_t *session) +{ + int ret; + _CHECK_SESSION_RETURN(); + _CHECK_STATE_RETURN(CAMERA_STATE_OPENED | CAMERA_STATE_MODE_SET | + CAMERA_STATE_RUNNING); + + // Init channel's ID and Status + if (! _get_info_status(session, CHANNEL_INIT_READY)) { + for (int i = CSI_CAMERA_CHANNEL_0; i < CSI_CAMERA_CHANNEL_MAX_COUNT; i++) { + session->chn_cfg[i].chn_id = i; // CSI_CAMERA_CHANNEL_x + session->chn_cfg[i].status = CSI_CAMERA_CHANNEL_INVALID; + } + } + + for (int i = CSI_CAMERA_CHANNEL_0; i < CSI_CAMERA_CHANNEL_MAX_COUNT; i++) { + if (_get_info_status(session, CHANNEL_INIT_READY)) { + if (session->chn_cfg[i].status == CSI_CAMERA_CHANNEL_INVALID) { + continue; + } + } + ret = csi_camera_channel_query(session->camera_handle, &session->chn_cfg[i]); + if (ret != 0) { + LOG_E("Get %d channel configuration from camera[%d] failed\n", + i, session->camera_id); + continue; + } else { + //LOG_D("channel[%d] status=%s\n", session->chn_cfg[i].chn_id, + // camera_string_chn_status(session->chn_cfg[i].status)); + } + } + + _set_info_status(session, CHANNEL_INIT_READY, true); + + return 0; +} + +int camera_channel_open(cams_t *session, csi_camera_channel_cfg_s *chn_cfg) +{ + int ret; + _CHECK_SESSION_RETURN(); + _CHECK_STATE_RETURN(CAMERA_STATE_OPENED | CAMERA_STATE_MODE_SET | + CAMERA_STATE_RUNNING); + + if (! _get_info_status(session, CHANNEL_INIT_READY)) { + camera_channel_query_list(session); + } + + int chn_id = chn_cfg->chn_id; + csi_camera_channel_cfg_s *channel = &(session->chn_cfg[chn_id]); + if (channel->status != CSI_CAMERA_CHANNEL_CLOSED) { + LOG_E("Channel[%d] status(%d) is not CLOSED\n", + chn_id, channel->status); + return -1; + } + + ret = csi_camera_channel_open(session->camera_handle, chn_cfg); + if (ret != 0) { + LOG_E("Open channel(%d) failed, ret=%d\n", chn_id, ret); + return -1; + } + + // Update session->chn_cfg + csi_camera_channel_query(session->camera_handle, + &(session->chn_cfg[chn_cfg->chn_id])); + + LOG_I("Open channel[%d] OK\n", chn_id); + return 0; +} + +int camera_channel_close(cams_t *session, csi_camera_channel_id_e chn_id) +{ + int ret; + _CHECK_SESSION_RETURN(); + _CHECK_STATE_RETURN(CAMERA_STATE_OPENED | CAMERA_STATE_MODE_SET | + CAMERA_STATE_RUNNING); + + csi_camera_channel_cfg_s *channel = &(session->chn_cfg[chn_id]); + if (channel->status != CSI_CAMERA_CHANNEL_OPENED) { + LOG_E("Channel[%d] status(%d) is not CLOSED\n", + chn_id, channel->status); + return -1; + } + + ret = csi_camera_channel_close(session->camera_handle, chn_id); + if (ret != 0) { + LOG_E("Close channel(%d) failed, ret=%d\n", chn_id, ret); + return -1; + } + + LOG_I("Close channel[%d] OK\n", chn_id); + return 0; +} + +int camera_subscribe_event(cams_t *session) +{ + int ret; + struct csi_camera_event_subscription subscribe; + camera_event_action_union_t *event_action; + + _CHECK_SESSION_RETURN(); + _CHECK_STATE_RETURN(CAMERA_STATE_OPENED | CAMERA_STATE_MODE_SET); + + for (int i = 0; i < CSI_CAMERA_EVENT_MAX_COUNT; i++) { + event_action = &session->camera_event_action[i]; + if (!event_action->camera.supported) { + LOG_D("Camera event(%08x) is not supported\n", + event_action->camera.event); + continue; + } + + LOG_D("Camera event(%08x) is supported\n", event_action->camera.event); + + subscribe.type = CSI_CAMERA_EVENT_TYPE_CAMERA; + subscribe.id = event_action->camera.event; + if (event_action->camera.subscribed) { + ret = csi_camera_subscribe_event(session->event_handle, &subscribe); + if (ret == 0) + LOG_D("subscribe_event(type=CAMERA, id=0x%08x(%s)) OK\n", + subscribe.id, camera_string_camera_event_type(subscribe.id)); + else + LOG_E("subscribe_event(type=CAMERA, id=0x%08x(%s)) Failed\n", + subscribe.id, camera_string_camera_event_type(subscribe.id)); + } else { + ret = csi_camera_unsubscribe_event(session->event_handle, &subscribe); + if (ret == 0) + LOG_D("unsubscribe_event(type=CAMERA, id=0x%08x(%s)) OK\n", + subscribe.id, camera_string_camera_event_type(subscribe.id)); + else + LOG_E("unsubscribe_event(type=CAMERA, id=0x%08x(%s)) Failed\n", + subscribe.id, camera_string_camera_event_type(subscribe.id)); + } + } + + for (int chn = 0; chn < CSI_CAMERA_CHANNEL_MAX_COUNT; chn++) { + if (session->chn_cfg[chn].status != CSI_CAMERA_CHANNEL_OPENED) { + LOG_D("Camera[%d]:Channel[%d] is not OPENED\n", session->camera_id, chn); + continue; + } + for (int i = 0; i < CSI_CAMERA_CHANNEL_EVENT_MAX_COUNT; i++) { + event_action = &session->channel_event_action[chn][i]; + if (!event_action->channel.supported) { + LOG_D("channel(%d) event(%08x) is not supported\n", + chn, event_action->channel.event); + continue; + } + LOG_D("channel(%d) event(%08x) is supported\n", + chn, event_action->channel.event); + + subscribe.type = CSI_CAMERA_EVENT_TYPE_CHANNEL0 + chn; + subscribe.id = event_action->camera.event; + if (event_action->camera.subscribed) { // FIXME: event_action->camera.subscribed in dialog + ret = csi_camera_subscribe_event(session->event_handle, &subscribe); + if (ret == 0) + LOG_D("subscribe_event(type=CHANNEL%d, id=0x%08x(%s)) OK\n", + chn, subscribe.id, camera_string_channel_event_type(subscribe.id)); + else + LOG_E("subscribe_event(type=CHANNEL%d, id=0x%08x(%s)) Failed\n", + chn, subscribe.id, camera_string_channel_event_type(subscribe.id)); + } else { + ret = csi_camera_unsubscribe_event(session->event_handle, &subscribe); + if (ret == 0) + LOG_D("unsubscribe_event(type=CHANNEL%d, id=0x%08x(%s)) OK\n", + chn, subscribe.id, camera_string_channel_event_type(subscribe.id)); + else + LOG_E("unsubscribe_event(type=CHANNEL%d, id=0x%08x(%s)) Failed\n", + chn, subscribe.id, camera_string_channel_event_type(subscribe.id)); + } + } + } + + return 0; +} + +int camera_register_event_action(cams_t *session, + camera_action_fun_t camera_action_fun, + camera_action_fun_t channel_action_fun) +{ + _CHECK_SESSION_RETURN(); + ENTER_VOID(); + + session->camera_action_fun = camera_action_fun; + session->channel_action_fun = channel_action_fun; + return 0; +} + +static void *camera_event_action_thread(void *arg) +{ + int ret; + cams_t *session = (cams_t *)arg; + LOG_D("pthread start\n"); + + if (session == NULL) { + LOG_E("session is NULL\n"); + return NULL; + } + + csi_camera_event_s event; + int timeout = -1; // unit: ms, -1 means wait forever, or until error occurs + int loop_count = 0; + while (camera_thread_flag) { + LOG_D("while(true)...loog_count=%d\n", ++loop_count); + ret = csi_camera_get_event(session->event_handle, &event, timeout); + if (ret != 0) { + LOG_E("csi_camera_get_event() failed, ret=%d\n", ret); + continue; + } + + LOG_D("event{.type=%d, .id=%d}\n", event.type, event.id); + if (event.type == CSI_CAMERA_EVENT_TYPE_CAMERA) { + if (session->camera_action_fun != NULL) { + session->camera_action_fun(session, &event); + } else { + LOG_D("session->camera_action_fun is NULL\n"); + } + } else if (event.type >= CSI_CAMERA_EVENT_TYPE_CHANNEL0 && + event.type <= CSI_CAMERA_EVENT_TYPE_CHANNEL7) { + if (session->channel_action_fun != NULL) { + session->channel_action_fun(session, &event); + } else { + LOG_D("session->channel_action_fun is NULL\n"); + } + } else { + LOG_E("Unknown event type:%d\n", event.type); + } + } + pthread_exit(NULL); +} + +int camera_channel_start(cams_t *session, csi_camera_channel_id_e chn_id) +{ + int ret; + _CHECK_SESSION_RETURN(); + _CHECK_STATE_RETURN(CAMERA_STATE_OPENED | CAMERA_STATE_MODE_SET); + + LOG_D("camera_channel_start = %d\n",chn_id); + ret = csi_camera_channel_start(session->camera_handle, chn_id); + if (ret != 0) { + LOG_E("csi_camera_channel_start() failed, ret=%d", ret); + return -1; + } + + // start event action thread + camera_thread_flag = true; + ret = pthread_create(&session->event_action_thread_id, NULL, + (void *)camera_event_action_thread, session); + if (ret != 0) { + LOG_E("pthread_create() failed, ret=%d", ret); + return -1; + } + + session->state = CAMERA_STATE_RUNNING; + LOG_D("camera_channel_start(chn_id=%d) OK\n", chn_id); + return ret; +} + +int camera_channel_stop(cams_t *session, csi_camera_channel_id_e chn_id) +{ + int ret; + _CHECK_SESSION_RETURN(); + _CHECK_STATE_RETURN(CAMERA_STATE_RUNNING); + int i = 0; + + camera_thread_flag = false; + if (pthread_join(session->event_action_thread_id, NULL)) { + LOG_E("pthread_join() failed, %s\n", strerror(errno)); + return -2; + } + session->event_action_thread_id = -1; + LOG_D("pthread canceled\n"); + + ret = csi_camera_channel_stop(session->camera_handle, chn_id); + if (ret != 0) { + LOG_E("csi_camera_channel_stop() faild, ret=%d\n", ret); + return -1; + } + + LOG_D("camera_channel_stop ok\n"); + session->state = CAMERA_STATE_OPENED; + return ret; +} + diff --git a/src/lib_camera/camera_manager_utils.c b/src/lib_camera/camera_manager_utils.c new file mode 100644 index 0000000..b6d7b20 --- /dev/null +++ b/src/lib_camera/camera_manager_utils.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "cam_mng_utils" +#include +#include +#include "camera_manager_utils.h" + +void cam_mng_dump_event_action_union(camera_event_action_union_t *event_action) +{ + if (event_action == NULL) { + LOG_E("event_action is NULL\n"); + } + + LOG_D("Dumping event_action:\n"); + LOG_F("event_action = {\n"); + if (event_action->target == MANAGE_TARGET_CAMERA) { + LOG_F("\t .target=CAMERA, .camera_id=%d\n", + event_action->camera_id); + LOG_F("\t .camera = {.event=%d, .support=%d, .subscribed=%d, .action=0x%08x}\n", + event_action->camera.event, event_action->camera.supported, + event_action->camera.subscribed, event_action->camera.action); + } else if (event_action->target == MANAGE_TARGET_CHANNEL) { + LOG_F("\t .target=CHANNEL, .camera_id=%d, .channel_id = %d\n", + event_action->camera_id, event_action->channel_id); + LOG_F("\t .channel = {.event=%d, .support=%d, .subscribed=%d, .action=0x%08x}\n", + event_action->channel.event, event_action->channel.supported, + event_action->channel.subscribed, event_action->channel.action); + } else { + LOG_F("\t .target=Unknown(%d) !!\n", event_action->target); + } + LOG_F("}\n"); +} + diff --git a/src/lib_camera/camera_manager_utils.h b/src/lib_camera/camera_manager_utils.h new file mode 100644 index 0000000..1cc4174 --- /dev/null +++ b/src/lib_camera/camera_manager_utils.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CAMERA_MANAGER_UTILS_H__ +#define __CAMERA_MANAGER_UTILS_H__ + +#include +#include + +void cam_mng_dump_event_action_union(camera_event_action_union_t *event_action); + +#endif /* __CAMERA_MANAGER_UTILS_H__ */ diff --git a/src/platform/Makefile b/src/platform/Makefile new file mode 100644 index 0000000..b66d051 --- /dev/null +++ b/src/platform/Makefile @@ -0,0 +1,22 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## + +DIR_TO_ROOT=../.. +include $(DIR_TO_ROOT)/build.param + +all: platform + +platform: + make -C $(PLATFORM) + +clean: + make -C $(PLATFORM) clean + + +.PHONY: clean all info platform common diff --git a/src/platform/cv182x/codec/vdec.c b/src/platform/cv182x/codec/vdec.c new file mode 100644 index 0000000..e69de29 diff --git a/src/platform/d1/codec/vdec.c b/src/platform/d1/codec/vdec.c new file mode 100644 index 0000000..e69de29 diff --git a/src/platform/d1/codec/venc.c b/src/platform/d1/codec/venc.c new file mode 100644 index 0000000..e69de29 diff --git a/src/platform/d1/g2d/g2d.c b/src/platform/d1/g2d/g2d.c new file mode 100644 index 0000000..e69de29 diff --git a/src/platform/light/Makefile b/src/platform/light/Makefile new file mode 100644 index 0000000..2375119 --- /dev/null +++ b/src/platform/light/Makefile @@ -0,0 +1,42 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../../.. +include $(DIR_TO_ROOT)/build.param + +TARGET := libhal_platform.a +OUTPUT_DIR := $(DIR_TO_ROOT)/output/hal + +#CFLAGS = -Wall -g -O0 +INCLUDE += -I./include +INCLUDE += -I$(VI_INC) + +SRCS = $(wildcard *.c) +OBJS = $(SRCS:.c=.o) + +all: # $(TARGET) fro Light do nothing, the lib libhal_platform.so from ISP repo + +#camera: +# make -C camera + +$(TARGET): $(OBJS) + @mkdir -p $(OUTPUT_DIR) + @echo "Linking" $@ "..." + $(AR) -r -o $(OUTPUT_DIR)/$@ .obj/*.o + +$(OBJS): %.o:%.c + @mkdir -p .obj + @echo "Compiling" $< "..." + $(CC) $(CFLAGS) $(INCLUDE) -c -o .obj/$(notdir $@) $< + +clean: + rm -rf .obj + rm -f $(OUTPUT_DIR)/$(TARGET) +# make -C camera clean + +.PHONY: clean all camera diff --git a/src/platform/light/camera/camera.c b/src/platform/light/camera/camera.c new file mode 100644 index 0000000..5b09a9f --- /dev/null +++ b/src/platform/light/camera/camera.c @@ -0,0 +1 @@ +#include diff --git a/src/platform/light/csi_camera.c b/src/platform/light/csi_camera.c new file mode 100644 index 0000000..91e835b --- /dev/null +++ b/src/platform/light/csi_camera.c @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +int csi_camera_query_list(csi_camera_infos_t *infos) +{ + + return 0; +} + + +csi_cam_handle_t csi_camera_open(const char *cam_name) +{ + + + return NULL; +} + +int csi_camera_close(csi_cam_handle_t cam_handle) +{ + + + return 0; +} + +int csi_camera_get_modes(csi_cam_handle_t cam_handle, csi_camera_modes_t *modes) +{ + + return 0; +} + +int csi_camera_set_mode(csi_cam_handle_t cam_handle, csi_camera_mode_cfg_t *cfg) +{ + return 0; +} + +int csi_camera_query_property(csi_cam_handle_t cam_handle, + csi_camera_property_description_t *desc) +{ + return 0; +} + + +int csi_camera_get_property(csi_cam_handle_t cam_handle, + csi_camera_properties_t *properties) +{ + + return 0; +} + + +const char *csi_camera_ctrl_id_name(unsigned int id) +{ + return NULL; +} + +const char *csi_camera_ctrl_type_name(unsigned int type) +{ + + return NULL; +} + +int csi_camera_set_property(csi_cam_handle_t cam_handle, + csi_camera_properties_t *properties) +{ + return 0; +} + +int csi_camera_query_frame(csi_cam_handle_t cam_handle, + csi_camera_channel_id_e chn_id, + int frm_id, + csi_frame_t *frm) + +{ + return 0; +} + +static int cam_channel_open(csi_cam_handle_t cam_handle, csi_camera_channel_id_e chn_id, + csi_camera_channel_cfg_t *cfg) +{ + + return 0; +} + +int csi_camera_channel_open(csi_cam_handle_t cam_handle, + csi_camera_channel_cfg_t *cfg) +{ + + return 0; +} + +int csi_camera_channel_close(csi_cam_handle_t cam_handle, + csi_camera_channel_id_e chn_id) +{ + return 0; +} + +int csi_camera_channel_query(csi_cam_handle_t cam_handle, + csi_camera_channel_cfg_t *cfg) +{ + + return 0; +} + + +csi_cam_event_handle_t csi_camera_create_event(csi_cam_handle_t cam_handle) +{ + + return 0; +} + +int csi_camera_destory_event(csi_cam_event_handle_t event_handle) +{ + + return 0; +} + +int csi_camera_subscribe_event(csi_cam_event_handle_t event_handle, + csi_camera_event_subscription_t *subscribe) +{ + return 0; +} +int csi_camera_unsubscribe_event(csi_cam_event_handle_t event_handle, + csi_camera_event_subscription_t *subscribe) +{ + + return 0; +} + +int csi_camera_get_event(csi_cam_event_handle_t event_handle, + csi_camera_event_t *event, + int timeout) +{ + + return 0; +} + +int csi_camera_get_frame_count(csi_cam_handle_t cam_handle, + csi_camera_channel_id_e chn_id) +{ + return 0; +} + +int csi_camera_get_frame(csi_cam_handle_t cam_handle, + csi_camera_channel_id_e chn_id, + csi_frame_t *frame, + int timeout) +{ + + return 0; +} + +int csi_camera_put_frame(csi_frame_t *frame) +{ + return 0; +} + +static int cam_channel_set_stream(csi_cam_handle_t cam_handle, int chn_id, int on) +{ + + return 0; +} + +int csi_camera_channel_start(csi_cam_handle_t cam_handle, csi_camera_channel_id_e chn_id) +{ + + + return 0; +} + +int csi_camera_channel_stop(csi_cam_handle_t cam_handle, csi_camera_channel_id_e chn_id) +{ + return 0; +} diff --git a/src/platform/light/csi_camera_frame.c b/src/platform/light/csi_camera_frame.c new file mode 100644 index 0000000..5e88d0b --- /dev/null +++ b/src/platform/light/csi_camera_frame.c @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include + +#define LOG_LEVEL 2 +#define LOG_PREFIX "camera_meta" +#include +#include + +int csi_camera_frame_alloc_meta(csi_camera_meta_s **meta, int meta_count, size_t *meta_data_size) +{ + return 0; +} + +int csi_camera_frame_free_meta(csi_camera_meta_s *meta) +{ + return 0; +} + +int csi_camera_frame_get_meta_unit(csi_camrea_meta_unit_s *meta_unit, + csi_camera_meta_s *meta_data, + csi_camera_meta_id_e meta_field) +{ + return 0; +} + diff --git a/src/platform/light/csi_camera_platform_spec.c b/src/platform/light/csi_camera_platform_spec.c new file mode 100644 index 0000000..50b2a71 --- /dev/null +++ b/src/platform/light/csi_camera_platform_spec.c @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +/******************************************************************************/ +/*********** Platform Spec in Enum ********************************************/ + +const camera_spec_enums_s *camera_spec_get_enum_array(int property_id) +{ + return NULL; +} + +const camera_spec_bitmasks_t *camera_spec_get_bitmask_array(int property_id) +{ + return NULL; +} + + diff --git a/src/platform/light/csi_frame.c b/src/platform/light/csi_frame.c new file mode 100644 index 0000000..0d3e75d --- /dev/null +++ b/src/platform/light/csi_frame.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include + +int csi_frame_create(csi_frame_s *frame, + csi_img_type_e type, + csi_img_s img_info) +{ + if (type == CSI_IMG_TYPE_SHM) { + } else { + printf("Not supported img type:%d\n", type); + return -1; + } + return 0; +} + +int csi_frame_reference(csi_frame_s *frame_dest, csi_frame_s *frame_src) +{ + return 0; +} + +int csi_frame_release(csi_frame_s *frame) +{ + return 0; +} + +void* csi_frame_mmap(csi_frame_s *frame) +{ + return NULL; +} + +int csi_frame_munmap(csi_frame_s *frame) +{ + return 0; +} + diff --git a/src/platform/light/g2d/g2d.c b/src/platform/light/g2d/g2d.c new file mode 100644 index 0000000..e69de29 diff --git a/src/platform/simulator/Makefile b/src/platform/simulator/Makefile new file mode 100644 index 0000000..8df043d --- /dev/null +++ b/src/platform/simulator/Makefile @@ -0,0 +1,57 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../../.. +include $(DIR_TO_ROOT)/build.param + +TARGET_STATIC := libhal_platform.a +TARGET_DYNAMIC := libhal_platform.so +OUTPUT_DIR := $(DIR_TO_ROOT)/output/hal + +#CFLAGS = -Wall -g -O0 +INCLUDE += -I./opencv/ + +SRCS = $(wildcard *.c) +OBJS = $(SRCS:.c=.o) + +all: drivers opencv $(TARGET_STATIC) $(TARGET_DYNAMIC) #app + +opencv: + make -C opencv + +app: + make -C app + +drivers: $(TARGET_STATIC) + make -C drivers + +$(TARGET_DYNAMIC): $(OBJS) opencv + @mkdir -p $(OUTPUT_DIR)/$(SO_LIB_DIR) + @echo "Linking" $@ "..." + $(CC) -shared -fPIC -o $(OUTPUT_DIR)/$(SO_LIB_DIR)/$@ .obj/*.o $(CFLAGS) $(INCLUDE) \ + -L$(DIR_TO_ROOT)/output/common/ -L$(DIR_TO_ROOT)/output/hal/ \ + -lhal_common -lcamera_platform -lpthread $(LIBOPENCV_LIBS) + +$(TARGET_STATIC): $(OBJS) + @mkdir -p $(OUTPUT_DIR) + @echo "Linking" $@ "..." + $(AR) -r -o $(OUTPUT_DIR)/$@ .obj/*.o + +$(OBJS): %.o:%.c + @mkdir -p .obj + @echo "Compiling" $< "..." + $(CC) $(CFLAGS) $(INCLUDE) -c -o .obj/$(notdir $@) $< + +clean: + rm -rf .obj + rm -f $(OUTPUT_DIR)/$(TARGET_STATIC) + rm -f $(OUTPUT_DIR)/$(SO_LIB_DIR)/$(TARGET_DYNAMIC) + make -C drivers clean + make -C opencv clean + +.PHONY: clean all drivers opencv app diff --git a/src/platform/simulator/app/.gitignore b/src/platform/simulator/app/.gitignore new file mode 100644 index 0000000..a04c6c4 --- /dev/null +++ b/src/platform/simulator/app/.gitignore @@ -0,0 +1,2 @@ +menu +7_input_function diff --git a/src/platform/simulator/app/7_input_function.c b/src/platform/simulator/app/7_input_function.c new file mode 100644 index 0000000..819366c --- /dev/null +++ b/src/platform/simulator/app/7_input_function.c @@ -0,0 +1,20 @@ +#include /* ncurses.h includes stdio.h */ +#include + +int main() +{ + char mesg[]="Enter a string: "; /* message to be appeared on the screen */ + char str[80]; + int row,col; /* to store the number of rows and * + * the number of colums of the screen */ + initscr(); /* start the curses mode */ + getmaxyx(stdscr,row,col); /* get the number of rows and columns */ + mvprintw(row/2,(col-strlen(mesg))/2,"%s",mesg); + /* print the message at the center of the screen */ + getstr(str); + mvprintw(LINES - 2, 0, "You Entered: %s", str); + getch(); + endwin(); + + return 0; +} diff --git a/src/platform/simulator/app/Makefile b/src/platform/simulator/app/Makefile new file mode 100644 index 0000000..11333b9 --- /dev/null +++ b/src/platform/simulator/app/Makefile @@ -0,0 +1,33 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../../../.. +include $(DIR_TO_ROOT)/build.param + +#CFLAGS = -Wall -g -O0 +OUTPUT_DIR := $(DIR_TO_ROOT)/output/driver/ipc_fd_transfer +LIBS += -lcommon -lcurses +#INCLUDE += -I../../ion + +TARGET_1 = menu +SRCS_1 = menu.c + +TARGET_2 = 7_input_function +SRCS_2 = 7_input_function.c + + +all: $(TARGET_1) $(TARGET_2) + +clean: + rm -rf .obj + rm -rf $(TARGET_1) $(OUTPUT_DIR)/$(TARGET_1) + rm -rf $(TARGET_2) $(OUTPUT_DIR)/$(TARGET_2) + +include $(DIR_TO_ROOT)/common_target.mk + +.PHONY: clean all diff --git a/src/platform/simulator/app/input_function.c b/src/platform/simulator/app/input_function.c new file mode 100644 index 0000000..e8bf018 Binary files /dev/null and b/src/platform/simulator/app/input_function.c differ diff --git a/src/platform/simulator/app/menu.c b/src/platform/simulator/app/menu.c new file mode 100644 index 0000000..5e0d5ac --- /dev/null +++ b/src/platform/simulator/app/menu.c @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* ref: https://my.oschina.net/u/1387955/blog/182281 */ +/************* 主程序 *************/ +/***gcc menu.c -lcurses -o menu***/ +#include +#include +#include +#include +#include +#include +#define ENTER 10 +#define ESCAPE 27 + +WINDOW *menubar, *messagebar, *temp, *temp1; +char param[10][10][13]; + +void init_curses() +{ + initscr(); + start_color(); + init_pair(1, COLOR_WHITE, COLOR_BLACK); + init_pair(2, COLOR_BLACK, COLOR_WHITE); + init_pair(3, COLOR_RED, COLOR_WHITE); + init_pair(4, COLOR_WHITE, COLOR_RED); + curs_set(0); + noecho(); + keypad(stdscr, TRUE); +} + +void GetSubStr(char *des, char *src, char ch, int n) +{ + int i, len; + char *p1, *p, tmp[300]; + strcpy(tmp, src); + *des = 0; + p1 = tmp; + i = 0; + while (i < n) { + i++; + p = (char *)strchr(p1, ch); + if (p != NULL) { + *p++ = 0; + p1 = p; + } + } + p = (char *)strchr(p1, ch); + if (p != NULL) { + *p = 0; + strcpy(des, p1); + } +} + +int get_param(char *name) +{ + FILE *fp; + char ss[201], xm[3], gs[3]; + int i, j; + sprintf(ss, "%s.conf", name); + if ((fp = fopen(ss, "r")) == NULL) return (-1); + for (j = 0; j < 10; j++) + for (i = 0; i < 10; i++) + memset(param[j][i], 0, 13); + while (1) { + memset(ss, 0, 201); + fgets(ss, 200, fp); + if (feof(fp)) break; + if (ss[0] == '#') continue; + GetSubStr(xm, ss, '|', 0); + GetSubStr(gs, ss, '|', 1); + j = atoi(xm); + for (i = 1; i <= atoi(gs); i++) { + sprintf(param[j][0], "%s", gs); + GetSubStr(param[j][i], ss, '|', i + 1); + } + } + fclose(fp); + return (0); +} + +void draw_menubar(WINDOW *menubar) +{ + int i; + wbkgd(menubar, COLOR_PAIR(2)); + for (i = 0; i < atoi(param[0][0]); i++) { + wattron(menubar, COLOR_PAIR(3)); + mvwprintw(menubar, 0, i * 14 + 2, "%1d.", i + 1); + wattroff(menubar, COLOR_PAIR(3)); + mvwprintw(menubar, 0, i * 14 + 4, "%-12s", param[0][i + 1]); + } +} + +WINDOW **draw_menu(int menu) +{ + int i, start_col; + WINDOW **items; + items = (WINDOW **)malloc((atoi(param[menu][0]) + 1) * sizeof(WINDOW *)); + start_col = (menu - 1) * 14 + 2; + items[0] = newwin(atoi(param[menu][0]) + 2, 14, 3, start_col); + wbkgd(items[0], COLOR_PAIR(2)); + box(items[0], ACS_VLINE, ACS_HLINE); + for (i = 1; i <= atoi(param[menu][0]); i++) { + items[i] = subwin(items[0], 1, 12, 3 + i, start_col + 1); + wprintw(items[i], "%s", param[menu][i]); + } + wbkgd(items[1], COLOR_PAIR(4)); + wrefresh(items[0]); + return items; +} + +void delete_menu(WINDOW **items, int count) +{ + int i; + for (i = 0; i < count; i++) delwin(items[i]); + free(items); +} + +int scroll_menu(WINDOW **items, int menu) +{ + int key, count, selected = 0; + count = atoi(param[menu][0]); + while (1) { + key = getch(); + if (key == KEY_DOWN || key == KEY_UP) { + wbkgd(items[selected + 1], COLOR_PAIR(2)); + wnoutrefresh(items[selected + 1]); + if (key == KEY_DOWN) + selected = (selected + 1) % count; + else + selected = (selected + count - 1) % count; + wbkgd(items[selected + 1], COLOR_PAIR(4)); + wnoutrefresh(items[selected + 1]); + doupdate(); + } else if (key == KEY_LEFT || key == KEY_RIGHT) { + delete_menu(items, count + 1); + touchwin(stdscr); + refresh(); + if (key == KEY_LEFT) { + menu -= 1; + if (menu <= 0) menu = atoi(param[0][0]); + items = draw_menu(menu); + return scroll_menu(items, menu); + } + if (key == KEY_RIGHT) { + menu += 1; + if (menu > atoi(param[0][0])) menu = 1; + items = draw_menu(menu); + return scroll_menu(items, menu); + } + } else if (key == ESCAPE || key == '0' || key == 'q') { + delete_menu(items, count + 1); + return -1; + } else if (key == ENTER) { + delete_menu(items, count + 1); + return selected; + } + } +} + +void message(char *ss) +{ + wbkgd(messagebar, COLOR_PAIR(2)); + wattron(messagebar, COLOR_PAIR(3)); + mvwprintw(messagebar, 0, 0, "%80s", " "); + mvwprintw(messagebar, 0, (80 - strlen(ss)) / 2 - 1, "%s", ss); + wattroff(messagebar, COLOR_PAIR(3)); + wrefresh(messagebar); +} + +void copyright() +{ + char *str[] = { "Orignal Author:htldm@bbs.chinaunix.net", + "Modified By:Frank.Z @wh.cn", + "mailto:[frankzhang02010@gmail.com]", + "Last Modified:2013.12.7 [GMT+8]" + }; + int rows, cols; + int i; + getmaxyx(stdscr, rows, cols); + attron(A_UNDERLINE | COLOR_PAIR(1)); + for (i = 0; i < 4; i++) + mvaddstr((rows - 2) / 2 + i, (cols - strlen(str[2])) / 2, str[i]); + attroff(A_UNDERLINE | COLOR_PAIR(1)); + refresh(); +} + +int main(int argc, char **argv) +{ + int key; + int selected_item; + char ss[81]; + WINDOW **menu_items; + + if (get_param(argv[0])) { + printf("\n打开配置文件 %s.conf 错!\n", argv[0]); + return (-1); + } + + init_curses(); + bkgd(COLOR_PAIR(1)); + menubar = subwin(stdscr, 1, 80, 1, 0); + messagebar = subwin(stdscr, 1, 80, 24, 0); + temp = subwin(stdscr, 22, 80, 2, 0); + temp1 = subwin(stdscr, 20, 78, 3, 1); + strcpy(ss, "General Menu Generate Program"); + mvwprintw(stdscr, 0, (80 - strlen(ss)) / 2 - 1, "%s", ss); + draw_menubar(menubar); + message("Use nub key to choses menu. ESC or '0'to Exit"); + box(temp, ACS_VLINE, ACS_HLINE); + refresh(); + copyright(); + do { + key = getch(); + if (isdigit(key) && key > '0' && key <= atoi(param[0][0]) + '0') { + werase(messagebar); + wrefresh(messagebar); + menu_items = draw_menu(key - '0'); + selected_item = scroll_menu(menu_items, key - '0'); + touchwin(stdscr); + refresh(); + } + } while (key != ESCAPE && key != 'q' && key != '0'); + + delwin(temp1); + delwin(temp); + delwin(menubar); + delwin(messagebar); + endwin(); + return (0); +} + diff --git a/src/platform/simulator/app/menu.conf b/src/platform/simulator/app/menu.conf new file mode 100644 index 0000000..3bd21b7 --- /dev/null +++ b/src/platform/simulator/app/menu.conf @@ -0,0 +1,11 @@ +# +# 格式为: 菜单号|项目个数|项目名称...... +# 菜单数量最大为10个 +# +0|6|Choice1|Chioce2|Chioce3|Choice4|Choice5|Chioce6| +1|3|menu11|menu12|menu13| +2|8|menu21|menu22|menu23|menu24|menu25|menu26|menu27|menu28| +3|5|menu31|menu32|menu33|menu34|menu35| +4|4|menu41|menu42|menu43|menu44| +5|2|menu51|menu52| +6|1|menu61| diff --git a/src/platform/simulator/csi_allocator.c b/src/platform/simulator/csi_allocator.c new file mode 100644 index 0000000..ac35d40 --- /dev/null +++ b/src/platform/simulator/csi_allocator.c @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#if 0 +csi_allocator_s *csi_allocator_get(csi_allocator_type_e type) +{ + return NULL; +} +#endif + diff --git a/src/platform/simulator/csi_camera.c b/src/platform/simulator/csi_camera.c new file mode 100644 index 0000000..921d157 --- /dev/null +++ b/src/platform/simulator/csi_camera.c @@ -0,0 +1,898 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#define LOG_LEVEL 2 +#define CHANNEL_NUM 3 +#define CAMERA_FRAME_NUM 10 +#define PROPERTY_NUM 100 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "simulator_camera.h" +#include +#include +#include + +#define MAGIC_NUMBER 0xdeadbeaf +#define DEVICENAME0 "/dev/video0" +#define DEVICENAME1 "/dev/video1" +#define DEVICENAME2 "/dev/video2" +#define DEVICENAME3 "/dev/video3" +#define DEVICEFAKE "/dev/fake" +#define CAMERA_HEIGHT 640 +#define CAMERA_WIDTH 480 + + +int pca_consumer_process_chn0(pca_data_info_t *data_info); +int pca_consumer_process_chn1(pca_data_info_t *data_info); +int pca_consumer_process_chn2(pca_data_info_t *data_info); + +static sem_t frame_csem; +static pthread_mutex_t mutex_event = PTHREAD_MUTEX_INITIALIZER; +static pthread_t camera_ptid = -1; +static pthread_mutex_t mutex_chn[CHANNEL_NUM] = {PTHREAD_MUTEX_INITIALIZER,PTHREAD_MUTEX_INITIALIZER,PTHREAD_MUTEX_INITIALIZER}; +static bool camera_producer_flag = true; +static csi_camera_channel_id_e g_chn_id[CHANNEL_NUM] = {CSI_CAMERA_CHANNEL_0, CSI_CAMERA_CHANNEL_1, CSI_CAMERA_CHANNEL_2}; +int (*pca_consumer_process_chn[CHANNEL_NUM])(pca_data_info_t *data_info) = {pca_consumer_process_chn0, pca_consumer_process_chn1,pca_consumer_process_chn2}; + +static pca_dispatcher_info_t *s_dispatcher = NULL; +static pca_consumer_processor_info_t process_info[CHANNEL_NUM] = { + {.data_type = 1,.process = pca_consumer_process_chn0,.name = "chn0_process"}, + {.data_type = 1,.process = pca_consumer_process_chn1,.name = "chn1_process"}, + {.data_type = 1,.process = pca_consumer_process_chn2,.name = "chn2_process"} +}; + +static const csi_camera_infos_s g_camera_infos = { + 3, + { + {"RGB_Camera", "/dev/video0", "CSI-MIPI", CSI_CAMERA_CAP_META_CAPTURE | CSI_CAMERA_CAP_VIDEO_CAPTURE }, + {"Mono_Camera", "/dev/video1", "USB", CSI_CAMERA_CAP_VIDEO_CAPTURE}, + {"fake_Camera", "/dev/fake", "USB", CSI_CAMERA_CAP_VIDEO_CAPTURE} + } +}; + +static csi_camera_modes_s g_camera_modes = { + 2, {{1, "default"}, {2, "portrait"}} +}; +static csi_camera_channel_cfg_s g_camera_channel_cfgs[CHANNEL_NUM] = { + { + CSI_CAMERA_CHANNEL_0, CSI_CAMERA_CHANNEL_CAPTURE_VIDEO | CSI_CAMERA_CHANNEL_CAPTURE_META, 4, + {640, 480, CSI_PIX_FMT_I420}, CSI_IMG_TYPE_DMA_BUF, CSI_CAMERA_META_DEFAULT_FIELDS, CSI_CAMERA_CHANNEL_CLOSED + }, + { + CSI_CAMERA_CHANNEL_1, CSI_CAMERA_CHANNEL_CAPTURE_VIDEO | CSI_CAMERA_CHANNEL_CAPTURE_META, 4, + {640, 480, CSI_PIX_FMT_I420}, CSI_IMG_TYPE_DMA_BUF, CSI_CAMERA_META_DEFAULT_FIELDS, CSI_CAMERA_CHANNEL_CLOSED + }, + { + CSI_CAMERA_CHANNEL_2, CSI_CAMERA_CHANNEL_CAPTURE_VIDEO | CSI_CAMERA_CHANNEL_CAPTURE_META, 4, + {640, 480, CSI_PIX_FMT_I420}, CSI_IMG_TYPE_DMA_BUF, CSI_CAMERA_META_DEFAULT_FIELDS, CSI_CAMERA_CHANNEL_CLOSED + }, +}; + +static csi_camera_property_description_s +g_camera_property_descriptions[PROPERTY_NUM] = { + { + CSI_CAMERA_PID_HFLIP, CSI_CAMERA_PROPERTY_TYPE_BOOLEAN, "horization flip", 0, 1, 1, {.bool_value = true}, + {.bool_value = false}, 0, {0, 0} + }, + { + CSI_CAMERA_PID_VFLIP, CSI_CAMERA_PROPERTY_TYPE_BOOLEAN, "vertically flip", 0, 1, 1, {.bool_value = true}, + {.bool_value = false}, 0, {0, 0} + }, + { + CSI_CAMERA_PID_ROTATE, CSI_CAMERA_PROPERTY_TYPE_INTEGER, "rotate", 0, 270, 90, {.int_value = 0}, + {.int_value = 0}, 0, {0, 0} + }, + { + CSI_CAMERA_PID_EXPOSURE_MODE, CSI_CAMERA_PROPERTY_TYPE_ENUM, "exposure mode", 0, 3, 1, + {.enum_value = CSI_CAMERA_EXPOSURE_MODE_AUTO}, {.enum_value = CSI_CAMERA_EXPOSURE_MODE_AUTO}, 0, {0, 0} + }, + { + CSI_CAMERA_PID_EXPOSURE_ABSOLUTE, CSI_CAMERA_PROPERTY_TYPE_INTEGER, "exposure absolute", 0, + 10000, 1, {.int_value = 1}, {.int_value = 1}, 0, {0, 0} + }, + { + CSI_CAMERA_PID_3A_LOCK, CSI_CAMERA_PROPERTY_TYPE_BITMASK, "3A LOCK", 0, 4, 2, {.bitmask_value = CSI_CAMERA_LOCK_EXPOSURE}, + {.bitmask_value = CSI_CAMERA_LOCK_EXPOSURE}, 0, {0, 0} + }, + { + CSI_CAMERA_PID_RED_GAIN, CSI_CAMERA_PROPERTY_TYPE_INTEGER, "red gain", 0, 2147483647, 1, {.int_value = 1}, + {.int_value = 1}, 0, {0, 0} + }, + { + CSI_CAMERA_PID_GREEN_GAIN, CSI_CAMERA_PROPERTY_TYPE_INTEGER, "green gain", 0, 2147483647, 1, {.int_value = 1}, + {.int_value = 1}, 0, {0, 0} + }, + { + CSI_CAMERA_PID_BLUE_GAIN, CSI_CAMERA_PROPERTY_TYPE_INTEGER, "blue gain", 0, 2147483647, 1, {.int_value = 1}, + {.int_value = 1}, 0, {0, 0} + }, +}; +static LLIST *g_camera_mode_cfg; +static LLIST *g_camera_subscribe_event; +static LLIST *g_camera_event; + +static frame_channel_info_s g_frame_channel_infos[CHANNEL_NUM]; +static camera_frame_info_s g_camera_frame_infos[CAMERA_FRAME_NUM]; + +int32_t csi_camera_get_version(csi_api_version_u *version) +{ + version->major = CSI_CAMERA_VERSION_MAJOR; + version->minor = CSI_CAMERA_VERSION_MINOR; + return 0; +} + +int csi_camera_query_list(csi_camera_infos_s *infos) +{ + if (infos == NULL) { + LOG_E("pass parameter error!\n"); + return -1; + } + memcpy(infos, &g_camera_infos, sizeof(csi_camera_infos_s)); + return 0; +} + +static int getcameraindex(const char *cam_name) +{ + int index; + if (!strcmp(cam_name, DEVICENAME0)) { + index = 0; + } else if (!strcmp(cam_name, DEVICENAME1)) { + index = 1; + } else if (!strcmp(cam_name, DEVICENAME2)) { + index = 2; + } else if (!strcmp(cam_name, DEVICENAME3)) { + index = 3; + } else if (!strcmp(cam_name, DEVICEFAKE)) { + index = 4; + } else { + LOG_E("name is error!\n"); + index = -1; + } + return index; +} + +static int32_t data_consumed_callback(struct pca_data_info* data_info) +{ + camera_frame_info_s *data = (camera_frame_info_s *)data_info->data; + printf("data->frame_status = %d\n",data->frame_status); + data->frame_status = CSI_FRAME_IDLE; + return 0; +} + +void *camera_producer_frame(void *arg) +{ + pca_data_info_t data_info; + int i = 0,ret = 0; + static int loop = 0; + while (camera_producer_flag) { + //LOG_D("camera_producer_frame!\n"); + loop++; + for(i = 0;i < CAMERA_FRAME_NUM;i++){ + if(g_camera_frame_infos[i].frame_status == CSI_FRAME_IDLE){ + break; + } + } + LOG_D("camera_producer_frame! before %d\n",loop); + if(i == CAMERA_FRAME_NUM){ + //usleep(100); + continue; + } + LOG_D("camera_producer_frame! %d\n",loop); + csi_camera_opencv_get_picture( + CAMERA_HEIGHT, + CAMERA_WIDTH, + g_camera_frame_infos[i].frame_bufs); + data_info.type = 1; + data_info.length = 4; + data_info.data = &g_camera_frame_infos[i]; + data_info.consumed_callback = data_consumed_callback; + ret = pca_feed_dispatcher(s_dispatcher,&data_info); + if(ret != 0){ + LOG_E("pca_feed_dispatcher\n"); + } + } + pthread_exit(NULL); +} + +static int channel_feeddata_thread_run(void) +{ + int ret = 0; + camera_producer_flag = true; + ret = pthread_create(&camera_ptid,NULL,camera_producer_frame,NULL); + CHECK_RET_RETURN(ret); + return 0; +} + +static int channel_feeddata_thread_stop(void) +{ + int ret = 0; + camera_producer_flag = false; + ret = pthread_join(camera_ptid,NULL); + CHECK_RET_RETURN(ret); + camera_ptid = -1; + return 0; +} + +int csi_camera_open(csi_cam_handle_t *cam_handle, const char *device_name) +{ + int idx = 0,ret = 0; + csi_cam_handle_info_t *retp; + g_camera_mode_cfg = llist_create(sizeof(csi_camera_mode_cfg_s)); + if (g_camera_mode_cfg == NULL) { + LOG_E("g_camera_mode_cfg llist_create() error!\n"); + return -1; + } + g_camera_subscribe_event = llist_create(sizeof( + csi_camera_event_subscription_s)); + if (g_camera_subscribe_event == NULL) { + LOG_E("g_camera_subscribe_event llist_create() error!\n"); + return -1; + } + g_camera_event = llist_create(sizeof(csi_camera_event_s)); + if (g_camera_event == NULL) { + LOG_E("g_camera_event llist_create() error!\n"); + return -1; + } + idx = getcameraindex(device_name); + retp = malloc(sizeof(csi_cam_handle_info_t)); + retp->idx = idx; + if (idx != 4 && camera_open_opencv(idx)) { + LOG_E("camera cannot open!\n"); + return -1; + } + for(int i = 0;i < CAMERA_FRAME_NUM;i++){ + g_camera_frame_infos[i].frame_bufs = malloc(sizeof(unsigned char) * CAMERA_HEIGHT * CAMERA_WIDTH * 3); + g_camera_frame_infos[i].frame_status = CSI_FRAME_IDLE; + } + ret = pca_dispatcher_create(&s_dispatcher,"camera_picture_producer"); + *cam_handle = retp; + return 0; +} + +int csi_camera_close(csi_cam_handle_t cam_handle) +{ + int ret = 0; + camera_close_opencv(cam_handle); + llist_destroy(g_camera_mode_cfg); + llist_destroy(g_camera_subscribe_event); + llist_destroy(g_camera_event); + if (cam_handle != NULL) { + free(cam_handle); + } + for(int i = 0;i < CAMERA_FRAME_NUM;i++){ + if(g_camera_frame_infos[i].frame_bufs != NULL){ + free(g_camera_frame_infos[i].frame_bufs); + } + } + ret = pca_dispatcher_destory(s_dispatcher); + CHECK_RET_RETURN(ret); + return 0; +} + +int csi_camera_get_modes(csi_cam_handle_t cam_handle, csi_camera_modes_s *modes) +{ + if (modes == NULL) { + LOG_E("pass paramter error!\n"); + return -1; + } + memcpy(modes, &g_camera_modes, sizeof(csi_camera_modes_s)); + return 0; +} + +static void mode_cfg_print(const void *data) +{ + const csi_camera_mode_cfg_s *d = data; + printf("camera mode cfg->mode_id: %d\n", d->mode_id); +} + +int csi_camera_set_mode(csi_cam_handle_t cam_handle, csi_camera_mode_cfg_s *cfg) +{ + int i; + csi_cam_handle_info_t *cam_info = cam_handle; + if (cfg == NULL) { + LOG_E("pass parameter error!\n"); + return -1; + } + if (cfg->mode_id < 1 && cfg->mode_id > g_camera_modes.count) { + LOG_E("camera does not have the cfg modes!\n"); + return -1; + } + g_camera_mode_cfg->insert(g_camera_mode_cfg, cfg, LLIST_BACKWARD); + if (cam_info->idx != 4 && camera_set_mode_opencv(cfg)) { + LOG_E("camera_set_mode_opencv error!\n"); + return -1; + } + //g_camera_mode_cfg->travel(g_camera_mode_cfg,mode_cfg_print); + return 0; +} + +int csi_camera_query_property(csi_cam_handle_t cam_handle, + csi_camera_property_description_s *desc) +{ + int i = 0; + if (desc == NULL) { + LOG_E("pass parameter error\n"); + return -1; + } + if (desc->id & CSI_CAMERA_FLAG_NEXT_CTRL) { + desc->id = (desc->id & ~(CSI_CAMERA_FLAG_NEXT_CTRL)); + for (i = 0; i < PROPERTY_NUM; i++) { + if (desc->id == g_camera_property_descriptions[i].id) { + i++; + break; + } + } + } else { + for (i = 0; i < PROPERTY_NUM; i++) { + if (desc->id == g_camera_property_descriptions[i].id) { + break; + } + } + } + if (i == PROPERTY_NUM || !g_camera_property_descriptions[i].id) { + return -1; + } + memcpy(desc, &g_camera_property_descriptions[i], + sizeof(csi_camera_property_description_s)); + return 0; +} + +int csi_camera_get_property(csi_cam_handle_t cam_handle, + csi_camera_properties_s *properties) +{ + if (properties == NULL) { + LOG_E("pass parameter error\n"); + return -1; + } + for (int i = 0; i < properties->count; i++) { + unsigned int id; + int j; + for (j = 0; j < PROPERTY_NUM; j++) { + if (properties->property[i].id == g_camera_property_descriptions[j].id) { + break; + } + } + if (j == PROPERTY_NUM || !g_camera_property_descriptions[id].id) { + LOG_E("error configuration\n"); + return -1; + } + properties->property[i].type = g_camera_property_descriptions[j].type; + memcpy(&properties->property[i].value, + &g_camera_property_descriptions[j].value, sizeof(csi_camera_property_data_u)); + } + return 0; +} + +const char *csi_camera_ctrl_id_name(unsigned int id) +{ + return NULL; +} + +const char *csi_camera_ctrl_type_name(unsigned int type) +{ + + return NULL; +} + +int csi_camera_set_property(csi_cam_handle_t cam_handle, + csi_camera_properties_s *properties) +{ + csi_cam_handle_info_t *cam_info = cam_handle; + if (properties == NULL) { + LOG_E("pass parameter error\n"); + return -1; + } + for (int i = 0; i < properties->count; i++) { + unsigned int id; + int j; + for (j = 0; j < PROPERTY_NUM; j++) { + if (properties->property[i].id == g_camera_property_descriptions[j].id) { + break; + } + } + if (j == PROPERTY_NUM || !g_camera_property_descriptions[j].id) { + LOG_E("error configuration\n"); + return -1; + } + if (cam_info->idx != 4 && + camera_property_set_opencv(&properties->property[i])) { + LOG_E("error configuration!\n"); + return -1; + } + memcpy(&g_camera_property_descriptions[j].value, + &properties->property[i].value, sizeof(csi_camera_property_data_u)); + } + return 0; +} + +int csi_camera_query_frame(csi_cam_handle_t cam_handle, + csi_camera_channel_id_e chn_id, + int frm_id, + csi_frame_s *frm) + +{ + return 0; +} + +int csi_camera_channel_open(csi_cam_handle_t cam_handle, + csi_camera_channel_cfg_s *cfg) +{ + int ret = 0; + if (cfg == NULL) { + LOG_E("pass parameter error\n"); + return -1; + } + if (cfg->chn_id < CSI_CAMERA_CHANNEL_0 || cfg->chn_id > CSI_CAMERA_CHANNEL_2) { + LOG_E("select error channel!\n"); + return -1; + } + if(camera_ptid != -1){ + channel_feeddata_thread_stop(); + ret = pca_dispatcher_stop(s_dispatcher); + CHECK_RET_RETURN(ret); + } + for (int i = 0; i < CHANNEL_NUM; i++) { + for (int j = 0; j < MAX_FRAME_COUNT; j++) { + g_frame_channel_infos[i].frame_bufs[j] = NULL; + g_frame_channel_infos[i].frame_status[j] = CSI_FRAME_IDLE; + g_frame_channel_infos[i].refcount[j] = 0; + } + } + memcpy(&g_camera_channel_cfgs[cfg->chn_id], cfg, + sizeof(csi_camera_channel_cfg_s)); + g_camera_channel_cfgs[cfg->chn_id].status = CSI_CAMERA_CHANNEL_OPENED; + return 0; +} + +int csi_camera_channel_close(csi_cam_handle_t cam_handle, + csi_camera_channel_id_e chn_id) +{ + g_camera_channel_cfgs[chn_id].status = CSI_CAMERA_CHANNEL_CLOSED; + return 0; +} + +int csi_camera_channel_query(csi_cam_handle_t cam_handle, + csi_camera_channel_cfg_s *cfg) +{ + if (cfg == NULL) { + LOG_E("pass parameter error\n"); + return -1; + } + if (cfg->chn_id < CSI_CAMERA_CHANNEL_0 || cfg->chn_id > CSI_CAMERA_CHANNEL_2) { + LOG_E("select error channel!\n"); + return -1; + } + memcpy(cfg, &g_camera_channel_cfgs[cfg->chn_id], + sizeof(csi_camera_channel_cfg_s)); + return 0; +} + +int csi_camera_create_event(csi_cam_event_handle_t *event_handle, + csi_cam_handle_t cam_handle) +{ + return 0; +} + +int csi_camera_destory_event(csi_cam_event_handle_t event_handle) +{ + return 0; +} + +static int type_cmp(const void *data, const void *findtype) +{ + const csi_camera_event_subscription_s *d = data; + const csi_camera_event_type_e *type = findtype; + return (d->type - *type); +} + +static void print_subscribe(const void *data) +{ + const csi_camera_event_subscription_s *d = data; + printf("subscribe type: %d id: %d\n", d->type, d->id); +} + +int csi_camera_subscribe_event(csi_cam_event_handle_t event_handle, + csi_camera_event_subscription_s *subscribe) +{ + int i; + if (subscribe == NULL) { + LOG_E("pass parameter error!\n"); + return -1; + } + csi_camera_event_subscription_s *retp; + retp = g_camera_subscribe_event->find(g_camera_subscribe_event, + &subscribe->type, type_cmp); + if (retp == NULL) { + g_camera_subscribe_event->insert(g_camera_subscribe_event, subscribe, + LLIST_BACKWARD); + } else { + retp->id |= subscribe->id; + } + //for printf test + //g_camera_subscribe_event->travel(g_camera_subscribe_event,print_subscribe); + return 0; +} + +int csi_camera_unsubscribe_event(csi_cam_event_handle_t event_handle, + csi_camera_event_subscription_s *subscribe) +{ + int i = 0; + csi_camera_event_subscription_s *retp; + if (subscribe == NULL) { + LOG_E("pass parameter error!\n"); + return -1; + } + retp = g_camera_subscribe_event->find(g_camera_subscribe_event, + &subscribe->type, type_cmp); + if (retp == NULL) { + LOG_E("can't find subscribe event!\n"); + return -1; + } + if ((retp->id &= ~(subscribe->id)) == 0) { + g_camera_subscribe_event->delete (g_camera_subscribe_event, &subscribe->type, + type_cmp); + } + return 0; +} + +static int camera_id_cmp(const void *data, const void *findid) +{ + return 0; +} + +static void event_print(const void *data) +{ + const csi_camera_event_s *d = data; + printf("camera event type: %d id: %d\n", d->type, d->id); +} + +int csi_camera_get_event(csi_cam_event_handle_t event_handle, + csi_camera_event_s *event, + int timeout) +{ + struct timeval time1; + int ret; + int semvalue = -1; + int findid = -1; + if (timeout == -1) { + sem_wait(&frame_csem); + } else { + gettimeofday(&time1, NULL); + event->timestamp.tv_sec = time1.tv_sec + timeout; + semvalue = sem_timedwait(&frame_csem, &event->timestamp); + if (semvalue == -1) { + LOG_E("do not get camera event!\n"); + return -1; + } + } + pthread_mutex_lock(&mutex_event); + //print for test + //g_camera_event->travel(g_camera_event,event_print); + ret = g_camera_event->fetch(g_camera_event, NULL, camera_id_cmp, event); + if (ret == -1) { + LOG_E("do not get camera event!\n"); + pthread_mutex_unlock(&mutex_event); + return -1; + } + LOG_D("event->type = %d event->id = %d\n", event->type, event->id); + pthread_mutex_unlock(&mutex_event); + return 0; +} + +int csi_camera_get_frame_count(csi_cam_handle_t cam_handle, + csi_camera_channel_id_e chn_id) +{ + int i; + pthread_mutex_lock(&mutex_chn[chn_id]); + for (i = 0; i < g_camera_channel_cfgs[chn_id].frm_cnt; i++) { + if (g_frame_channel_infos[chn_id].frame_status[i] == CSI_FRAME_READY) { + i++; + } + } + pthread_mutex_unlock(&mutex_chn[chn_id]); + return i; +} + +int csi_camera_get_frame(csi_cam_handle_t cam_handle, + csi_camera_channel_id_e chn_id, + csi_frame_s *frame, + int timeout) +{ + int ret = -1; + int i = 0, j = 0; + + if (frame == NULL) { + LOG_E("pass parameter error!\n"); + return -1; + } + if (chn_id < CSI_CAMERA_CHANNEL_0 || chn_id > CSI_CAMERA_CHANNEL_2) { + LOG_E("select error ch_id!\n"); + return -1; + } + pthread_mutex_lock(&mutex_chn[chn_id]); + frame->img.type = CSI_IMG_TYPE_UMALLOC; + frame->img.width = g_camera_channel_cfgs[chn_id].img_fmt.width; + frame->img.height = g_camera_channel_cfgs[chn_id].img_fmt.height; + frame->img.pix_format = g_camera_channel_cfgs[chn_id].img_fmt.pix_fmt; + frame->img.size = camera_image_size_opencv(frame); + frame->img.num_planes = CSI_IMAGE_I420_PLANES; + frame->img.strides[0] = frame->img.width; + frame->img.strides[1] = frame->img.width; + + frame->img.offsets[0] = 0; + frame->img.offsets[1] = frame->img.width * frame->img.height; + if (frame->img.type == CSI_IMG_TYPE_UMALLOC) { + for (int i = 0; i < g_camera_channel_cfgs[chn_id].frm_cnt; i++) { + if (g_frame_channel_infos[chn_id].frame_status[i] == CSI_FRAME_READY) { + frame->img.usr_addr[0] = g_frame_channel_infos[chn_id].frame_bufs[i]; + frame->img.usr_addr[1] = NULL; + g_frame_channel_infos[chn_id].refcount[i] = 0; + ret = 0; + break; + } + } + } + pthread_mutex_unlock(&mutex_chn[chn_id]); + return ret; +} + +int csi_camera_put_frame(csi_frame_s *frame) +{ + int ret = 0; + switch (frame->img.type) { + case CSI_IMG_TYPE_DMA_BUF: + ret = -1; + break; + case CSI_IMG_TYPE_SYSTEM_CONTIG: + ret = -1; + break; + case CSI_IMG_TYPE_CARVEOUT: + ret = -1; + break; + case CSI_IMG_TYPE_UMALLOC: + for (int i = 0; i < CHANNEL_NUM; i++) { + pthread_mutex_lock(&mutex_chn[i]); + for (int j = 0; j < g_camera_channel_cfgs[i].frm_cnt; j++) { + //LOG_D("frame->img.usr_addr[0] = %p g_frame_channel_infos[%d].frame_bufs[%d] = %p\n", + // frame->img.usr_addr[0], i, j, g_frame_channel_infos[i].frame_bufs[j]); + if (frame->img.usr_addr[0] == g_frame_channel_infos[i].frame_bufs[j]) { + g_frame_channel_infos[i].frame_status[j] = CSI_FRAME_IDLE; + } + } + pthread_mutex_unlock(&mutex_chn[i]); + } + break; + case CSI_IMG_TYPE_SHM: + ret = -1; + break; + default: + LOG_E("Uknown format\n"); + ret = -1; + break; + } + return ret; +} + + +int pca_consumer_process_chn0(pca_data_info_t *data_info) +{ + + LOG_D("pca_consumer_process_chn0\n"); + csi_camera_event_subscription_s tmp,*retp; + camera_frame_info_s *data = (camera_frame_info_s *)data_info->data; + tmp.type = CSI_CAMERA_EVENT_TYPE_CHANNEL0; + retp = g_camera_subscribe_event->find(g_camera_subscribe_event,&tmp.type,type_cmp); + if(retp == NULL){ + return -1; + } + pthread_mutex_lock(&mutex_chn[0]); + for (int i = 0; i < g_camera_channel_cfgs[0].frm_cnt; i++) { + if (g_frame_channel_infos[0].frame_status[i] == CSI_FRAME_IDLE) { + csi_camera_opencv_format_picture( + g_camera_channel_cfgs[0].img_fmt.pix_fmt, + g_camera_channel_cfgs[0].img_fmt.height, + g_camera_channel_cfgs[0].img_fmt.width, + g_frame_channel_infos[0].frame_bufs[i], + CAMERA_HEIGHT, + CAMERA_WIDTH, + data->frame_bufs, + g_camera_property_descriptions); + g_frame_channel_infos[0].frame_status[i] = CSI_FRAME_READY; + tmp.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY; + } + } + pthread_mutex_lock(&mutex_event); + g_camera_event->insert(g_camera_event,&tmp,LLIST_FORWARD); + pthread_mutex_unlock(&mutex_event); + data->frame_status = CSI_FRAME_READY; + pthread_mutex_unlock(&mutex_chn[0]); + sem_post(&frame_csem); + return 0; +} + +int pca_consumer_process_chn1(pca_data_info_t *data_info) +{ + + LOG_D("pca_consumer_process_chn1\n"); + csi_camera_event_subscription_s tmp,*retp; + camera_frame_info_s *data = (camera_frame_info_s *)data_info->data; + tmp.type = CSI_CAMERA_EVENT_TYPE_CHANNEL1; + retp = g_camera_subscribe_event->find(g_camera_subscribe_event,&tmp.type,type_cmp); + if(retp == NULL){ + return -1; + } + pthread_mutex_lock(&mutex_chn[1]); + for (int i = 0; i < g_camera_channel_cfgs[0].frm_cnt; i++) { + if (g_frame_channel_infos[1].frame_status[i] == CSI_FRAME_IDLE) { + csi_camera_opencv_format_picture( + g_camera_channel_cfgs[1].img_fmt.pix_fmt, + g_camera_channel_cfgs[1].img_fmt.height, + g_camera_channel_cfgs[1].img_fmt.width, + g_frame_channel_infos[1].frame_bufs[i], + CAMERA_HEIGHT, + CAMERA_WIDTH, + data->frame_bufs, + g_camera_property_descriptions); + g_frame_channel_infos[1].frame_status[i] = CSI_FRAME_READY; + tmp.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY; + } + } + pthread_mutex_lock(&mutex_event); + g_camera_event->insert(g_camera_event,&tmp,LLIST_FORWARD); + pthread_mutex_unlock(&mutex_event); + //g_camera_event->travel(g_camera_event,event_print); + data->frame_status = CSI_FRAME_READY; + pthread_mutex_unlock(&mutex_chn[1]); + sem_post(&frame_csem); + return 0; +} + +int pca_consumer_process_chn2(pca_data_info_t *data_info) +{ + LOG_D("pca_consumer_process_chn2\n"); + csi_camera_event_subscription_s tmp,*retp; + camera_frame_info_s *data = (camera_frame_info_s *)data_info->data; + tmp.type = CSI_CAMERA_EVENT_TYPE_CHANNEL2; + retp = g_camera_subscribe_event->find(g_camera_subscribe_event,&tmp.type,type_cmp); + if(retp == NULL){ + return -1; + } + pthread_mutex_lock(&mutex_chn[2]); + for (int i = 0; i < g_camera_channel_cfgs[2].frm_cnt; i++) { + if (g_frame_channel_infos[2].frame_status[i] == CSI_FRAME_IDLE) { + csi_camera_opencv_format_picture( + g_camera_channel_cfgs[2].img_fmt.pix_fmt, + g_camera_channel_cfgs[2].img_fmt.height, + g_camera_channel_cfgs[2].img_fmt.width, + g_frame_channel_infos[2].frame_bufs[i], + CAMERA_HEIGHT, + CAMERA_WIDTH, + data->frame_bufs, + g_camera_property_descriptions); + g_frame_channel_infos[2].frame_status[i] = CSI_FRAME_READY; + tmp.id = CSI_CAMERA_CHANNEL_EVENT_FRAME_READY; + } + } + pthread_mutex_lock(&mutex_event); + g_camera_event->insert(g_camera_event,&tmp,LLIST_FORWARD); + pthread_mutex_unlock(&mutex_event); + data->frame_status = CSI_FRAME_READY; + pthread_mutex_unlock(&mutex_chn[2]); + sem_post(&frame_csem); + return 0; +} + +int csi_camera_channel_start(csi_cam_handle_t cam_handle, + csi_camera_channel_id_e chn_id) +{ + //wait for complete + int ret = 0; + csi_cam_handle_info_t *cam_info = cam_handle; + csi_camera_channel_id_e p_chn_id = chn_id; + csi_camera_mode_cfg_s *cfg; + int framesize = g_camera_channel_cfgs[chn_id].img_fmt.height * + g_camera_channel_cfgs[chn_id].img_fmt.width; + LOG_D("chn_id = %d\n", chn_id); + sem_init(&frame_csem, 0, 0); + for (int i = 0; i < g_camera_channel_cfgs[chn_id].frm_cnt; i++) { + g_frame_channel_infos[chn_id].frame_status[i] = CSI_FRAME_IDLE; + g_frame_channel_infos[chn_id].refcount[i] = 0; + } + switch (g_camera_channel_cfgs[chn_id].img_fmt.pix_fmt) { + case CSI_PIX_FMT_I420: + framesize = framesize * 3 / 2; + for (int i = 0; i < g_camera_channel_cfgs[chn_id].frm_cnt; i++) { + g_frame_channel_infos[chn_id].frame_bufs[i] = malloc(sizeof( + unsigned char) * framesize); + } + break; + case CSI_PIX_FMT_NV12: + framesize = framesize * 3 / 2; + for (int i = 0; i < g_camera_channel_cfgs[chn_id].frm_cnt; i++) { + g_frame_channel_infos[chn_id].frame_bufs[i] = malloc(sizeof( + unsigned char) * framesize); + } + break; + case CSI_PIX_FMT_BGR: + framesize = framesize * 3; + for (int i = 0; i < g_camera_channel_cfgs[chn_id].frm_cnt; i++) { + g_frame_channel_infos[chn_id].frame_bufs[i] = malloc(sizeof( + unsigned char) * framesize); + } + break; + default: + LOG_E("Uknown format\n"); + ret = -1; + break; + } + if(camera_ptid != -1){ + channel_feeddata_thread_stop(); + } + + if (cam_info->idx != 4) { + ret = pca_add_consumer_processor(s_dispatcher,&process_info[chn_id]); + CHECK_RET_RETURN(ret); + } else { + + } + ret = pca_dispatcher_run(s_dispatcher); + CHECK_RET_RETURN(ret); + channel_feeddata_thread_run(); + g_camera_channel_cfgs[chn_id].status = CSI_CAMERA_CHANNEL_RUNNING; + return ret; +} + +int csi_camera_channel_stop(csi_cam_handle_t cam_handle, + csi_camera_channel_id_e chn_id) +{ + int ret = 0; + csi_cam_handle_info_t *cam_info = cam_handle; + LOG_D("csi_camera_channel_stop\n"); + for (int i = 0; i < g_camera_channel_cfgs[chn_id].frm_cnt; i++) { + g_frame_channel_infos[chn_id].frame_status[i] = CSI_FRAME_IDLE; + g_frame_channel_infos[chn_id].refcount[i] = 0; + } + switch (g_camera_channel_cfgs[chn_id].img_fmt.pix_fmt) { + case CSI_PIX_FMT_I420: + for (int i = 0; i < g_camera_channel_cfgs[chn_id].frm_cnt; i++) { + free(g_frame_channel_infos[chn_id].frame_bufs[i]); + } + break; + case CSI_PIX_FMT_NV12: + for (int i = 0; i < g_camera_channel_cfgs[chn_id].frm_cnt; i++) { + free(g_frame_channel_infos[chn_id].frame_bufs[i]); + } + break; + case CSI_PIX_FMT_BGR: + for (int i = 0; i < g_camera_channel_cfgs[chn_id].frm_cnt; i++) { + free(g_frame_channel_infos[chn_id].frame_bufs[i]); + } + break; + default: + LOG_E("Uknown format\n"); + ret = -1; + break; + } + if(camera_ptid != -1){ + channel_feeddata_thread_stop(); + ret = pca_dispatcher_stop(s_dispatcher); + CHECK_RET_RETURN(ret); + } + g_camera_channel_cfgs[chn_id].status = CSI_CAMERA_CHANNEL_OPENED; + LOG_D("csi_camera_channel_stop ok\n"); + return ret; +} diff --git a/src/platform/simulator/csi_camera_frame.c b/src/platform/simulator/csi_camera_frame.c new file mode 100644 index 0000000..443d7cc --- /dev/null +++ b/src/platform/simulator/csi_camera_frame.c @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include + +#define LOG_LEVEL 2 +#define LOG_PREFIX "camera_meta" +#include +#include + +int csi_camera_frame_alloc_meta(csi_camera_meta_s **meta, int meta_count, size_t *meta_data_size) +{ + size_t malloc_size; + if (meta_count <= 0) { + LOG_E("meta_count=%d\n", meta_count); + *meta_data_size = 0; + return -1; + } + + malloc_size = sizeof(csi_camera_meta_s) + sizeof(csi_camrea_meta_unit_s) * meta_count; + LOG_D("malloc_size=(%zd+%zd)=%zd\n", sizeof(csi_camera_meta_s), sizeof(csi_camrea_meta_unit_s) * meta_count, malloc_size); + + *meta = malloc(malloc_size); + if (*meta == NULL) { + LOG_E("malloc *meta(%zu) failed\n", malloc_size); + return -1; + } + + memset(*meta, 0, malloc_size); + (*meta)->count = meta_count; + (*meta)->size = sizeof(csi_camrea_meta_unit_s) * meta_count; + (*meta)->units = (csi_camrea_meta_unit_s *)((char*)(*meta) + sizeof(csi_camera_meta_s)); + + LOG_D("*meta=%p, (*meta)->units=%p\n", *meta, (*meta)->units); + + *meta_data_size = malloc_size; + + return 0; +} + +int csi_camera_frame_free_meta(csi_camera_meta_s *meta) +{ + if (meta == NULL) { + LOG_E("[%s:%d] meta = NULL\n", __func__, __LINE__); + return -1; + } + + free(meta); + return 0; +} + +int csi_camera_frame_get_meta_unit(csi_camrea_meta_unit_s *meta_unit, + csi_camera_meta_s *meta_data, + csi_camera_meta_id_e meta_field) +{ + int i; + + LOG_D("meta_data->count=%d\n", meta_data->count); + + for (i = 0; i < meta_data->count; i++) { + if (meta_data->units[i].id == meta_field) { + *meta_unit = meta_data->units[i]; + return 0; + } + } + return -1; +} + diff --git a/src/platform/simulator/csi_camera_platform_spec.c b/src/platform/simulator/csi_camera_platform_spec.c new file mode 100644 index 0000000..547ffd3 --- /dev/null +++ b/src/platform/simulator/csi_camera_platform_spec.c @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "dlg_cam_enums" +#include + +/******************************************************************************/ +/*********** Platform Spec in Enum ********************************************/ +/******************************************************************************/ + +/* The enums platform supports for Property ID: CSI_CAMERA_PID_EXPOSURE_MODE */ +const camera_spec_enums_s camera_spec_camera_exposure_modes[] = { + 4, + { + CSI_CAMERA_EXPOSURE_MODE_AUTO, + CSI_CAMERA_EXPOSURE_MANUAL, + CSI_CAMERA_EXPOSURE_SHUTTER_PRIORITY, + CSI_CAMERA_EXPOSURE_APERTURE_PRIORITY + } +}; + +/* The enums platform supports for Channel pix_fmt */ +const camera_spec_enums_s camera_spec_chn_pix_fmt[] = { + 3, + { + CSI_PIX_FMT_I420, + CSI_PIX_FMT_NV12, + CSI_PIX_FMT_BGR, + } +}; + +/* The enums platform supports for Channel pix_fmt */ +const camera_spec_enums_s camera_spec_chn_img_type[] = { + 2, + { + CSI_IMG_TYPE_DMA_BUF, + CSI_IMG_TYPE_SYSTEM_CONTIG, + } +}; + +/* The enums platform supports for camera event subscribe */ +const camera_spec_enums_s camera_spec_camera_event_type[] = { + 4, + { + CSI_CAMERA_EVENT_WARNING, + CSI_CAMERA_EVENT_ERROR, + CSI_CAMERA_EVENT_SENSOR_FIRST_IMAGE_ARRIVE, + CSI_CAMERA_EVENT_ISP_3A_ADJUST_READY, + } +}; + +/* The enums platform supports for camera channel event subscribe */ +const camera_spec_enums_s camera_spec_channel_event_type[] = { + 3, + { + CSI_CAMERA_CHANNEL_EVENT_FRAME_READY, + CSI_CAMERA_CHANNEL_EVENT_FRAME_PUT, + CSI_CAMERA_CHANNEL_EVENT_OVERFLOW, + } +}; + +const camera_spec_enums_s *camera_spec_get_enum_array(int property_id) +{ + switch(property_id) { + case CAMERA_SPEC_ENUM_CAMERA_EXPOSURE_MODES: + return camera_spec_camera_exposure_modes; + case CAMERA_SPEC_ENUM_CAMERA_EVENT_TYPES: + return camera_spec_camera_event_type; + case CAMERA_SPEC_ENUM_CHANNEL_PIX_FMT: + return camera_spec_chn_pix_fmt; + case CAMERA_SPEC_ENUM_CHANNEL_IMG_TYPE: + return camera_spec_chn_img_type; + case CAMERA_SPEC_ENUM_CHANNEL_EVENT_TYPES: + return camera_spec_channel_event_type; + default: + LOG_E("Unknown property_id:%d\n", property_id); + return NULL; + } +} + + +/******************************************************************************/ +/*********** Platform Spec in Bitmask *****************************************/ +/******************************************************************************/ + +/* The enums platform supports for Property ID: CSI_CAMERA_PID_EXPOSURE_MODE */ +const camera_spec_bitmasks_t camera_spec_3a_lock[] = { + 3, + { + CSI_CAMERA_LOCK_EXPOSURE, + CSI_CAMERA_LOCK_WHITE_BALANCE, + CSI_CAMERA_LOCK_FOCUS, + } +}; + +/* The enums platform supports for Property ID: CSI_CAMERA_PID_EXPOSURE_MODE */ +const camera_spec_bitmasks_t channel_spec_support_capture_type[] = { + 2, + { + CSI_CAMERA_CHANNEL_CAPTURE_VIDEO, + CSI_CAMERA_CHANNEL_CAPTURE_META, + } +}; + +/* The enums platform supports for Property ID: CSI_CAMERA_PID_EXPOSURE_MODE */ +const camera_spec_bitmasks_t channel_spec_support_meta_type[] = { + 5, + { + CSI_CAMERA_META_ID_CAMERA_NAME, + CSI_CAMERA_META_ID_CHANNEL_ID, + CSI_CAMERA_META_ID_FRAME_ID, + CSI_CAMERA_META_ID_TIMESTAMP, + CSI_CAMERA_META_ID_HDR, + } +}; + +const camera_spec_bitmasks_t *camera_spec_get_bitmask_array(int property_id) +{ + switch(property_id) { + case CAMERA_SPEC_BITMAKS_CAMERA_3A_LOCK: + return camera_spec_3a_lock; + case CAMERA_SPEC_BITMAKS_CHANNEL_CAPTURE_TYPE: + return channel_spec_support_capture_type; + case CAMERA_SPEC_BITMAKS_CHANNEL_META_TYPE: + return channel_spec_support_meta_type; + default: + LOG_E("Unknown property_id:%d\n", property_id); + return NULL; + } +} + + diff --git a/src/platform/simulator/csi_frame.c b/src/platform/simulator/csi_frame.c new file mode 100644 index 0000000..0d3e75d --- /dev/null +++ b/src/platform/simulator/csi_frame.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include + +int csi_frame_create(csi_frame_s *frame, + csi_img_type_e type, + csi_img_s img_info) +{ + if (type == CSI_IMG_TYPE_SHM) { + } else { + printf("Not supported img type:%d\n", type); + return -1; + } + return 0; +} + +int csi_frame_reference(csi_frame_s *frame_dest, csi_frame_s *frame_src) +{ + return 0; +} + +int csi_frame_release(csi_frame_s *frame) +{ + return 0; +} + +void* csi_frame_mmap(csi_frame_s *frame) +{ + return NULL; +} + +int csi_frame_munmap(csi_frame_s *frame) +{ + return 0; +} + diff --git a/src/platform/simulator/csi_vdec.c b/src/platform/simulator/csi_vdec.c new file mode 100644 index 0000000..def3728 --- /dev/null +++ b/src/platform/simulator/csi_vdec.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +int csi_vdec_get_version(csi_api_version_u *version) +{ + version->major = CSI_VDEC_VERSION_MAJOR; + version->minor = CSI_VDEC_VERSION_MINOR; + return 0; +} + +int csi_vdec_query_list(csi_vdec_infos_s *infos) +{ + infos->count = 0; + return 0; +} + +int csi_vdec_open(csi_vdec_dev_t *dec, const char *device_name) +{ + return 0; +} + +int csi_vdec_close(csi_vdec_dev_t dec) +{ + return 0; +} + +int csi_vdec_create_channel(csi_vdec_chn_t *chn, csi_vdec_dev_t dec, csi_vdec_config_s *cfg) +{ + return 0; +} + +int csi_vdec_destory_channel(csi_vdec_chn_t chn) +{ + return 0; +} + +#if 0 +int csi_vdec_set_memory_allocator(csi_vdec_chn_t chn, csi_allocator_s *allocator) +{ + return 0; +} +#endif + +int csi_vdec_set_mode(csi_vdec_chn_t chn, csi_vdec_mode_s *mode) +{ + return 0; +} + +int csi_vdec_get_mode(csi_vdec_chn_t chn, csi_vdec_mode_s *mode) +{ + return 0; +} + +int csi_vdec_set_chn_config(csi_vdec_chn_t chn, csi_vdec_config_s *cfg) +{ + return 0; +} + +int csi_vdec_get_chn_config(csi_vdec_chn_t chn, csi_vdec_config_s *cfg) +{ + return 0; +} + +int csi_vdec_set_pp_config(csi_vdec_chn_t chn, csi_vdec_pp_config_s *cfg) +{ + return 0; +} + +int csi_vdec_get_pp_config(csi_vdec_chn_t chn, csi_vdec_pp_config_s *cfg) +{ + return 0; +} + +int csi_vdec_start(csi_vdec_chn_t chn) +{ + return 0; +} + +int csi_vdec_stop(csi_vdec_chn_t chn) +{ + return 0; +} + +int csi_vdec_reset(csi_vdec_chn_t chn) +{ + return 0; +} + +int csi_vdec_send_stream_buf(csi_vdec_chn_t chn, csi_vdec_stream_s *stream, int32_t timeout) +{ + return 0; +} + +int csi_vdec_register_frames(csi_vdec_chn_t chn, csi_frame_s *frame[], int count) +{ + return 0; +} + +int csi_vdec_put_frame(csi_vdec_chn_t chn, csi_frame_s *frame) +{ + return 0; +} + +int csi_vdec_get_frame(csi_vdec_chn_t chn, csi_frame_s **frame, int32_t timeout) +{ + return 0; +} + +int csi_vdec_query_status(csi_vdec_chn_t chn, csi_vdec_chn_status_s *pstStatus) +{ + return 0; +} + +int csi_vdec_create_event_handle(csi_vdec_event_handle_t *handle, csi_vdec_dev_t event_handle) +{ + return 0; +} + +int csi_vdec_destory_event(csi_vdec_event_handle_t event_handle) +{ + return 0; +} + +int csi_vdec_subscribe_event(csi_vdec_event_handle_t event_, + csi_vdec_event_subscription_t *subscribe) +{ + return 0; +} + +int csi_vdec_unsubscribe_event(csi_vdec_event_handle_t event_handle, + csi_vdec_event_subscription_t *subscribe) +{ + return 0; +} + +int csi_vdec_get_event(csi_vdec_event_handle_t event_handle, + csi_vdec_event_s *event, int timeout) +{ + return 0; +} diff --git a/src/platform/simulator/csi_venc.c b/src/platform/simulator/csi_venc.c new file mode 100644 index 0000000..86733da --- /dev/null +++ b/src/platform/simulator/csi_venc.c @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +int csi_venc_get_version(csi_api_version_u *version) +{ + version->major = CSI_VENC_VERSION_MAJOR; + version->minor = CSI_VENC_VERSION_MINOR; + return 0; +} + +int csi_venc_query_list(csi_venc_infos_s *infos) +{ + infos->count = 0; + return 0; +} + +int csi_venc_open(csi_venc_dev_t *enc, const char *device_name) +{ + return 0; +} + +int csi_venc_close(csi_venc_dev_t enc) +{ + return 0; +} + +int csi_venc_create_channel(csi_venc_chn_t *chn, csi_venc_dev_t enc, csi_venc_chn_cfg_s *cfg) +{ + return 0; +} + +int csi_venc_destory_channel(csi_venc_chn_t chn) +{ + return 0; +} + +#if 0 +int csi_venc_set_memory_allocator(csi_venc_chn_t chn, csi_allocator_s *allocator) +{ + return 0; +} +#endif + +int csi_venc_set_ext_property(csi_venc_chn_t chn, csi_venc_chn_ext_property_s *prop) +{ + return 0; +} + +int csi_venc_get_ext_property(csi_venc_chn_t chn, csi_venc_chn_ext_property_s *prop) +{ + return 0; +} + +int csi_venc_start(csi_venc_chn_t chn) +{ + return 0; +} + +int csi_venc_stop(csi_venc_chn_t chn) +{ + return 0; +} + +int csi_venc_reset(csi_venc_chn_t chn) +{ + return 0; +} + + +int csi_venc_send_frame(csi_venc_chn_t chn, csi_frame_s *frame, int timeout) +{ + return 0; +} + +int csi_venc_send_frame_ex(csi_venc_chn_t chn, csi_frame_s *frame, int timeout, + csi_venc_frame_prop_s *prop, int prop_count) +{ + return 0; +} + +int csi_venc_get_stream(csi_venc_chn_t chn, csi_stream_s *stream, int timeout) +{ + return 0; +} + +int csi_venc_release_stream(csi_venc_chn_t chn, csi_stream_s *stream) +{ + return 0; +} + +int csi_venc_query_status(csi_venc_chn_t chn, csi_venc_chn_status_s *status) +{ + return 0; +} + +int csi_venc_create_event_handle(csi_venc_event_handle_t *chn, csi_venc_dev_t event_handle) +{ + return 0; +} + +int csi_venc_destory_event(csi_venc_event_handle_t event_handle) +{ + return 0; +} + +int csi_venc_subscribe_event(csi_venc_event_handle_t event_handle, + csi_venc_event_subscription_s *subscribe) +{ + return 0; +} + +int csi_venc_unsubscribe_event(csi_venc_event_handle_t event_handle, + csi_venc_event_subscription_s *subscribe) +{ + return 0; +} + +int csi_venc_get_event(csi_venc_event_handle_t event_handle, + csi_venc_event_s *event, int timeout) +{ + return 0; +} diff --git a/src/platform/simulator/drivers/Makefile b/src/platform/simulator/drivers/Makefile new file mode 100644 index 0000000..021bbb6 --- /dev/null +++ b/src/platform/simulator/drivers/Makefile @@ -0,0 +1,30 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../../../.. +include $(DIR_TO_ROOT)/build.param + +all: helloworld ion + +helloworld: + @echo $(BUILD_LOG_START) + make -C helloworld + @echo $(BUILD_LOG_END) + +ion: + @echo $(BUILD_LOG_START) + make -C ion + make -C tests/ion + @echo $(BUILD_LOG_END) + +clean: + make -C helloworld clean + make -C ion clean + make -C tests/ion clean + +.PHONY: clean all helloworld ion diff --git a/src/platform/simulator/drivers/helloworld/Makefile b/src/platform/simulator/drivers/helloworld/Makefile new file mode 100644 index 0000000..093510e --- /dev/null +++ b/src/platform/simulator/drivers/helloworld/Makefile @@ -0,0 +1,31 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## + +KVERS = $(shell uname -r) +CURDIR = $(shell pwd) + +# Kernel modules +obj-m += helloworld.o + +# Specify flags for the module compilation. +EXTRA_CFLAGS = -g -O0 + +DIR_TO_ROOT=../../../../.. +OUTPUT_DIR = $(DIR_TO_ROOT)/output/driver + +build: kernel_modules + +kernel_modules: + mkdir -p $(OUTPUT_DIR) + make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules + cp ./*.ko $(OUTPUT_DIR) + +clean: + rm -f $(OUTPUT_DIR)/helloworld.ko + make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean diff --git a/src/platform/simulator/drivers/helloworld/helloworld.c b/src/platform/simulator/drivers/helloworld/helloworld.c new file mode 100644 index 0000000..ec3b4d1 --- /dev/null +++ b/src/platform/simulator/drivers/helloworld/helloworld.c @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +MODULE_LICENSE("Dual BSD/GPL"); + +extern int fun(void); +static int hello_init(void) +{ + printk(KERN_INFO " Hello World enter\n"); + return 0; +} + +static void hello_exit(void) +{ + printk(KERN_INFO " Hello World exit\n"); +} + +module_init(hello_init); +module_exit(hello_exit); + +MODULE_AUTHOR("LuChongzhi"); +MODULE_DESCRIPTION("A simple Hello World Module"); +MODULE_ALIAS("a simplest module"); +MODULE_LICENSE("GPL"); diff --git a/src/platform/simulator/drivers/ion/Makefile b/src/platform/simulator/drivers/ion/Makefile new file mode 100644 index 0000000..7634a9e --- /dev/null +++ b/src/platform/simulator/drivers/ion/Makefile @@ -0,0 +1,32 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## + +KVERS = $(shell uname -r) +CURDIR = $(shell pwd) + +# Kernel modules +obj-m += ion_thead.o + +# Specify flags for the module compilation. +EXTRA_CFLAGS = -g -O1 +#EXTRA_CFLAGS += -O0 is not supported in ubuntu + +DIR_TO_ROOT=../../../../.. +OUTPUT_DIR =$(DIR_TO_ROOT)/output/driver + +build: kernel_modules + +kernel_modules: + @mkdir -p $(OUTPUT_DIR) + make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules + cp ./*.ko $(OUTPUT_DIR) + +clean: + rm -f $(OUTPUT_DIR)/ion_thead.ko + make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean diff --git a/src/platform/simulator/drivers/ion/ion.h b/src/platform/simulator/drivers/ion/ion.h new file mode 100644 index 0000000..c338032 --- /dev/null +++ b/src/platform/simulator/drivers/ion/ion.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * drivers/staging/android/uapi/ion.h + * + * Copyright (C) 2011 Google, Inc. + */ + +#ifndef _UAPI_LINUX_ION_H +#define _UAPI_LINUX_ION_H + +#include +#include + +/** + * enum ion_heap_types - list of all possible types of heaps + * @ION_HEAP_TYPE_SYSTEM: memory allocated via vmalloc + * @ION_HEAP_TYPE_SYSTEM_CONTIG: memory allocated via kmalloc + * @ION_HEAP_TYPE_CARVEOUT: memory allocated from a prereserved + * carveout heap, allocations are physically + * contiguous + * @ION_HEAP_TYPE_DMA: memory allocated via DMA API + * @ION_NUM_HEAPS: helper for iterating over heaps, a bit mask + * is used to identify the heaps, so only 32 + * total heap types are supported + */ +enum ion_heap_type { + ION_HEAP_TYPE_SYSTEM, + ION_HEAP_TYPE_SYSTEM_CONTIG, + ION_HEAP_TYPE_CARVEOUT, + ION_HEAP_TYPE_DMA, + ION_HEAP_TYPE_CUSTOM, /* + * must be last so device specific heaps always + * are at the end of this enum + */ +}; + +#define ION_NUM_HEAP_IDS (sizeof(unsigned int) * 8) + +/** + * allocation flags - the lower 16 bits are used by core ion, the upper 16 + * bits are reserved for use by the heaps themselves. + */ + +/* + * mappings of this buffer should be cached, ion will do cache maintenance + * when the buffer is mapped for dma + */ +#define ION_FLAG_CACHED 1 + +/** + * DOC: Ion Userspace API + * + * create a client by opening /dev/ion + * most operations handled via following ioctls + * + */ + +/** + * struct ion_allocation_data - metadata passed from userspace for allocations + * @len: size of the allocation + * @heap_id_mask: mask of heap ids to allocate from + * @flags: flags passed to heap + * @handle: pointer that will be populated with a cookie to use to + * refer to this allocation + * + * Provided by userspace as an argument to the ioctl + */ +struct ion_allocation_data { + __u64 len; + __u32 heap_id_mask; + __u32 flags; + __u32 fd; + __u32 unused; +}; + +#define MAX_HEAP_NAME 32 + +/** + * struct ion_heap_data - data about a heap + * @name - first 32 characters of the heap name + * @type - heap type + * @heap_id - heap id for the heap + */ +struct ion_heap_data { + char name[MAX_HEAP_NAME]; + __u32 type; + __u32 heap_id; + __u32 reserved0; + __u32 reserved1; + __u32 reserved2; +}; + +/** + * struct ion_heap_query - collection of data about all heaps + * @cnt - total number of heaps to be copied + * @heaps - buffer to copy heap data + */ +struct ion_heap_query { + __u32 cnt; /* Total number of heaps to be copied */ + __u32 reserved0; /* align to 64bits */ + __u64 heaps; /* buffer to be populated */ + __u32 reserved1; + __u32 reserved2; +}; + +#define ION_IOC_MAGIC 'I' + +/** + * DOC: ION_IOC_ALLOC - allocate memory + * + * Takes an ion_allocation_data struct and returns it with the handle field + * populated with the opaque handle for the allocation. + */ +#define ION_IOC_ALLOC _IOWR(ION_IOC_MAGIC, 0, \ + struct ion_allocation_data) + +/** + * DOC: ION_IOC_HEAP_QUERY - information about available heaps + * + * Takes an ion_heap_query structure and populates information about + * available Ion heaps. + */ +#define ION_IOC_HEAP_QUERY _IOWR(ION_IOC_MAGIC, 8, \ + struct ion_heap_query) + +#endif /* _UAPI_LINUX_ION_H */ diff --git a/src/platform/simulator/drivers/ion/ion_thead.c b/src/platform/simulator/drivers/ion/ion_thead.c new file mode 100644 index 0000000..015f8b5 --- /dev/null +++ b/src/platform/simulator/drivers/ion/ion_thead.c @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include "ion.h" +#define ION_DEBUG + +struct ion_data { + int npages; + struct page *pages[]; +}; + +#ifdef ION_DEBUG +static void dump_dma_buf(struct dma_buf *dmabuf) +{ + int i; + struct ion_data *data = dmabuf->priv; + + pr_info("dma_buf(0x%p) = {\n", dmabuf); + pr_info("\t size=%ld\n", dmabuf->size); + pr_info("\t priv(0x%p)={\n", data); + if (data != NULL) { + pr_info("\t\t data->npages=%d\n", data->npages); + for (i = 0; i < data->npages; i++) { + pr_info("\t\t pages[%d]=0x%p\n", i, data->pages[i]); + } + } + pr_info("}\n"); +} +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,4,0) +static int ion_attach(struct dma_buf *dmabuf, + struct dma_buf_attachment *attachment) +{ + pr_info("dmabuf attach device: %s\n", dev_name(attachment->dev)); + return 0; +} + +#else +static int ion_attach(struct dma_buf *dmabuf, + struct device *dev, + struct dma_buf_attachment *attachment) +{ + pr_info("dmabuf attach device:%s\n",dev_name(attachment->dev)); + return 0; +} + +#endif + +static void ion_detach(struct dma_buf *dmabuf, + struct dma_buf_attachment *attachment) +{ + pr_info("dmabuf detach device: %s\n", dev_name(attachment->dev)); +} + +static struct sg_table *ion_map_dma_buf(struct dma_buf_attachment *attachment, + enum dma_data_direction dir) +{ + struct ion_data *data = attachment->dmabuf->priv; + struct sg_table *table; + struct scatterlist *sg; + int i; + + table = kmalloc(sizeof(*table), GFP_KERNEL); + if (!table) + return ERR_PTR(-ENOMEM); + + if (sg_alloc_table(table, data->npages, GFP_KERNEL)) { + kfree(table); + return ERR_PTR(-ENOMEM); + } + + sg = table->sgl; + for (i = 0; i < data->npages; i++) { + sg_set_page(sg, data->pages[i], PAGE_SIZE, 0); + sg = sg_next(sg); + } + + if (!dma_map_sg(NULL, table->sgl, table->nents, dir)) { + sg_free_table(table); + kfree(table); + return ERR_PTR(-ENOMEM); + } + + return table; +} + +static void ion_unmap_dma_buf(struct dma_buf_attachment *attachment, + struct sg_table *table, + enum dma_data_direction dir) +{ + dma_unmap_sg(NULL, table->sgl, table->nents, dir); + sg_free_table(table); + kfree(table); +} + +static void ion_release(struct dma_buf *dma_buf) +{ + struct ion_data *data = dma_buf->priv; + int i; + + pr_info("dmabuf release\n"); + + for (i = 0; i < data->npages; i++) + put_page(data->pages[i]); + + kfree(data); +} + +static void *ion_vmap(struct dma_buf *dma_buf) +{ + struct ion_data *data = dma_buf->priv; + + return vm_map_ram(data->pages, data->npages, 0, PAGE_KERNEL); +} + +static void ion_vunmap(struct dma_buf *dma_buf, void *vaddr) +{ + struct ion_data *data = dma_buf->priv; + + vm_unmap_ram(vaddr, data->npages); +} + +static void *ion_kmap(struct dma_buf *dma_buf, unsigned long page_num) +{ + struct ion_data *data = dma_buf->priv; + + return kmap(data->pages[page_num]); +} + +static void ion_kunmap(struct dma_buf *dma_buf, unsigned long page_num, + void *addr) +{ + struct ion_data *data = dma_buf->priv; + + return kunmap(data->pages[page_num]); +} + +static int ion_mmap(struct dma_buf *dma_buf, struct vm_area_struct *vma) +{ + struct ion_data *data = dma_buf->priv; + unsigned long vm_start = vma->vm_start; + int i; + +#ifdef ION_DEBUG + pr_info("[%s:%d] dma_buf=%p, vma=%p\n", __func__, __LINE__, dma_buf, vma); + pr_info("[%s:%d] data=%p, vm_start=0x%lx\n", __func__, __LINE__, data, vm_start); + pr_info("[%s:%d] vma->vm_flags=0x%lx\n", __func__, __LINE__, vma->vm_flags); + dump_dma_buf(dma_buf); +#endif + vma->vm_flags |= VM_IO; + + for (i = 0; i < data->npages; i++) { + remap_pfn_range(vma, vm_start, page_to_pfn(data->pages[i]), + PAGE_SIZE, vma->vm_page_prot); + vm_start += PAGE_SIZE; + } + + pr_info("[%s:%d] vm_start=0x%lx, vm_end=0x%lx\n", __func__, __LINE__, + vma->vm_start, vma->vm_end); + + return 0; +} + +static int ion_begin_cpu_access(struct dma_buf *dmabuf, + enum dma_data_direction dir) +{ + struct dma_buf_attachment *attachment; + struct sg_table *table; + + if (list_empty(&dmabuf->attachments)) + return 0; + + attachment = list_first_entry(&dmabuf->attachments, struct dma_buf_attachment, + node); + table = attachment->priv; + dma_sync_sg_for_cpu(NULL, table->sgl, table->nents, dir); + + return 0; +} + +static int ion_end_cpu_access(struct dma_buf *dmabuf, + enum dma_data_direction dir) +{ + struct dma_buf_attachment *attachment; + struct sg_table *table; + + if (list_empty(&dmabuf->attachments)) + return 0; + + attachment = list_first_entry(&dmabuf->attachments, struct dma_buf_attachment, + node); + table = attachment->priv; + dma_sync_sg_for_device(NULL, table->sgl, table->nents, dir); + + return 0; +} + +static const struct dma_buf_ops exp_dmabuf_ops = { + .attach = ion_attach, + .detach = ion_detach, + .map_dma_buf = ion_map_dma_buf, + .unmap_dma_buf = ion_unmap_dma_buf, + .release = ion_release, + .map = ion_kmap, + .unmap = ion_kunmap, + .mmap = ion_mmap, + .vmap = ion_vmap, + .vunmap = ion_vunmap, + .begin_cpu_access = ion_begin_cpu_access, + .end_cpu_access = ion_end_cpu_access, +}; + +static struct dma_buf *ion_alloc(size_t size) +{ + DEFINE_DMA_BUF_EXPORT_INFO(exp_info); + struct dma_buf *dmabuf; + struct ion_data *data; + int i, npages; + unsigned long data_size; +#ifdef ION_DEBUG + char *kvaddr; +#endif + + npages = PAGE_ALIGN(size) / PAGE_SIZE; + if (!npages) + return ERR_PTR(-EINVAL); + + data_size = sizeof(*data) + npages * sizeof(struct page *); + data = kmalloc(data_size, GFP_KERNEL); + if (!data) { + pr_err("[%s:%d] kmalloc(%ld) failed\n", __func__, __LINE__, data_size); + return ERR_PTR(-ENOMEM); + } + + for (i = 0; i < npages; i++) { + data->pages[i] = alloc_page(GFP_KERNEL); + if (!data->pages[i]) + goto err; + } + data->npages = npages; + + exp_info.ops = &exp_dmabuf_ops; + exp_info.size = npages * PAGE_SIZE; + exp_info.flags = O_CLOEXEC | O_RDWR; + exp_info.priv = data; + + dmabuf = dma_buf_export(&exp_info); + if (IS_ERR(dmabuf)) { + pr_err("[%s:%d] dma_buf_export failed\n", __func__, __LINE__); + goto err; + } + +#ifdef ION_DEBUG + pr_info("[%s:%d] exp_info.size=%ld\n", __func__, __LINE__, exp_info.size); + kvaddr = ion_vmap(dmabuf); + pr_info("fill each page header with 'dma-buf page[000i]'\n"); + for (i = 0; i < npages; i++) { + memset(kvaddr + PAGE_SIZE *i, 0, 32); + sprintf(kvaddr + PAGE_SIZE *i, "dma-buf page[%04d]", i); + } + ion_vunmap(dmabuf, kvaddr); + dump_dma_buf(dmabuf); +#endif + + return dmabuf; + +err: + pr_err("[%s:%d] err\n", __func__, __LINE__); + while (i--) + put_page(data->pages[i]); + kfree(data); + return ERR_PTR(-ENOMEM); +} + +static long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct dma_buf *dmabuf; + struct ion_allocation_data alloc_data; + + if (copy_from_user(&alloc_data, (void __user *)arg, sizeof(alloc_data))) { + pr_err("ioctl copy_from_user failed!"); + return -EFAULT; + } + + if (cmd != ION_IOC_ALLOC) { + pr_err("ioctl %u is not supported!", cmd); + return -EINVAL; + } + + dmabuf = ion_alloc(alloc_data.len); + if (!dmabuf) { + pr_err("error: exporter alloc page failed\n"); + return -ENOMEM; + } + + alloc_data.fd = dma_buf_fd(dmabuf, O_CLOEXEC); + + if (copy_to_user((void __user *)arg, &alloc_data, sizeof(alloc_data))) + return -EFAULT; + + return 0; +} + +static struct file_operations ion_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = ion_ioctl, +}; + +static struct miscdevice mdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "ion", + .fops = &ion_fops, +}; + +static int __init ion_init(void) +{ + pr_info("ion_init()\n"); + return misc_register(&mdev); +} + +static void __exit ion_exit(void) +{ + pr_info("ion_exit()\n"); + misc_deregister(&mdev); +} + +module_init(ion_init); +module_exit(ion_exit); + +MODULE_AUTHOR("LuChongzhi "); +MODULE_DESCRIPTION("T-Head ion dma-buf exporter based on continous system heap"); +MODULE_LICENSE("GPL v2"); diff --git a/src/platform/simulator/drivers/tests/ion/.gitignore b/src/platform/simulator/drivers/tests/ion/.gitignore new file mode 100644 index 0000000..3b79e8c --- /dev/null +++ b/src/platform/simulator/drivers/tests/ion/.gitignore @@ -0,0 +1,6 @@ +ion_test1 +ion_test2 +ion_test3 +ion_test2_process_consumer +ion_test2_process_producer +ion_test_alloc diff --git a/src/platform/simulator/drivers/tests/ion/Makefile b/src/platform/simulator/drivers/tests/ion/Makefile new file mode 100644 index 0000000..8bd0f35 --- /dev/null +++ b/src/platform/simulator/drivers/tests/ion/Makefile @@ -0,0 +1,47 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../../../../../.. +include $(DIR_TO_ROOT)/build.param + +#CFLAGS += -DXX +OUTPUT_DIR := $(DIR_TO_ROOT)/output/driver +LIBS += -lhal_common -lhal_platform +INCLUDE += -I../../ion + +TARGET_1 = ion_test1 +SRCS_1 = ion_test1.c + +TARGET_2 = ion_test2 +SRCS_2 = ion_test2.c + +TARGET_3 = ion_test2_process_producer +SRCS_3 = ion_test2_process_producer.c $(DIR_TO_ROOT)/examples/frame/frame_demo_common.c + +TARGET_4 = ion_test2_process_consumer +SRCS_4 = ion_test2_process_consumer.c $(DIR_TO_ROOT)/examples/frame/frame_demo_common.c + +TARGET_5 = ion_test3 +SRCS_5 = ion_test3.c ion_test3_process_producer.c ion_test3_process_consumer.c +SRCS_5 += $(DIR_TO_ROOT)/examples/frame/frame_demo_common.c + +all: $(TARGET_1) $(TARGET_2) $(TARGET_3) $(TARGET_4) $(TARGET_5) + cp -f ion_test.txt $(OUTPUT_DIR) + +clean: + rm -rf .obj + rm -f $(TARGET_1) $(OUTPUT_DIR)/$(TARGET_1) + rm -f $(TARGET_2) $(OUTPUT_DIR)/$(TARGET_2) + rm -f $(TARGET_3) $(OUTPUT_DIR)/$(TARGET_3) + rm -f $(TARGET_4) $(OUTPUT_DIR)/$(TARGET_4) + rm -f $(TARGET_5) $(OUTPUT_DIR)/$(TARGET_5) + rm -f $(OUTPUT_DIR)/ion_test.txt + +include $(DIR_TO_ROOT)/common_target.mk + +.PHONY: clean all diff --git a/src/platform/simulator/drivers/tests/ion/ion_test.txt b/src/platform/simulator/drivers/tests/ion/ion_test.txt new file mode 100644 index 0000000..55e1323 --- /dev/null +++ b/src/platform/simulator/drivers/tests/ion/ion_test.txt @@ -0,0 +1,16 @@ +This is ion_test.txt line01 +This is ion_test.txt line02 +This is ion_test.txt line03 +This is ion_test.txt line04 +This is ion_test.txt line05 +This is ion_test.txt line06 +This is ion_test.txt line07 +This is ion_test.txt line08 +This is ion_test.txt line09 +This is ion_test.txt line10 +This is ion_test.txt line11 +This is ion_test.txt line12 +This is ion_test.txt line13 +This is ion_test.txt line14 +This is ion_test.txt line15 +This is ion_test.txt line16 diff --git a/src/platform/simulator/drivers/tests/ion/ion_test1.c b/src/platform/simulator/drivers/tests/ion/ion_test1.c new file mode 100644 index 0000000..a3be96c --- /dev/null +++ b/src/platform/simulator/drivers/tests/ion/ion_test1.c @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ion.h" + +#define LOG_LEVEL 3 +#define LOG_PREFIX "ion_test1" +#include + +#define PAGE_SIZE 4096 +#define TEST_PAGE_COUNT (16*1024/PAGE_SIZE) /* To make shoure PAGE_SIZE aligned */ + +int main(int argc, char *argv[]) +{ + int fd; + int i,j; + struct ion_allocation_data alloc_data; + LOG_I("Starting\n"); + + fd = open("/dev/ion", O_RDWR); + if (fd < 0) { + LOG_E("open /dev/ion failed, %s\n", strerror(errno)); + return -1; + } + + alloc_data.len = TEST_PAGE_COUNT * PAGE_SIZE; + if (ioctl(fd, ION_IOC_ALLOC, &alloc_data)) { + LOG_E("ion ioctl failed, %s\n", strerror(errno)); + close(fd); + return -1; + } + + LOG_F("ion alloc success: size = 0x%llx, dmabuf_fd = %u\n", + alloc_data.len, alloc_data.fd); + + void *buf = mmap(NULL, alloc_data.len, PROT_READ | PROT_WRITE, + MAP_SHARED, alloc_data.fd, 0); + LOG_D("buf=%p\n", buf); + if (buf != NULL && buf != MAP_FAILED) { + for (i = 0; i < TEST_PAGE_COUNT; i++) { + char *str_page = (char*)buf + PAGE_SIZE * i; + str_page[0] = 'D'; // Try to re-write + printf("Page header string: '%s'\n", str_page); + } + munmap(buf, alloc_data.len); + } else { + LOG_E("mmap failed, buf=%p, %s\n", buf, strerror(errno)); + } + + LOG_F("Close dma-buf fd\n"); + close(alloc_data.fd); + + close(fd); + LOG_I("Exiting\n"); + return 0; +} + diff --git a/src/platform/simulator/drivers/tests/ion/ion_test2.c b/src/platform/simulator/drivers/tests/ion/ion_test2.c new file mode 100644 index 0000000..b51e506 --- /dev/null +++ b/src/platform/simulator/drivers/tests/ion/ion_test2.c @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "master" +#include + +static const char* PRODUCER_APP_NAME = "ion_test2_process_producer"; +static const char* CONSUMER_APP_NAME = "ion_test2_process_consumer"; + + +int main(int argc, char *argv[]) +{ + int ret; + int socket_pair[2]; /* sock pair() */ + char str_sockfd[10]; /* store the string format */ + pid_t producer_pid; + pid_t consumer_pid; + int process_status; /* the status of child process */ + + /* Create socket pare, must be AF_LOCAL or AF_UNIX */ + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, socket_pair) == -1) { + LOG_E("create socketpair error(%d), %s\n", errno, strerror(errno)); + exit(1); + } + LOG_I("Create socketpair OK:{%d, %d}\n", socket_pair[0], socket_pair[1]); + + /* run producer process, using socket_pair[0] */ + if ((producer_pid = fork()) == 0) { + + LOG_D("launch producer process\n"); + close(socket_pair[1]); /* producer only use socket_pair[0] */ + snprintf(str_sockfd, sizeof(str_sockfd), "%d", socket_pair[0]); + + ret = execl(PRODUCER_APP_NAME, PRODUCER_APP_NAME, str_sockfd, (char *)NULL); + if (ret != 0) + LOG_E("execl('%s') error(%d), %s\n", + PRODUCER_APP_NAME, errno, strerror(errno)); + exit(0); + } + + /* run consumer process, using socket_pair[1] */ + if ((consumer_pid = fork()) == 0) { + + LOG_D("launch consumer process\n"); + close(socket_pair[0]); /* consumer only use socket_pair[1] */ + snprintf(str_sockfd, sizeof(str_sockfd), "%d", socket_pair[1]); + + ret = execl(CONSUMER_APP_NAME, CONSUMER_APP_NAME, str_sockfd, (char *)NULL); + if (ret != 0) + LOG_E("execl('%s') error(%d), %s\n", + PRODUCER_APP_NAME, errno, strerror(errno)); + exit(0); + } + + /* wait the children process end */ + waitpid(producer_pid, &process_status, 0); + if (WIFEXITED(process_status) == 0) { + LOG_E("producer exit abnormal\n"); + } + + waitpid(consumer_pid, &process_status, 0); + if (WIFEXITED(process_status) == 0) { + LOG_E("consumer eixt abnormal\n"); + } + + /* close the socket_pair */ + ret = close(socket_pair[0]); + LOG_D("close(socket_pair[0]) ret=%d, %s\n", errno, strerror(errno)); + + ret = close(socket_pair[1]); + LOG_D("close(socket_pair[1]) ret=%d, %s\n", errno, strerror(errno)); + + LOG_D("Exit master\n"); + return 0; +} + + diff --git a/src/platform/simulator/drivers/tests/ion/ion_test2_process_consumer.c b/src/platform/simulator/drivers/tests/ion/ion_test2_process_consumer.c new file mode 100644 index 0000000..e088519 --- /dev/null +++ b/src/platform/simulator/drivers/tests/ion/ion_test2_process_consumer.c @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "ion.h" + +#define LOG_LEVEL 3 +#define LOG_PREFIX "consumer" +#include + +#if 0 + Even there are multiple fd sent by sendmsg, only one cmsghdr will be received + + // https://datatracker.ietf.org/doc/html/draft-stevens-advanced-api-02 + |<--------------------------- msg_controllen -------------------------->| + | | + |<----- ancillary data object ----->|<----- ancillary data object ----->| + |<---------- CMSG_SPACE() --------->|<---------- CMSG_SPACE() --------->| + | | | + |<---------- cmsg_len ---------->| |<--------- cmsg_len ----------->| | + |<--------- CMSG_LEN() --------->| |<-------- CMSG_LEN() ---------->| | + | | | | | + +-----+-----+-----+--+-----------+--+-----+-----+-----+--+-----------+--+ + |cmsg_|cmsg_|cmsg_|XX| |XX| + |len |level|type |XX|cmsg_data[]|XX| + +-----+-----+-----+--+-----------+--+ + ^ There are multi-fd in same cmsg_data[] +#endif + +int main(int argc, char **argv) +{ + int ret; + int socket_fd = -1; + int dmabuf_fd = -1; + int file_fd = -1; + + LOG_D("Starting consumer\n"); + sleep(1); // waiting for producer exit to watch log easily + + /* Check arg count */ + if (argc != 2) { + LOG_E("useg:\n"); + goto LAB_ERROR; + } + + /* Check socket_fd */ + socket_fd = atoi(argv[1]); + if (socket_fd <= 0) { + LOG_E("socket_fd(%d) is invalid\n", socket_fd); + goto LAB_ERROR; + } + LOG_D("The input socket_fd=%d\n", socket_fd); + + struct msghdr msghdr_recv; // the info struct + struct iovec iov[1]; // io vector + + struct cmsghdr *pCmsghdr = NULL; // the pointer of control + + char ctls[CMSG_SPACE(sizeof(int))][2] = {}; + msghdr_recv.msg_control = ctls; + msghdr_recv.msg_controllen = sizeof(ctls); + + msghdr_recv.msg_name = NULL; // the name + msghdr_recv.msg_namelen = 0; // len of name + + /* transfer dmabuf_len in iov */ + long dmabuf_len; + iov[0].iov_base = &dmabuf_len; + iov[0].iov_len = sizeof(dmabuf_len); + + msghdr_recv.msg_iov = iov; // the io/vector info + msghdr_recv.msg_iovlen = 1; // the num of iov + + ssize_t recv_len; + if ((recv_len = recvmsg(socket_fd, &msghdr_recv, 0)) < 0) { // recv msg + // the msg is recv by msghdr_recv + LOG_E("recv error:%d, %s\n", errno, strerror(errno)); + goto LAB_ERROR; + } + LOG_D("recvmsg() length=%zd\n", recv_len); + + LOG_F("ctrls in hex mode:\n"); + LOG_F(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n"); + LOG_F(" -----------------------------------------------------\n"); + char *pctrl = &ctls[0][0]; + for (int i = 0; i < sizeof(ctls); i++) { + if (i % 16 == 0) + LOG_F(" %04x: ", i); + LOG_F("%02x ", pctrl[i]); + if (i % 16== 15) + printf("\n"); + } + LOG_F("\n"); + + int msghdr_count = 0; + for (pCmsghdr = CMSG_FIRSTHDR(&msghdr_recv); pCmsghdr != NULL; + pCmsghdr = CMSG_NXTHDR(&msghdr_recv,pCmsghdr)) { + msghdr_count++; + } + LOG_D("msghdr_count=%d\n", msghdr_count); + + if ((pCmsghdr = CMSG_FIRSTHDR(&msghdr_recv)) != NULL) { + if (pCmsghdr->cmsg_level != SOL_SOCKET) { + LOG_E("Ctl level should be SOL_SOCKET\n"); + goto LAB_ERROR; + } + + if (pCmsghdr->cmsg_type != SCM_RIGHTS) { + LOG_E("Ctl type should be SCM_RIGHTS\n"); + goto LAB_ERROR; + } + + LOG_D("pCmsghdr->cmsg_len=%zd\n", pCmsghdr->cmsg_len); + int *fdptr = (int *) CMSG_DATA(pCmsghdr); + dmabuf_fd = fdptr[0]; + file_fd = fdptr[1]; + } else { + if (pCmsghdr == NULL) + LOG_E("pCmsghdr=%p\n", pCmsghdr); + else + LOG_E("pCmsghdr->cmsg_len=%zu, should=%zu\n", + pCmsghdr->cmsg_len, CMSG_LEN(sizeof(int))); + + goto LAB_ERROR; + } + LOG_I("recvmsg() with dmabuf_fd=%d, file_fd=%d\n", dmabuf_fd, file_fd); + + LOG_D("recvmsg() dmabuf_len=%ld\n", dmabuf_len); + void *buf = mmap(NULL, dmabuf_len, PROT_READ | PROT_WRITE, + MAP_SHARED, dmabuf_fd, 0); + if (buf != NULL && buf != MAP_FAILED) { + char str_out[448+1]; + snprintf(str_out, sizeof(str_out), "%s", (char*)buf); + LOG_I("Dump dma-buf content in consumer begin vvvv\n"); + LOG_F("%s", str_out); + LOG_I("Dump dma-buf content in consumer end ^^^^\n"); + munmap(buf, dmabuf_len); + buf = NULL; + } else { + LOG_E("mmap failed, buf=%p, %s\n", buf, strerror(errno)); + } + + LOG_I("Please watching dmesg, 'dmabuf release' will appear in 3 seconds\n"); + sleep(3); + + ret = close(dmabuf_fd); + LOG_D("close(dmabuf_fd) ret=%d, %s\n", errno, strerror(errno)); + + int n_read; + char buf_file[1024]; + LOG_I("Dump test file content in consumer begin vvvv\n"); + lseek(file_fd, 0, SEEK_SET); + + while ((n_read = read(file_fd, buf_file, 1024)) > 0) { // read from fd + write(1, buf_file, n_read); // write to std_out + } + LOG_I("Dump test file content in consumer end ^^^^\n"); + + ret = close(file_fd); + LOG_D("close(file_fd) ret=%d, %s\n", errno, strerror(errno)); + + ret = close(socket_fd); + LOG_D("close(socket_fd) ret=%d, %s\n", errno, strerror(errno)); + + LOG_D("Exiting consumer\n"); + return 0; + +LAB_ERROR: + if (buf != NULL) { + ret = munmap(buf, dmabuf_len); + LOG_D("munmap() ret=%d, %s\n", ret, strerror(errno)); + } + + LOG_E("Exiting consumer with error\n"); + exit(EXIT_FAILURE); +} + diff --git a/src/platform/simulator/drivers/tests/ion/ion_test2_process_producer.c b/src/platform/simulator/drivers/tests/ion/ion_test2_process_producer.c new file mode 100644 index 0000000..6f76c7c --- /dev/null +++ b/src/platform/simulator/drivers/tests/ion/ion_test2_process_producer.c @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "producer" +#include + +#include +#include "ion.h" +/* use fdc (frame demo common) functions */ +#include "../../../../../examples/frame/frame_demo_common.h" + +#if 0 + When sendmsg with multiple fd, should store in dependent cmsghdr + + // https://datatracker.ietf.org/doc/html/draft-stevens-advanced-api-02 + |<--------------------------- msg_controllen -------------------------->| + | | + |<----- ancillary data object ----->|<----- ancillary data object ----->| + |<---------- CMSG_SPACE() --------->|<---------- CMSG_SPACE() --------->| + | | | + |<---------- cmsg_len ---------->| |<--------- cmsg_len ----------->| | + |<--------- CMSG_LEN() --------->| |<-------- CMSG_LEN() ---------->| | + | | | | | + +-----+-----+-----+--+-----------+--+-----+-----+-----+--+-----------+--+ + |cmsg_|cmsg_|cmsg_|XX| |XX|cmsg_|cmsg_|cmsg_|XX| |XX| + |len |level|type |XX|cmsg_data[]|XX|len |level|type |XX|cmsg_data[]|XX| + +-----+-----+-----+--+-----------+--+-----+-----+-----+--+-----------+--+ +#endif + +static const char *TEST_FILENAME = "ion_test.txt"; + +int main(int argc, char **argv) +{ + int ret = -1; + int socket_fd; + + LOG_D("Starting producer\n"); + + /* Check arg count */ + if (argc != 2) { + LOG_E("useg:\n"); + goto LAB_EXIT; + } + + /* Check socket_fd */ + socket_fd = atoi(argv[1]); + if (socket_fd <= 0) { + LOG_E("socket_fd(%d) is invalid\n", socket_fd); + goto LAB_EXIT; + } + LOG_D("The input socket_fd=%d\n", socket_fd); + + long file_size; /* Test file size */ + int file_fd; /* Test file file descriptor */ + int ion_fd; /* fd of /dev/ion */ + int dmabuf_fd; /* dma-buf fd to transfer to consumer */ + void *dmabuf_addr; /* dma-buf mmap to user addr */ + + /* alloc ion dma-buf */ + if (fdc_get_img_file_size(&file_size, TEST_FILENAME) != 0) + goto LAB_ERR_SOCKET_FD_OPENED; + + // open ion device + if (fdc_get_ion_device_fd(&ion_fd) != 0) + goto LAB_ERR_SOCKET_FD_OPENED; + + // alloc dma-buf from ion device + struct ion_allocation_data alloc_data; + if (fdc_alloc_ion_buf(&alloc_data, ion_fd, file_size) != 0) + goto LAB_ERR_ION_OPENED; + dmabuf_fd = alloc_data.fd; + + // map dma-buf to user addr + if (fdc_mmap_dmabuf_addr(&dmabuf_addr, file_size, dmabuf_fd) != 0) + goto LAB_ERR_BUF_ALLOCED; + + // open file + file_fd = open(TEST_FILENAME, O_RDONLY); + if (file_fd < 0) { + LOG_E("Open test file '%s' failed, %s\n", + TEST_FILENAME, strerror(errno)); + goto LAB_ERR_MMAPPED; + } + + // store test file content into dma-buf + if (fdc_store_img_to_dmabuf(dmabuf_addr, file_fd, file_size) != 0) + goto LAB_ERR_TEST_FILE_OPENED; + + struct msghdr msghdr_send; // the info struct + struct cmsghdr *pCmsghdr = NULL; // the pointer of control + + /* create 2 cmsg space, must add "= {}" */ + char ctrls[CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(int))] = {}; + msghdr_send.msg_control = ctrls; + msghdr_send.msg_controllen = sizeof(ctrls); + LOG_D("msg_controllen = %zd\n", msghdr_send.msg_controllen); + + // the first info stores dma-buf fd + pCmsghdr = CMSG_FIRSTHDR(&msghdr_send); // the info of head + pCmsghdr->cmsg_len = CMSG_LEN(sizeof(int)); // the msg len + pCmsghdr->cmsg_level = SOL_SOCKET; // stream mode + pCmsghdr->cmsg_type = SCM_RIGHTS; // file descriptor + + int *fdptr = (int *)CMSG_DATA(pCmsghdr); + *fdptr = dmabuf_fd; // data: dma-buf fd !!!!! + LOG_I("sendmsg() with dmabuf_fd=%d\n", dmabuf_fd); + + // the second info stores test file fd + pCmsghdr = CMSG_NXTHDR(&msghdr_send, pCmsghdr); + pCmsghdr->cmsg_len = CMSG_LEN(sizeof(int)); // the msg len + pCmsghdr->cmsg_level = SOL_SOCKET; // stream mode + pCmsghdr->cmsg_type = SCM_RIGHTS; // file descriptor + lseek(file_fd, 0, SEEK_SET); // seek to file head + *((int *)CMSG_DATA(pCmsghdr)) = file_fd; // data: test file fd !!!!! + LOG_I("sendmsg() with file_fd=%d\n", file_fd); + + LOG_F("ctrls in hex mode:\n"); + LOG_F(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n"); + LOG_F(" -----------------------------------------------------\n"); + for (int i = 0; i < sizeof(ctrls); i++) { + if (i % 16 == 0) + LOG_F(" %04x: ", i); + LOG_F("%02x ", ctrls[i]); + if (i % 16== 15) + printf("\n"); + } + LOG_F("\n"); + + // these infos are nosignification + msghdr_send.msg_name = NULL; // the name + msghdr_send.msg_namelen = 0; // len of name + + struct iovec iov[1]; // io vector + long dmabuf_len = file_size; + iov[0].iov_base = &dmabuf_len; // no data here + iov[0].iov_len = sizeof(dmabuf_len); // the len of data + + msghdr_send.msg_iov = iov; // the io/vector info + msghdr_send.msg_iovlen = 1; // the num of iov + + ssize_t size = sendmsg(socket_fd, &msghdr_send, 0); // send msg now + if (size < 0) { + LOG_E("sendmsg() failed, errno=%d, %s\n", errno, strerror(errno)); + } else if (size == 0) { + LOG_W("sendmsg() %zd bytes\n", size); + } else { + LOG_D("sendmsg() %zd bytes\n", size); + } + + ret = 0; + +LAB_ERR_TEST_FILE_OPENED: + ret = close(file_fd); + LOG_D("close(file_fd) ret=%d, %s\n", ret, strerror(errno)); + +LAB_ERR_MMAPPED: + ret = munmap(dmabuf_addr, file_size); + LOG_D("munmap() ret=%d, %s\n", ret, strerror(errno)); + +LAB_ERR_BUF_ALLOCED: + ret = close(dmabuf_fd); + LOG_D("close(dmabuf_fd) ret=%d, %s\n", ret, strerror(errno)); + +LAB_ERR_ION_OPENED: + close(ion_fd); + LOG_D("close(ion_fd) ret=%d, %s\n", ret, strerror(errno)); + +LAB_ERR_SOCKET_FD_OPENED: + ret = close(socket_fd); + LOG_D("close(socket_fd) ret=%d, %s\n", ret, strerror(errno)); + +LAB_EXIT: + if (ret) + LOG_E("Exiting producer\n"); + else + LOG_D("Exiting producer\n"); + exit(ret); +} + diff --git a/src/platform/simulator/drivers/tests/ion/ion_test3.c b/src/platform/simulator/drivers/tests/ion/ion_test3.c new file mode 100644 index 0000000..b1861aa --- /dev/null +++ b/src/platform/simulator/drivers/tests/ion/ion_test3.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "master" +#include + +extern int producer_process(int socket_fd); +extern int consumer_process(int socket_fd); + +int main(int argc, char *argv[]) +{ + int ret; + int socket_pair[2]; /* sock pair() */ + pid_t producer_pid; + pid_t consumer_pid; + int process_status; /* the status of child process */ + + /* Create socket pare, must be AF_LOCAL or AF_UNIX */ + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, socket_pair) == -1) { + LOG_E("create socketpair error(%d), %s\n", errno, strerror(errno)); + exit(1); + } + LOG_I("Create socketpair OK:{%d, %d}\n", socket_pair[0], socket_pair[1]); + + /* run producer process, using socket_pair[0] */ + if ((producer_pid = fork()) == 0) { + + LOG_D("launch producer process\n"); + close(socket_pair[1]); /* producer only use socket_pair[0] */ + + producer_process(socket_pair[0]); + LOG_D("producer process exiting ...\n"); + exit(0); + exit(0); + } + + /* run consumer process, using socket_pair[1] */ + if ((consumer_pid = fork()) == 0) { + + LOG_D("launch consumer process\n"); + close(socket_pair[0]); /* consumer only use socket_pair[1] */ + consumer_process(socket_pair[1]); + LOG_D("consumer process exiting...\n"); + exit(0); + } + + /* wait the children process end */ + waitpid(producer_pid, &process_status, 0); + if (WIFEXITED(process_status) == 0) { + LOG_E("producer exit abnormal\n"); + } + + waitpid(consumer_pid, &process_status, 0); + if (WIFEXITED(process_status) == 0) { + LOG_E("consumer eixt abnormal\n"); + } + + /* close the socket_pair */ + ret = close(socket_pair[0]); + LOG_D("close(socket_pair[0]) ret=%d, %s\n", errno, strerror(errno)); + + ret = close(socket_pair[1]); + LOG_D("close(socket_pair[1]) ret=%d, %s\n", errno, strerror(errno)); + + LOG_D("Exit master\n"); + return 0; +} + + diff --git a/src/platform/simulator/drivers/tests/ion/ion_test3_process_consumer.c b/src/platform/simulator/drivers/tests/ion/ion_test3_process_consumer.c new file mode 100644 index 0000000..65c1069 --- /dev/null +++ b/src/platform/simulator/drivers/tests/ion/ion_test3_process_consumer.c @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "ion.h" + +#define LOG_LEVEL 3 +#define LOG_PREFIX "consumer" +#include + +#if 0 + Even there are multiple fd sent by sendmsg, only one cmsghdr will be received + + // https://datatracker.ietf.org/doc/html/draft-stevens-advanced-api-02 + |<--------------------------- msg_controllen -------------------------->| + | | + |<----- ancillary data object ----->|<----- ancillary data object ----->| + |<---------- CMSG_SPACE() --------->|<---------- CMSG_SPACE() --------->| + | | | + |<---------- cmsg_len ---------->| |<--------- cmsg_len ----------->| | + |<--------- CMSG_LEN() --------->| |<-------- CMSG_LEN() ---------->| | + | | | | | + +-----+-----+-----+--+-----------+--+-----+-----+-----+--+-----------+--+ + |cmsg_|cmsg_|cmsg_|XX| |XX| + |len |level|type |XX|cmsg_data[]|XX| + +-----+-----+-----+--+-----------+--+ + ^ There are multi-fd in same cmsg_data[] +#endif + +int consumer_process(int socket_fd) +{ + int ret; + int dmabuf_fd = -1; + int file_fd = -1; + + LOG_D("Starting consumer\n"); + sleep(1); // waiting for producer exit to watch log easily + LOG_D("The input socket_fd=%d\n", socket_fd); + + struct msghdr msghdr_recv; // the info struct + struct iovec iov[1]; // io vector + + struct cmsghdr *pCmsghdr = NULL; // the pointer of control + + char ctls[CMSG_SPACE(sizeof(int))][2] = {}; + msghdr_recv.msg_control = ctls; + msghdr_recv.msg_controllen = sizeof(ctls); + + msghdr_recv.msg_name = NULL; // the name + msghdr_recv.msg_namelen = 0; // len of name + + /* transfer dmabuf_len in iov */ + long dmabuf_len; + iov[0].iov_base = &dmabuf_len; + iov[0].iov_len = sizeof(dmabuf_len); + + msghdr_recv.msg_iov = iov; // the io/vector info + msghdr_recv.msg_iovlen = 1; // the num of iov + + ssize_t recv_len; + if ((recv_len = recvmsg(socket_fd, &msghdr_recv, 0)) < 0) { // recv msg + // the msg is recv by msghdr_recv + LOG_E("recv error:%d, %s\n", errno, strerror(errno)); + goto LAB_ERROR; + } + LOG_D("recvmsg() length=%zd\n", recv_len); + + LOG_F("ctrls in hex mode:\n"); + LOG_F(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n"); + LOG_F(" -----------------------------------------------------\n"); + char *pctrl = &ctls[0][0]; + for (int i = 0; i < sizeof(ctls); i++) { + if (i % 16 == 0) + LOG_F(" %04x: ", i); + LOG_F("%02x ", pctrl[i]); + if (i % 16== 15) + printf("\n"); + } + LOG_F("\n"); + + int msghdr_count = 0; + for (pCmsghdr = CMSG_FIRSTHDR(&msghdr_recv); pCmsghdr != NULL; + pCmsghdr = CMSG_NXTHDR(&msghdr_recv,pCmsghdr)) { + msghdr_count++; + } + LOG_D("msghdr_count=%d\n", msghdr_count); + + if ((pCmsghdr = CMSG_FIRSTHDR(&msghdr_recv)) != NULL) { + if (pCmsghdr->cmsg_level != SOL_SOCKET) { + LOG_E("Ctl level should be SOL_SOCKET\n"); + goto LAB_ERROR; + } + + if (pCmsghdr->cmsg_type != SCM_RIGHTS) { + LOG_E("Ctl type should be SCM_RIGHTS\n"); + goto LAB_ERROR; + } + + LOG_D("pCmsghdr->cmsg_len=%zd\n", pCmsghdr->cmsg_len); + int *fdptr = (int *) CMSG_DATA(pCmsghdr); + dmabuf_fd = fdptr[0]; + file_fd = fdptr[1]; + } else { + if (pCmsghdr == NULL) + LOG_E("pCmsghdr=%p\n", pCmsghdr); + else + LOG_E("pCmsghdr->cmsg_len=%zu, should=%zu\n", + pCmsghdr->cmsg_len, CMSG_LEN(sizeof(int))); + + goto LAB_ERROR; + } + LOG_I("recvmsg() with dmabuf_fd=%d, file_fd=%d\n", dmabuf_fd, file_fd); + + LOG_D("recvmsg() dmabuf_len=%ld\n", dmabuf_len); + void *buf = mmap(NULL, dmabuf_len, PROT_READ | PROT_WRITE, + MAP_SHARED, dmabuf_fd, 0); + if (buf != NULL && buf != MAP_FAILED) { + char str_out[448+1]; + snprintf(str_out, sizeof(str_out), "%s", (char*)buf); + LOG_I("Dump dma-buf content in consumer begin vvvv\n"); + LOG_F("%s", str_out); + LOG_I("Dump dma-buf content in consumer end ^^^^\n"); + munmap(buf, dmabuf_len); + buf = NULL; + } else { + LOG_E("mmap failed, buf=%p, %s\n", buf, strerror(errno)); + } + + LOG_I("Please watching dmesg, 'dmabuf release' will appear in 3 seconds\n"); + sleep(3); + + ret = close(dmabuf_fd); + LOG_D("close(dmabuf_fd) ret=%d, %s\n", errno, strerror(errno)); + + + //// file_fd + int n_read; + char buf_file[1024]; + LOG_I("Dump test file content in consumer begin vvvv\n"); + lseek(file_fd, 0, SEEK_SET); + +#if 0 + while ((n_read = read(file_fd, buf_file, 1024)) > 0) { // read from fd + write(1, buf_file, n_read); // write to std_out + } + LOG_I("Dump test file content in consumer end ^^^^\n"); +#endif + ret = close(file_fd); + LOG_D("close(file_fd) ret=%d, %s\n", errno, strerror(errno)); + /////////// + + ret = close(socket_fd); + LOG_D("close(socket_fd) ret=%d, %s\n", errno, strerror(errno)); + + LOG_D("Exiting consumer\n"); + return 0; + +LAB_ERROR: + if (buf != NULL) { + ret = munmap(buf, dmabuf_len); + LOG_D("munmap() ret=%d, %s\n", ret, strerror(errno)); + } + + LOG_E("Exiting consumer with error\n"); + exit(EXIT_FAILURE); +} + diff --git a/src/platform/simulator/drivers/tests/ion/ion_test3_process_producer.c b/src/platform/simulator/drivers/tests/ion/ion_test3_process_producer.c new file mode 100644 index 0000000..4d7d37b --- /dev/null +++ b/src/platform/simulator/drivers/tests/ion/ion_test3_process_producer.c @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "producer" +#include + +#include +#include "ion.h" +/* use fdc (frame demo common) functions */ +#include "../../../../../examples/frame/frame_demo_common.h" + +#if 0 + When sendmsg with multiple fd, should store in dependent cmsghdr + + // https://datatracker.ietf.org/doc/html/draft-stevens-advanced-api-02 + |<--------------------------- msg_controllen -------------------------->| + | | + |<----- ancillary data object ----->|<----- ancillary data object ----->| + |<---------- CMSG_SPACE() --------->|<---------- CMSG_SPACE() --------->| + | | | + |<---------- cmsg_len ---------->| |<--------- cmsg_len ----------->| | + |<--------- CMSG_LEN() --------->| |<-------- CMSG_LEN() ---------->| | + | | | | | + +-----+-----+-----+--+-----------+--+-----+-----+-----+--+-----------+--+ + |cmsg_|cmsg_|cmsg_|XX| |XX|cmsg_|cmsg_|cmsg_|XX| |XX| + |len |level|type |XX|cmsg_data[]|XX|len |level|type |XX|cmsg_data[]|XX| + +-----+-----+-----+--+-----------+--+-----+-----+-----+--+-----------+--+ +#endif + +static const char *TEST_FILENAME = "yuv420_1280x720_csky2016.yuv"; + +int producer_process(int socket_fd) +{ + int ret = -1; + + LOG_D("Starting producer\n"); + LOG_D("The input socket_fd=%d\n", socket_fd); + + long file_size; /* Test file size */ + int file_fd; /* Test file file descriptor */ + int ion_fd; /* fd of /dev/ion */ + int dmabuf_fd; /* dma-buf fd to transfer to consumer */ + void *dmabuf_addr; /* dma-buf mmap to user addr */ + + /* alloc ion dma-buf */ + if (fdc_get_img_file_size(&file_size, TEST_FILENAME) != 0) + goto LAB_ERR_SOCKET_FD_OPENED; + + // open ion device + if (fdc_get_ion_device_fd(&ion_fd) != 0) + goto LAB_ERR_SOCKET_FD_OPENED; + + // alloc dma-buf from ion device + struct ion_allocation_data alloc_data; + if (fdc_alloc_ion_buf(&alloc_data, ion_fd, file_size) != 0) + goto LAB_ERR_ION_OPENED; + dmabuf_fd = alloc_data.fd; + + // map dma-buf to user addr + if (fdc_mmap_dmabuf_addr(&dmabuf_addr, file_size, dmabuf_fd) != 0) + goto LAB_ERR_BUF_ALLOCED; + + // open file + file_fd = open(TEST_FILENAME, O_RDONLY); + if (file_fd < 0) { + LOG_E("Open test file '%s' failed, %s\n", + TEST_FILENAME, strerror(errno)); + goto LAB_ERR_MMAPPED; + } + + // store test file content into dma-buf + if (fdc_store_img_to_dmabuf(dmabuf_addr, file_fd, file_size) != 0) + goto LAB_ERR_TEST_FILE_OPENED; + + struct msghdr msghdr_send; // the info struct + struct cmsghdr *pCmsghdr = NULL; // the pointer of control + + /* create 2 cmsg space, must add "= {}" */ + char ctrls[CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(int))] = {}; + msghdr_send.msg_control = ctrls; + msghdr_send.msg_controllen = sizeof(ctrls); + LOG_D("msg_controllen = %zd\n", msghdr_send.msg_controllen); + + // the first info stores dma-buf fd + pCmsghdr = CMSG_FIRSTHDR(&msghdr_send); // the info of head + pCmsghdr->cmsg_len = CMSG_LEN(sizeof(int)); // the msg len + pCmsghdr->cmsg_level = SOL_SOCKET; // stream mode + pCmsghdr->cmsg_type = SCM_RIGHTS; // file descriptor + + int *fdptr = (int *)CMSG_DATA(pCmsghdr); + *fdptr = dmabuf_fd; // data: dma-buf fd !!!!! + LOG_I("sendmsg() with dmabuf_fd=%d\n", dmabuf_fd); + + // the second info stores test file fd + pCmsghdr = CMSG_NXTHDR(&msghdr_send, pCmsghdr); + pCmsghdr->cmsg_len = CMSG_LEN(sizeof(int)); // the msg len + pCmsghdr->cmsg_level = SOL_SOCKET; // stream mode + pCmsghdr->cmsg_type = SCM_RIGHTS; // file descriptor + lseek(file_fd, 0, SEEK_SET); // seek to file head + *((int *)CMSG_DATA(pCmsghdr)) = file_fd; // data: test file fd !!!!! + LOG_I("sendmsg() with file_fd=%d\n", file_fd); + + LOG_F("ctrls in hex mode:\n"); + LOG_F(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n"); + LOG_F(" -----------------------------------------------------\n"); + for (int i = 0; i < sizeof(ctrls); i++) { + if (i % 16 == 0) + LOG_F(" %04x: ", i); + LOG_F("%02x ", ctrls[i]); + if (i % 16== 15) + printf("\n"); + } + LOG_F("\n"); + + // these infos are nosignification + msghdr_send.msg_name = NULL; // the name + msghdr_send.msg_namelen = 0; // len of name + + struct iovec iov[1]; // io vector + long dmabuf_len = file_size; + iov[0].iov_base = &dmabuf_len; // no data here + iov[0].iov_len = sizeof(dmabuf_len); // the len of data + + msghdr_send.msg_iov = iov; // the io/vector info + msghdr_send.msg_iovlen = 1; // the num of iov + + ssize_t size = sendmsg(socket_fd, &msghdr_send, 0); // send msg now + if (size < 0) { + LOG_E("sendmsg() failed, errno=%d, %s\n", errno, strerror(errno)); + } else if (size == 0) { + LOG_W("sendmsg() %zd bytes\n", size); + } else { + LOG_D("sendmsg() %zd bytes\n", size); + } + + ret = 0; + +LAB_ERR_TEST_FILE_OPENED: + ret = close(file_fd); + LOG_D("close(file_fd) ret=%d, %s\n", ret, strerror(errno)); + +LAB_ERR_MMAPPED: + ret = munmap(dmabuf_addr, file_size); + LOG_D("munmap() ret=%d, %s\n", ret, strerror(errno)); + +LAB_ERR_BUF_ALLOCED: + ret = close(dmabuf_fd); + LOG_D("close(dmabuf_fd) ret=%d, %s\n", ret, strerror(errno)); + +LAB_ERR_ION_OPENED: + close(ion_fd); + LOG_D("close(ion_fd) ret=%d, %s\n", ret, strerror(errno)); + +LAB_ERR_SOCKET_FD_OPENED: + ret = close(socket_fd); + LOG_D("close(socket_fd) ret=%d, %s\n", ret, strerror(errno)); + +LAB_EXIT: + if (ret) + LOG_E("Exiting producer\n"); + else + LOG_D("Exiting producer\n"); + exit(ret); +} + diff --git a/src/platform/simulator/drivers/tests/ipc_fd_transfer/.gitignore b/src/platform/simulator/drivers/tests/ipc_fd_transfer/.gitignore new file mode 100644 index 0000000..ac34cba --- /dev/null +++ b/src/platform/simulator/drivers/tests/ipc_fd_transfer/.gitignore @@ -0,0 +1,3 @@ +ipc_fd_transfer +producer +consumer diff --git a/src/platform/simulator/drivers/tests/ipc_fd_transfer/Makefile b/src/platform/simulator/drivers/tests/ipc_fd_transfer/Makefile new file mode 100644 index 0000000..632f700 --- /dev/null +++ b/src/platform/simulator/drivers/tests/ipc_fd_transfer/Makefile @@ -0,0 +1,36 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../../../../../.. +include $(DIR_TO_ROOT)/build.param + +#CFLAGS = -Wall -g -O0 +OUTPUT_DIR := $(DIR_TO_ROOT)/output/driver/ipc_fd_transfer +LIBS += -lcommon +#INCLUDE += -I../../ion + +TARGET_1 = ipc_fd_transfer +SRCS_1 = ipc_fd_transfer.c + +TARGET_2 = producer +SRCS_2 = process_producer.c + +TARGET_3 = consumer +SRCS_3 = process_consumer.c + +all: $(TARGET_1) $(TARGET_2) $(TARGET_3) + cp -f test.txt $(OUTPUT_DIR) + +clean: + rm -rf .obj + rm -rf $(TARGET_1) $(TARGET_2) $(TARGET_3) + rm -rf $(OUTPUT_DIR) + +include $(DIR_TO_ROOT)/common_target.mk + +.PHONY: clean all diff --git a/src/platform/simulator/drivers/tests/ipc_fd_transfer/ipc_fd_transfer.c b/src/platform/simulator/drivers/tests/ipc_fd_transfer/ipc_fd_transfer.c new file mode 100644 index 0000000..1388416 --- /dev/null +++ b/src/platform/simulator/drivers/tests/ipc_fd_transfer/ipc_fd_transfer.c @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "master" +#include + +#include "ipc_fd_transfer.h" + + +int main(int argc, char *argv[]) +{ + int ret; + int socket_pair[2]; /* sockpair() */ + char str_sockfd[10]; /* store the string format */ + pid_t producer_pid; + pid_t consumer_pid; + int process_status; // the status of child process + + /* Create socket pare */ + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, socket_pair) == -1) { /* must be AF_LOCAL or AF_UNIX !!!!*/ + LOG_E("create socketpair error(%d), %s\n", errno, strerror(errno)); + exit(1); + } + LOG_I("Create socketpair OK:{%d, %d}\n", socket_pair[0], socket_pair[1]); + + /* run producer process, using socket_pair[0] */ + if ((producer_pid = fork()) == 0) { + + LOG_D("launch producer process\n"); + close(socket_pair[1]); // producer only use socket_pair[0] + snprintf(str_sockfd, sizeof(str_sockfd), "%d", socket_pair[0]); + + execl("./producer", "producer", str_sockfd, (char *)NULL); + exit(0); + } + + /* run consumer process, using socket_pair[1] */ + if ((consumer_pid = fork()) == 0) { + + LOG_D("launch consumer process\n"); + close(socket_pair[0]); // consumer only use socket_pair[1] + snprintf(str_sockfd, sizeof(str_sockfd), "%d", socket_pair[1]); + + execl("./consumer", "consumer", str_sockfd, (char *)NULL); + exit(0); + } + + // wait the child process + waitpid(producer_pid, &process_status, 0); + if (WIFEXITED(process_status) == 0) { + LOG_E("producer eixt abnormal\n"); + } + + waitpid(consumer_pid, &process_status, 0); + if (WIFEXITED(process_status) == 0) { + LOG_E("consumer eixt abnormal\n"); + } + + /* close the socket_pair */ + ret = close(socket_pair[0]); + LOG_D("close(socket_pair[0]) ret=%d, %s\n", errno, strerror(errno)); + + ret = close(socket_pair[1]); + LOG_D("close(socket_pair[1]) ret=%d, %s\n", errno, strerror(errno)); + + LOG_I("Exit master\n"); + return 0; +} + diff --git a/src/platform/simulator/drivers/tests/ipc_fd_transfer/process_consumer.c b/src/platform/simulator/drivers/tests/ipc_fd_transfer/process_consumer.c new file mode 100644 index 0000000..4760995 --- /dev/null +++ b/src/platform/simulator/drivers/tests/ipc_fd_transfer/process_consumer.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_LEVEL 3 +#define LOG_PREFIX "consumer" +#include + +#include "ipc_fd_transfer.h" + +int main(int argc, char **argv) +{ + int ret; + int socket_fd; + int file_fd; + + LOG_D("Starting consumer\n"); + + /* Check arg count */ + if (argc != 2) { + LOG_E("useg:\n"); + goto LAB_ERROR; + } + + /* Check socket_fd */ + socket_fd = atoi(argv[1]); + if (socket_fd <= 0) { + LOG_E("socket_fd(%d) is invalid\n", socket_fd); + goto LAB_ERROR; + } + LOG_D("The input socket_fd=%d\n", socket_fd); + + struct msghdr msghdr_recv; // the info struct + + struct cmsghdr *pCmsghdr = NULL; // the pointer of control + msghdr_recv.msg_control = ctl_un.ctl; + msghdr_recv.msg_controllen = sizeof(ctl_un.ctl); + + // these infos are no signification + msghdr_recv.msg_name = NULL; // the name of protocol address + msghdr_recv.msg_namelen = 0; // len of name + + struct iovec iov[1]; // io vector + char iov_data[128]; + iov[0].iov_base = &iov_data; + iov[0].iov_len = sizeof(iov_data); + + msghdr_recv.msg_iov = iov; // the io/vector info + msghdr_recv.msg_iovlen = 1; // the num of iov + + ssize_t recv_len; + if ((recv_len = recvmsg(socket_fd, &msghdr_recv, 0)) < 0) { // recv msg + // the msg is recv by msghdr_recv + LOG_E("recv error:%d, %s\n", errno, strerror(errno)); + goto LAB_ERROR; + } + LOG_D("recvmsg() length=%zd, dump below:\n", recv_len); + if (recv_len > 0) { + int i; + LOG_F("msg_iov in string mode:\n"); + LOG_F(" 0123456789ABCDEF\n"); + LOG_F(" ----------------------\n"); + for (i = 0; i < recv_len; i++) { + if (i % 16 == 0) + LOG_F(" %04x: ", i); + LOG_F("%c", iov_data[i]); + if (i % 16 == 15) + LOG_F("\n"); + } + LOG_F("\n"); + + LOG_F("msg_iov in hex mode:\n"); + LOG_F(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n"); + LOG_F(" -----------------------------------------------------\n"); + for (i = 0; i < recv_len; i++) { + if (i % 16 == 0) + LOG_F(" %04x: ", i); + LOG_F("%02x ", iov_data[i]); + if (i % 16== 15) + printf("\n"); + } + LOG_F("\n"); + } + + if ((pCmsghdr = CMSG_FIRSTHDR(&msghdr_recv)) != NULL && // now we need only one, + pCmsghdr->cmsg_len == CMSG_LEN(sizeof(int))) { // we should use 'for' when + // there are many fds + if (pCmsghdr->cmsg_level != SOL_SOCKET) { + LOG_E("Ctl level should be SOL_SOCKET\n"); + goto LAB_ERROR; + } + + if (pCmsghdr->cmsg_type != SCM_RIGHTS) { + LOG_E("Ctl type should be SCM_RIGHTS\n"); + goto LAB_ERROR; + } + + file_fd = *((int *)CMSG_DATA(pCmsghdr)); // get the data : the file des* + } else { + if (pCmsghdr == NULL) + LOG_E("pCmsghdr=%p\n", pCmsghdr); + else + LOG_E("pCmsghdr->cmsg_len=%zu\n", pCmsghdr->cmsg_len); + + file_fd = -1; + goto LAB_ERROR; + } + LOG_D("receive file_fd=%d\n", file_fd); + + int n_read; + char buf[1024]; + LOG_I("Dump test file content in consumer begin vvvv\n"); + while ((n_read = read(file_fd, buf, 1024)) > 0) { // read from fd + write(1, buf, n_read); // write to std_out + } + LOG_I("Dump test file content in consumer end ^^^^\n"); + + ret = close(file_fd); + LOG_D("close(file_fd) ret=%d, %s\n", errno, strerror(errno)); + + ret = close(socket_fd); + LOG_D("close(socket_fd) ret=%d, %s\n", errno, strerror(errno)); + + LOG_D("Exiting consumer\n"); + return 0; + +LAB_ERROR: + LOG_E("Exiting consumer with error\n"); + exit(EXIT_FAILURE); +} + diff --git a/src/platform/simulator/drivers/tests/ipc_fd_transfer/process_producer.c b/src/platform/simulator/drivers/tests/ipc_fd_transfer/process_producer.c new file mode 100644 index 0000000..41b522e --- /dev/null +++ b/src/platform/simulator/drivers/tests/ipc_fd_transfer/process_producer.c @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEST_FILENAME "test.txt" + +#define LOG_LEVEL 3 +#define LOG_PREFIX "producer" +#include + +#include "ipc_fd_transfer.h" + +int main(int argc, char **argv) +{ + int ret; + int socket_fd; + int file_fd; + + LOG_D("Starting producer\n"); + + /* Check arg count */ + if (argc != 2) { + LOG_E("useg:\n"); + exit(EXIT_FAILURE); + } + + /* Check socket_fd */ + socket_fd = atoi(argv[1]); + if (socket_fd <= 0) { + LOG_E("socket_fd(%d) is invalid\n", socket_fd); + exit(1); + } + LOG_D("The input socket_fd=%d\n", socket_fd); + + /* open test file */ + file_fd = open(TEST_FILENAME, O_RDONLY); + if (file_fd < 0) { + LOG_E("Open test file:'%s' failed, errno=%d, %s", + TEST_FILENAME, errno, strerror(errno)); + exit(1); + } + LOG_D("The file_fd=%d\n", file_fd); + + /* send test file fd */ + struct msghdr msghdr_send; // the info struct + struct cmsghdr *pCmsghdr = NULL; // the pointer of control + + msghdr_send.msg_control = ctl_un.ctl; + msghdr_send.msg_controllen = sizeof(ctl_un.ctl); + + // the first info + pCmsghdr = CMSG_FIRSTHDR(&msghdr_send); // the info of head + pCmsghdr->cmsg_len = CMSG_LEN(sizeof(int)); // the msg len + pCmsghdr->cmsg_level = SOL_SOCKET; // stream mode + pCmsghdr->cmsg_type = SCM_RIGHTS; // to file descriptor !!!! + *((int *)CMSG_DATA(pCmsghdr)) = file_fd; // data: the file fd + + // these infos are nosignification + msghdr_send.msg_name = NULL; // the name of protocol address + msghdr_send.msg_namelen = 0; // len of name + + + struct iovec iov[2]; // io vector + char iov_data0[16] = "This's iov"; // 16 byts string + char iov_data1[25] = "This's an other iov_data";// 25 byts string + iov[0].iov_base = iov_data0; // + iov[0].iov_len = sizeof(iov_data0); // the len of data + iov[1].iov_base = iov_data1; // + iov[1].iov_len = sizeof(iov_data1); // the len of data + + msghdr_send.msg_iov = iov; // the io/vector info + msghdr_send.msg_iovlen = 2; // the num of iov + + ssize_t size = sendmsg(socket_fd, &msghdr_send, 0); // send msg now + if (size < 0) { + LOG_E("sendmsg() failed, errno=%d, %s\n", errno, strerror(errno)); + } else if (size == 0) { + LOG_W("sendmsg() %zd bytes\n", size); + } else { + LOG_D("sendmsg() %zd bytes\n", size); + } + + ret = close(file_fd); + LOG_D("close(file_fd) ret=%d, %s\n", errno, strerror(errno)); + + ret = close(socket_fd); + LOG_D("close(socket_fd) ret=%d, %s\n", errno, strerror(errno)); + + LOG_D("Exiting producer\n"); + return 0; +} + diff --git a/src/platform/simulator/drivers/tests/ipc_fd_transfer/test.txt b/src/platform/simulator/drivers/tests/ipc_fd_transfer/test.txt new file mode 100644 index 0000000..29c4555 --- /dev/null +++ b/src/platform/simulator/drivers/tests/ipc_fd_transfer/test.txt @@ -0,0 +1,4 @@ +To test ipc_fd_transfer - line1 +To test ipc_fd_transfer - line2 +To test ipc_fd_transfer - line3 +To test ipc_fd_transfer - line4 diff --git a/src/platform/simulator/opencv/Makefile b/src/platform/simulator/opencv/Makefile new file mode 100644 index 0000000..b9b2a99 --- /dev/null +++ b/src/platform/simulator/opencv/Makefile @@ -0,0 +1,39 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../../../.. + +include $(DIR_TO_ROOT)/build.param + +TARGET := libcamera_platform.a +OUTPUT_DIR := $(DIR_TO_ROOT)/output/hal/ + +#CFLAGS = -Wall -g -O0 + +SRCS = $(wildcard *.cpp) +OBJS = $(SRCS:.cpp=.o) + +all: $(TARGET) + +$(TARGET): $(OBJS) + @mkdir -p $(OUTPUT_DIR) + @echo "Linking" $@ "..." + $(AR) -r -o $(OUTPUT_DIR)/$@ .obj/*.o + +$(OBJS): %.o:%.cpp + @echo $(BUILD_LOG_START) + @mkdir -p .obj + @echo "Compiling" $< "..." + $(CXX) $(CFLAGS) $(INCLUDE) $(LIBOPENCV_INC) $(LIBOPENCV_LIBS) -c -o .obj/$(notdir $@) $< + @echo $(BUILD_LOG_END) + +clean: + rm -rf .obj + rm -f $(OUTPUT_DIR)/$(TARGET) + +.PHONY: clean all diff --git a/src/platform/simulator/opencv/simulator_camera.cpp b/src/platform/simulator/opencv/simulator_camera.cpp new file mode 100644 index 0000000..b24678c --- /dev/null +++ b/src/platform/simulator/opencv/simulator_camera.cpp @@ -0,0 +1,329 @@ +#include +#include +#include +#include +#include +#include +using namespace cv; +using namespace std; + +#define LOG_LEVEL 2 +#include +#include "simulator_camera.h" + +VideoCapture capture; +#define local_yuv_height 720 +#define local_yuv_width 1280 +#define PROPERTY_NUM 100 + +//char filename[] = "./examples/resource/yuv420_1280x720_csky2016.yuv"; +char filename[] = "../../../examples/resource/yuv420_1280x720_csky2016.yuv"; +static void swapyuv_i420tonv12(unsigned char *i420bytes, + unsigned char *nv12bytes, int height, int width) +{ + int nleny = height * width; + int nlenu = nleny / 4; + memcpy(nv12bytes, i420bytes, height * width); + for (int i = 0; i < nlenu; i++) { + nv12bytes[nleny + 2 * i] = i420bytes[nleny + i]; //U + nv12bytes[nleny + 2 * i + 1] = i420bytes[nleny + nlenu + i]; //V + } +} +int csi_camera_opencv_get_picture_local(csi_pixel_fmt_e fmt, int height, + int width, unsigned char *data, + csi_camera_property_description_s *property_description) +{ + int ret = 0; + int framesize = local_yuv_width * local_yuv_height * 3 / 2; + Mat local_mat_i420, mat_i420, mat_nv12, mat_bgr, mat_channel; + local_mat_i420.create(local_yuv_height * 3 / 2, local_yuv_width, CV_8UC1); + mat_i420.create(height * 3 / 2, width, CV_8UC1); + mat_nv12.create(height * 3 / 2, width, CV_8UC1); + mat_bgr.create(height, width, CV_8UC3); + FILE *fileIn = fopen(filename, "rb+"); + fread(local_mat_i420.data, framesize * sizeof(unsigned char), 1, fileIn); + cvtColor(local_mat_i420, mat_channel, COLOR_YUV2BGR_I420); + resize(mat_channel, mat_bgr, mat_bgr.size()); + framesize = height * width; + Size mat_bgr_sz(mat_bgr.cols, mat_bgr.rows); + Point2f center(static_cast(mat_bgr.cols / 2.), + static_cast(mat_bgr.rows / 2.)); + Mat rot_mat; + for (int i = 0; i < PROPERTY_NUM; i++) { + switch (property_description[i].id) { + case CSI_CAMERA_PID_HFLIP: + if (property_description[i].value.bool_value == true) { + flip(mat_bgr, mat_bgr, 1); + } + break; + case CSI_CAMERA_PID_VFLIP: + if (property_description[i].value.bool_value == true) { + flip(mat_bgr, mat_bgr, 0); + } + break; + case CSI_CAMERA_PID_ROTATE: + rot_mat = getRotationMatrix2D(center, property_description[i].value.int_value, + 1.0); + warpAffine(mat_bgr, mat_bgr, rot_mat, mat_bgr_sz, INTER_LINEAR, + BORDER_REPLICATE); + break; + case CSI_CAMERA_PID_EXPOSURE_ABSOLUTE: + for (int row = 0; row < mat_bgr.rows; row++) { + for (int col = 0; col < mat_bgr.cols; col++) { + mat_bgr.at(row, col)[0] = mat_bgr.at(row, + col)[0] + property_description[i].value.int_value; + mat_bgr.at(row, col)[1] = mat_bgr.at(row, + col)[1] + property_description[i].value.int_value; + mat_bgr.at(row, col)[2] = mat_bgr.at(row, + col)[2] + property_description[i].value.int_value; + } + } + break; + case CSI_CAMERA_PID_RED_GAIN: + for (int row = 0; row < mat_bgr.rows; row++) { + for (int col = 0; col < mat_bgr.cols; col++) { + mat_bgr.at(row, col)[2] = mat_bgr.at(row, + col)[2] + property_description[i].value.int_value; + } + } + break; + case CSI_CAMERA_PID_GREEN_GAIN: + for (int row = 0; row < mat_bgr.rows; row++) { + for (int col = 0; col < mat_bgr.cols; col++) { + mat_bgr.at(row, col)[1] = mat_bgr.at(row, + col)[1] + property_description[i].value.int_value; + } + } + break; + case CSI_CAMERA_PID_BLUE_GAIN: + for (int row = 0; row < mat_bgr.rows; row++) { + for (int col = 0; col < mat_bgr.cols; col++) { + mat_bgr.at(row, col)[0] = mat_bgr.at(row, + col)[0] + property_description[i].value.int_value; + } + } + break; + default: + break; + } + } + switch (fmt) { + case CSI_PIX_FMT_I420: + framesize = framesize * 3 / 2; + cvtColor(mat_bgr, mat_i420, COLOR_BGR2YUV_I420); + memcpy(data, mat_i420.data, framesize * sizeof(unsigned char)); + break; + case CSI_PIX_FMT_NV12: + framesize = framesize * 3 / 2; + cvtColor(mat_bgr, mat_i420, COLOR_BGR2YUV_I420); + swapyuv_i420tonv12(mat_i420.data, mat_nv12.data, height, width); + memcpy(data, mat_nv12.data, framesize * sizeof(unsigned char)); + break; + case CSI_PIX_FMT_BGR: + framesize = framesize * 3; + memcpy(data, mat_bgr.data, framesize * sizeof(unsigned char)); + break; + default: + ret = -1; + break; + } + + mat_channel.release(); + mat_i420.release(); + mat_nv12.release(); + mat_bgr.release(); + rot_mat.release(); + fclose(fileIn); + return ret; +} +int csi_camera_opencv_get_picture(int height, int width, unsigned char *data) +{ + Mat mat_channel,tmp; + mat_channel.create(height,width,CV_8UC3); + capture >> tmp; + resize(tmp,mat_channel,mat_channel.size()); + memcpy(data, mat_channel.data, height * width * 3 * sizeof(unsigned char)); + return 0; +} +int csi_camera_opencv_format_picture(csi_pixel_fmt_e fmt, int dst_height, + int dst_width, unsigned char *data_dst, + int src_height, int src_width, unsigned char *data_src, + csi_camera_property_description_s *property_description) +{ + Mat mat_channel, mat_i420, mat_nv12,mat_tmp; + int ret = 0; + int framesize = dst_height * dst_width; + mat_i420.create(dst_height * 3 / 2, dst_width, CV_8UC1); + mat_nv12.create(dst_height * 3 / 2, dst_width, CV_8UC1); + mat_channel.create(dst_height, dst_width, CV_8UC3); + mat_tmp.create(src_height,src_width,CV_8UC3); + memcpy(mat_tmp.data,data_src,src_height * src_width * 3 * sizeof(unsigned char)); + resize(mat_tmp, mat_channel, mat_channel.size()); + + /*reserve to debug + printf("mat_channel.width = %d, mat_channel.height = %d\n",mat_channel.cols,mat_channel.rows); + imshow("mat_channel",mat_channel); + waitKey(0); + */ + Size mat_channel_sz(mat_channel.cols, mat_channel.rows); + Point2f center(static_cast(mat_channel.cols / 2.), + static_cast(mat_channel.rows / 2.)); + Mat rot_mat; + for (int i = 0; i < PROPERTY_NUM; i++) { + switch (property_description[i].id) { + case CSI_CAMERA_PID_HFLIP: + if (property_description[i].value.bool_value == true) { + flip(mat_channel, mat_channel, 1); + } + break; + case CSI_CAMERA_PID_VFLIP: + if (property_description[i].value.bool_value == true) { + flip(mat_channel, mat_channel, 0); + } + break; + case CSI_CAMERA_PID_ROTATE: + rot_mat = getRotationMatrix2D(center, property_description[i].value.int_value, + 1.0); + warpAffine(mat_channel, mat_channel, rot_mat, mat_channel_sz, INTER_LINEAR, + BORDER_REPLICATE); + break; + default: + break; + } + } + switch (fmt) { + case CSI_PIX_FMT_I420: + framesize = framesize * 3 / 2; + cvtColor(mat_channel, mat_i420, COLOR_BGR2YUV_I420); + memcpy(data_dst, mat_i420.data, framesize * sizeof(unsigned char)); + break; + case CSI_PIX_FMT_NV12: + framesize = framesize * 3 / 2; + cvtColor(mat_channel, mat_i420, COLOR_BGR2YUV_I420); + swapyuv_i420tonv12(mat_i420.data, mat_nv12.data, dst_height, dst_width); + memcpy(data_dst, mat_nv12.data, framesize * sizeof(unsigned char)); + break; + case CSI_PIX_FMT_BGR: + framesize = framesize * 3; + memcpy(data_dst, mat_channel.data, framesize * sizeof(unsigned char)); + break; + default: + ret = -1; + break; + } + mat_channel.release(); + mat_i420.release(); + mat_nv12.release(); + rot_mat.release(); + return ret; +} +int camera_open_opencv(int idx) +{ + bool ret = capture.open(idx); + if (ret != true) { + return -1; + } + return 0; +} +int camera_property_set_opencv(csi_camera_property_s *property) +{ + int ret = 0; + switch (property->id) { + case CSI_CAMERA_PID_HFLIP: + break; + case CSI_CAMERA_PID_VFLIP: + break; + case CSI_CAMERA_PID_ROTATE: + break; + case CSI_CAMERA_EXPOSURE_MODE_AUTO: + if (property->value.enum_value == CSI_CAMERA_EXPOSURE_MODE_AUTO) { + capture.set(CV_CAP_PROP_AUTO_EXPOSURE, true); + } else if (property->value.enum_value == CSI_CAMERA_EXPOSURE_MANUAL) { + capture.set(CV_CAP_PROP_AUTO_EXPOSURE, false); + } else if (property->value.enum_value == CSI_CAMERA_EXPOSURE_SHUTTER_PRIORITY) { + capture.set(CV_CAP_PROP_AUTO_EXPOSURE, false); + } else if (property->value.enum_value == + CSI_CAMERA_EXPOSURE_APERTURE_PRIORITY) { + capture.set(CV_CAP_PROP_AUTO_EXPOSURE, false); + } else { + ret - 1; + } + break; + case CSI_CAMERA_PID_EXPOSURE_ABSOLUTE: + capture.set(CV_CAP_PROP_BRIGHTNESS, property->value.int_value); + break; + case CSI_CAMERA_PID_3A_LOCK: + if (property->value.bitmask_value & CSI_CAMERA_LOCK_EXPOSURE) { + capture.set(CV_CAP_PROP_AUTO_EXPOSURE, false); + } else if (property->value.bitmask_value & CSI_CAMERA_LOCK_WHITE_BALANCE) { + capture.set(CV_CAP_PROP_TEMPERATURE, false); + } else if (property->value.bitmask_value & CSI_CAMERA_LOCK_FOCUS) { + LOG_D("supportted by lens\n"); + } else { + ret = -1; + } + break; + case CSI_CAMERA_PID_RED_GAIN: + capture.set(CV_CAP_PROP_WHITE_BALANCE_RED_V, property->value.int_value); + break; + case CSI_CAMERA_PID_GREEN_GAIN: + capture.set(CV_CAP_PROP_GAIN, property->value.int_value); + break; + case CSI_CAMERA_PID_BLUE_GAIN: + capture.set(CV_CAP_PROP_WHITE_BALANCE_BLUE_U, property->value.int_value); + break; + default: + break; + } + return ret; +} +int camera_cfg_opencv(csi_cam_handle_t cam_handle, int height, int width) +{ + capture.set(CV_CAP_PROP_FRAME_HEIGHT, height); + capture.set(CV_CAP_PROP_FRAME_WIDTH, width); + return 0; +} +int camera_set_mode_opencv(csi_camera_mode_cfg_s *cfg) +{ + int ret = 0; + switch (cfg->mode_id) { + case 1: + capture.set(CV_CAP_PROP_FPS, 30); + break; + case 2: + capture.set(CV_CAP_PROP_FPS, 10); + break; + default: + capture.set(CV_CAP_PROP_FPS, 20); + ret = -1; + break; + } + return ret; +} +int camera_close_opencv(csi_cam_handle_t cam_handle) +{ + capture.release(); + return 0; +} +//cannot find macro and use this function temporarily and change in the future +int camera_image_size_opencv(csi_frame_s *frame) +{ + int ret = 0; + switch (frame->img.pix_format) { + case CSI_PIX_FMT_I420: + ret = frame->img.height * frame->img.width * 3 / 2; + break; + case CSI_PIX_FMT_NV12: + ret = frame->img.height * frame->img.width * 3 / 2; + break; + case CSI_PIX_FMT_BGR: + ret = frame->img.height * frame->img.width * 3; + break; + default: + LOG_E("Unkonw pix_format:%d, return image size = 0\n", + frame->img.pix_format); + ret = 0; + break; + } + return ret; +} diff --git a/src/platform/simulator/opencv/simulator_camera.h b/src/platform/simulator/opencv/simulator_camera.h new file mode 100644 index 0000000..edb2add --- /dev/null +++ b/src/platform/simulator/opencv/simulator_camera.h @@ -0,0 +1,30 @@ +#ifndef __SIMULATOR_CAMERA_H__ +#define __SIMULATOR_CAMERA_H__ + +#ifdef __cplusplus +extern "C" { +#endif +#include + +//wait for complete +typedef void *csi_cam_handle_t; +int csi_camera_opencv_get_picture_local(csi_pixel_fmt_e fmt, int height, + int width, unsigned char *data, + csi_camera_property_description_s *property_description); +int csi_camera_opencv_get_picture(int height, int width, unsigned char *data); +int csi_camera_opencv_format_picture(csi_pixel_fmt_e fmt, int dst_height, + int dst_width, unsigned char *data_dst, + int src_height, int src_width, unsigned char *data_src, + csi_camera_property_description_s *property_description); +int camera_open_opencv(int idx); +int camera_cfg_opencv(csi_cam_handle_t cam_handle, int height, int width); +int camera_property_set_opencv(csi_camera_property_s *property); +int camera_set_mode_opencv(csi_camera_mode_cfg_s *cfg); +int camera_close_opencv(csi_cam_handle_t cam_handle); +int camera_image_size_opencv(csi_frame_s *frame); + +#ifdef __cplusplus +}; +#endif + +#endif diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..a5ef04f --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,22 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=.. +include $(DIR_TO_ROOT)/build.param + +all: camera + +camera: + @echo $(BUILD_LOG_START) + make -C camera/ + @echo $(BUILD_LOG_END) + +clean: + make -C camera/ clean + +.PHONY: clean all camera diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..e69de29 diff --git a/tests/camera/Makefile b/tests/camera/Makefile new file mode 100644 index 0000000..c656ddd --- /dev/null +++ b/tests/camera/Makefile @@ -0,0 +1,40 @@ +## + # Copyright (C) 2021 Alibaba Group Holding Limited + # Author: LuChongzhi + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License version 2 as + # published by the Free Software Foundation. +## +DIR_TO_ROOT=../.. +include $(DIR_TO_ROOT)/build.param + +$(info INCLUDE=$(INCLUDE)) + +#CFLAGS = -Wall -g -O0 +LIBS += -lhal_common -lhal_platform +OUTPUT_DIR := ../../output/test + +TARGET_TEST1 := camera_test1 +SRCS_TEST1 = camera_test1.c +OBJS_TEST1 = $(SRCS_TEST1:.c=.o) + +#$(error $(OBJS)) +all: $(TARGET_TEST1) + + +$(TARGET_TEST1): $(OBJS_TEST1) + @mkdir -p $(OUTPUT_DIR) + @echo ">>> Linking" $@ "..." + $(CC) -o $(OUTPUT_DIR)/$@ .obj/*.o $(LIBS) + +$(OBJS_TEST1): %.o:%.c + @mkdir -p .obj + @echo ">>> Compiling" $< "..." + $(CC) $(INCLUDE) -c -o .obj/$(notdir $@) $< + +clean: + rm -rf .obj + rm -f $(OUTPUT_DIR)/$(TARGET_TEST1) + +.PHONY: clean all diff --git a/tests/camera/camera_test1.c b/tests/camera/camera_test1.c new file mode 100644 index 0000000..b06a167 --- /dev/null +++ b/tests/camera/camera_test1.c @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 Alibaba Group Holding Limited + * Author: LuChongzhi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#define LOG_LEVEL 2 +#define LOG_PREFIX "test1" +#include + +#include +#include + +#define TEST_DEVICE_NAME "/dev/video" +int main(int argc, char *argv[]) +{ + LOG_I("This is camera_test1\n"); + return 0; +} +