Funpack第11期使用LPC55S69实现音频播放
Funpack第11期:使用LPC55S69配套的示例实现TF卡上音频播放
标签
嵌入式系统
Flying费
更新2021-11-03
1375

.项目介绍  

1.NXP LPC55S69开发板

LPCXpresso55S69 开发板为评估和开发基于 Arm ® Cortex ® -M33 架构的 LPC55S6x MCU 提供了理想的平台。该板包括高性能板载调试探针、音频子系统和加速度计,并提供多种选项,用于添加用于网络、传感器、显示器和其他接口的现成附加板。。

FiPUfR_Wv2ETeW9oH7EC4Lg2mnsi

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”.

FvBmMw_X561BHGtE-BQOHoSVRNnu

 

第二步:实现和调试

①Bulid工程后;

FmpiY-aGUnr-Ui0jmw0xqd44_KeP

②选择核心0运行程序。等待下载完成后,使用“Terminal”选项卡新建串行终端,并使用如下的设置。

      FpBS-estbXjwpWrSkOweBVJC3ERn

      Serial Terminal

      - 115200 baud rate
      - 8 data bits
      - No parity
      - One stop bit
      - No flow control

③使用“Quickstart Panel”下的“Debug”选项进行工程的编译与下载。Fo3QwkJieTXEudAhAoG-ucliee5v

随后按下开发板上的重置按钮(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

FgS9lo3Vmo6JYf2_aPPf6Wd-1M8h

 

第三步:使用与演示

①准备工作

将TF卡插入卡槽

将耳机连接到 Audio HP / Line-Out 接口。

注意:需要将音频线完整插入音频输出口,否则很可能出现无音频输出的结果。

 

②指令的使用说明:

输入“help”以查看命令列表。

在串口控制台上,显示:

>> help

“help”::列出所有已注册的命令

“exit”:退出程序

“version”:显示组件版本

"file": 执行音频文件解码和播放

用法:file [list|<audio_file>|all]

list 列出 SD 卡上可供播放的音频文件

<audio_file> 从 SD 卡中选择文件并开始播放

all 从 SD 卡播放所有支持的音频文件。

FhtB09eDQi7RUmNTwLGA7Bp_rMxs

(eap 选项:

1:所有效果关闭,

2:语音增强器,

3:音乐增强器

4:自动音量调节器,

5:最大响度,

6:3D 音乐,

7:自定义,

8:Tone generator,

9:分频两路扬声器,

10:低音分频器

示例:

使用 eap 效果播放:file <audio_file> eap 3)

 

此外还有“record_mic”录制 MIC 音频等多种功能。

 

第四步:演示实拍

FvgZajX79ixXL5-6rwfLe2anIeJdFg4ek3_7Xc9uMT4EJZVLAT6Lnep1

 

.活动总结与感想

本次活动的任务实现是非常简单的。

通过这个项目倒是熟悉了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(&params);
    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(&params);
    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(&params);
    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}*/

 

附件下载
Audio_Example_maestro_demo.7z
团队介绍
很久没有开发过嵌入式设备了(目前主要写写简单的Java,刚工作)。
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号