SDK DEMO项目:ap3bp_evb_vos_pcm_recorder_20210901
pcm_recorder.c
//*****************************************************************************
//
// Options
//
//*****************************************************************************
#define PRINT_UART 0
#define USE_OPUS 1
//*****************************************************************************
//
// Cache configuration
//
//*****************************************************************************
const am_hal_cachectrl_config_t am_hal_cachectrl_benchmark =
{
.bLRU = 0,
.eDescript = AM_HAL_CACHECTRL_DESCR_1WAY_128B_512E,
.eMode = AM_HAL_CACHECTRL_CONFIG_MODE_INSTR,
};
#define RTT_LOGGER_BUFFER_LENGTH_BYTE (128*1024)
uint8_t ui8RttLoggerBuffer[RTT_LOGGER_BUFFER_LENGTH_BYTE];
#define AUDIO_FRAME_MS 20
#define AUDIO_FRAME_SIZE_SAMPLES ((16000)/(1000/AUDIO_FRAME_MS))
#define AUDIO_FRAME_SIZE_MONO_BYTES (AUDIO_FRAME_SIZE_SAMPLES*2)
#define AUDIO_FRAME_SIZE_STEREO_BYTES (AUDIO_FRAME_SIZE_SAMPLES*4)
#define ENCODED_HEADER_SIZE_BYITES 8
#define ENCODED_COMPRESS_RATE 8
#define ENCODED_FRAME_SIZE_BYTES ((AUDIO_FRAME_SIZE_SAMPLES*2/ENCODED_COMPRESS_RATE) + ENCODED_HEADER_SIZE_BYITES)
uint8_t g_opusAudioInputBuffer[AUDIO_FRAME_SIZE_MONO_BYTES]; // 20ms mono raw audio frame
bool g_opusInputReadyFlag = false;
#if USE_OPUS
uint8_t g_opusAudioOutputBuffer[ENCODED_FRAME_SIZE_BYTES]; // encoded audio frame, with header
uint16_t g_opusInputIndex = 0;
#endif // #if USE_OPUS
//*****************************************************************************
//
// PDM configuration information.
//
//*****************************************************************************
void *PDMHandle = NULL;
#define PDM_CLK 12
#define PDM_CLK_PIN_CFG AM_HAL_PIN_12_PDMCLK
#define PDM_DATA 11
#define PDM_DATA_PIN_CFG AM_HAL_PIN_11_PDMDATA
am_hal_pdm_config_t g_sPdmConfig =
{
.eClkDivider = AM_HAL_PDM_MCLKDIV_1, //主时钟分频设置为1,不分频
.eLeftGain = AM_HAL_PDM_GAIN_P105DB, //左声道增益延迟设置为105dB
.eRightGain = AM_HAL_PDM_GAIN_P105DB,
.ui32DecimationRate = 48, // CLK 1.5 Mhz //降频采样倍数
.bHighPassEnable = 0, //禁用高通滤波器
.ui32HighPassCutoff = 0xB,
.ePDMClkSpeed = AM_HAL_PDM_CLK_1_5MHZ, //PDM采样频率,1.5M转换后是16K音频频率
.bInvertI2SBCLK = 0,
.ePDMClkSource = AM_HAL_PDM_INTERNAL_CLK, //使用内部时钟源
.bPDMSampleDelay = 0, //PDM采样延迟
.bDataPacking = 1, //启用数据换包
.ePCMChannels = AM_HAL_PDM_CHANNEL_LEFT, //录音声道选择
.bLRSwap = 0, //不交换左右声道
.bSoftMute = 0, //禁用软静音
};
//*****************************************************************************
//
// Start a transaction to get some number of bytes from the PDM interface.
//
//*****************************************************************************
uint32_t g_ui32PDMDataBuffer[AUDIO_FRAME_SIZE_MONO_BYTES/4];
void
pdm_trigger_dma(void)
{
//
// Configure DMA and target address.
//
am_hal_pdm_transfer_t sTransfer;
sTransfer.ui32TargetAddr = (uint32_t ) g_ui32PDMDataBuffer;
sTransfer.ui32TotalCount = (AUDIO_FRAME_SIZE_MONO_BYTES);
//
// Start the data transfer.
//
am_hal_pdm_dma_start(PDMHandle, &sTransfer);
}
//*****************************************************************************
//
// Structure for handling PDM register state information for power up/down
//
//*****************************************************************************
typedef struct
{
bool bValid;
}
am_hal_pdm_register_state_t;
//*****************************************************************************
//
// Structure for handling PDM HAL state information.
//
//*****************************************************************************
typedef struct
{
am_hal_handle_prefix_t prefix;
am_hal_pdm_register_state_t sRegState;
uint32_t ui32Module;
}
am_hal_pdm_state_t;
void am_app_KWD_pdm_dma_disable(void *pHandle)
{
am_hal_pdm_state_t *pState = (am_hal_pdm_state_t *) pHandle;
uint32_t ui32Module = pState->ui32Module;
//
// Disable DMA
//
PDMn(ui32Module)->DMACFG_b.DMAEN = PDM_DMACFG_DMAEN_DIS;
}
//-----------------------------------------------------------------------------
// METHOD: PDM_Init
// PURPOSE: PDM module configuration
//-----------------------------------------------------------------------------
void PDMInit(void)
{
if (PDMHandle != NULL)
return;
//
// Initialize, power-up, and configure the PDM.
//
am_hal_pdm_initialize(0, &PDMHandle);
//
// Configure the necessary pins.
//
am_hal_gpio_pincfg_t sPinCfg = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
sPinCfg.uFuncSel = PDM_CLK_PIN_CFG;
am_hal_gpio_pinconfig(PDM_CLK, sPinCfg);
sPinCfg.uFuncSel = PDM_DATA_PIN_CFG;
am_hal_gpio_pinconfig(PDM_DATA, sPinCfg);
am_hal_pdm_power_control(PDMHandle, AM_HAL_PDM_POWER_ON, false);
am_hal_pdm_configure(PDMHandle, &g_sPdmConfig);
am_hal_pdm_fifo_flush(PDMHandle);
//
// Configure and enable PDM interrupts (set up to trigger on DMA
// completion).
//
am_hal_pdm_interrupt_enable(PDMHandle, (AM_HAL_PDM_INT_DERR
| AM_HAL_PDM_INT_DCMP
| AM_HAL_PDM_INT_UNDFL
| AM_HAL_PDM_INT_OVF));
NVIC_EnableIRQ(PDM_IRQn);
}
void PDMDeInit()
{
if(PDMHandle == NULL)
return;
am_hal_pdm_interrupt_clear(PDMHandle, (AM_HAL_PDM_INT_DERR
| AM_HAL_PDM_INT_DCMP
| AM_HAL_PDM_INT_UNDFL
| AM_HAL_PDM_INT_OVF));
am_hal_pdm_interrupt_disable(PDMHandle, (AM_HAL_PDM_INT_DERR
| AM_HAL_PDM_INT_DCMP
| AM_HAL_PDM_INT_UNDFL
| AM_HAL_PDM_INT_OVF));
NVIC_DisableIRQ(PDM_IRQn);
am_hal_gpio_pinconfig(PDM_CLK, g_AM_HAL_GPIO_DISABLE);
am_hal_gpio_pinconfig(PDM_DATA, g_AM_HAL_GPIO_DISABLE);
// This was a workaround code to measure power consumption. Need to review!!
am_app_KWD_pdm_dma_disable(PDMHandle);
am_hal_pdm_disable(PDMHandle);
am_hal_pdm_power_control(PDMHandle, AM_HAL_PDM_POWER_OFF, false);
am_hal_pdm_deinitialize(PDMHandle);
PDMHandle = NULL;
}
void am_pdm0_isr(void)
{
uint32_t ui32Status;
//
// Read the interrupt status.
//
am_hal_pdm_interrupt_status_get(PDMHandle, &ui32Status, true);
am_hal_pdm_interrupt_clear(PDMHandle, ui32Status);
//
// Once our DMA transaction completes, we will disable the PDM and send a
// flag back down to the main routine. Disabling the PDM is only necessary
// because this example only implemented a single buffer for storing FFT
// data. More complex programs could use a system of multiple buffers to
// allow the CPU to run the FFT in one buffer while the DMA pulls PCM data
// into another buffer.
//
if (ui32Status & AM_HAL_PDM_INT_DCMP)
{
// trigger next traction
PDMn(0)->DMATOTCOUNT = AUDIO_FRAME_SIZE_MONO_BYTES; // FIFO unit in bytes
amu2s_send(Amu2s_pcm, g_ui32PDMDataBuffer, AMU2S_PCM_BYTES);
#if USE_OPUS
//
// move data to opus buffer
//
memmove(g_opusAudioInputBuffer, g_ui32PDMDataBuffer,AUDIO_FRAME_SIZE_MONO_BYTES);
g_opusInputReadyFlag = true;
#endif // #if USE_OPUS
}
else if(ui32Status & (AM_HAL_PDM_INT_UNDFL | AM_HAL_PDM_INT_OVF))
{
am_hal_pdm_fifo_flush(PDMHandle);
}
}
#define AM_CRITICAL_BEGIN_VOS \
if ( 1 ) \
{ \
volatile uint32_t ui32Primask_04172010; \
ui32Primask_04172010 = am_hal_interrupt_master_disable();
#define AM_CRITICAL_END_VOS \
am_hal_interrupt_master_set(ui32Primask_04172010); \
}
//*****************************************************************************
//
// Main Function.
//
//*****************************************************************************
int
main(void)
{
//
// Set the clock frequency.
//
am_hal_clkgen_control(AM_HAL_CLKGEN_CONTROL_SYSCLK_MAX, 0);
//
// Set the default cache configuration
//
am_hal_cachectrl_config(&am_hal_cachectrl_benchmark);
am_hal_cachectrl_enable();
//
// Configure the board for low power operation.
//
am_bsp_low_power_init();
#if (PRINT_UART == 1)
am_bsp_uart_printf_enable();
//
// Clear the terminal and print the banner.
//
am_util_stdio_terminal_clear();
am_util_stdio_printf("Ambiq Micro 'while' example.\n\n");
//
// Brief description
//
am_util_stdio_printf("Used for measuring power in an infinite while loop.\n");
//
// Print the compiler version.
//
am_util_stdio_printf("App Compiler: %s\n", COMPILER_VERSION);
am_util_stdio_printf("HAL Compiler: %s\n", g_ui8HALcompiler);
am_util_stdio_printf("HAL SDK version: %d.%d.%d\n",
g_ui32HALversion.s.Major,
g_ui32HALversion.s.Minor,
g_ui32HALversion.s.Revision);
am_util_stdio_printf("HAL compiled with %s-style registers\n",
g_ui32HALversion.s.bAMREGS ? "AM_REG" : "CMSIS");
#endif // PRINT_UART
amu2s_init();
PDMInit();
am_hal_pdm_enable(PDMHandle);
pdm_trigger_dma();
SEGGER_RTT_ConfigUpBuffer(1, "TestDataLogger", ui8RttLoggerBuffer, RTT_LOGGER_BUFFER_LENGTH_BYTE, SEGGER_RTT_MODE_NO_BLOCK_SKIP);
#if USE_OPUS
/* initialize the audio encoder */
audio_enc_init(true); // true for with header
#endif // #if USE_OPUS
// master interrupt enable
am_hal_interrupt_master_enable();
while(1)
{
#if USE_OPUS
if(g_opusInputReadyFlag == true)
{
g_opusInputReadyFlag = false;
// data ready to encode, run encoder
uint32_t encoded_bytes = audio_enc_encode_frame((short*)g_opusAudioInputBuffer, AUDIO_FRAME_SIZE_SAMPLES, (unsigned char*)g_opusAudioOutputBuffer);
// send to RTT
SEGGER_RTT_Write(1, (uint8_t*)g_opusAudioOutputBuffer, encoded_bytes);
// send to AmU2S
amu2s_send(Amu2s_opus, g_opusAudioOutputBuffer, AMU2S_OPUS_BYTES);
#endif // #if USE_OPUS
}
}
项目编译,在开发板上运行,打开官方配备的工具,抓取录音数据,如下图:
按任意键停止抓取,即可抓取到非标准的PCM音频,如下图:
使用提供的 Python 脚本
运行以下命令解码为 PCM 文件:
python ftdi_bin_decoder.py decoder input.bin --output=audio --format=raw
生成文件:audio_pcm
(原始 PCM 数据)
执行成功,同时生成标准的PCM数据BIN文件和OPUS压缩的BIN文件,如下图:
如果出现以下错误提示
import pandas as pd
ModuleNotFoundError: No module named 'pandas'
遇到 ModuleNotFoundError: No module named 'pandas' 错误,说明你的 Python 环境中缺少 pandas 库
通过 pip 直接安装:pip install pandas
转换为 WAV 播放命令 :
python bin_to_wav.py mono -i audio_pcm
在PCM音频(BIN)文件加上WAV文件头,
执行成功后,生成可播放音频文件,如下图:
如果出现以下错误提示:
import soundfile as sf
ModuleNotFoundError: No module named 'soundfile'
错误 ModuleNotFoundError: No module named 'soundfile' 表明 Python 环境中缺少 soundfile 库。该库用于读写音频文件(如 WAV 格式),是 bin_to_wav.py 脚本的依赖项。
解决方法
安装 soundfile 库
通过 pip 直接安装:pip install soundfile
bin_to_wav.py这个python脚本重要补充说明
-
硬件适配特性:
-
声道交换:通过
--swap 1
参数修正 Apollo PDM 模块的左右声道反接问题 -
位深度处理:假设输入为 16-bit PCM,通过
/32768
实现标准化
-
-
音频处理流程: 原始PCM --> 解析为整数 --> 声道分离 --> 浮点归一化 --> 峰值归一化 --> 输出WAV