#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
#include "driver/i2s.h"

#if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 0))
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "freertos/queue.h"
#include "freertos/event_groups.h"
#else
#include "board.h"
#include "periph_wifi.h"
#include "sys/socket.h"
#include "netdb.h"
#include "audio_sys.h"
#include "audio_mem.h"
#define BRTC_DEMO_STATS
#define BRTC_DEMO_VIDEO
#define BRTC_DEMO_AUDIO
#endif

#ifdef BRTC_DEMO_AUDIO
#include "audio_element.h"
#include "audio_pipeline.h"
#include "audio_common.h"
#include "i2s_stream.h"
#include "aec_stream.h"
#include "algorithm_stream.h"
#include "raw_stream.h"
#endif

#ifdef BRTC_DEMO_VIDEO
#include "audio_mutex.h"
#include "esp_camera.h"
#include "esp_err.h"
#include "esp_jpeg_enc.h"
#include "esp_jpeg_dec.h"
#include "esp_jpeg_common.h"
#include "esp_lcd_panel_ops.h"
#endif
#include "esp_log.h"
#include "esp_random.h"
#include "baidu_rtc_client.h"

//==============================================================================
// General Definitions and Global Variables
//==============================================================================
#define BRTC_LOG printf
#define BRTC_WIFI_SSID      "BD"         // AP 网络名称 "brtcwf"
#define BRTC_WIFI_PASS      "11111111"       // AP 密码 "12345678"
#define TAG "MAIN"

static void* gBrtcClient = NULL;
static esp_lcd_panel_handle_t panel_handle = NULL;
static bool brtcDemoRunning = false;
static bool brtcDemoCapture = false;
static bool g_brtc_need_exit = false;

static void brtc_demo_exit();
static void brtc_demo_auto_reconnect(void *h);
static void OnAudioFrame(int64_t feedid, const char *audio, int len, int samlplerate, int channels);
static void OnVideoFrame(int64_t feedid, const char *img, int len, RtcImageType imgtype, int width, int height);
/******************************************************************************
 ******************************************************************************
 **
 ** Wi-Fi Section
 **
 ******************************************************************************
 ******************************************************************************/
#define BRTC_WIFI_SSID      "BD"         // AP 网络名称 "brtcwf"
#define BRTC_WIFI_PASS      "11111111"       // AP 密码 "12345678"

#if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 0))
static EventGroupHandle_t wifi_event_group;
const static int CONNECTED_BIT = BIT0;

static esp_err_t brtc_demo_event_handler(void *ctx, system_event_t *event)
{
    switch(event->event_id) {
        case SYSTEM_EVENT_STA_START:
            esp_wifi_connect();
            break;
        case SYSTEM_EVENT_STA_GOT_IP:
            xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
            break;
        case SYSTEM_EVENT_STA_DISCONNECTED:
            esp_wifi_connect();
            xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
            break;
        default:
            break;
    }
    return ESP_OK;
}

void  brtc_demo_init_wifi(void) {
    tcpip_adapter_init();
    ESP_ERROR_CHECK(esp_event_loop_init(brtc_demo_event_handler, NULL));
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = BRTC_WIFI_SSID,
            .password = BRTC_WIFI_PASS,
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());
    xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY);
}

#else

void brtc_demo_init_wifi(void)
{
    // Initialize NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0))
    ESP_ERROR_CHECK(esp_netif_init());
#else
    tcpip_adapter_init();
#endif

    esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
    esp_periph_set_handle_t set = esp_periph_set_init(&periph_cfg);
#ifdef BRTC_DEMO_VIDEO
    panel_handle = audio_board_lcd_init(set, NULL);
#endif
    periph_wifi_cfg_t wifi_cfg = {
        .wifi_config.sta.ssid = BRTC_WIFI_SSID,
        .wifi_config.sta.password = BRTC_WIFI_PASS,
    };
    esp_periph_handle_t wifi_handle = periph_wifi_init(&wifi_cfg);
    esp_periph_start(set, wifi_handle);
    periph_wifi_wait_for_connected(wifi_handle, portMAX_DELAY);
}
#endif

#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0))
void brtc_demo_link_lwip() {
    char addr[64] = "";
    char serv[6] = "3";
    struct addrinfo hints, *res = NULL;
    int af = AF_INET;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = af;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_protocol = IPPROTO_UDP;

    getaddrinfo(addr, serv, &hints, &res);
}
#endif

/******************************************************************************
 ******************************************************************************
 **
 ** Utility
 **
 ******************************************************************************
 ******************************************************************************/
