mp3_demo - Changes for audio subsystem and file_iterator components
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
idf_component_register(
|
||||
SRCS
|
||||
"mp3_demo.c"
|
||||
"audio.c"
|
||||
"ui_audio.c"
|
||||
INCLUDE_DIRS
|
||||
"include")
|
||||
|
||||
@@ -1,466 +0,0 @@
|
||||
/**
|
||||
* @file
|
||||
* @version 0.1
|
||||
* @date 2021-12-06
|
||||
*
|
||||
* @copyright Copyright 2021 Espressif Systems (Shanghai) Co. Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <dirent.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "driver/i2s.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#include "audio.h"
|
||||
#include "bsp_i2s.h"
|
||||
#include "bsp_codec.h"
|
||||
#include "mp3dec.h"
|
||||
|
||||
static const char *TAG = "audio";
|
||||
|
||||
/* **************** PRIVATE STRUCT **************** */
|
||||
typedef struct {
|
||||
char header[3]; /*!< Always "TAG" */
|
||||
char title[30]; /*!< Audio title */
|
||||
char artist[30]; /*!< Audio artist */
|
||||
char album[30]; /*!< Album name */
|
||||
char year[4]; /*!< Char array of year */
|
||||
char comment[30]; /*!< Extra comment */
|
||||
char genre; /*!< See "https://en.wikipedia.org/wiki/ID3" */
|
||||
} __attribute__((packed)) mp3_id3_header_v1_t;
|
||||
|
||||
typedef struct {
|
||||
char header[3]; /*!< Always "ID3" */
|
||||
char ver; /*!< Version, equals to3 if ID3V2.3 */
|
||||
char revision; /*!< Revision, should be 0 */
|
||||
char flag; /*!< Flag byte, use Bit[7..5] only */
|
||||
char size[4]; /*!< TAG size */
|
||||
} __attribute__((packed)) mp3_id3_header_v2_t;
|
||||
|
||||
/* **************** AUDIO CALLBACK **************** */
|
||||
static audio_cb_t s_audio_cb = NULL;
|
||||
static void *audio_cb_usrt_ctx = NULL;
|
||||
|
||||
esp_err_t audio_callback_register(audio_cb_t call_back, void *user_ctx)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_executable(call_back), ESP_ERR_INVALID_ARG,
|
||||
TAG, "Not a valid call back");
|
||||
|
||||
s_audio_cb = call_back;
|
||||
audio_cb_usrt_ctx = user_ctx;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* **************** AUDIO FILE SCAN **************** */
|
||||
static size_t audio_count = 0;
|
||||
static size_t audio_index = 0;
|
||||
static char **audio_list = NULL;
|
||||
|
||||
static esp_err_t audio_file_scan(char *base_path)
|
||||
{
|
||||
audio_count = 0;
|
||||
struct dirent *p_dirent = NULL;
|
||||
DIR *p_dir_stream = opendir(base_path);
|
||||
|
||||
do { /* Get total file count */
|
||||
p_dirent = readdir(p_dir_stream);
|
||||
if (NULL != p_dirent) {
|
||||
audio_count++;
|
||||
} else {
|
||||
closedir(p_dir_stream);
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
audio_list = (char **) malloc(audio_count * sizeof(char *));
|
||||
ESP_RETURN_ON_FALSE(NULL != audio_list, ESP_ERR_NO_MEM,
|
||||
TAG, "Failed allocate audio list buffer");
|
||||
|
||||
p_dir_stream = opendir(base_path);
|
||||
for (size_t i = 0; i < audio_count; i++) {
|
||||
p_dirent = readdir(p_dir_stream);
|
||||
if (NULL != p_dirent) {
|
||||
audio_list[i] = malloc(sizeof(p_dirent->d_name));
|
||||
ESP_LOGI(TAG, "File : %s", strcpy(audio_list[i], p_dirent->d_name));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "The file system may be corrupted");
|
||||
closedir(p_dir_stream);
|
||||
for (int j = i - 1; j >= 0; j--) {
|
||||
free(audio_list[i]);
|
||||
}
|
||||
free(audio_list);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(p_dir_stream);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
size_t audio_get_index(void)
|
||||
{
|
||||
return audio_index;
|
||||
}
|
||||
|
||||
char *audio_get_name_from_index(size_t index, char *base_path)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(index < audio_count, NULL,
|
||||
TAG, "File index out of range");
|
||||
|
||||
ESP_RETURN_ON_FALSE(NULL != audio_list, NULL,
|
||||
TAG, "Audio file not found");
|
||||
|
||||
ESP_RETURN_ON_FALSE(NULL != audio_list[index], NULL,
|
||||
TAG, "Audio file not found");
|
||||
|
||||
if (NULL != base_path) {
|
||||
strcat(strcat(base_path, "/"), audio_list[index]);
|
||||
}
|
||||
|
||||
return audio_list[index];
|
||||
}
|
||||
|
||||
/* **************** AUDIO DECODE **************** */
|
||||
static QueueHandle_t audio_event_queue = NULL;
|
||||
|
||||
static esp_err_t aplay_mp3(const char *path)
|
||||
{
|
||||
ESP_LOGI(TAG, "start to decode %s", path);
|
||||
|
||||
FILE *fp = NULL;
|
||||
int sample_rate = 0;
|
||||
esp_err_t ret = ESP_OK;
|
||||
uint8_t *output = NULL;
|
||||
uint8_t *read_buf = NULL;
|
||||
MP3FrameInfo frame_info;
|
||||
HMP3Decoder mp3_decoder = MP3InitDecoder();
|
||||
audio_event_t audio_event = AUDIO_EVENT_NONE;
|
||||
|
||||
ESP_RETURN_ON_FALSE(NULL != mp3_decoder, ESP_ERR_NO_MEM,
|
||||
TAG, "Failed create MP3 decoder");
|
||||
|
||||
read_buf = malloc(MAINBUF_SIZE);
|
||||
ESP_GOTO_ON_FALSE(NULL != read_buf, ESP_ERR_NO_MEM, clean_up,
|
||||
TAG, "Failed allocate read buffer");
|
||||
|
||||
output = malloc(1152 * sizeof(int16_t) * 2);
|
||||
ESP_GOTO_ON_FALSE(NULL != output, ESP_ERR_NO_MEM, clean_up,
|
||||
TAG, "Failed allocate output buffer");
|
||||
|
||||
/* Read audio file from given path */
|
||||
fp = fopen(path, "rb");
|
||||
ESP_GOTO_ON_FALSE(NULL != fp, ESP_ERR_NOT_FOUND, clean_up,
|
||||
TAG, "File \"%s\" does not exist", path);
|
||||
|
||||
/* Get ID3 head */
|
||||
mp3_id3_header_v2_t tag;
|
||||
if (sizeof(mp3_id3_header_v2_t) == fread(&tag, 1, sizeof(mp3_id3_header_v2_t), fp)) {
|
||||
if (memcmp("ID3", (const void *) &tag, sizeof(tag.header)) == 0) {
|
||||
int tag_len =
|
||||
((tag.size[0] & 0x7F) << 21) +
|
||||
((tag.size[1] & 0x7F) << 14) +
|
||||
((tag.size[2] & 0x7F) << 7) +
|
||||
((tag.size[3] & 0x7F) << 0);
|
||||
fseek(fp, tag_len - sizeof(mp3_id3_header_v2_t), SEEK_SET);
|
||||
} else {
|
||||
/* Not ID3 header */
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
}
|
||||
}
|
||||
|
||||
/* Start MP3 decoding */
|
||||
int bytes_left = 0;
|
||||
unsigned char *read_ptr = read_buf;
|
||||
do {
|
||||
/* Process audio event sent from other task */
|
||||
if (pdPASS == xQueueReceive(audio_event_queue, &audio_event, 0)) {
|
||||
if (AUDIO_EVENT_PAUSE == audio_event) {
|
||||
i2s_zero_dma_buffer(I2S_NUM_0);
|
||||
xQueuePeek(audio_event_queue, &audio_event, portMAX_DELAY);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (AUDIO_EVENT_CHANGE == audio_event) {
|
||||
i2s_zero_dma_buffer(I2S_NUM_0);
|
||||
ret = ESP_FAIL;
|
||||
goto clean_up;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read `mainDataBegin` size to RAM */
|
||||
if (bytes_left < MAINBUF_SIZE) {
|
||||
memmove(read_buf, read_ptr, bytes_left);
|
||||
size_t bytes_read = fread(read_buf + bytes_left, 1, MAINBUF_SIZE - bytes_left, fp);
|
||||
ESP_GOTO_ON_FALSE(bytes_read > 0, ESP_OK, clean_up,
|
||||
TAG, "No data read from strorage device");
|
||||
bytes_left = bytes_left + bytes_read;
|
||||
read_ptr = read_buf;
|
||||
}
|
||||
|
||||
/* Find MP3 sync word from read buffer */
|
||||
int offset = MP3FindSyncWord(read_buf, MAINBUF_SIZE);
|
||||
|
||||
if (offset >= 0) {
|
||||
read_ptr += offset; /*!< Data start point */
|
||||
bytes_left -= offset; /*!< In buffer */
|
||||
int mp3_dec_err = MP3Decode(mp3_decoder, &read_ptr, &bytes_left, (int16_t *) output, 0);
|
||||
ESP_GOTO_ON_FALSE(ERR_MP3_NONE == mp3_dec_err, ESP_FAIL, clean_up,
|
||||
TAG, "Can't decode MP3 frame");
|
||||
|
||||
/* Get MP3 frame info and configure I2S clock */
|
||||
MP3GetLastFrameInfo(mp3_decoder, &frame_info);
|
||||
|
||||
// if mono, convert to stereo as es8311 requires stereo input
|
||||
// even though it is mono output
|
||||
if(frame_info.nChans == 1) {
|
||||
size_t new_output_sample_count = frame_info.outputSamps * 2;
|
||||
|
||||
// convert from back to front to allow conversion in-place
|
||||
int16_t *out = (int16_t*)output + new_output_sample_count;
|
||||
int16_t *in = (int16_t*)output + frame_info.outputSamps;
|
||||
size_t samples = frame_info.outputSamps;
|
||||
while(samples) {
|
||||
// write right channel
|
||||
*out = *in;
|
||||
out--;
|
||||
|
||||
// write left channel
|
||||
*out = *in;
|
||||
out--;
|
||||
|
||||
// move input buffer back and decrement samples
|
||||
in--;
|
||||
samples--;
|
||||
}
|
||||
|
||||
// adjust channels to 2
|
||||
frame_info.nChans = 2;
|
||||
|
||||
// recalculate output sample count as its calculated by (channels * samples per frame)
|
||||
frame_info.outputSamps = new_output_sample_count;
|
||||
}
|
||||
|
||||
/* Configure I2S clock if sample rate changed. Always reconfigure at first frame */
|
||||
if (sample_rate != frame_info.samprate) {
|
||||
sample_rate = frame_info.samprate;
|
||||
uint32_t bits_cfg = frame_info.bitsPerSample;
|
||||
i2s_channel_t channel = (frame_info.nChans == 1) ? I2S_CHANNEL_MONO : I2S_CHANNEL_STEREO;
|
||||
i2s_set_clk(I2S_NUM_0, sample_rate, bits_cfg, channel);
|
||||
}
|
||||
|
||||
/* Write decoded data to audio decoder */
|
||||
size_t i2s_bytes_written = 0;
|
||||
size_t output_size = frame_info.outputSamps * frame_info.nChans;
|
||||
i2s_write(I2S_NUM_0, output, output_size, &i2s_bytes_written, portMAX_DELAY);
|
||||
} else {
|
||||
/* Sync word not found in frame. Try to read next frame */
|
||||
ESP_LOGE(TAG, "MP3 sync word not found");
|
||||
bytes_left = 0;
|
||||
continue;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
clean_up:
|
||||
/* This will prevent from sending dumy data to audio decoder */
|
||||
i2s_zero_dma_buffer(I2S_NUM_0);
|
||||
|
||||
/* Clean up resources */
|
||||
if (NULL != mp3_decoder) MP3FreeDecoder(mp3_decoder);
|
||||
if (NULL != fp) fclose(fp);
|
||||
if (NULL != read_buf) free(read_buf);
|
||||
if (NULL != output) free(output);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void audio_task(void *pvParam)
|
||||
{
|
||||
/* Scan audio file */
|
||||
char *base_path = (char *) pvParam;
|
||||
if (NULL != base_path) {
|
||||
audio_file_scan(base_path);
|
||||
if (esp_ptr_executable(s_audio_cb)) {
|
||||
audio_cb_ctx_t ctx = {
|
||||
.audio_event = AUDIO_EVENT_FILE_SCAN_DONE,
|
||||
.user_ctx = audio_cb_usrt_ctx,
|
||||
};
|
||||
s_audio_cb(&ctx);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid base path");
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize I2S and audio codec
|
||||
*
|
||||
* @note `MP3GetLastFrameInfo` is used to fill the `MP3FrameInfo`, which includes `samprate`,
|
||||
* and the sampling rate is updated during playback using this value.
|
||||
*/
|
||||
bsp_codec_init(AUDIO_HAL_44K_SAMPLES);
|
||||
bsp_i2s_init(I2S_NUM_0, 44100);
|
||||
|
||||
/* Audio control event queue */
|
||||
audio_event_queue = xQueueCreate(4, sizeof(audio_event_t));
|
||||
if (NULL == audio_event_queue) {
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/* Get name of first song to play */
|
||||
char full_name[256] = { [0 ... sizeof(full_name) - 1] = '\0' };
|
||||
strcpy(full_name, base_path);
|
||||
char *file_name = audio_get_name_from_index(audio_index, full_name);
|
||||
|
||||
/* Start play music */
|
||||
while (vTaskDelay(1), NULL != file_name) {
|
||||
esp_err_t ret_val = aplay_mp3(full_name);
|
||||
|
||||
/* Get next audio's name if audio played without error */
|
||||
if (ESP_OK == ret_val) {
|
||||
audio_index ++;
|
||||
if (audio_index >= audio_count) {
|
||||
audio_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Callback for audio index change */
|
||||
if (esp_ptr_executable(s_audio_cb)) {
|
||||
audio_cb_ctx_t ctx = {
|
||||
.audio_event = AUDIO_EVENT_CHANGE,
|
||||
.user_ctx = audio_cb_usrt_ctx,
|
||||
};
|
||||
s_audio_cb(&ctx);
|
||||
}
|
||||
|
||||
/* Get next song's file name */
|
||||
file_name = audio_get_name_from_index(audio_index, strcpy(full_name, base_path));
|
||||
}
|
||||
|
||||
/* Task never returns */
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/* **************** AUDIO PLAY CONTROL **************** */
|
||||
esp_err_t audio_play(void)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(NULL != audio_event_queue, ESP_ERR_INVALID_STATE,
|
||||
TAG, "Audio task not started yet");
|
||||
|
||||
audio_event_t event = AUDIO_EVENT_PLAY;
|
||||
BaseType_t ret_val = xQueueSend(audio_event_queue, &event, 0);
|
||||
|
||||
ESP_RETURN_ON_FALSE(pdPASS == ret_val, ESP_ERR_INVALID_STATE,
|
||||
TAG, "The last event has not been processed yet");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t audio_pause(void)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(NULL != audio_event_queue, ESP_ERR_INVALID_STATE,
|
||||
TAG, "Audio task not started yet");
|
||||
|
||||
audio_event_t event = AUDIO_EVENT_PAUSE;
|
||||
BaseType_t ret_val = xQueueSend(audio_event_queue, &event, 0);
|
||||
|
||||
ESP_RETURN_ON_FALSE(pdPASS == ret_val, ESP_ERR_INVALID_STATE,
|
||||
TAG, "The last event has not been processed yet");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t audio_play_next(void)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(NULL != audio_event_queue, ESP_ERR_INVALID_STATE,
|
||||
TAG, "Audio task not started yet");
|
||||
|
||||
audio_index ++;
|
||||
if (audio_index >= audio_count) {
|
||||
audio_index = 0;
|
||||
}
|
||||
audio_event_t event = AUDIO_EVENT_CHANGE;
|
||||
BaseType_t ret_val = xQueueSend(audio_event_queue, &event, 0);
|
||||
|
||||
ESP_RETURN_ON_FALSE(pdPASS == ret_val, ESP_ERR_INVALID_STATE,
|
||||
TAG, "The last event has not been processed yet");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t audio_play_prev(void)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(NULL != audio_event_queue, ESP_ERR_INVALID_STATE,
|
||||
TAG, "Audio task not started yet");
|
||||
|
||||
if (audio_index == 0) {
|
||||
audio_index = audio_count;
|
||||
}
|
||||
audio_index--;
|
||||
audio_event_t event = AUDIO_EVENT_CHANGE;
|
||||
BaseType_t ret_val = xQueueSend(audio_event_queue, &event, 0);
|
||||
|
||||
ESP_RETURN_ON_FALSE(pdPASS == ret_val, ESP_ERR_INVALID_STATE,
|
||||
TAG, "The last event has not been processed yet");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t audio_play_index(size_t index)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(NULL != audio_event_queue, ESP_ERR_INVALID_STATE,
|
||||
TAG, "Audio task not started yet");
|
||||
|
||||
ESP_RETURN_ON_FALSE(index < audio_count, ESP_ERR_INVALID_ARG,
|
||||
TAG, "File index out of range");
|
||||
|
||||
audio_index = index;
|
||||
audio_event_t event = AUDIO_EVENT_CHANGE;
|
||||
BaseType_t ret_val = xQueueSend(audio_event_queue, &event, 0);
|
||||
|
||||
ESP_RETURN_ON_FALSE(pdPASS == ret_val, ESP_ERR_INVALID_STATE,
|
||||
TAG, "The last event has not been processed yet");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* **************** START AUDIO PLAYER **************** */
|
||||
esp_err_t mp3_player_start(char *file_path)
|
||||
{
|
||||
BaseType_t ret_val = xTaskCreatePinnedToCore(
|
||||
(TaskFunction_t) audio_task,
|
||||
(const char * const) "Audio Task",
|
||||
(const uint32_t) 4 * 1024,
|
||||
(void * const) file_path,
|
||||
(UBaseType_t) configMAX_PRIORITIES - 1,
|
||||
(TaskHandle_t * const) NULL,
|
||||
(const BaseType_t) 0);
|
||||
|
||||
ESP_RETURN_ON_FALSE(pdPASS == ret_val, ESP_ERR_NO_MEM,
|
||||
TAG, "Failed create audio task");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -20,15 +20,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "file_iterator.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Create audio player UI
|
||||
*
|
||||
*
|
||||
*/
|
||||
void ui_audio_start(void);
|
||||
void ui_audio_start(file_iterator_instance_t *i);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -18,16 +18,47 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "audio.h"
|
||||
#include "audio_player.h"
|
||||
#include "bsp_board.h"
|
||||
#include "bsp_lcd.h"
|
||||
#include "bsp_storage.h"
|
||||
#include "bsp_i2s.h"
|
||||
#include "bsp_codec.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "lv_port.h"
|
||||
#include "lvgl.h"
|
||||
#include "ui_audio.h"
|
||||
#include "file_iterator.h"
|
||||
#include "es8311.h"
|
||||
|
||||
static const char *TAG = "mp3_demo";
|
||||
|
||||
static esp_err_t audio_mute_function(AUDIO_PLAYER_MUTE_SETTING setting) {
|
||||
// Volume saved when muting and restored when unmuting. Restoring volume is necessary
|
||||
// as es8311_set_voice_mute(true) results in voice volume (REG32) being set to zero.
|
||||
static int last_volume;
|
||||
|
||||
ESP_LOGI(TAG, "mute setting %d", setting);
|
||||
int volume;
|
||||
|
||||
ESP_RETURN_ON_ERROR(es8311_codec_get_voice_volume(&volume), TAG, "get voice volume");
|
||||
if(volume != 0) {
|
||||
last_volume = volume;
|
||||
}
|
||||
|
||||
ESP_RETURN_ON_ERROR(es8311_set_voice_mute(setting == AUDIO_PLAYER_MUTE ? true : false), TAG, "set voice mute");
|
||||
|
||||
// restore the voice volume upon unmuting
|
||||
if(setting == AUDIO_PLAYER_UNMUTE) {
|
||||
es8311_codec_set_voice_volume(last_volume);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
@@ -38,8 +69,33 @@ void app_main(void)
|
||||
ESP_ERROR_CHECK(lv_port_init());
|
||||
bsp_lcd_set_backlight(true);
|
||||
|
||||
ui_audio_start();
|
||||
ESP_ERROR_CHECK(mp3_player_start("/spiffs"));
|
||||
file_iterator_instance_t *file_iterator = file_iterator_new("/spiffs");
|
||||
assert(file_iterator != NULL);
|
||||
|
||||
/**
|
||||
* @brief Initialize I2S and audio codec
|
||||
*
|
||||
* @note `MP3GetLastFrameInfo` is used to fill the `MP3FrameInfo`, which includes `samprate`,
|
||||
* and the sampling rate is updated during playback using this value.
|
||||
*/
|
||||
esp_err_t ret = bsp_codec_init(AUDIO_HAL_44K_SAMPLES);
|
||||
if(ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "bsp_codec_init failed with %d", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
bsp_i2s_init(I2S_NUM_0, 44100);
|
||||
// TODO: original code isn't checking return value here, this is failing with a value of 0x103 which is
|
||||
// who the heck knows what error
|
||||
// ESP_RETURN_ON_ERROR(ret, TAG, "bsp_i2s_init");
|
||||
|
||||
audio_player_config_t config = { .port = I2S_NUM_0,
|
||||
.mute_fn = audio_mute_function,
|
||||
.priority = 1 };
|
||||
|
||||
ESP_ERROR_CHECK(audio_player_new(config));
|
||||
|
||||
ui_audio_start(file_iterator);
|
||||
|
||||
do {
|
||||
lv_task_handler();
|
||||
|
||||
@@ -19,14 +19,16 @@
|
||||
*/
|
||||
|
||||
#include "lvgl.h"
|
||||
#include "audio.h"
|
||||
#include "playlist.h"
|
||||
#include "audio_player.h"
|
||||
#include "file_iterator.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "es8311.h"
|
||||
|
||||
static const char *TAG = "ui_audio";
|
||||
|
||||
static file_iterator_instance_t* file_iterator;
|
||||
|
||||
#if CONFIG_ESP32_S3_BOX_LITE_BOARD
|
||||
static void register_button_callback(lv_obj_t *btn_list[]);
|
||||
#endif
|
||||
@@ -36,12 +38,13 @@ static void btn_play_pause_cb(lv_event_t *event)
|
||||
lv_obj_t *btn = (lv_obj_t *) event->target;
|
||||
lv_obj_t *lab = (lv_obj_t *) btn->user_data;
|
||||
|
||||
if (lv_obj_has_state(btn, LV_STATE_CHECKED)) {
|
||||
lv_label_set_text_static(lab, LV_SYMBOL_PLAY);
|
||||
audio_pause();
|
||||
} else {
|
||||
audio_player_state_t state = audio_player_get_state();
|
||||
if(state == AUDIO_PLAYER_STATE_PAUSE) {
|
||||
lv_label_set_text_static(lab, LV_SYMBOL_PAUSE);
|
||||
audio_resume();
|
||||
audio_player_resume();
|
||||
} else if(state == AUDIO_PLAYER_STATE_PLAYING) {
|
||||
lv_label_set_text_static(lab, LV_SYMBOL_PLAY);
|
||||
audio_player_pause();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +52,7 @@ static void play_index(int index) {
|
||||
ESP_LOGI(TAG, "play_index(%d)", index);
|
||||
|
||||
char filename[128];
|
||||
int retval = playlist_get_full_path_from_index(index, filename, sizeof(filename));
|
||||
int retval = file_iterator_get_full_path_from_index(file_iterator, index, filename, sizeof(filename));
|
||||
if(retval == 0) {
|
||||
ESP_LOGE(TAG, "unable to retrieve filename");
|
||||
return;
|
||||
@@ -60,7 +63,7 @@ static void play_index(int index) {
|
||||
FILE* fp = fopen(filename, "rb");
|
||||
if(fp) {
|
||||
ESP_LOGI(TAG, "Playing '%s'", filename);
|
||||
audio_play(fp);
|
||||
audio_player_play(fp);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "unable to open index %d, filename '%s'", index, filename);
|
||||
}
|
||||
@@ -71,17 +74,22 @@ static void btn_prev_next_cb(lv_event_t *event)
|
||||
bool is_next = (bool) event->user_data;
|
||||
|
||||
if (is_next) {
|
||||
playlist_next();
|
||||
ESP_LOGI(TAG, "btn next");
|
||||
file_iterator_next(file_iterator);
|
||||
} else {
|
||||
playlist_prev();
|
||||
ESP_LOGI(TAG, "btn prev");
|
||||
file_iterator_prev(file_iterator);
|
||||
}
|
||||
|
||||
audio_player_state_t state = audio_player_get_state();
|
||||
if(state == AUDIO_PLAYER_STATE_IDLE) {
|
||||
// nothing to do, changing songs while not playing
|
||||
// doesn't start or stop playback
|
||||
ESP_LOGI(TAG, "idle, nothing to do");
|
||||
} else if(state == AUDIO_PLAYER_STATE_PLAYING) {
|
||||
play_index(playlist_get_index());
|
||||
int index = file_iterator_get_index(file_iterator);
|
||||
ESP_LOGI(TAG, "playing index '%d'", index);
|
||||
play_index(index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,13 +108,13 @@ static void build_file_list(lv_obj_t *music_list)
|
||||
|
||||
size_t i = 0;
|
||||
while (true) {
|
||||
const char *file_name = playlist_get_name_from_index(i);
|
||||
const char *file_name = file_iterator_get_name_from_index(file_iterator, i);
|
||||
if (NULL != file_name) {
|
||||
lv_dropdown_add_option(music_list, file_name, i);
|
||||
} else {
|
||||
lv_dropdown_set_selected(music_list, 0);
|
||||
lv_label_set_text_static(label_title,
|
||||
playlist_get_name_from_index(0));
|
||||
file_iterator_get_name_from_index(file_iterator, 0));
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
@@ -120,20 +128,22 @@ static void audio_callback(audio_player_cb_ctx_t *ctx)
|
||||
lv_obj_t *btn_play_pause = (lv_obj_t *) label_title->user_data;
|
||||
lv_obj_t *label_play_pause = (lv_obj_t *) btn_play_pause->user_data;
|
||||
|
||||
if(ctx->audio_event == AUDIO_PLAYER_STATE_IDLE)
|
||||
if(ctx->audio_event == AUDIO_PLAYER_CALLBACK_EVENT_IDLE)
|
||||
{
|
||||
playlist_next();
|
||||
play_index(playlist_get_index());
|
||||
ESP_LOGI(TAG, "audio_callback IDLE");
|
||||
file_iterator_next(file_iterator);
|
||||
play_index(file_iterator_get_index(file_iterator));
|
||||
}
|
||||
|
||||
size_t index = playlist_get_index();
|
||||
size_t index = file_iterator_get_index(file_iterator);
|
||||
|
||||
lv_dropdown_set_selected(music_list, index);
|
||||
|
||||
lv_label_set_text_static(label_title,
|
||||
playlist_get_name_from_index(index));
|
||||
file_iterator_get_name_from_index(file_iterator, index));
|
||||
|
||||
if(ctx->audio_event == AUDIO_PLAYER_STATE_PLAYING) {
|
||||
if((ctx->audio_event == AUDIO_PLAYER_CALLBACK_EVENT_PLAYING) ||
|
||||
(ctx->audio_event == AUDIO_PLAYER_CALLBACK_EVENT_COMPLETED_PLAYING_NEXT)) {
|
||||
lv_obj_clear_state(btn_play_pause, LV_STATE_CHECKED);
|
||||
lv_label_set_text_static(label_play_pause, LV_SYMBOL_PAUSE);
|
||||
} else {
|
||||
@@ -150,13 +160,15 @@ static void music_list_cb(lv_event_t *event)
|
||||
if(audio_player_get_state() == AUDIO_PLAYER_STATE_PLAYING) {
|
||||
uint16_t selected = lv_dropdown_get_selected(music_list);
|
||||
ESP_LOGI(TAG, "switching index to '%d'", selected);
|
||||
playlist_set_index(selected);
|
||||
file_iterator_set_index(file_iterator, selected);
|
||||
play_index(selected);
|
||||
}
|
||||
}
|
||||
|
||||
void ui_audio_start(void)
|
||||
void ui_audio_start(file_iterator_instance_t *i)
|
||||
{
|
||||
file_iterator = i;
|
||||
|
||||
/* Create audio control button */
|
||||
lv_obj_t *btn_play_pause = lv_btn_create(lv_scr_act());
|
||||
lv_obj_align(btn_play_pause, LV_ALIGN_CENTER, 0, 40);
|
||||
@@ -223,14 +235,14 @@ void ui_audio_start(void)
|
||||
|
||||
build_file_list(music_list);
|
||||
|
||||
audio_callback_register(audio_callback, (void *) music_list);
|
||||
audio_player_callback_register(audio_callback, (void *) music_list);
|
||||
|
||||
#if CONFIG_ESP32_S3_BOX_LITE_BOARD
|
||||
register_button_callback((lv_obj_t *[]) { label_prev, label_next, btn_play_pause });
|
||||
#endif
|
||||
|
||||
// initiate playback
|
||||
play_index(playlist_get_index());
|
||||
play_index(file_iterator_get_index(file_iterator));
|
||||
}
|
||||
|
||||
#if CONFIG_ESP32_S3_BOX_LITE_BOARD
|
||||
|
||||
Reference in New Issue
Block a user