#include #include #include #include "ql_audio.h" #include "ql_fs.h" #include "ql_gpio.h" #include "ql_rtos.h" #include "factory_audio.h" #include "factory_test.h" #define ES8311_ADDR 0x18 #define ES8311_DEVICE_ID_REG 0xFD #define ES8311_DEVICE_ID 0x83 #define ES8311_VOLUME_100 0x8E #define FT_PCM_CHUNK_BYTES 640 #define FT_PCM_CHUNK_MS 20 #define FT_PCM_WRITE_RETRIES 40 #define FT_MP3_TIMEOUT_MS 15000 extern void sw_i2c_init(void); extern int8 sw_i2c_start(void); extern void sw_i2c_stop(void); extern int8 sw_i2c_write_byte(uint8_t data); extern uint8_t sw_i2c_read_byte(uint8_t ack); static const uint16_t g_es8311_init_regs[] = { 0x0045, 0x3001, 0x1002, 0x0002, 0x1003, 0x2416, 0x2004, 0x0005, 0x2006, 0x0007, 0xff08, 0x0f09, 0x0f0a, 0x000b, 0x000c, 0x1310, 0x7f11, 0x3f01, 0x8000, 0x010d, 0x1014, 0x2812, 0x1013, 0x020e, 0x440f, 0x0015, 0x0a1b, 0x6a1c, 0x0837, 0x0044, 0xa717, 0x8e32, 0x0018 }; static ql_sem_t g_mp3_sem; static volatile int g_mp3_result; static Mp3PlaybackHandle g_mp3_handle; static int es8311_write(uint8_t reg, uint8_t value) { if (sw_i2c_start() != 0) return -1; if (sw_i2c_write_byte((uint8_t)(ES8311_ADDR << 1)) != 0 || sw_i2c_write_byte(reg) != 0 || sw_i2c_write_byte(value) != 0) { sw_i2c_stop(); return -1; } sw_i2c_stop(); return 0; } static int es8311_read(uint8_t reg, uint8_t *value) { if (!value || sw_i2c_start() != 0) return -1; if (sw_i2c_write_byte((uint8_t)(ES8311_ADDR << 1)) != 0 || sw_i2c_write_byte(reg) != 0 || sw_i2c_start() != 0 || sw_i2c_write_byte((uint8_t)((ES8311_ADDR << 1) | 1)) != 0) { sw_i2c_stop(); return -1; } *value = sw_i2c_read_byte(0); sw_i2c_stop(); return 0; } static void factory_pa_control(unsigned int on) { ql_gpio_init(FT_PA_PIN, PIN_DIRECTION_OUT, PIN_PULL_DISABLE, on ? PIN_LEVEL_HIGH : PIN_LEVEL_LOW); } static void factory_mp3_callback(Mp3PlayEventType event, int value) { if (event != MP3_PLAYBACK_EVENT_STATUS) return; if (value == MP3_PLAYBACK_STATUS_STARTED) { g_mp3_result = 1; } else if (value == MP3_PLAYBACK_STATUS_ENDED) { g_mp3_result = 2; if (g_mp3_sem) ql_rtos_semaphore_release(g_mp3_sem); } else if (value == MP3_PLAYBACK_STATUS_TIMEOUT) { g_mp3_result = -1; if (g_mp3_sem) ql_rtos_semaphore_release(g_mp3_sem); } } static void factory_track_callback(audio_track_handle handle, acm_audio_track_event_t event) { (void)handle; (void)event; } static void factory_record_callback(audio_record_handle handle, acm_audio_record_event_t event) { (void)handle; (void)event; } int factory_audio_init(void) { uint8_t id = 0; unsigned int i; u32 start = ft_now_ms(); ql_codecpcm_config pcm_config[] = { PCM_FS_256, PCM_CODEC_SAMPLE_16000, 0 }; while (!ql_get_audio_state()) { if (ft_now_ms() - start > 10000) return -1; ql_rtos_task_sleep_ms(50); } sw_i2c_init(); if (es8311_read(ES8311_DEVICE_ID_REG, &id) != 0 || id != ES8311_DEVICE_ID) { return -1; } ql_bind_speakerpa_cb(factory_pa_control); ql_codec_choose(AUD_EXTERNAL_CODEC, pcm_config); for (i = 0; i < sizeof(g_es8311_init_regs) / sizeof(g_es8311_init_regs[0]); ++i) { if (es8311_write((uint8_t)(g_es8311_init_regs[i] & 0xff), (uint8_t)(g_es8311_init_regs[i] >> 8)) != 0) { return -1; } } ql_set_audio_path_speaker(); ql_set_volume(AUDIOHAL_SPK_VOL_11); es8311_write(0x32, ES8311_VOLUME_100); factory_pa_control(0); if (!g_mp3_sem && ql_rtos_semaphore_create(&g_mp3_sem, 0) != 0) { return -1; } return 0; } int factory_audio_play_prompt(const char *path) { Mp3PlayConfigInfo config; int ret; if (!path || ql_access(path, 0) != 0) return -2; while (ql_rtos_semaphore_wait(g_mp3_sem, QL_NO_WAIT) == 0) { } memset(&config, 0, sizeof(config)); config.listener = factory_mp3_callback; config.sync_mode = 0; g_mp3_result = 0; es8311_write(0x32, ES8311_VOLUME_100); ql_set_volume(AUDIOHAL_SPK_VOL_11); factory_pa_control(1); ret = ql_play_mp3((char *)path, &g_mp3_handle, &config); if (ret != 0) { factory_pa_control(0); return -1; } ret = ql_rtos_semaphore_wait(g_mp3_sem, FT_MP3_TIMEOUT_MS); if (ret != 0 || g_mp3_result != 2) { ql_stop_mp3_play(g_mp3_handle, 0); factory_pa_control(0); return -1; } factory_pa_control(0); return 0; } static int wait_loopback_key_level(PIN_LEVEL_E expected, u32 timeout_ms) { PIN_LEVEL_E level = PIN_LEVEL_HIGH; u32 start = ft_now_ms(); do { ql_gpio_get_level(FT_LOOPBACK_KEY_PIN, &level); if (level == expected) { ql_rtos_task_sleep_ms(40); ql_gpio_get_level(FT_LOOPBACK_KEY_PIN, &level); if (level == expected) return 0; } ql_rtos_task_sleep_ms(10); } while (ft_now_ms() - start < timeout_ms); return -1; } int factory_audio_wait_and_loopback(u32 wait_timeout_ms, u32 *recorded_ms) { unsigned char *buffer = NULL; unsigned int total = 0; unsigned int request; unsigned int offset; audio_record_handle record_handle = 0; audio_track_handle track_handle = 0; int ret = -1; u32 release_wait_start = 0; if (recorded_ms) *recorded_ms = 0; ql_gpio_init(FT_LOOPBACK_KEY_PIN, PIN_DIRECTION_IN, PIN_PULL_PU, PIN_LEVEL_HIGH); if (wait_loopback_key_level(PIN_LEVEL_HIGH, 5000) != 0 || wait_loopback_key_level(PIN_LEVEL_LOW, wait_timeout_ms) != 0) { return -4; } buffer = (unsigned char *)malloc(FT_AUDIO_BUFFER_BYTES); if (!buffer) { ret = -1; goto cleanup; } record_handle = ql_audio_record_open(FT_AUDIO_RATE_HZ, FT_AUDIO_CHANNELS, 0, factory_record_callback); if (!record_handle) goto cleanup; while (total < FT_AUDIO_BUFFER_BYTES) { PIN_LEVEL_E level = PIN_LEVEL_HIGH; ql_gpio_get_level(FT_LOOPBACK_KEY_PIN, &level); if (level != PIN_LEVEL_LOW) break; request = FT_PCM_CHUNK_BYTES; if (request > FT_AUDIO_BUFFER_BYTES - total) { request = FT_AUDIO_BUFFER_BYTES - total; } if (ql_audio_record_read(record_handle, (char *)(buffer + total), &request) == 0 && request > 0) { total += request; } ql_rtos_task_sleep_ms(10); } ql_audio_record_close(record_handle, 0); record_handle = 0; { PIN_LEVEL_E level = PIN_LEVEL_HIGH; ql_gpio_get_level(FT_LOOPBACK_KEY_PIN, &level); if (total == FT_AUDIO_BUFFER_BYTES && level == PIN_LEVEL_LOW) { release_wait_start = ft_now_ms(); while (1) { ql_gpio_get_level(FT_LOOPBACK_KEY_PIN, &level); if (level != PIN_LEVEL_LOW) break; if (ft_now_ms() - release_wait_start >= 30000) goto cleanup; ql_rtos_task_sleep_ms(10); } ql_rtos_task_sleep_ms(40); } else if (wait_loopback_key_level(PIN_LEVEL_HIGH, 3000) != 0) { goto cleanup; } } if (total < FT_PCM_CHUNK_BYTES) { ret = -2; goto cleanup; } if (recorded_ms) { *recorded_ms = total * 1000U / (FT_AUDIO_RATE_HZ * 2U * FT_AUDIO_CHANNELS); } es8311_write(0x32, ES8311_VOLUME_100); ql_set_volume(AUDIOHAL_SPK_VOL_11); factory_pa_control(1); /* Match the SDK PCM path and feed the asynchronous track in real time. */ track_handle = ql_audio_track_open(FT_AUDIO_RATE_HZ, FT_AUDIO_CHANNELS, 0x100, factory_track_callback); if (!track_handle) { ret = -3; goto cleanup; } for (offset = 0; offset < total; offset += FT_PCM_CHUNK_BYTES) { unsigned int size = total - offset; unsigned int retries = 0; if (size > FT_PCM_CHUNK_BYTES) size = FT_PCM_CHUNK_BYTES; while (ql_audio_track_write(track_handle, (char *)(buffer + offset), size) != 0) { if (++retries > FT_PCM_WRITE_RETRIES) { ret = -3; goto cleanup; } ql_rtos_task_sleep_ms(5); } ql_rtos_task_sleep_ms(FT_PCM_CHUNK_MS); } ql_rtos_task_sleep_ms(total * 1000U / (FT_AUDIO_RATE_HZ * 2U * FT_AUDIO_CHANNELS) + 300U); ret = 0; cleanup: if (record_handle) ql_audio_record_close(record_handle, 1); if (track_handle) ql_audio_track_close(track_handle, ret == 0 ? 0 : 1); factory_pa_control(0); if (buffer) free(buffer); return ret; }