#define TRACE_NUM (6)
int trace_size(char *tag, int size) {
    static uint32_t last_time[TRACE_NUM] = {};
    static char* trace_tag[TRACE_NUM] = {};
    static int total_size[TRACE_NUM] = {};
    static int total_count[TRACE_NUM] = {};
    for (int i = 0; i < TRACE_NUM; i++) {
         if (trace_tag[i] == NULL) {
            trace_tag[i] = tag;
        } else if (trace_tag[i] != tag) {
            continue;
        }
        uint32_t cur_time = esp_timer_get_time() / 1000;
        if (last_time[i] == 0 || cur_time > last_time[i] + 2000) {
            last_time[i] = cur_time;
            total_size[i] = 0;
            total_count[i] = 0;
        }
        total_size[i] += size;
         total_count[i]++;
        if (cur_time > last_time[i] + 1000) {
            ESP_LOGW("TRACE", "%s Size %d:%d", tag, total_count[i], total_size[i]);
            total_size[i] = 0;
            total_count[i] = 0;
            last_time[i] = cur_time;
        }
        return 0;
    }
    return -1;
}
#ifdef BRTC_DEMO_STATS
static bool brtc_stats_looping = false;
static int looping_c = 0;

static void brtc_demo_stats(void *pv) {
    BRTC_LOG("brtc_demo_stats start\n");
    while (1) {
        if (g_brtc_need_exit) {
            if (gBrtcClient) {
                BRTC_LOG("baidu_rtc_destroy start\n");
                brtc_demo_exit();
                BRTC_LOG("baidu_rtc_destroy success\n");
            }
            break;
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0))
    brtc_demo_link_lwip();
#endif

    vTaskDelete(NULL);
}
#endif
/******************************************************************************
 ******************************************************************************
 **
 ** Audio Section
 **
 ******************************************************************************
 ******************************************************************************/
#ifdef BRTC_DEMO_AUDIO
#define BRTC_AUDIO_FRAME_SIZE 320
#define BRTC_AUDIO_RATE       8000
#define BRTC_AUDIO_BITS       16
#define BRTC_AUDIO_CHANNEL    1

static audio_element_handle_t i2s_stream_writer = NULL;

typedef struct {
    audio_pipeline_handle_t rec_pipe;
    audio_element_handle_t  i2s_read;
    audio_element_handle_t  aec_stream;
    audio_element_handle_t  raw_stream;
    bool                    running;
} rec_res_t;

static rec_res_t rec_res;

static void destroy_record_pipe(void)
{
    if (rec_res.rec_pipe) {
        if (rec_res.running) {
            rec_res.running = false;
            audio_pipeline_stop(rec_res.rec_pipe);
            audio_pipeline_wait_for_stop(rec_res.rec_pipe);
        } else {
            if (rec_res.i2s_read) {
                audio_element_deinit(rec_res.i2s_read);
            }
            if (rec_res.aec_stream) {
                audio_element_deinit(rec_res.aec_stream);
            }
            if (rec_res.raw_stream) {
                audio_element_deinit(rec_res.raw_stream);
            }
        }
        audio_pipeline_terminate(rec_res.rec_pipe);
        audio_pipeline_deinit(rec_res.rec_pipe);
    }
    memset(&rec_res, 0, sizeof(rec_res));
}

#define BREAD_ON_NULL_HD(hd) if (hd == NULL) {break;}

