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


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

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

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

    本文作者twowinter,转载请注明:http://blog.csdn.net/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只调用了如下这个函数

    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;
    }

    总结

    源码拓扑结构

  • 相关阅读:
    LeetCode
    LeetCode
    LeetCode
    位运算实现加法运算
    反转字符串
    数组中的逆序对
    矩阵中的路径
    机器人的运动范围
    滑动窗口的最大值
    HTML5全屏浏览器兼容方案
  • 原文地址:https://www.cnblogs.com/zhugeanran/p/9230000.html
Copyright © 2020-2023  润新知