本文基于合宙开发csdk,参考example_sfud和dowmload_play_mp3实现片外flash中音频播放功能
相关头文件和变量声明
#include "common_api.h"
#include "luat_rtos.h"
#include "luat_audio_play_ec618.h"
#include "luat_i2s_ec618.h"
#include "luat_gpio.h"
#include "luat_debug.h"
#include "luat_mobile.h"
#include "luat_network_adapter.h"
#include "networkmgr.h"
#include "luat_http.h"
#include "luat_spi.h"
#include "sfud.h"
#include "luat_fs.h"
#include "bsp_custom.h"
#include "luat_mem.h"
#include "lfs.h"
#include "usr_main.h"
//AIR780E+TM8211开发板配置
#define CODEC_PWR_PIN HAL_GPIO_12
#define CODEC_PWR_PIN_ALT_FUN 4
#define PA_PWR_PIN HAL_GPIO_10
#define PA_PWR_PIN_ALT_FUN 0
#define LED2_PIN HAL_GPIO_24
#define LED2_PIN_ALT_FUN 0
#define LED3_PIN HAL_GPIO_23
#define LED3_PIN_ALT_FUN 0
#define LED4_PIN HAL_GPIO_27
#define LED4_PIN_ALT_FUN 0
#define CHARGE_EN_PIN HAL_GPIO_10
#define SPI_FLASH_VCC_PIN (HAL_GPIO_26)
#define FATFS_READ_BUFF_LEN (2*1024)
#define MP3_BUFFER_LEN_MIN (1 * 1024) //MP3数据必须大于10KB才开始解码,需要根据实际情况确定
#define MP3_BUFFER_LEN_LOW (30 * 1024)
#define MP3_BUFFER_LEN_HIGH (40 * 1024)
#define MP3_FRAME_LEN (4 * 1152)
#define MP3_MAX_CODED_FRAME_SIZE 1792
static Buffer_Struct g_s_mp3_buffer = {0};
static Buffer_Struct g_s_pcm_buffer = {0};
static uint8_t g_s_mp3_wait_start;
static uint32_t g_s_mp3_downloading;
static uint8_t g_s_mp3_pause;
static HANDLE g_s_delay_timer;
static uint32_t g_s_mp3_data_tx; //发送到audio线程的数据
static uint32_t g_s_mp3_data_rx; //audio接收到的数据,差值就是缓存的数据
static uint8_t g_s_mp3_error;
static uint8_t g_s_mp3_wait_start;
static uint8_t g_s_mp3_pause;
static uint8_t g_s_mp3_data_mode;
static uint16_t g_s_mp3_dummy;
static void *g_s_mp3_decoder; //不用的时候可以free掉,本demo由于是循环播放,没有free
static uint8_t audio_is_pause;
luat_rtos_task_handle audio_task_handle;
luat_rtos_task_handle ex_audio_read_handle;
luat_rtos_queue_t audio_play_queue;
luat_rtos_semaphore_t single_play_semaphore_handle;
enum
{
FATFS_GET_MP3_HEAD =0,
FATFS_GET_DATA,
FATFS_GET_DATA_DONE,
FATFS_FILE_UNEXIST,
FATFS_FILE_EMPTY,
FATFS_FILE_OPEN_FILE,
FATFS_FAILED,
};
音频相关硬件初始化和主任务开启
void usr_audio_play_init(void)
{
luat_gpio_cfg_t gpio_cfg;
luat_gpio_set_default_cfg(&gpio_cfg);
gpio_cfg.pin = LED2_PIN;
gpio_cfg.pull = LUAT_GPIO_DEFAULT;
luat_gpio_open(&gpio_cfg);
gpio_cfg.pin = LED3_PIN;
luat_gpio_open(&gpio_cfg);
gpio_cfg.pin = LED4_PIN;
// luat_gpio_open(&gpio_cfg);
// gpio_cfg.pin = CHARGE_EN_PIN;
// luat_gpio_open(&gpio_cfg);
gpio_cfg.pin = PA_PWR_PIN;
luat_gpio_open(&gpio_cfg);
gpio_cfg.pin = CODEC_PWR_PIN;
luat_gpio_open(&gpio_cfg);
gpio_cfg.alt_fun = CODEC_PWR_PIN_ALT_FUN;
luat_gpio_open(&gpio_cfg);
gpio_cfg.pin = SPI_FLASH_VCC_PIN;
gpio_cfg.alt_fun = 0;
gpio_cfg.mode = LUAT_GPIO_OUTPUT;
gpio_cfg.output_level = 1;
luat_gpio_open(&gpio_cfg);
luat_rtos_task_create(&audio_task_handle, 1024 * 40, 20, "audio_play_task", audio_play_task, NULL, NULL);
}
static void audio_play_task(void *param)
{
size_t total, used, max_used;
luat_rtos_timer_create(&g_s_delay_timer);
luat_audio_play_global_init_with_task_priority(audio_event_cb, audio_data_cb, NULL, NULL, NULL, 90);
luat_i2s_base_setup(0, I2S_MODE_MSB, I2S_FRAME_SIZE_16_16);
luat_rtos_queue_create(&audio_play_queue, 10, sizeof(AUDIO_PLAY_PARAM_T));
luat_rtos_task_create(&ex_audio_read_handle, 2048*2, 20, "ex_audio_read_task", ex_audio_read_task, NULL, 64);
uint32_t start, i;
uint8_t is_error;
uint8_t mp3_head_len = 0;
uint32_t all,now_free_block,min_free_block;
luat_event_t event;
while (1)
{
luat_meminfo_sys(&all, &now_free_block, &min_free_block);
LUAT_DEBUG_PRINT("meminfo %d,%d,%d",all,now_free_block,min_free_block);
g_s_mp3_data_mode = 0;
g_s_mp3_error = 0;
g_s_mp3_data_tx = 0;
g_s_mp3_data_rx = 0;
g_s_mp3_wait_start = 0;
is_error = 0;
luat_rtos_event_recv(audio_task_handle, 0, &event, NULL, LUAT_WAIT_FOREVER);
LUAT_DEBUG_PRINT("event.id %d",event.id);
switch(event.id)
{
case FATFS_GET_MP3_HEAD:
LUAT_DEBUG_PRINT("FATFS_MP3_HEAD");
break;
case FATFS_GET_DATA:
LUAT_DEBUG_PRINT("FATFS_GET_DATA");
break;
case FATFS_GET_DATA_DONE:
LUAT_DEBUG_PRINT("FATFS_GET_DATA_DONE");
break;
case FATFS_FILE_UNEXIST:
LUAT_DEBUG_PRINT("FATFS_FILE_UNEXIST");
break;
case FATFS_FAILED:
LUAT_DEBUG_PRINT("FATFS_FAILED");
break;
}
}
}
音频文件读取和mp3解码
static void ex_audio_read_task(void *param)
{
AUDIO_PLAY_PARAM_T event = {0};
uint8_t tmp[FATFS_READ_BUFF_LEN];
while(1)
{
luat_rtos_semaphore_take(single_play_semaphore_handle, LUAT_WAIT_FOREVER);
if(!luat_rtos_queue_recv(audio_play_queue, (AUDIO_PLAY_PARAM_T *)&event, NULL, LUAT_WAIT_FOREVER))
{
usr_log("type %d fileName %s",event.type, event.fileName);
if (!luat_fs_fexist(event.fileName))
{
LUAT_DEBUG_PRINT("file unexist %s", event.fileName);
luat_rtos_event_send(audio_task_handle, FATFS_FILE_UNEXIST, 0, 0, 0, 0);
luat_rtos_semaphore_release(single_play_semaphore_handle);
continue;
}
size_t file_size = luat_fs_fsize(event.fileName);
if(!file_size)
{
LUAT_DEBUG_PRINT("file size is empty %s", event.fileName);
luat_rtos_event_send(audio_task_handle, FATFS_FILE_UNEXIST, 0, 0, 0, 0);
luat_rtos_semaphore_release(single_play_semaphore_handle);
continue;
}
FILE *fp = luat_fs_fopen(event.fileName, "r");
if (!fp) {
LUAT_DEBUG_PRINT("file open failed %s", event.fileName);
luat_rtos_event_send(audio_task_handle, FATFS_FILE_OPEN_FILE, 0, 0, 0, 0);
luat_rtos_semaphore_release(single_play_semaphore_handle);
continue;
}
size_t ret = 0;
uint8_t get_mp3_head = 0;
do
{
if(!get_mp3_head)
{
ret = luat_fs_fread(tmp, 12, 1, fp);
if (ret <= 0) {
// LUAT_DEBUG_PRINT("file read done");
luat_rtos_event_send(audio_task_handle, FATFS_GET_DATA_DONE, 0, 0, 0, 0);
}else{
// usr_log("111 %x %x %x %x %x %x %x %x %x %x %x %x\r\n", tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11]);
if (!memcmp(tmp, "ID3", 3) || (tmp[0] == 0xff))
{
uint32_t start = 0;
if (tmp[0] != 0xff)
{
//跳过无用的数据
for(int i = 0; i < 4; i++)
{
start <<= 7;
start |= tmp[6 + i] & 0x7f;
}
}
uint8_t re = luat_fs_fseek(fp, start, SEEK_SET);
// LUAT_DEBUG_PRINT("re %d",re);
// LUAT_DEBUG_PRINT("是MP3文件");
g_s_mp3_wait_start = 1;
luat_rtos_event_send(audio_task_handle, FATFS_GET_MP3_HEAD, 0, 0, 0, 0);
}
else
{
LUAT_DEBUG_PRINT("不是MP3文件,退出");
luat_rtos_semaphore_release(lte_semaphore_handle);
break;
}
get_mp3_head = 1;
}
}else{
while(audio_is_pause)
{
usr_delay_ms(10);
}
luat_audio_play_set_user_lock(0, 1);
ret = luat_fs_fread(tmp, FATFS_READ_BUFF_LEN, 1, fp);
if (ret <= 0) {
// LUAT_DEBUG_PRINT("file read done");
luat_rtos_event_send(audio_task_handle, FATFS_GET_DATA_DONE, 0, 0, 0, 0);
g_s_mp3_downloading = 0;
luat_audio_play_set_user_lock(0, 0);
}else{
// usr_log("222 %x %x %x %x %x %x %x %x %x %x %x %x\r\n", tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11]);
luat_rtos_event_send(audio_task_handle, FATFS_GET_DATA, tmp, FATFS_READ_BUFF_LEN, 0, 0);
g_s_mp3_downloading = 1;
g_s_mp3_data_tx += 1024;
soc_call_function_in_audio(run_mp3_decode, tmp, FATFS_READ_BUFF_LEN, LUAT_WAIT_FOREVER);
if ((g_s_mp3_data_tx - g_s_mp3_data_rx + g_s_mp3_buffer.Pos) >= MP3_BUFFER_LEN_HIGH)
{
if (!audio_is_pause)
{
audio_is_pause = 1;
}
}
}
}
} while (ret>0);
luat_fs_fclose(fp);
}
}
}
1.接收语音播放指令
2.读取文件前12个字节内容,判断是否是mp3文件
3.根据前12个字节内容的第7-10个字节,获取文件头信息大小,根据该信息将文件指针偏移到音频数据位置
4.分段读取音频数据,直至读完
5.当文件还没有读取完毕时,使用luat_audio_play_set_user_lock(0, 1)保证播放不会因数据没有及时写入而导致中断;当文件读取完毕后,使用luat_audio_play_set_user_lock(0, 0),当没有播放数据时,正确退出
6.缓冲区数据量大于MP3_BUFFER_LEN_HIGH时,暂停文件读取
mp3数据解码和写入相关api
void app_pa_on(uint32_t arg)
{
luat_gpio_set(PA_PWR_PIN, 1);
}
用于硬件静音开启/关闭
void run_mp3_add_blank(uint8_t *data, uint32_t len)
{
int16_t *dummy_data = malloc(8192);
for(int i = 0; i < 4096; i++)
{
dummy_data[i] = g_s_mp3_dummy;
}
luat_audio_play_write_raw(0, dummy_data, 8192);
luat_audio_play_write_raw(0, dummy_data, 8192);
free(dummy_data);
}
写入空白块
void run_mp3_decode(uint8_t *data, uint32_t len)
{
LUAT_DEBUG_PRINT("len %d", len);
if (data)
{
OS_BufferWrite(&g_s_mp3_buffer, data, len);
g_s_mp3_data_rx += len;
if (g_s_mp3_wait_start)
{
LUAT_DEBUG_PRINT("g_s_mp3_buffer.Pos %d", g_s_mp3_buffer.Pos);
if (g_s_mp3_buffer.Pos >= MP3_BUFFER_LEN_MIN)
{
g_s_mp3_wait_start = 0;
run_mp3_play(1);
}
return;
}
// if (g_s_mp3_pause)
// {
// if ((g_s_mp3_buffer.Pos >= MP3_BUFFER_LEN_MIN) || !g_s_mp3_downloading)
// {
// g_s_mp3_pause = 0;
// run_mp3_play(0);
// }
// return;
// }
}
run_mp3_play(0);
if (g_s_mp3_buffer.Pos < MP3_BUFFER_LEN_LOW)
{
if (audio_is_pause)
{
audio_is_pause = 0;
}
}
Audio_StreamStruct *stream = (Audio_StreamStruct *)luat_audio_play_get_stream(0);
if (g_s_mp3_downloading && (llist_num(&stream->DataHead) <= 1))
{
LUAT_DEBUG_PRINT("no data %d, %d", g_s_mp3_downloading, llist_num(&stream->DataHead));
run_mp3_add_blank(NULL, 0);
return;
}
}
1.第一次数据量大于MP3_BUFFER_LEN_MIN时进行解码,仅作用一次
2.进行数据写入
3.当缓冲区数据量小于MP3_BUFFER_LEN_LOW时,开启文件读取
void audio_event_cb(uint32_t event, void *param)
{
switch(event)
{
case LUAT_MULTIMEDIA_CB_AUDIO_NEED_DATA:
soc_call_function_in_audio(run_mp3_decode, NULL, 0, LUAT_WAIT_FOREVER);
break;
case LUAT_MULTIMEDIA_CB_AUDIO_DONE:
// if (g_s_mp3_downloading)
// {
// LUAT_DEBUG_PRINT("pause");
// g_s_mp3_pause = 1;
// soc_call_function_in_audio(run_mp3_add_blank, NULL, 0, LUAT_WAIT_FOREVER);
// return;
// }
OS_DeInitBuffer(&g_s_mp3_buffer);
luat_audio_play_stop_raw(0);
luat_rtos_timer_stop(g_s_delay_timer);
luat_gpio_set(PA_PWR_PIN, 0);
luat_gpio_set(CODEC_PWR_PIN, 0);
luat_rtos_semaphore_release(single_play_semaphore_handle);
break;
}
}
void audio_data_cb(uint8_t *data, uint32_t len, uint8_t bits, uint8_t channels)
{
}
int run_mp3_play(uint8_t is_start)
{
Audio_StreamStruct *stream = (Audio_StreamStruct *)luat_audio_play_get_stream(0);
uint8_t num_channels = 1;
uint32_t sample_rate = 0;
int result, len;
if (!g_s_mp3_decoder)
{
g_s_mp3_decoder = mp3_decoder_create();
}
if (is_start)
{
mp3_decoder_init(g_s_mp3_decoder);
// LUAT_DEBUG_PRINT("%x %x %x %x %x %x",g_s_mp3_buffer.Data[0],g_s_mp3_buffer.Data[1],g_s_mp3_buffer.Data[2],g_s_mp3_buffer.Data[3],g_s_mp3_buffer.Pos);
result = mp3_decoder_get_info(g_s_mp3_decoder, g_s_mp3_buffer.Data, g_s_mp3_buffer.Pos, &sample_rate, &num_channels);
if (result)
{
mp3_decoder_init(g_s_mp3_decoder);
LUAT_DEBUG_PRINT("mp3 %d,%d", sample_rate, num_channels);
len = (num_channels * sample_rate >> 2) + MP3_FRAME_LEN * 2;
OS_ReInitBuffer(&g_s_pcm_buffer, len);
}
else
{
LUAT_DEBUG_PRINT("mp3 decode fail!");
g_s_mp3_error = 1;
return -1;
}
}
uint32_t pos = 0;
uint32_t out_len, hz, used;
if (!g_s_mp3_buffer.Pos)
{
return -1;
}
while ((llist_num(&stream->DataHead) < 3) && (g_s_mp3_buffer.Pos > (MP3_MAX_CODED_FRAME_SIZE * g_s_mp3_downloading + 1)) )
{
while (( g_s_pcm_buffer.Pos < (g_s_pcm_buffer.MaxLen - MP3_FRAME_LEN * 2) ) && (g_s_mp3_buffer.Pos > (MP3_MAX_CODED_FRAME_SIZE * g_s_mp3_downloading + 1)))
{
pos = 0;
do
{
result = mp3_decoder_get_data(g_s_mp3_decoder, g_s_mp3_buffer.Data + pos, g_s_mp3_buffer.Pos - pos,
(int16_t *)&g_s_pcm_buffer.Data[g_s_pcm_buffer.Pos], &out_len, &hz, &used);
luat_wdt_feed();
if (result > 0)
{
g_s_pcm_buffer.Pos += out_len;
//记录下最后一个采样数据,用于pause时加入空白音
memcpy(&g_s_mp3_dummy, g_s_pcm_buffer.Data[g_s_pcm_buffer.Pos - 2], 2);
}
pos += used;
if (!result || (g_s_pcm_buffer.Pos >= (g_s_pcm_buffer.MaxLen - MP3_FRAME_LEN * 2)))
{
break;
}
} while ( ((g_s_mp3_buffer.Pos - pos) >= (MP3_MAX_CODED_FRAME_SIZE * g_s_mp3_downloading + 1)));
OS_BufferRemove(&g_s_mp3_buffer, pos);
}
if (is_start)
{
luat_audio_play_start_raw(0, AUSTREAM_FORMAT_PCM, num_channels, sample_rate, 16, 1);
//打开外部DAC,由于要配合PA的启动,需要播放一段空白音
luat_gpio_set(CODEC_PWR_PIN, 1);
luat_audio_play_write_blank_raw(0, 6, 1);
is_start = 0;
luat_rtos_timer_start(g_s_delay_timer, 200, 0, app_pa_on, NULL);
}
luat_audio_play_write_raw(0, g_s_pcm_buffer.Data, g_s_pcm_buffer.Pos);
g_s_pcm_buffer.Pos = 0;
}
}
业务层函数,具体播放哪个文件
void usr_audio_play_mp3_file(char *fileName, AUDIO_TYPE_E type)
{
AUDIO_PLAY_PARAM_T param = {
.fileName = fileName,
.type = type,
};
usr_audio_play(param);
}