static int i2s_read_cb(audio_element_handle_t el, char *buf, int len, TickType_t wait_time, void *ctx)
{
    size_t bytes_read = audio_element_input(rec_res.i2s_read, buf, len);
    if (bytes_read <= 0) {
        ESP_LOGE(TAG, "I2S read failed, %d", len);
    } 
    return bytes_read;
}
#include "es7210.h"
static int create_rec_pipeline(void)
{
    do {
        audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
        rec_res.rec_pipe = audio_pipeline_init(&pipeline_cfg);
        BREAD_ON_NULL_HD(rec_res.rec_pipe);

        i2s_stream_cfg_t i2s_r_cfg = I2S_STREAM_CFG_DEFAULT_WITH_PARA(CODEC_ADC_I2S_PORT, 16000, 32, AUDIO_STREAM_READER);
        i2s_r_cfg.task_stack = -1;
        i2s_stream_set_channel_type(&i2s_r_cfg, I2S_CHANNEL_FMT_ONLY_LEFT);
        rec_res.i2s_read = i2s_stream_init(&i2s_r_cfg);

        es7210_adc_set_gain(ES7210_INPUT_MIC3, GAIN_0DB);
        es7210_adc_set_gain(ES7210_INPUT_MIC2 | ES7210_INPUT_MIC1, GAIN_33DB);

        algorithm_stream_cfg_t algo_config = ALGORITHM_STREAM_CFG_DEFAULT();
        algo_config.input_format = "RM";
        algo_config.sample_rate = 16000;
        algo_config.out_rb_size = 256;
        rec_res.aec_stream = algo_stream_init(&algo_config);
        audio_element_set_music_info(rec_res.aec_stream, 16000, 1, ALGORITHM_STREAM_DEFAULT_SAMPLE_BIT);
        audio_element_set_read_cb(rec_res.aec_stream, i2s_read_cb, NULL);
        audio_element_set_input_timeout(rec_res.aec_stream, portMAX_DELAY);

        raw_stream_cfg_t raw_cfg = RAW_STREAM_CFG_DEFAULT();
        raw_cfg.type = AUDIO_STREAM_WRITER;
        raw_cfg.out_rb_size = 2 * 1024;
        rec_res.raw_stream = raw_stream_init(&raw_cfg);
        BREAD_ON_NULL_HD(rec_res.raw_stream);
        audio_element_set_output_timeout(rec_res.raw_stream, portMAX_DELAY);

        const char *link_tag[] = { "aec", "raw"};
        audio_pipeline_register(rec_res.rec_pipe, rec_res.aec_stream , link_tag[0]);
        audio_pipeline_register(rec_res.rec_pipe, rec_res.raw_stream , link_tag[1]);
        audio_pipeline_link(rec_res.rec_pipe, &link_tag[0], sizeof(link_tag)/sizeof(link_tag[0]));
        audio_pipeline_run(rec_res.rec_pipe);
        rec_res.running = true;
        return 0;
    } while (0);
    destroy_record_pipe();
    return -1;
}

static void brtc_demo_audio(void * arg) {
    size_t rs = 0;
    char abuf[BRTC_AUDIO_FRAME_SIZE] = {0};

    audio_board_handle_t board_handle = audio_board_init();
    audio_hal_ctrl_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_BOTH, AUDIO_HAL_CTRL_START);

    i2s_stream_cfg_t i2w_cfg = I2S_STREAM_CFG_DEFAULT();
    i2w_cfg.type = AUDIO_STREAM_WRITER;
    i2s_std_slot_config_t slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(32, 1);
    slot_cfg.slot_mask = (i2s_std_slot_mask_t)1;
    i2w_cfg.task_stack = -1;
    slot_cfg.data_bit_width = 16;
    slot_cfg.slot_bit_width = 32;
    i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(BRTC_AUDIO_RATE);
    i2w_cfg.std_cfg.clk_cfg = clk_cfg;
    i2w_cfg.std_cfg.slot_cfg = slot_cfg;
    // i2w_cfg.buffer_len = 1024 * 16;
    audio_hal_set_volume(board_handle->audio_hal, 100);
    i2s_stream_writer = i2s_stream_init(&i2w_cfg);

    create_rec_pipeline();
    audio_element_run(i2s_stream_writer);
    BRTC_LOG("brtc_demo_audio start\n");
    if (rec_res.running) {
        uint32_t last_time = esp_timer_get_time() / 1000;
        while (brtcDemoRunning) {
            rs = raw_stream_read(rec_res.raw_stream, (char *)abuf, BRTC_AUDIO_FRAME_SIZE);
            //trace_size("Asend", rs);
            int16_t* v = abuf;
            if (rs > 0 && gBrtcClient) {
                brtc_send_audio(gBrtcClient, (const uint8_t*)abuf, rs);
            }
        }
    }
    destroy_record_pipe();
    audio_element_deinit(i2s_stream_writer);
    BRTC_LOG("brtc_demo_audio exit\n");
    vTaskDelete(NULL);
}
#endif // BRTC_DEMO_AUDIO

// Callback for receiving remote audio frames from RTC
static void OnAudioFrame(int64_t feedid, const char *audio, int len, int samlplerate, int channels) {
    if (brtcDemoRunning == false) {
        return;
    }
#ifdef BRTC_DEMO_AUDIO
    if (i2s_stream_writer && len > 0) {
        //trace_size("ARECV", len);
        audio_element_output(i2s_stream_writer, audio, len);
    }
#endif
}

/******************************************************************************
 ******************************************************************************
 **
 ** Video Section
 **
 ******************************************************************************
 ******************************************************************************/
