ISD9160学习笔记03_ISD9160音频解码代码分析

本文详细解析了音视频播放过程中的核心函数App_StartPlay和App_ProcessPlay,重点介绍了NuLiteExApp_DecodeStartPlayByAddr函数如何完成音频文件的读取、解码及PCM数据的生成,并通过Playback模块实现声音播放。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

录音例程涉及了录音和播放两大块内容,这篇笔记就先来说说播放,暂且先击破解码这部分功能。

我的锤子便签中有上个月记下的一句话,“斯蒂芬·平克说,写作之难,在于把网状思考,用树状结构,体现在线性展开的语句里。”这篇代码解析也有类似的困难,代码的网状结构,如何用文章这种线性载体来体现。我尽量挑出了主干,来讲解自己的理解。另外在文章最后添加了一个模块拓扑图来帮助消化。

我还是建议大家还是多琢磨下源码,代码的事还是让代码来说话,笔记是一个辅助的概括梳理。

本文作者twowinter,转载请注明:http://blog.youkuaiyun.com/iotisan/

查看代码主逻辑,主要是App_StartPlay和App_ProcessPlay这两个函数。下面就分别进行分析。

第一部分 App_StartPlay

BOOL App_StartPlay(void)
{
	// Initiate NuLiteEx audio decode lib with callback functions stored in g_asAppCallBack[0]
	NuLiteExApp_DecodeInitiate(&g_sApp.sNuLiteExAppDecode, (UINT8 *)&g_sApp.uTempBuf, 0);
	
	// Start NuLiteEx decode lib to decode NuLiteEx file stored from address and played from audio channel 0.
	// And decode the first frame of PCMs.
	if ( NuLiteExApp_DecodeStartPlayByAddr(&g_sApp.sNuLiteExAppDecode, AUDIOROM_STORAGE_START_ADDR, 0) == FALSE )
		return FALSE;

	// Light playback led(PB9) for display status.
	OUT4(0);
	
	// Start Ultraio Timer & HW pwm for UltraIO curve output
	ULTRAIO_START();
	
	// Start to playback audio. 
	Playback_StartPlay();
}

可以看到App_StartPlay主要牵扯了NuLiteExApp和Playback两部分子函数。

重中之重 NuLiteExApp_DecodeStartPlayByAddr

由于对音频编解码这块比较陌生,我还是给对应代码做了中文注解方便消化。

BOOL NuLiteExApp_DecodeStartPlayByAddr(S_NULITEEX_APP_DECODE *psNuLiteExAppDecode, UINT32 u32NuLiteExStorageStartAddr, UINT8 u8PlaybackChannel)
{
	UINT16 u16SampleRate;
	// NuLiteEx解码库初始化对应的工作缓冲区,应用层传入temp缓存来方便解码库内部工作。另外根据传入的SPI地址从SPI取文件,获取采样率。
	// NuLiteEx decoder initiates work buffer and returns sample rate.
	if ( (u16SampleRate = NuLiteEx_DecodeInitiate(	(UINT8*)psNuLiteExAppDecode->au32DecodeWorkBuf, 
													psNuLiteExAppDecode->pau8TempBuf, 
													u32NuLiteExStorageStartAddr, 
													g_asAppCallBack[psNuLiteExAppDecode->u8CallbackIndex].pfnReadDataCallback )) == 0 )
		return FALSE;	

	// 给Playback模块对接对应的工作缓冲区,方便其下一步播放。
	// Initiate and set output buffer variable(include frame size, buffer size etc.) 
	Playback_SetOutputBuf( 	&psNuLiteExAppDecode->sOutBufCtrl,
							NULITEEXAPP_OUT_BUF_SIZE,
							psNuLiteExAppDecode->i16OutBuf,
							NULITEEXAPP_OUT_SAMPLES_PER_FRAME,
							u16SampleRate );
	
	// 工作缓冲区,置有效位。
	// Trigger active flag of output buffer for NuLiteEx decoding
	BUF_CTRL_SET_ACTIVE(&psNuLiteExAppDecode->sOutBufCtrl);

	// 工作缓冲区中的读写指针赋值。
	// Pre-decode one frame
	psNuLiteExAppDecode->sOutBufCtrl.u16BufWriteIdx = NULITEEXAPP_OUT_SAMPLES_PER_FRAME;
	if ( NuLiteExApp_DecodeProcess(psNuLiteExAppDecode) == FALSE )
	{
		BUF_CTRL_SET_INACTIVE(&psNuLiteExAppDecode->sOutBufCtrl);
		return FALSE;
	}
	psNuLiteExAppDecode->sOutBufCtrl.u16BufReadIdx = NULITEEXAPP_OUT_SAMPLES_PER_FRAME;
	
	// 记录当前播放的channel,用来停止播放。
	// Record play channel index for stopping to play.
	psNuLiteExAppDecode->u8PlaybackChannel = u8PlaybackChannel;
	// 准备播放,把这里的循环缓冲区同playback共用。
	// Add audio codec into channel and preper to play codec.
	Playback_Add(psNuLiteExAppDecode->u8PlaybackChannel, &psNuLiteExAppDecode->sOutBufCtrl);

	return TRUE;
}

