一.项目介绍
1.NXP LPC55S69开发板:
LPCXpresso55S69 开发板为评估和开发基于 Arm ® Cortex ® -M33 架构的 LPC55S6x MCU 提供了理想的平台。该板包括高性能板载调试探针、音频子系统和加速度计,并提供多种选项,用于添加用于网络、传感器、显示器和其他接口的现成附加板。。
2.MCUXpresso IDE:
MCUXpresso 工具套件完全支持 LPCXpresso55S69,该套件提供设备驱动程序、中间件和示例以允许快速开发,以及配置工具和可选的免费 IDE。MCUXpresso 软件与开源 MCU 操作系统 FreeRTOS、来自 Arm 和 IAR 等流行工具供应商的工具兼容,并且 LPCXpresso55S69 还可与 SEGGER 和 P&E Micro 提供的流行调试探针一起使用。
二.任务完成思路和实现过程
Maestro是一个音频处理软件框架,可为多种不同的音频设备提供连接和播放功能。
该框架包含各种抽象功能的模块,并为应用程序开发人员提供标准的编程接口。 Maestro 主要支持的功能有:
- 带有流媒体的完整音频框架,支持播放控制和音频流媒体及解码。
- 对存储在 FAT32 格式媒介上的音频文件支持多种音频格式进行解码。
- 有助于调试和分析系统的各种实用程序。
正如先前提到的,MCUXpresso 工具套件提供设备驱动程序、中间件和示例以允许快速开发。
本作品使用了Maestro Audio Framework进行开发及演示。
以下为作品的实现步骤:
第一步:创建Maestro Audio Framework工程
启动MCUXpresso IDE后指定工作空间路径(注意:路径不可包含中文,否则编译时会找不到部分头文件),选择“switch to main IDE”按键打开IDE工作页面。在“Installed SDKs”选项卡导入正确的LPC55S69 SDK包。随后使用“Quickstart Panel”下的“Import SDK examples”导入“audio_examples” -“maestro_demo”.
第二步:实现和调试
①Bulid工程后;
②选择核心0运行程序。等待下载完成后,使用“Terminal”选项卡新建串行终端,并使用如下的设置。
Serial Terminal
- 115200 baud rate
- 8 data bits
- No parity
- One stop bit
- No flow control
③使用“Quickstart Panel”下的“Debug”选项进行工程的编译与下载。
随后按下开发板上的重置按钮(RESET)或在 IDE 中启动调试器以开始运行程序。此时,串口控制台会有如下成功提示。
**********************************
Maestro audio solutions demo start
**********************************
[APP_SDCARD_Task] start
[APP_Shell_Task] start
SHELL build: Nov 5 2020
Copyright 2020 NXP
>> [APP_SDCARD_Task] SD card drive mounted
第三步:使用与演示
①准备工作
将TF卡插入卡槽
将耳机连接到 Audio HP / Line-Out 接口。
注意:需要将音频线完整插入音频输出口,否则很可能出现无音频输出的结果。
②指令的使用说明:
输入“help”以查看命令列表。
在串口控制台上,显示:
>> help
“help”::列出所有已注册的命令
“exit”:退出程序
“version”:显示组件版本
"file": 执行音频文件解码和播放
用法:file [list|<audio_file>|all]
list 列出 SD 卡上可供播放的音频文件
<audio_file> 从 SD 卡中选择文件并开始播放
all 从 SD 卡播放所有支持的音频文件。
(eap 选项:
1:所有效果关闭,
2:语音增强器,
3:音乐增强器
4:自动音量调节器,
5:最大响度,
6:3D 音乐,
7:自定义,
8:Tone generator,
9:分频两路扬声器,
10:低音分频器
示例:
使用 eap 效果播放:file <audio_file> eap 3)
此外还有“record_mic”录制 MIC 音频等多种功能。
第四步:演示实拍
三.活动总结与感想
本次活动的任务实现是非常简单的。
通过这个项目倒是熟悉了MCUXpresso IDE的基础操作,重温了嵌入式开发的流程和步骤。借助Maestro Audio Framework,轻松实现了音频读取与输出。
但限于个人原因,没有过多精力在此,仅使用demo实现了任务,算是比较偷懒。
感谢电子森林和硬禾学堂,提供此次实践机会。
四.部分源码
main.c
/*
* Copyright 2020-2021 NXP
* All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/* Board includes */
#include "pin_mux.h"
#include "board.h"
#include "main.h"
#include "cmd.h"
#include "fsl_sd.h"
#include "ff.h"
#include "diskio.h"
#include "fsl_sd_disk.h"
#include "sdmmc_config.h"
#include "fsl_debug_console.h"
#include <stdbool.h>
#include "fsl_sysctl.h"
#include "fsl_codec_common.h"
#include "fsl_wm8904.h"
#include "fsl_codec_adapter.h"
#include "fsl_power.h"
/*******************************************************************************
* Definitions
******************************************************************************/
#include "app_definitions.h"
#define SHELL_TASK_STACK_SIZE (1024)
#define SDCARD_TASK_STACK_SIZE (512)
/*******************************************************************************
* Prototypes
******************************************************************************/
int BOARD_CODEC_Init(void);
static void APP_SDCARD_DetectCallBack(bool isInserted, void *userData);
/*******************************************************************************
* Variables
******************************************************************************/
codec_handle_t codecHandle = {0};
wm8904_config_t wm8904Config = {
.i2cConfig = {.codecI2CInstance = BOARD_CODEC_I2C_INSTANCE, .codecI2CSourceClock = BOARD_CODEC_I2C_CLOCK_FREQ},
.recordSource = kWM8904_RecordSourceLineInput,
.recordChannelLeft = kWM8904_RecordChannelLeft2,
.recordChannelRight = kWM8904_RecordChannelRight2,
.playSource = kWM8904_PlaySourceDAC,
.slaveAddress = WM8904_I2C_ADDRESS,
.protocol = kWM8904_ProtocolI2S,
.format = {.sampleRate = kWM8904_SampleRate48kHz, .bitWidth = kWM8904_BitWidth16},
.mclk_HZ = DEMO_I2S_MASTER_CLOCK_FREQUENCY,
.master = false,
};
codec_config_t boardCodecConfig = {.codecDevType = kCODEC_WM8904, .codecDevConfig = &wm8904Config};
static app_handle_t app;
/*******************************************************************************
* Code
******************************************************************************/
int BOARD_CODEC_Init(void)
{
CODEC_Init(&codecHandle, &boardCodecConfig);
/* Invert the DAC data in order to output signal with correct polarity - set DACL_DATINV and DACR_DATINV = 1 */
WM8904_WriteRegister((wm8904_handle_t *)codecHandle.codecDevHandle, WM8904_AUDIO_IF_0, 0x1850);
/* Initial volume kept low for hearing safety. */
CODEC_SetVolume(&codecHandle, kCODEC_PlayChannelHeadphoneLeft | kCODEC_PlayChannelHeadphoneRight, 75);
return 0;
}
void BOARD_InitSysctrl(void)
{
SYSCTL_Init(SYSCTL);
/* select signal source for share set */
SYSCTL_SetShareSignalSrc(SYSCTL, kSYSCTL_ShareSet0, kSYSCTL_SharedCtrlSignalSCK, kSYSCTL_Flexcomm7);
SYSCTL_SetShareSignalSrc(SYSCTL, kSYSCTL_ShareSet0, kSYSCTL_SharedCtrlSignalWS, kSYSCTL_Flexcomm7);
/* select share set for special flexcomm signal */
SYSCTL_SetShareSet(SYSCTL, kSYSCTL_Flexcomm7, kSYSCTL_FlexcommSignalSCK, kSYSCTL_ShareSet0);
SYSCTL_SetShareSet(SYSCTL, kSYSCTL_Flexcomm7, kSYSCTL_FlexcommSignalWS, kSYSCTL_ShareSet0);
SYSCTL_SetShareSet(SYSCTL, kSYSCTL_Flexcomm6, kSYSCTL_FlexcommSignalSCK, kSYSCTL_ShareSet0);
SYSCTL_SetShareSet(SYSCTL, kSYSCTL_Flexcomm6, kSYSCTL_FlexcommSignalWS, kSYSCTL_ShareSet0);
}
static void APP_SDCARD_DetectCallBack(bool isInserted, void *userData)
{
app_handle_t *app = (app_handle_t *)userData;
app->sdcardInserted = isInserted;
xSemaphoreGiveFromISR(app->sdcardSem, NULL);
}
void APP_SDCARD_Task(void *param)
{
const TCHAR driverNumberBuffer[3U] = {SDDISK + '0', ':', '/'};
FRESULT error;
app_handle_t *app = (app_handle_t *)param;
app->sdcardSem = xSemaphoreCreateBinary();
BOARD_SD_Config(&g_sd, APP_SDCARD_DetectCallBack, BOARD_SDMMC_SD_HOST_IRQ_PRIORITY, app);
PRINTF("[APP_SDCARD_Task] start\r\n");
/* SD host init function */
if (SD_HostInit(&g_sd) != kStatus_Success)
{
PRINTF("[APP_SDCARD_Task] SD host init failed.\r\n");
vTaskSuspend(NULL);
}
/* Small delay for SD card detection logic to process */
vTaskDelay(100 / portTICK_PERIOD_MS);
while (1)
{
/* Block waiting for SDcard detect interrupt */
xSemaphoreTake(app->sdcardSem, portMAX_DELAY);
if (app->sdcardInserted != app->sdcardInsertedPrev)
{
app->sdcardInsertedPrev = app->sdcardInserted;
SD_SetCardPower(&g_sd, false);
if (app->sdcardInserted)
{
/* power on the card */
SD_SetCardPower(&g_sd, true);
if (f_mount(&app->fileSystem, driverNumberBuffer, 0U))
{
PRINTF("[APP_SDCARD_Task] Mount volume failed.\r\n");
continue;
}
#if (FF_FS_RPATH >= 2U)
error = f_chdrive((char const *)&driverNumberBuffer[0U]);
if (error)
{
PRINTF("[APP_SDCARD_Task] Change drive failed.\r\n");
continue;
}
#endif
PRINTF("[APP_SDCARD_Task] SD card drive mounted\r\n");
xSemaphoreGive(app->sdcardSem);
}
}
}
}
void handleShellMessage(void *arg)
{
/* Wait for response message to be processed before returning to shell. */
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
}
void APP_Shell_Task(void *param)
{
PRINTF("[APP_Shell_Task] start\r\n");
/* Handle shell commands. Return when 'exit' command entered. */
shellCmd(handleShellMessage, param);
while (1)
;
}
int main(void)
{
int ret;
/* set BOD VBAT level to 1.65V */
POWER_SetBodVbatLevel(kPOWER_BodVbatLevel1650mv, kPOWER_BodHystLevel50mv, false);
CLOCK_EnableClock(kCLOCK_InputMux);
CLOCK_EnableClock(kCLOCK_Iocon);
CLOCK_EnableClock(kCLOCK_Gpio0);
CLOCK_EnableClock(kCLOCK_Gpio1);
/* USART0 clock */
CLOCK_AttachClk(BOARD_DEBUG_UART_CLK_ATTACH);
/* I2C clock */
CLOCK_AttachClk(kFRO12M_to_FLEXCOMM4);
PMC->PDRUNCFGCLR0 |= PMC_PDRUNCFG0_PDEN_XTAL32M_MASK; /*!< Ensure XTAL16M is on */
PMC->PDRUNCFGCLR0 |= PMC_PDRUNCFG0_PDEN_LDOXO32M_MASK; /*!< Ensure XTAL16M is on */
SYSCON->CLOCK_CTRL |= SYSCON_CLOCK_CTRL_CLKIN_ENA_MASK; /*!< Ensure CLK_IN is on */
ANACTRL->XO32M_CTRL |= ANACTRL_XO32M_CTRL_ENABLE_SYSTEM_CLK_OUT_MASK;
/*!< Switch PLL0 clock source selector to XTAL16M */
CLOCK_AttachClk(kEXT_CLK_to_PLL0);
const pll_setup_t pll0Setup = {
.pllctrl = SYSCON_PLL0CTRL_CLKEN_MASK | SYSCON_PLL0CTRL_SELI(2U) | SYSCON_PLL0CTRL_SELP(31U),
.pllndec = SYSCON_PLL0NDEC_NDIV(125U),
.pllpdec = SYSCON_PLL0PDEC_PDIV(8U),
.pllsscg = {0x0U, (SYSCON_PLL0SSCG1_MDIV_EXT(3072U) | SYSCON_PLL0SSCG1_SEL_EXT_MASK)},
.pllRate = 24576000U,
.flags = PLL_SETUPFLAG_WAITLOCK};
/*!< Configure PLL to the desired values */
CLOCK_SetPLL0Freq(&pll0Setup);
CLOCK_SetClkDiv(kCLOCK_DivPll0Clk, 0U, true);
CLOCK_SetClkDiv(kCLOCK_DivPll0Clk, 1U, false);
/* I2S clocks */
CLOCK_AttachClk(kPLL0_DIV_to_FLEXCOMM6);
CLOCK_AttachClk(kPLL0_DIV_to_FLEXCOMM7);
/* Attach PLL clock to MCLK for I2S, no divider */
CLOCK_AttachClk(kPLL0_to_MCLK);
SYSCON->MCLKDIV = SYSCON_MCLKDIV_DIV(0U);
SYSCON->MCLKIO = 1U;
/* reset FLEXCOMM for I2C */
RESET_PeripheralReset(kFC4_RST_SHIFT_RSTn);
/* reset FLEXCOMM for DMA0 */
RESET_PeripheralReset(kDMA0_RST_SHIFT_RSTn);
/* reset FLEXCOMM for I2S */
RESET_PeripheralReset(kFC6_RST_SHIFT_RSTn);
RESET_PeripheralReset(kFC7_RST_SHIFT_RSTn);
/* reset NVIC for FLEXCOMM6 and FLEXCOMM7 */
NVIC_ClearPendingIRQ(FLEXCOMM6_IRQn);
NVIC_ClearPendingIRQ(FLEXCOMM7_IRQn);
/* Enable interrupts for I2S */
EnableIRQ(FLEXCOMM6_IRQn);
EnableIRQ(FLEXCOMM7_IRQn);
/* Initialize the rest */
BOARD_InitPins();
BOARD_BootClockPLL1_150M();
BOARD_InitDebugConsole();
BOARD_InitSysctrl();
PRINTF("\r\n");
PRINTF("**********************************\r\n");
PRINTF("Maestro audio solutions demo start\r\n");
PRINTF("**********************************\r\n");
PRINTF("\r\n");
ret = BOARD_CODEC_Init();
if (ret)
{
PRINTF("CODEC_Init failed\r\n");
return -1;
}
if (xTaskCreate(APP_SDCARD_Task, "SDCard Task", SDCARD_TASK_STACK_SIZE, &app, configMAX_PRIORITIES - 4, NULL) !=
pdPASS)
{
PRINTF("\r\nFailed to create application task\r\n");
while (1)
;
}
/* Set shell command task priority = 1 */
if (xTaskCreate(APP_Shell_Task, "Shell Task", SHELL_TASK_STACK_SIZE, &app, configMAX_PRIORITIES - 5,
&app.shell_task_handle) != pdPASS)
{
PRINTF("\r\nFailed to create application task\r\n");
while (1)
;
}
/* Run RTOS */
vTaskStartScheduler();
/* Should not reach this statement */
return 0;
}
/**
* @brief Loop forever if stack overflow is detected.
*
* If configCHECK_FOR_STACK_OVERFLOW is set to 1,
* this hook provides a location for applications to
* define a response to a stack overflow.
*
* Use this hook to help identify that a stack overflow
* has occurred.
*
*/
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
portDISABLE_INTERRUPTS();
/* Loop forever */
for (;;)
;
}
/**
* @brief Warn user if pvPortMalloc fails.
*
* Called if a call to pvPortMalloc() fails because there is insufficient
* free memory available in the FreeRTOS heap. pvPortMalloc() is called
* internally by FreeRTOS API functions that create tasks, queues, software
* timers, and semaphores. The size of the FreeRTOS heap is set by the
* configTOTAL_HEAP_SIZE configuration constant in FreeRTOSConfig.h.
*
*/
void vApplicationMallocFailedHook()
{
PRINTF(("\r\nERROR: Malloc failed to allocate memory\r\n"));
}
app_streamer.c
(侦听消息队列,接收错误信息、音频播放状态和音频播放位置的更新。)
/*
* Copyright 2020-2021 NXP
* All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "osa_common.h"
#include "fsl_common.h"
#include "fsl_debug_console.h"
#include "app_streamer.h"
#include "streamer_pcm.h"
#ifdef VIT_PROC
#include "vit_proc.h"
#endif
#ifdef EAP_PROC
#include "eap_proc.h"
static ext_proc_args eap_args;
#endif
#define APP_STREAMER_MSG_QUEUE "app_queue"
#define STREAMER_TASK_NAME "Streamer"
#define STREAMER_MESSAGE_TASK_NAME "StreamerMessage"
#define STREAMER_TASK_STACK_SIZE 4 * 1024
#define STREAMER_MESSAGE_TASK_STACK_SIZE 512
ringbuf_t *audioBuffer;
OsaMutex audioMutex;
OsaThread msg_thread;
/*!
* @brief Streamer task for communicating messages
*
* This function is the entry point of a task that is manually created by
* STREAMER_Create. It listens on a message queue and receives status updates
* about errors, audio playback state and position. The application can make
* use of this data.
*
* @param arg Data to be passed to the task
*/
static void STREAMER_MessageTask(void *arg)
{
OsaMq mq;
STREAMER_MSG_T msg;
streamer_handle_t *handle;
bool exit_thread = false;
int ret;
handle = (streamer_handle_t *)arg;
PRINTF("[STREAMER] Message Task started\r\n");
ret = osa_mq_open(&mq, APP_STREAMER_MSG_QUEUE, STREAMER_MSG_SIZE, true);
if (ERRCODE_NO_ERROR != ret)
{
PRINTF("osa_mq_open failed: %d\r\n", ret);
return;
}
do
{
ret = osa_mq_receive(&mq, (void *)&msg, STREAMER_MSG_SIZE, 0, NULL);
if (ret != ERRCODE_NO_ERROR)
{
PRINTF("osa_mq_receiver error: %d\r\n", ret);
continue;
}
switch (msg.id)
{
case STREAM_MSG_ERROR:
PRINTF("STREAM_MSG_ERROR\r\n");
exit_thread = true;
STREAMER_Stop(handle);
break;
case STREAM_MSG_EOS:
PRINTF("\nSTREAM_MSG_EOS\r\n");
/* Indicate to other software layers that playing has ended. */
STREAMER_Stop(handle);
break;
case STREAM_MSG_UPDATE_POSITION:
PRINTF("STREAM_MSG_UPDATE_POSITION..");
PRINTF(" position: %d ms\r", msg.event_data);
break;
case STREAM_MSG_CLOSE_TASK:
PRINTF("STREAM_MSG_CLOSE_TASK\r\n");
exit_thread = true;
break;
default:
break;
}
} while (!exit_thread);
osa_mq_close(&mq);
osa_mq_destroy(APP_STREAMER_MSG_QUEUE);
osa_thread_destroy(msg_thread);
}
int STREAMER_Read(uint8_t *data, uint32_t size)
{
uint32_t bytes_read;
osa_mutex_lock(&audioMutex);
bytes_read = ringbuf_read(audioBuffer, data, size);
osa_mutex_unlock(&audioMutex);
if (bytes_read != size)
{
PRINTF("[STREAMER WARN] read underrun: size: %d, read: %d\r\n", size, bytes_read);
}
return bytes_read;
}
int STREAMER_Write(uint8_t *data, uint32_t size)
{
uint32_t written;
osa_mutex_lock(&audioMutex);
written = ringbuf_write(audioBuffer, data, size);
osa_mutex_unlock(&audioMutex);
if (written != size)
{
PRINTF("[STREAMER ERR] write overflow: size %d, written %d\r\n", size, written);
}
return written;
}
bool STREAMER_IsPlaying(streamer_handle_t *handle)
{
return handle->audioPlaying;
}
void STREAMER_Start(streamer_handle_t *handle)
{
PRINTF("[STREAMER] start playback\r\n");
handle->audioPlaying = true;
streamer_set_state(handle->streamer, 0, STATE_PLAYING, true);
}
void STREAMER_Stop(streamer_handle_t *handle)
{
PRINTF("[STREAMER] stop playback\r\n");
handle->audioPlaying = false;
streamer_set_state(handle->streamer, 0, STATE_NULL, true);
/* Empty input ringbuffer. */
if (audioBuffer)
{
ringbuf_clear(audioBuffer);
}
}
status_t STREAMER_Create(streamer_handle_t *handle)
{
STREAMER_CREATE_PARAM params;
ELEMENT_PROPERTY_T prop;
OsaThreadAttr thread_attr;
int ret;
osa_mutex_create(&audioMutex, false);
audioBuffer = ringbuf_create(AUDIO_BUFFER_SIZE);
if (!audioBuffer)
{
return kStatus_Fail;
}
/* Create message process thread */
osa_thread_attr_init(&thread_attr);
osa_thread_attr_set_name(&thread_attr, STREAMER_MESSAGE_TASK_NAME);
osa_thread_attr_set_stack_size(&thread_attr, STREAMER_MESSAGE_TASK_STACK_SIZE);
ret = osa_thread_create(&msg_thread, &thread_attr, STREAMER_MessageTask, (void *)handle);
osa_thread_attr_destroy(&thread_attr);
if (ERRCODE_NO_ERROR != ret)
{
return kStatus_Fail;
}
/* Create streamer */
strcpy(params.out_mq_name, APP_STREAMER_MSG_QUEUE);
params.stack_size = STREAMER_TASK_STACK_SIZE;
params.pipeline_type = STREAM_PIPELINE_NETBUF;
params.task_name = STREAMER_TASK_NAME;
params.in_dev_name = "";
params.out_dev_name = "";
handle->streamer = streamer_create(¶ms);
if (!handle->streamer)
{
return kStatus_Fail;
}
prop.prop = PROP_NETBUFSRC_SET_CALLBACK;
prop.val = (uintptr_t)STREAMER_Read;
streamer_set_property(handle->streamer, prop, true);
prop.prop = PROP_DECODER_DECODER_TYPE;
prop.val = DECODER_TYPE_MP3;
streamer_set_property(handle->streamer, prop, true);
handle->audioPlaying = false;
return kStatus_Success;
}
status_t STREAMER_mic_Create(streamer_handle_t *handle, out_sink_t out_sink)
{
STREAMER_CREATE_PARAM params;
OsaThreadAttr thread_attr;
ELEMENT_PROPERTY_T prop;
int ret;
/* Create message process thread */
osa_thread_attr_init(&thread_attr);
osa_thread_attr_set_name(&thread_attr, STREAMER_MESSAGE_TASK_NAME);
osa_thread_attr_set_stack_size(&thread_attr, STREAMER_MESSAGE_TASK_STACK_SIZE);
ret = osa_thread_create(&msg_thread, &thread_attr, STREAMER_MessageTask, (void *)handle);
osa_thread_attr_destroy(&thread_attr);
if (ERRCODE_NO_ERROR != ret)
{
return kStatus_Fail;
}
/* Create streamer */
strcpy(params.out_mq_name, APP_STREAMER_MSG_QUEUE);
params.stack_size = STREAMER_TASK_STACK_SIZE;
switch (out_sink)
{
case AUDIO_SINK:
params.pipeline_type = STREAM_PIPELINE_PCM;
params.out_dev_name = "";
break;
case FILE_SINK:
params.pipeline_type = STREAM_PIPELINE_MIC2FILE;
params.out_dev_name = "file";
break;
case VIT_SINK:
params.pipeline_type = STREAM_PIPELINE_VIT;
params.out_dev_name = "";
break;
default:
PRINTF("[STREAMER ERR] wrong type of sink\r\n");
return kStatus_InvalidArgument;
}
params.task_name = STREAMER_TASK_NAME;
params.in_dev_name = "microphone";
handle->streamer = streamer_create(¶ms);
if (!handle->streamer)
{
return kStatus_Fail;
}
#ifdef VIT_PROC
if (params.pipeline_type == STREAM_PIPELINE_VIT)
{
EXT_PROCESS_DESC_T vit_proc = {VIT_Initialize_func, VIT_Execute_func, VIT_Deinit_func, &Vit_Language, 0};
prop.prop = PROP_VITSINK_FPOINT;
prop.val = (uintptr_t)&vit_proc;
streamer_set_property(handle->streamer, prop, true);
}
#else
if (params.pipeline_type == STREAM_PIPELINE_VIT)
{
PRINTF("[STREAMER] VIT pipeline not available for this config\r\n switching to audio sink");
params.pipeline_type = STREAM_PIPELINE_PCM;
}
#endif
prop.prop = PROP_AUDIOSRC_SET_SAMPLE_RATE;
prop.val = 16000;
streamer_set_property(handle->streamer, prop, true);
prop.prop = PROP_AUDIOSRC_SET_CHUNK_SIZE;
prop.val = 160;
streamer_set_property(handle->streamer, prop, true);
return kStatus_Success;
}
status_t STREAMER_file_Create(streamer_handle_t *handle, char *filename, int eap_par)
{
STREAMER_CREATE_PARAM params;
OsaThreadAttr thread_attr;
int ret;
/* Create message process thread */
osa_thread_attr_init(&thread_attr);
osa_thread_attr_set_name(&thread_attr, STREAMER_MESSAGE_TASK_NAME);
osa_thread_attr_set_stack_size(&thread_attr, STREAMER_MESSAGE_TASK_STACK_SIZE);
ret = osa_thread_create(&msg_thread, &thread_attr, STREAMER_MessageTask, (void *)handle);
osa_thread_attr_destroy(&thread_attr);
if (ERRCODE_NO_ERROR != ret)
{
return kStatus_Fail;
}
/* Create streamer */
strcpy(params.out_mq_name, APP_STREAMER_MSG_QUEUE);
params.stack_size = STREAMER_TASK_STACK_SIZE;
#ifdef EAP_PROC
params.pipeline_type = STREAM_PIPELINE_EAP;
#else
params.pipeline_type = STREAM_PIPELINE_FILESYSTEM;
#endif
params.task_name = STREAMER_TASK_NAME;
params.in_dev_name = "";
params.out_dev_name = "";
handle->streamer = streamer_create(¶ms);
if (!handle->streamer)
{
return kStatus_Fail;
}
streamer_set_file(handle->streamer, 0, filename, STATE_NULL, true);
#ifdef EAP_PROC
ELEMENT_PROPERTY_T prop;
eap_args.preset_num = eap_par;
EXT_PROCESS_DESC_T eap_proc = {EAP_Initialize_func, EAP_Execute_func, EAP_Deinit_func, &eap_args, 0};
prop.prop = PROP_EAP_FPOINT;
prop.val = (uintptr_t)&eap_proc;
streamer_set_property(handle->streamer, prop, true);
#endif
handle->audioPlaying = false;
return kStatus_Success;
}
void STREAMER_Destroy(streamer_handle_t *handle)
{
streamer_destroy(handle->streamer);
if (audioMutex != NULL)
{
osa_mutex_destroy(&audioMutex);
audioMutex = NULL;
}
if (audioBuffer != NULL)
{
ringbuf_destroy(audioBuffer);
audioBuffer = NULL;
}
deinit_logging();
osa_deinit();
}
void STREAMER_Init(void)
{
/* Initialize OSA*/
osa_init();
/* Initialize logging */
init_logging();
add_module_name(LOGMDL_STREAMER, "STREAMER");
/* Uncomment below to turn on full debug logging for the streamer. */
// set_debug_module(0xffffffff);
// set_debug_level(LOGLVL_DEBUG);
// get_debug_state();
/* Initialize streamer PCM management library. */
streamer_pcm_init();
}
cmd.c
(命令定义与分发处理)
/*
* Copyright 2019 NXP
* All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*${header:start}*/
#include "cmd.h"
#include <string.h>
#include <stdint.h>
#include "fsl_debug_console.h"
#include "fsl_shell.h"
#include "app_streamer.h"
#include "fsl_sd_disk.h"
#ifdef VIT_PROC
#include "PL_platformTypes_CortexM7.h"
#include "VIT.h"
#include "vit_proc.h"
#endif
/*${header:end}*/
/*******************************************************************************
* Definitions
******************************************************************************/
/*${macro:start}*/
#define EAP_MAX_PRESET 10
/*${macro:end}*/
/*******************************************************************************
* Prototypes
******************************************************************************/
/*${prototype:start}*/
static shell_status_t shellEcho(shell_handle_t shellHandle, int32_t argc, char **argv);
static shell_status_t shellFile(shell_handle_t shellHandle, int32_t argc, char **argv);
static shell_status_t shellRecMIC(shell_handle_t shellHandle, int32_t argc, char **argv);
/*${prototype:end}*/
/*******************************************************************************
* Variables
******************************************************************************/
/*${variable:start}*/
SHELL_COMMAND_DEFINE(version, "\r\n\"version\": Display component versions\r\n", shellEcho, 0);
SHELL_COMMAND_DEFINE(file,
"\r\n\"file\": Perform audio file decode and playback\r\n"
"\r\n"
" USAGE: file [list|<audio_file>|all]\r\n"
" list List audio files on SD card available for playback\r\n"
" <audio_file> Select file from SD card and start playback\r\n"
" all Play all supported audio files from SD card.\r\n"
#ifdef EAP_PROC
" eap choose one of the options:\r\n"
" 1: All effect Off, 2: Voice enhancer, 3: Music enhancer\r\n"
" 4: Auto volume leveler, 5: Loudness max, 6: 3D Concert snd\r\n"
" 7: Custom, 8: Tone generator, 9: Crossover two way speakers\r\n"
" 10: Crossover for subwoofer\r\n"
" EXAMPLE: Playback with eap effect: file <audio_file> eap 3\r\n"
#endif
,
shellFile,
SHELL_IGNORE_PARAMETER_COUNT);
SHELL_COMMAND_DEFINE(record_mic,
"\r\n\"record_mic\": Record MIC audio and either:\r\n"
#ifdef VIT_PROC
" - perform voice recognition (VIT)\r\n"
#endif
" - playback on WM8904 codec\r\n"
" - store samples to file.\r\n"
"\r\n"
#ifdef VIT_PROC
" USAGE: record_mic [audio|file|vit] 20 [en|cn]\r\n"
#else
" USAGE: record_mic [audio|file] 20\r\n"
#endif
" The number defines length of recording in seconds.\r\n"
#ifdef VIT_PROC
" For voice recognition say supported WakeWord and in 3s frame supported command.\r\n"
" Please note that this VIT demo is near-field and uses 1 on-board microphone.\r\n"
#endif
" NOTE: this command returns to shell after record finished.\r\n",
shellRecMIC,
SHELL_IGNORE_PARAMETER_COUNT);
static bool file_playing = false;
SDK_ALIGN(static uint8_t s_shellHandleBuffer[SHELL_HANDLE_SIZE], 4);
static shell_handle_t s_shellHandle;
extern serial_handle_t g_serialHandle;
static handleShellMessageCallback_t *g_handleShellMessageCallback;
static void *g_handleShellMessageCallbackData;
streamer_handle_t streamerHandle;
/*${variable:end}*/
/*******************************************************************************
* Code
******************************************************************************/
/*${function:start}*/
void play_file(char *filename, int eap_par)
{
/* On platforms with limited RAM it is not possible to run EAP with Opus decoder.
* In order to use Opus decoder it is necessary to disable EAP removing EAP_PROC define from project
* settings. It is also necessary to make sure that MAX_SAMPLE_SIZE is defined to 960.*/
#if (defined(MAX_SAMPLE_SIZE) && (MAX_SAMPLE_SIZE < 960) && defined(EAP_PROC))
char *dot = strrchr(filename, '.');
if ((strncmp(dot + 1, "opus", 4) == 0) || (strncmp(dot + 1, "ogg", 3) == 0))
{
PRINTF("Opus files not supported with EAP. Please either disable EAP or use EAP with mp3.\r\n");
return;
}
#endif
STREAMER_Init();
int ret = STREAMER_file_Create(&streamerHandle, filename, eap_par);
if (ret != kStatus_Success)
{
PRINTF("STREAMER_file_Create failed\r\n");
goto file_error;
}
STREAMER_Start(&streamerHandle);
PRINTF("Starting playback\r\n");
file_playing = true;
while (streamerHandle.audioPlaying)
{
}
file_playing = false;
file_error:
PRINTF("Cleanup\r\n");
STREAMER_Destroy(&streamerHandle);
osa_time_delay(1000);
}
static shell_status_t shellEcho(shell_handle_t shellHandle, int32_t argc, char **argv)
{
PRINTF(" Maestro version: 1.0\r\n");
#ifdef EAP_PROC
PRINTF(" EAP version: 3.0\r\n");
#endif
#ifdef VIT_PROC
PRINTF(" VIT version: 5.0.0\r\n");
#endif
return kStatus_SHELL_Success;
}
static shell_status_t shellFile(shell_handle_t shellHandle, int32_t argc, char **argv)
{
app_handle_t *app = (app_handle_t *)g_handleShellMessageCallbackData;
DIR directory;
FILINFO fileInformation;
char *filename, *dot;
uint32_t count = 0;
FRESULT error;
int eap_par = 0;
if (!app->sdcardInserted)
{
PRINTF("Please insert an SD card with audio files and retry this command\r\n");
return kStatus_SHELL_Success;
}
// check if eap setting is presented
if ((argc > 2) && (strcmp(argv[2], "eap") == 0))
{
eap_par = abs(atoi(argv[3]));
if (eap_par < 1 || eap_par > EAP_MAX_PRESET)
{
PRINTF("EAP preset number out of range, setting EAP all effects OFF.\r\n");
eap_par = 0;
}
}
if (strcmp(argv[1], "list") == 0)
{
error = f_opendir(&directory, "/");
if (error)
{
PRINTF("Failed to open root directory of SD card\r\n");
return kStatus_SHELL_Error;
}
PRINTF("Available audio files:\r\n");
while (1)
{
error = f_readdir(&directory, &fileInformation);
/* When dir end or error detected, break out */
if ((error != FR_OK) || (fileInformation.fname[0U] == 0U))
{
break;
}
/* Skip root directory */
if (fileInformation.fname[0] == '.')
{
continue;
}
if (!(fileInformation.fattrib & AM_DIR))
{
/* Check file for supported audio extension */
dot = strrchr(fileInformation.fname, '.');
if ((dot && strncmp(dot + 1, "opus", 4) == 0) || (dot && strncmp(dot + 1, "ogg", 3) == 0) ||
(dot && strncmp(dot + 1, "mp3", 3) == 0))
{
PRINTF(" %s\r\n", fileInformation.fname);
count++;
}
}
}
if (error == FR_OK)
{
f_closedir(&directory);
}
if (!count)
{
PRINTF(" (none)\r\n");
}
}
else if (strcmp(argv[1], "stop") == 0)
{
if (file_playing)
{
g_handleShellMessageCallback(g_handleShellMessageCallbackData);
return kStatus_SHELL_Success;
}
else
{
PRINTF("File is not playing \r\n");
return kStatus_SHELL_Error;
}
}
else if (strcmp(argv[1], "all") == 0)
{
if (!file_playing)
{
error = f_opendir(&directory, "/");
if (error)
{
PRINTF("Failed to open root directory of SD card\r\n");
return kStatus_SHELL_Error;
}
PRINTF("Looking for available files:\r\n");
while (1)
{
error = f_readdir(&directory, &fileInformation);
/* When dir end or error detected, break out */
if ((error != FR_OK) || (fileInformation.fname[0U] == 0U))
{
break;
}
/* Skip root directory */
if (fileInformation.fname[0] == '.')
{
continue;
}
if (!(fileInformation.fattrib & AM_DIR))
{
/* Check file for supported audio extension */
dot = strrchr(fileInformation.fname, '.');
if ((dot && strncmp(dot + 1, "opus", 4) == 0) || (dot && strncmp(dot + 1, "ogg", 3) == 0) ||
(dot && strncmp(dot + 1, "mp3", 3) == 0))
{
PRINTF("Play file: %s \r\n", fileInformation.fname);
play_file(fileInformation.fname, eap_par);
count++;
}
}
}
if (error == FR_OK)
{
f_closedir(&directory);
}
if (!count)
{
PRINTF(" (none)\r\n");
}
}
else
{
PRINTF("File is already playing\r\n");
return kStatus_SHELL_Error;
}
}
else if (!file_playing)
{
filename = argv[1];
dot = strrchr(filename, '.');
if ((dot && strncmp(dot + 1, "mp3", 3) == 0) || (dot && strncmp(dot + 1, "ogg", 3) == 0) ||
(dot && strncmp(dot + 1, "opus", 4) == 0))
{
count = 1;
}
if (!count)
{
PRINTF("Unsupported file type %s\r\n", filename);
return kStatus_SHELL_Error;
}
PRINTF("\r\nStarting streamer demo application\r\n");
error = f_stat(filename, NULL);
if (error != FR_OK)
{
PRINTF("Error: File checking error.\r\n");
}
else
{
play_file(filename, eap_par);
}
}
else
{
PRINTF("File is already playing\r\n");
return kStatus_SHELL_Error;
}
return kStatus_SHELL_Success;
}
static shell_status_t shellRecMIC(shell_handle_t shellHandle, int32_t argc, char **argv)
{
status_t ret;
out_sink_t out_sink;
int duration = 20;
#ifdef VIT_PROC
Vit_Language = EN;
#endif
if ((argc >= 1) && (strcmp(argv[1], "file") == 0))
{
out_sink = FILE_SINK;
}
else if ((argc >= 1) && (strcmp(argv[1], "audio") == 0))
{
out_sink = AUDIO_SINK;
}
else if ((argc >= 1) && (strcmp(argv[1], "vit") == 0))
{
out_sink = VIT_SINK;
}
else
{
PRINTF("STREAMER_Create wrong out sink type, using default audio\r\n");
out_sink = AUDIO_SINK;
}
if ((argc >= 2))
{
if (strcmp(argv[2], '\0') != 0)
{
duration = abs(atoi(argv[2]));
}
}
if ((argc >= 3))
{
if (strcmp(argv[3], "cn") == 0)
{
#ifdef VIT_PROC
Vit_Language = CN;
#endif
}
}
PRINTF("\r\nStarting streamer demo application for %d sec\r\n", duration);
STREAMER_Init();
ret = STREAMER_mic_Create(&streamerHandle, out_sink);
if (ret != kStatus_Success)
{
PRINTF("STREAMER_Create failed\r\n");
goto error;
}
PRINTF("Starting recording\r\n");
#ifdef VIT_PROC
if (out_sink == VIT_SINK)
{
PRINTF("\r\nTo see VIT functionality say wake-word and command.\r\n");
}
#endif
STREAMER_Start(&streamerHandle);
osa_time_delay(duration * 1000);
STREAMER_Stop(&streamerHandle);
error:
PRINTF("Cleanup\r\n");
STREAMER_Destroy(&streamerHandle);
return kStatus_SHELL_Success;
}
void shellCmd(handleShellMessageCallback_t *handleShellMessageCallback, void *arg)
{
/* Init SHELL */
s_shellHandle = &s_shellHandleBuffer[0];
SHELL_Init(s_shellHandle, g_serialHandle, ">> ");
/* Add new command to commands list */
SHELL_RegisterCommand(s_shellHandle, SHELL_COMMAND(version));
SHELL_RegisterCommand(s_shellHandle, SHELL_COMMAND(file));
SHELL_RegisterCommand(s_shellHandle, SHELL_COMMAND(record_mic));
g_handleShellMessageCallback = handleShellMessageCallback;
g_handleShellMessageCallbackData = arg;
#if !(defined(SHELL_NON_BLOCKING_MODE) && (SHELL_NON_BLOCKING_MODE > 0U))
while (1)
{
SHELL_Task(s_shellHandle);
}
#endif
}
/*${function:end}*/