#ifdef BRTC_DEMO_VIDEO
#define BRTC_DEMO_LCD_H    (320)
#define BRTC_DEMO_LCD_V    (240)
#define BRTC_DEMO_JPG_W    (320)
#define BRTC_DEMO_JPG_H    (240)
typedef struct {
    uint8_t *buffer;
    size_t size;
} brtc_demo_video_pkg_t;

static uint8_t *dsp_buffer = NULL;
static uint8_t *rgb_buffer = NULL;
static uint8_t *cam_buffer = NULL;
static int cam_image_size = 0;
static QueueHandle_t brtc_demo_video_pkg_queue;

typedef struct {
    uint8_t *buffer;
    size_t size;
    void* handle;
    xSemaphoreHandle lock;
} brtc_enc_jpg_s;
static brtc_enc_jpg_s jpg_enc;

#define CAM_PIN_PWDN -1
#define CAM_PIN_RESET -1
#define CAM_PIN_XCLK 40
#define CAM_PIN_SIOD -1
#define CAM_PIN_SIOC -1
#define CAM_PIN_D7 39
#define CAM_PIN_D6 41
#define CAM_PIN_D5 42
#define CAM_PIN_D4 12
#define CAM_PIN_D3 3
#define CAM_PIN_D2 14
#define CAM_PIN_D1 47
#define CAM_PIN_D0 13
#define CAM_PIN_VSYNC 21
#define CAM_PIN_PCLK 11

static camera_config_t camera_config = {
    .pin_pwdn = CAM_PIN_PWDN,
    .pin_reset = CAM_PIN_RESET,
    .pin_xclk = CAM_PIN_XCLK,
    .pin_sscb_sda = CAM_PIN_SIOD,
    .pin_sscb_scl = CAM_PIN_SIOC,
    .pin_d7 = CAM_PIN_D7,
    .pin_d6 = CAM_PIN_D6,
    .pin_d5 = CAM_PIN_D5,
    .pin_d4 = CAM_PIN_D4,
    .pin_d3 = CAM_PIN_D3,
    .pin_d2 = CAM_PIN_D2,
    .pin_d1 = CAM_PIN_D1,
    .pin_d0 = CAM_PIN_D0,
    .pin_vsync = CAM_PIN_VSYNC,
    .pin_href = CAM_PIN_HREF,
    .pin_pclk = CAM_PIN_PCLK,
    .ledc_timer = LEDC_TIMER_0,
    .ledc_channel = LEDC_CHANNEL_0,
#if 0
    .pixel_format = PIXFORMAT_JPEG,
#else
     .pixel_format = PIXFORMAT_YUV422,
#endif
    .frame_size = FRAMESIZE_QVGA,
    .jpeg_quality = 12,
    .fb_count = 2,
    .grab_mode = CAMERA_GRAB_WHEN_EMPTY,
    .xclk_freq_hz = 20000000,
};
#endif // BRTC_DEMO_VIDEO

#ifdef BRTC_DEMO_VIDEO
#define JPEG_DEC_OUTPUT_FORMAT JPEG_PIXEL_FORMAT_RGB565_BE

int brtc_demo_jpeg_dec(unsigned char **pixels, const unsigned char *data, size_t len, int* width, int *height)
{
    static jpeg_dec_handle_t jpeg_dec = NULL;
    int ret = 0;
    if (jpeg_dec == NULL) {
        // Generate default configuration
        jpeg_dec_config_t config = DEFAULT_JPEG_DEC_CONFIG();
        config.output_type = JPEG_DEC_OUTPUT_FORMAT;
        // Empty handle to jpeg_decoder
        // Create jpeg_dec
        ret = jpeg_dec_open(&config, &jpeg_dec);
        if (jpeg_dec == NULL) {
            ESP_LOGE(TAG, "Got an error by jpeg_dec_open, ret:%d", ret);
            return -1;
        }
    }
    int outbuf_len = 0;
    jpeg_dec_io_t jpeg_io = {
        .inbuf = (uint8_t*)data,
        .inbuf_len = len,
    };
    jpeg_dec_header_info_t out_info = {0};
    // Parse jpeg picture header and get picture for user and decoder
    ret = jpeg_dec_parse_header(jpeg_dec, &jpeg_io, &out_info);
    if (ret < 0) {
        ESP_LOGE(TAG, "Got an error by jpeg_dec_parse_header, ret:%d", ret);
        return ret;
    }
    // Calloc out_put data buffer and update inbuf ptr and inbuf_len
    if (JPEG_DEC_OUTPUT_FORMAT== JPEG_PIXEL_FORMAT_RGB565_LE
        || JPEG_DEC_OUTPUT_FORMAT == JPEG_PIXEL_FORMAT_RGB565_BE) {
        outbuf_len = out_info.height * out_info.width * 2;
    } else if (JPEG_DEC_OUTPUT_FORMAT == JPEG_PIXEL_FORMAT_RGB888) {
        outbuf_len = out_info.height * out_info.width * 3;
    } else {
        return ESP_FAIL;
    }
    ESP_LOGD(TAG, "jpeg_dec_image:%d (%d * %d)", outbuf_len, out_info.width, out_info.height);
    unsigned char *out_buf = jpeg_calloc_align(outbuf_len, 16);
    jpeg_io.outbuf = out_buf;
    int inbuf_consumed = jpeg_io.inbuf_len - jpeg_io.inbuf_remain;
    jpeg_io.inbuf = (unsigned char *)(data + inbuf_consumed);
    jpeg_io.inbuf_len = jpeg_io.inbuf_remain;
    *width = out_info.width;
    *height = out_info.height;

    // Start decode jpeg raw data
    ret = jpeg_dec_process(jpeg_dec, &jpeg_io);
    if (ret < 0) {
        ESP_LOGE(TAG, "jpeg_dec_fail:%d", ret);
        return ret;
    }
    *pixels = out_buf;
    return ESP_OK;
}