也很重要的Playback_StartPlay

void Playback_StartPlay(void)
{
	INT16 *pi16PcmBuf;
	
	if( s_u8PlayCtrl == PLAYBACK_NOACTION ) // 这个s_u8PlayCtrl是playback模块内部处理的。
	{
		#if ( PLAYBACK_CHANNEL_COUNT > 1)
		pi16PcmBuf = g_ai16DACSamples;
		#else
		pi16PcmBuf = &g_psDacBufCtrl->pi16Buf[g_psDacBufCtrl->u16BufReadIdx];// PCM数据缓冲区复制。
		#endif
		
		#if ((APU_FILTER_ENABLE == 1)&&(APU_UPSAMPLE == 2))
		NuDACFilterEx_Up2Initial(g_au8Up2WorkBuf);
		#elif ((APU_FILTER_ENABLE == 1)&&(APU_UPSAMPLE == 4))
		NuDACFilterEx_Up4Initial(g_au8Up4WorkBuf);
		#endif
		g_u8AppCtrl|=APPCTRL_PLAY;
		s_u8PlayCtrl |= PLAYBACK_START;
		#if (APU_ENABLE)
		{
			UINT8 u8Count;
			
			for( u8Count = 0; u8Count < 8; u8Count ++)
				g_ai16DACSamples[u8Count] = 0;		//Clear virtual buffer
		}
		#endif
		
		Playback_ResetChannelVolume(0);
		
		SPK_Start(); // 这里头开始调用DPWM来播放DPWM->DATA,DPWM_START_PLAY(DPWM);
		
		#if (APU_PDMA_ENABLE)
		PdmaCtrl_Start(APU_PDMA_CH, (uint32_t *)pi16PcmBuf, (uint32_t *)&DPWM->DATA, 8);// 将PCM缓冲数据传到DPWM->DATA中。
		#endif
		
	}
}

第二部分 App_ProcessPlay

App_ProcessPlay只调用了如下这个函数

// Continue decode NuLiteEx data to produce PCMs for audio playback.
if ( NuLiteExApp_DecodeProcess(&g_sApp.sNuLiteExAppDecode) == TRUE )

这个函数拆解进去是这样:

BOOL NuLiteExApp_DecodeProcess(S_NULITEEX_APP_DECODE *psNuLiteExAppDecode)
{
	INT16 *pi16OutBuf;
	
	// 环形缓冲区非激活状态,这个只有在应用层置位(按键停止、或者启动失败等情况)
	if (BUF_CTRL_IS_INACTIVE(&psNuLiteExAppDecode->sOutBufCtrl))
		return FALSE;
	
	// 环形缓冲区还有未读数据
	if ( Playback_NeedUpdateOutputBuf(&psNuLiteExAppDecode->sOutBufCtrl) )
	{
		// 由核心库来判断这个文件是否解析完了
		// Check end of file
		if(NuLiteEx_DecodeIsEnd((UINT8*)psNuLiteExAppDecode->au32DecodeWorkBuf))
		{
			// Trigger inactive flag of output buffer to stop NuLiteEx decoding
			BUF_CTRL_SET_INACTIVE(&psNuLiteExAppDecode->sOutBufCtrl);
			// Use to represnt no active(or end) of decoding
			psNuLiteExAppDecode->sOutBufCtrl.u16SampleRate = 0;
			return FALSE;
		}

		// Record output data buffer pointer(for duplicate & process)
		pi16OutBuf = (PINT16)&psNuLiteExAppDecode->sOutBufCtrl.pi16Buf[psNuLiteExAppDecode->sOutBufCtrl.u16BufWriteIdx];
		
		// 核心库继续发挥其巨大作用,开足马力读取文件中PCM数据转到缓冲区。
		NuLiteEx_DecodeProcess(	(UINT8*)psNuLiteExAppDecode->au32DecodeWorkBuf, 
								psNuLiteExAppDecode->pau8TempBuf, 
								pi16OutBuf, 
								g_asAppCallBack[psNuLiteExAppDecode->u8CallbackIndex].pfnReadDataCallback, 
								g_asAppCallBack[psNuLiteExAppDecode->u8CallbackIndex].pfnUserEventCallback);

		// PlayBack依旧共享这个缓冲区,准备对数据进行进一步处理
		// Update write index of output buffer and avoid buffer overflow
		Playback_UpdateOutputBuf(&psNuLiteExAppDecode->sOutBufCtrl);

		// Duplicate data into buffer for using duplication callback function.
		if ( psNuLiteExAppDecode->u8CtrlFlag&(NULITEEXAPP_CTRL_DUPLICATE_TO_BUF|NULITEEXAPP_CTRL_DUPLICATE_TO_FUNC) )
		{
			if ( psNuLiteExAppDecode->u8CtrlFlag & NULITEEXAPP_CTRL_DUPLICATE_TO_BUF )
				BufCtrl_WriteWithCount(psNuLiteExAppDecode->psDuplicateOutBufCtrl, NULITEEXAPP_OUT_SAMPLES_PER_FRAME, pi16OutBuf );
			else 
				psNuLiteExAppDecode->pfnDuplicateFunc(NULITEEXAPP_OUT_SAMPLES_PER_FRAME, pi16OutBuf);
		}		
	}	
	return TRUE;
}

总结

源码拓扑结构


近年来,随着电子技术突飞猛进的发展,各种数字电子设备悄然兴起。这些电子设备虽然功能非常强大,但桌面按钮操作却十分复杂,同一个按键往往可以实现多种操作。目前,人们使用的电视遥控器基本上都是手动遥控器,虽然使用相对比较方便,但是对于儿童和老年人却存在以下四点不便: 1、键盘不易识别,使用起来有障碍; 2、容易按错键、选错台; 3、要看某个频道要记住它所对应的数字代码,由于频道比较多,大量的代码很容易混淆; 4、在夜晚不开灯的情况下,很难用普通手动遥控器轻易转换频道。 基于以上几点Nuvoton推出了基于ISD9160的智能语音遥控器方案。ISD9160是一款具有Cortex-M0内核的语音SOC,能够实现录音放音等应用。通过软件支持,ISD9160可以实现特定人和非特定人语音识别。其中非特定人语音识别支持九种语音,方便客户开发国际化的产品。客户在开发的时候,使用Nuvoton提供的ASR Tool工具,只需将所需命令写成文本模式,然后经由工具转换,就能生成用于项目文件的语音识别代码模块,简单并且容易使用。客户可以方便的进行自己遥控器命令的定制。 ► 核心技术优势 ISD91xxx系列是以内嵌的ARM:registered:Cortex:trade_mark:-M0 32位微控制器核心为低功率、录音及播放所优化的系统上芯片产品。在音讯功能部份,ISD9xxx系列包括一颗具备80dB SNR性能的Sigma-Delta ADC,搭配具有最高至56dB增益的可程序增益放大器(PGA),以直接连接麦克风。音讯输出是由能对8Ω喇叭输出1W功率的差动Class D放大器(DPWM)所提供的。ISD9160VI能提供对于少数字汇的简单指令,例如「开电视」、「关电视」、「音量+」、「音量-」等语音辨识,一旦辨识出指令(关键字),装置就能在预先储存在芯片上的闪存中的音讯提供音效。 ► 方案规格 •FCC/ETSI认证 •低功率消耗: o<5uA @ 待机模式 o17mA @ 按键矩阵操作模式 o30mA @ 麦克风操作模式 •FHSS跳频:164个频道 •操作:8公尺 •音频质量:16-bit PCM @16KHz •支持Skype/语音电话 •喇叭独立辨识(SIR) •适用于少数字汇之简单指令的优异精确性 •目前支持九种语言及多种方言 方案来源于大大通。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值