static void brtc_demo_lcd_draw(esp_lcd_panel_handle_t panel_handle, uint8_t *image) {
    uint32_t lines_num = 20;
    for (int i = 0; i < BRTC_DEMO_LCD_V / lines_num; ++i) {
        esp_lcd_panel_draw_bitmap(panel_handle, 0, i * lines_num, BRTC_DEMO_LCD_H,
                    lines_num + i * lines_num, image + BRTC_DEMO_LCD_H * i * lines_num * 2);
    }
}

static void brtc_demo_disp_jpg(const char *img, int len) {
    // BRTC_LOG("brtc demo jpg type:%d, w:%d, h:%d, len = %d\n", imgtype, width, height, len);
    unsigned char *lcd_pixels = NULL;
    int width = 0;
    int height = 0;
    brtc_demo_jpeg_dec(&lcd_pixels, (const unsigned char*)img, len, &width, &height);
    if (lcd_pixels != NULL) {
        for (int i = 0; i < BRTC_DEMO_LCD_V && i < height; i++) {
            memcpy(dsp_buffer + i * BRTC_DEMO_LCD_H * 2, lcd_pixels + i * width * 2,  width * 2);
        }
        jpeg_free_align(lcd_pixels);
        brtc_demo_lcd_draw(panel_handle, dsp_buffer);
    }
}

#define GET_RGB565_R(x) (((((x) >> 11) & 0x1F) << 3) | (((x) >> 8) & 0x07))
#define GET_RGB565_G(x) (((((x) >> 5) & 0x3F) << 2) | (((x) >> 3) & 0x03))
#define GET_RGB565_B(x) ((((x) & 0x1F) << 3) | (((x) & 0x1C) >> 2))
#define SWAP_EDIAN(x)   (((x) << 8) | ((x) >> 8))
static void brtc_demo_rgb565_2_rgb888_row(uint8_t *src565, uint8_t *dst888, int width) {
    for (int i = 0; i < width; ++i) {
        uint16_t rgb = *(uint16_t*)src565;
        rgb = SWAP_EDIAN(rgb);
        dst888[0] = GET_RGB565_R(rgb);
        dst888[1] = GET_RGB565_G(rgb);
        dst888[2] = GET_RGB565_B(rgb);
        dst888 += 3;
        src565 += 2;
    }
}

static int brtc_demo_jpeg_enc(unsigned char *outbuf, const unsigned char *inbuf, size_t in_len) {
    int jpeg_size = 0;

    if (!jpg_enc.handle) {
        jpeg_enc_config_t jpeg_enc_cfg = DEFAULT_JPEG_ENC_CONFIG();
        jpeg_enc_cfg.width = BRTC_DEMO_JPG_W;
        jpeg_enc_cfg.height = BRTC_DEMO_JPG_H;
        if (camera_config.pixel_format == PIXFORMAT_RGB565) {
            jpeg_enc_cfg.src_type = JPEG_PIXEL_FORMAT_RGB888;
        } else if (camera_config.pixel_format == PIXFORMAT_YUV422) {
            jpeg_enc_cfg.src_type = JPEG_PIXEL_FORMAT_YCbYCr;
        }
        jpeg_enc_cfg.subsampling = JPEG_SUBSAMPLE_420;
        jpeg_enc_cfg.quality = 30;
        jpeg_enc_cfg.rotate = JPEG_ROTATE_0D;
        jpeg_enc_cfg.task_enable = false;
        jpeg_enc_cfg.hfm_task_priority = 13;
        jpeg_enc_cfg.hfm_task_core = 1;
        jpeg_enc_open(&jpeg_enc_cfg, &jpg_enc.handle);
        if (jpg_enc.handle == NULL) {
            return jpeg_size;
        }
    }
    int out_len = BRTC_DEMO_JPG_W * BRTC_DEMO_JPG_H * 2;
    jpeg_enc_process(jpg_enc.handle, inbuf, in_len, outbuf, out_len, &jpeg_size);
    return jpeg_size;
}

static void brtc_demo_video_decoder_and_render(void * arg) {

    while (1) {
        brtc_demo_video_pkg_t pkg;
        if (xQueueReceive(brtc_demo_video_pkg_queue, &pkg, portMAX_DELAY) == pdPASS) {
            if (pkg.size > 0) {
                brtc_demo_disp_jpg(pkg.buffer, pkg.size);
                free(pkg.buffer);
                pkg.buffer = NULL;
                pkg.size = 0;
            }
        }
    }

}
static void brtc_demo_cam_task(void * arg) {
    int maxbytes = BRTC_DEMO_LCD_H * BRTC_DEMO_LCD_V * 2;

    while (brtcDemoRunning) {
        if (brtcDemoCapture) {
            camera_fb_t *pic = esp_camera_fb_get();
            if (pic) {
                if (pic->buf && pic->len > 0) {
                    mutex_lock(jpg_enc.lock);
                    memcpy(cam_buffer, pic->buf, pic->len < maxbytes ? pic->len : maxbytes);
                    cam_image_size = pic->len;
                    mutex_unlock(jpg_enc.lock);
                }
                esp_camera_fb_return(pic);
            } else {
                vTaskDelay(pdMS_TO_TICKS(100));
            }

        } else {
            vTaskDelay(pdMS_TO_TICKS(100));
        }
    }
    BRTC_LOG("cam task exit(%d)\n", uxTaskGetStackHighWaterMark(NULL));
    vTaskDelete(NULL);
}

static void brtc_demo_vsend_task(void * arg) {
    while (brtcDemoRunning) {
        uint32_t cur_time = esp_timer_get_time() / 1000;
        if (brtcDemoCapture) {
            int image_size = 0;
            mutex_lock(jpg_enc.lock);
            if (camera_config.pixel_format == PIXFORMAT_JPEG) {
                memcpy(rgb_buffer, cam_buffer, cam_image_size);
                jpg_enc.buffer = rgb_buffer;
                jpg_enc.size = cam_image_size;
            } if (camera_config.pixel_format == PIXFORMAT_YUV422) {
                memcpy(rgb_buffer, cam_buffer, cam_image_size);
                image_size = cam_image_size;
            } else {
                uint8_t* src565 = cam_buffer;
                uint8_t* dst888 = rgb_buffer;
                for (int i = 0; i < BRTC_DEMO_LCD_V; i++) {
                    brtc_demo_rgb565_2_rgb888_row(src565, dst888, BRTC_DEMO_JPG_W);
                    src565 += BRTC_DEMO_LCD_H * 2;
                    dst888 += BRTC_DEMO_JPG_W * 3;
                }
                image_size = BRTC_DEMO_JPG_W * BRTC_DEMO_JPG_H * 3;
            }
            mutex_unlock(jpg_enc.lock);
            if (camera_config.pixel_format != PIXFORMAT_JPEG) {
                jpg_enc.size = brtc_demo_jpeg_enc(jpg_enc.buffer, rgb_buffer, image_size);
            }
            if (jpg_enc.size) {
                //trace_size("Vsend", jpg_enc.size);
                brtc_send_image(gBrtcClient, jpg_enc.buffer, jpg_enc.size);
            }
        }
        vTaskDelay(pdMS_TO_TICKS(100 - (esp_timer_get_time() / 1000 - cur_time)));
    }
    vTaskDelete(NULL);
}
#endif // BRTC_DEMO_VIDEO

// Callback for receiving remote video frames from RTC
static void OnVideoFrame(int64_t feedid, const char *img, int len, RtcImageType imgtype, int width, int height){
    if (brtcDemoRunning == false) {
        return;
    }
    //trace_size("VRecv", len);
#ifdef BRTC_DEMO_VIDEO
    brtc_demo_video_pkg_t pkg;
    pkg.buffer = jpeg_calloc_align(len,16);
    memcpy(pkg.buffer, img, len);
    pkg.size = len;
    xQueueSend(brtc_demo_video_pkg_queue, &pkg, 10);
#endif
}

/******************************************************************************
 ******************************************************************************
 **
 ** Baidu RTC Section
 **
 ******************************************************************************
 ******************************************************************************/
static char usrId[20] = "9999";
static const char appId[] = "xxxx";  //使用你们自己的appId
static const char roomName[] = "1977";
static const char token[] = "no_token";
static const char display[30] = "espcall";
//const char brtcserver[125] = "udp://rtc-ss-udp.exp.bcelive.com:8202";
const char brtcserver[125] = "ws://rtc.exp.bcelive.com:8186/janus";

const char *ca_info = "-----BEGIN CERTIFICATE-----\r\n\
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\
-----END CERTIFICATE-----";
static void OnRtcMessage(RtcMessage* msg)
{
    BRTC_LOG("OnRtcMessage %d\n", msg->msgType);
    switch (msg->msgType) {
    case RTC_MESSAGE_ROOM_EVENT_LOGIN_OK: {
        BRTC_LOG("Login ok!\n");
    } break;
    case RTC_MESSAGE_ROOM_EVENT_LOGIN_ERROR: {
        BRTC_LOG("Login error, exit sdk!\n");
        g_brtc_need_exit = true;
        //brtc_demo_exit();
    } break;
    case RTC_MESSAGE_ROOM_EVENT_CONNECTION_LOST: {
        BRTC_LOG("auto reconnect !\n");
        //g_brtc_need_exit = true;
        //brtc_demo_exit();
        brtc_sdk_do_async(brtc_demo_auto_reconnect, NULL, 50);
    } break;
    case RTC_MESSAGE_ROOM_EVENT_REMOTE_COMING: {
        BRTC_LOG("Feed Coming! id: %lld, Name: %s.\n", msg->data.feedId, msg->extra_info);
    } break;
    case RTC_MESSAGE_ROOM_EVENT_REMOTE_LEAVING: {
        BRTC_LOG("Feed Leaving ! id: %lld.\n", msg->data.feedId);
    } break;
    case RTC_MESSAGE_STATE_STREAM_UP: {
        BRTC_LOG("stream up, send/got video now.\n");
        brtcDemoCapture = true;
    } break;
    case RTC_ROOM_EVENT_ON_USER_MESSAGE: {
        BRTC_LOG("Got User Message id: %lld, msg: '%s'.\n", msg->data.feedId, msg->extra_info);
    } break;
    case RTC_ROOM_EVENT_ON_USER_JOINED_ROOM: {
        BRTC_LOG("User joined room id: %lld, display: %s.\n", msg->data.feedId, msg->extra_info);
    } break;
    case RTC_ROOM_EVENT_ON_USER_LEAVING_ROOM: {
        BRTC_LOG("User leaving room id: %lld.\n", msg->data.feedId);
        g_brtc_need_exit = true;
        //brtc_demo_exit();
    } break;

    default:
        break;
    }
}

void brtc_demo_init() {
    sprintf(usrId, "600%04d", esp_random() % 10000 + 2);
    brtc_sdk_enable_log(0);
    brtc_sdk_enable_nack(1);

    brtc_sdk_init();

    if (gBrtcClient == NULL) {
        BRTC_LOG("create client success\n");
        gBrtcClient = brtc_create_client();
    }

    if (gBrtcClient) {
        brtc_register_message_listener(gBrtcClient, OnRtcMessage);
        brtc_register_audio_frame_observer(gBrtcClient, OnAudioFrame);
        brtc_register_video_frame_observer(gBrtcClient, OnVideoFrame);
        brtc_set_cer(gBrtcClient, ca_info);
        brtc_set_server_url(gBrtcClient, brtcserver);
        brtc_set_appid(gBrtcClient, appId);

        brtc_set_auto_publish(gBrtcClient, 1);
        brtc_set_auto_subscribe(gBrtcClient, 1);

        brtc_set_audiocodec(gBrtcClient, "pcmu");
#ifdef BRTC_DEMO_VIDEO
        brtc_set_videocodec(gBrtcClient, "jpeg");
#else
        brtc_set_usingvideo(gBrtcClient, 0);
        brtc_set_receivingvideo(gBrtcClient, 0);
#endif
        BRTC_LOG("brtc_init_sdk success\n");
    }
}

void brtc_demo_login() {
    bool ret = false;
    if (gBrtcClient) {
        ret = brtc_login_room(gBrtcClient, roomName, usrId, display, token);
        if (ret) {
            BRTC_LOG("brtc_login_room success\n");
        } else {
            BRTC_LOG("brtc_login_room failed\n");
        }
    }
}

static void brtc_demo_logout() {
    bool ret = false;
    if (gBrtcClient) {
        ret = brtc_logout_room(gBrtcClient);
        if (ret) {
            BRTC_LOG("logout room success\r\n");
        } else {
            BRTC_LOG("logout room failed\r\n");
        }
    }
}

static void brtc_demo_exit(void) {
    brtc_demo_logout();
    if (gBrtcClient) {
        brtcDemoCapture = false;
        brtcDemoRunning = false;
        brtc_destroy_client(gBrtcClient);
        brtc_sdk_deinit();
        gBrtcClient = NULL;
#ifdef BRTC_DEMO_VIDEO
        if (jpg_enc.lock) {
            mutex_destroy(jpg_enc.lock);
            jpg_enc.lock = NULL;
        }
#endif
        BRTC_LOG("destroy success\r\n");
    }
}

static void brtc_demo_auto_reconnect(void *h)
{
    brtc_demo_logout();
    brtc_sdk_do_async(brtc_demo_login, NULL, 50);
}

void brtc_demo_start(void* arg) {
    // init sdk and login room
    brtc_demo_init();
    brtc_demo_login();
    vTaskDelete(NULL);
}

/******************************************************************************
 ******************************************************************************
 **
 ** app_main Section: Entry Point and Task Orchestration
 **
 ******************************************************************************
 ******************************************************************************/
void brtc_demo_create_task(TaskFunction_t func, const char* task_name, uint32_t stack_size, UBaseType_t uxPriority) {
     audio_thread_create(NULL, task_name, func, NULL, stack_size, uxPriority, true, 0);
}

void app_main(void)
{
    brtcDemoRunning = true;
    brtcDemoCapture = false;

#ifdef BRTC_DEMO_STATS
    if (!brtc_stats_looping) {
        // xTaskCreatePinnedToCore(
        //     brtc_demo_stats, "brtc_demo_stats", 1024 * 8, NULL, 10, NULL, 0);
        audio_thread_create(NULL, "brtc_demo_stats", brtc_demo_stats, NULL, 1024 * 8, 10, true, 0);
    }
#endif

    brtc_demo_init_wifi();
    brtc_demo_create_task(brtc_demo_start, "brtc_demo_start", 1024 * 16, 20);

#ifdef BRTC_DEMO_AUDIO
    // xTaskCreatePinnedToCore(
    //         brtc_demo_audio, "brtc_demo_audio", 1024 * 4, NULL, 20, NULL, 0);
    audio_thread_create(NULL, "brtc_demo_audio", brtc_demo_audio, NULL, 1024 * 4, 20, true, 0);
#endif

#ifdef BRTC_DEMO_VIDEO
    memset(&jpg_enc, 0, sizeof(brtc_enc_jpg_s));

    esp_err_t err = esp_camera_init(&camera_config);
    if (err != ESP_OK) {
        BRTC_LOG("brtc demo camera init failed\n");
        return;
    }

    dsp_buffer = heap_caps_calloc(1, BRTC_DEMO_LCD_H * BRTC_DEMO_LCD_V * 2, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
    rgb_buffer = heap_caps_calloc(1, BRTC_DEMO_JPG_W * BRTC_DEMO_JPG_H * 3, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
    cam_buffer = heap_caps_calloc(1, BRTC_DEMO_LCD_H * BRTC_DEMO_LCD_V * 2, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
    jpg_enc.buffer = heap_caps_calloc(1, BRTC_DEMO_JPG_W * BRTC_DEMO_JPG_H * 2, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
    jpg_enc.lock = mutex_create();


    brtc_demo_video_pkg_queue = xQueueCreate(5, sizeof(brtc_demo_video_pkg_t));
    // create send image task, control the send fps
    audio_thread_create(NULL, "brtc_demo_vsend", brtc_demo_vsend_task, NULL, 20 * 1024, 15, true, 1);

    // create camera capture task
    audio_thread_create(NULL, "brtc_demo_cam", brtc_demo_cam_task, NULL, 8 * 1024, 14, true, 1);
    audio_thread_create(NULL, "brtc_demo_video_decoder_and_render", brtc_demo_video_decoder_and_render, NULL, 8 * 1024, 14, true, 0);
#endif
}
