【海思SS528 | MPP】音频例程 sample_audio.c 源码阅读笔记


在这里插入图片描述

🎄一、概述

上篇文章 【海思SS528】MPP媒体处理软件V5.0 | 音频模块 - 学习笔记 学习了海思MPP媒体处理平台的一小部分音频知识,这篇文章继续学习与音频相关的例程,这样可以更好理解《MPP 媒体处理软件 V5.0 开发参考.pdf》中的音频模块知识。

本篇文章涉及到的SDK文件及路径说明:

  • 《MPP 媒体处理软件 V5.0 开发参考.pdf》:在SDK的路径为SS528ReleaseDoc\software\board\MPP
  • 《22AP30 H.265编解码处理器用户指南.pdf》:在SDK的路径为SS528ReleaseDoc\hardware\chip
  • sample_audio.c :在SDK的路径为SS528V100_SDK_V2.0.0.3/mpp/sample/audio/sample_audio.c

在这里插入图片描述

🎄二、main 函数解析

先看 main 函数,主要做了以下工作:

  • 判断程序输入参数,错误的话,就打印程序功能及用法;
  • 初始化系统和VB:sample_comm_sys_init
  • 初始化 AAC 编码和解码:hi_mpi_aenc_aac_init、hi_mpi_adec_aac_init
  • 根据输入参数,调用对应例子:main_inner
  • 退出程序时的反初始化:hi_mpi_aenc_aac_deinit、hi_mpi_adec_aac_deinit、sample_comm_sys_exit

从main函数来看,主要的内容在 main_inner 函数。下面是main函数的代码:

/* function : main */
#ifdef __LITEOS__
hi_s32 app_main(int argc, char *argv[])
#else
hi_s32 main(int argc, char *argv[])
#endif
{
    hi_s32 ret;
    hi_vb_cfg vb_conf;
    hi_u32 index;

    if (argc != 2) {  /* 2:argv num */
        sample_audio_usage();
        return HI_FAILURE;
    }

    if (!strncmp(argv[1], "-h", 2)) { /* 2:arg num */
        sample_audio_usage();
        return HI_FAILURE;
    }

    if ((strlen(argv[1]) != 1) ||
        (argv[1][0] < '0' || argv[1][0] > '6')) { /* 6:arg num */
        sample_audio_usage();
        return HI_FAILURE;
    }

    index = atoi(argv[1]);

    sample_sys_signal(&sample_audio_handle_sig);
#if defined(OT_VQE_USE_STATIC_MODULE_REGISTER)
    ret = sample_audio_register_vqe_module();
    if (ret != HI_SUCCESS) {
        return HI_FAILURE;
    }
#endif
    ret = memset_s(&vb_conf, sizeof(hi_vb_cfg), 0, sizeof(hi_vb_cfg));
    if (ret != EOK) {
        printf("%s: vb_config init failed with %d!\n", __FUNCTION__, ret);
        return HI_FAILURE;
    }

    ret = sample_comm_sys_init(&vb_conf);
    if (ret != HI_SUCCESS) {
        printf("%s: system init failed with %d!\n", __FUNCTION__, ret);
        return HI_FAILURE;
    }

    hi_mpi_aenc_aac_init();
    hi_mpi_adec_aac_init();

    main_inner(index);

    hi_mpi_aenc_aac_deinit();
    hi_mpi_adec_aac_deinit();

    sample_comm_sys_exit();

    return ret;
}

在这里插入图片描述

🎄三、main_inner 函数解析

main_inner函数主要是根据程序输入的第二个参数来决定调用哪个例子,分别以下几种:

  • sample_audio_ai_ao:从 AI 设备采集音频,然后从 AO 设备播放,最简单的一个例子;
  • sample_audio_ai_aenc:从 AI 设备采集音频,然后进行编码写文件,最后再解码且从 AO 设备播放;
  • sample_audio_adec_ao:从文件读取音频数据,解码,从 AO 设备播放;
  • sample_audio_ai_vqe_process_ao:
  • sample_audio_ai_hdmi_ao:
  • sample_audio_ai_to_ao_sys_chn:
  • sample_audio_ai_to_ext_resample:

这后面几个例子,还没使用,后面补充。2023-06-30 21:25:34
main_inner的代码如下,这也是没什么内容的一个函数。

static hi_void main_inner(hi_u32 index)
{
    switch (index) {
        case 0: { /* 0:ai->ao */
            sample_audio_ai_ao();
            break;
        }
        case 1: { /* 1:ai->aenc->adec->ao */
            sample_audio_ai_aenc();
            break;
        }
        case 2: { /* 2:file->adec->ao */
            sample_audio_adec_ao();
            break;
        }
        case 3: { /* 3:ai->ao vqe */
            sample_audio_ai_vqe_process_ao();
            break;
        }
        case 4: { /* 4:ai->ao hdmi */
            sample_audio_ai_hdmi_ao();
            break;
        }
        case 5: { /* 5:ai->ao synchn */
            sample_audio_ai_to_ao_sys_chn();
            break;
        }
        case 6: { /* 6:resample test */
            sample_audio_ai_to_ext_resample();
            break;
        }
        default: {
            break;
        }
    }
}

在这里插入图片描述

🎄四、sample_audio_ai_ao 函数解析

这是 sample_audio.c 的第一个例子,可以带我们 熟悉AI设备和AO设备的启用流程

这个例子已经涉及到很多AI和AO的API函数了,需要对开发文档《MPP 媒体处理软件 V5.0 开发参考.pdf》中音频相关的API函数有一定了解,可以再阅读过程碰到不认识的API再去开发文档查询。

✨4.1 Audio Codec相关配置

正式阅读代码前,先看一下有关Audio Codec的相关配置,因为在初始化参数时会使用到,如果AIO参数没有配置好的话,可能出现各种问题。
我使用的板子没有使用内置的Audio Codec,而是使用 ES7243S 去做采集音频的模数转换,采样率、采集精度、声道是驱动工程师写驱动时写死的,而 sample_audio.c 的例子中提供了sample_comm_audio_cfg_acodec对Audio codec进行设置的,不设置的话,程序一跑就退出了。根据我的情况,我用不上这个函数,所以要注释掉,并且配置AIO参数时,也配置好对应的采样率、采集精度、声道和AI设备号。我的采样率、采集精度、声道和AI设备号如下:

  • 采样率:48kHz
  • 采样精度:16bit
  • 声道:双声道
  • AI设备号:1
  • AO设备号:0

AI、AO设备号的确定需要看自己板子电路图的Audio codec芯片接在I2S0_SD_RX,还是I2S1_SD_RX
接在I2S0_SD_RX则对应AI设备号为 0;
接在I2S1_SD_RX则对应AI设备号为 1;
下图是我的板子原理图,对应AI设备号为 1;
在这里插入图片描述

下图是接在AO的原理图,接在I2S0_SD_TX,对应的AO设备号为 0;
在这里插入图片描述

✨4.2 sample_audio_ai_ao 函数简析

这小节简单介绍 sample_audio_ai_ao 函数的基本内容:

  • 初始化AIO参数:sample_audio_ai_ao_init_param,这个函数里的参数要配置对,下小节会介绍这函数。
  • 初始化vqe参数:sample_audio_set_ai_vqe_param,这个函数只是几个赋值,按照默认即可;
  • 启用AI设备:sample_comm_audio_start_ai,这个函数有启用AI的流程,需要学习;
  • 启用AO设备:sample_comm_audio_start_ao,这个函数有启用AO的流程,也需要学习;
  • 配置Audio codec:sample_comm_audio_cfg_acodec,这个不同板子不一样,我这里不需要,所以注释掉;
  • 绑定AI到AO:sample_audio_ai_ao_inner,使AI的音频帧发送到AO设备播放。
  • 其他:其他的是退出程序的一些工作,如:停止AO设备、停止AI设备等。

sample_audio_ai_ao 函数代码:

/* function : ai -> ao(with fade in/out and volume adjust) */
hi_s32 sample_audio_ai_ao(hi_void)
{
    hi_s32 ret;
    hi_u32 ai_chn_cnt;
    hi_u32 ao_chn_cnt;
    hi_audio_dev ai_dev;
    hi_audio_dev ao_dev;
    const hi_ai_chn ai_chn = 0;
    const hi_ao_chn ao_chn = 0;
    hi_aio_attr aio_attr = {0};
    sample_comm_ai_vqe_param ai_vqe_param = {0};

    sample_audio_ai_ao_init_param(&aio_attr, &ai_dev, &ao_dev);

    /* enable AI channel */
    ai_chn_cnt = aio_attr.chn_cnt;
    sample_audio_set_ai_vqe_param(&ai_vqe_param, g_out_sample_rate, g_aio_resample, HI_NULL, 0);
    ret = sample_comm_audio_start_ai(ai_dev, ai_chn_cnt, &aio_attr, &ai_vqe_param, -1);
    if (ret != HI_SUCCESS) {
        sample_dbg(ret);
        goto ai_ao_err3;
    }

    /* enable AO channel */
    ao_chn_cnt = aio_attr.chn_cnt;
    ret = sample_comm_audio_start_ao(ao_dev, ao_chn_cnt, &aio_attr, g_in_sample_rate, g_aio_resample);
    if (ret != HI_SUCCESS) {
        sample_dbg(ret);
        goto ai_ao_err2;
    }

    /* config internal audio codec */
    ret = sample_comm_audio_cfg_acodec(&aio_attr);
    if (ret != HI_SUCCESS) {
        sample_dbg(ret);
        goto ai_ao_err1;
    }

    sample_audio_ai_ao_inner(ai_dev, ai_chn, ao_dev, ao_chn);

ai_ao_err1:
    ret = sample_comm_audio_stop_ao(ao_dev, ao_chn_cnt, g_aio_resample);
    if (ret != HI_SUCCESS) {
        sample_dbg(ret);
    }

ai_ao_err2:
    ret = sample_comm_audio_stop_ai(ai_dev, ai_chn_cnt, g_aio_resample, HI_FALSE);
    if (ret != HI_SUCCESS) {
        sample_dbg(ret);
    }

ai_ao_err3:
    return ret;
}

✨4.3 sample_audio_ai_ao 的 sample_audio_ai_ao_init_param 函数

sample_audio_ai_ao_init_param函数主要是初始化AIO单元的参数,参数结构体的具体解析可以查看开发手册的hi_aio_attr结构体。
这里只介绍几个与Audio codec相关的参数,我的参数在上面 4.1 节有介绍。采样率sample_rate、采样精度bit_width、声道chn_cnt,这三个参数根据自己 Audio Codec 配置即可,我的对应参数是 48KHZ、16bit、双声道 ;然后clk_share参数,我选择0。

下面是我修改过AIO参数的代码,你可以参考自己Audio codec的情况,参考修改:

/**
 * @brief 初始化AIO模块参数 2023-06-30 09:55:01
 * 
 * @param aio_attr :输出变量,模块参数
 * @param ai_dev :输出变量,传出AI设备号
 * @param ao_dev :输出变量,传出AO设备号
 * @return hi_void 
 */
static hi_void sample_audio_ai_ao_init_param(hi_aio_attr *aio_attr, hi_audio_dev *ai_dev, hi_audio_dev *ao_dev)
{
    /**
     * 注意:
     * 1、采样率sample_rate、采样精度bit_width、声道chn_cnt,这三个参数根据自己 Audio Codec 配置即可,我的对应参数是 48KHZ、16bit、双声道 
     * 2、AI设备号:需要看硬件原理图,如果采集音频接口最终接到 I2S0_BCLK ,则是第0个AI设备,设备号0;如果接到 I2S1_BCLK 则设备号 1。
     **/ 
    aio_attr->sample_rate   = HI_AUDIO_SAMPLE_RATE_48000;//HI_AUDIO_SAMPLE_RATE_16000;
    aio_attr->bit_width     = HI_AUDIO_BIT_WIDTH_16;
    aio_attr->work_mode     = HI_AIO_MODE_I2S_SLAVE;//HI_AIO_MODE_I2S_MASTER;
    aio_attr->snd_mode      = HI_AUDIO_SOUND_MODE_STEREO;
    aio_attr->expand_flag   = 0;
    aio_attr->frame_num     = 30; /* 30:frame num */
    aio_attr->point_num_per_frame = AACLC_SAMPLES_PER_FRAME;
    aio_attr->chn_cnt       = 2; /* 2:chn cnt */

    *ai_dev = 1;//SAMPLE_AUDIO_EXTERN_AI_DEV;
    *ao_dev = SAMPLE_AUDIO_EXTERN_AO_DEV;
    aio_attr->clk_share  = 0;//1;
    aio_attr->i2s_type   = HI_AIO_I2STYPE_EXTERN;

    g_aio_resample = HI_FALSE;
    /* config ao resample attr if needed */
    if (g_aio_resample == HI_TRUE) {
        /* ai 48k -> 32k */
        g_out_sample_rate = HI_AUDIO_SAMPLE_RATE_32000;

        /* ao 32k -> 48k */
        g_in_sample_rate  = HI_AUDIO_SAMPLE_RATE_32000;
    } else {
        g_in_sample_rate  = HI_AUDIO_SAMPLE_RATE_BUTT;
        g_out_sample_rate = HI_AUDIO_SAMPLE_RATE_BUTT;
    }

    /* resample and anr should be user get mode */
    g_user_get_mode = (g_aio_resample == HI_TRUE) ? HI_TRUE : HI_FALSE;
}

✨4.4 sample_audio_ai_ao 的 sample_comm_audio_start_ai 函数

sample_comm_audio_start_ai函数介绍了AI设备启用流程,可以学习后,应用到自己代码。基本流程如下:

  • 1、设置 AI 设备属性:hi_mpi_ai_set_pub_attr,参数在sample_audio_ai_ao_init_param已设置好。
  • 2、使能 AI 设备:hi_mpi_ai_enable,该函数只需要传入AI设备号;
  • 3、启用 AI 通道:hi_mpi_ai_enable_chn,启用指定设备的AI通道;
  • 4、根据标志,判断是否启用 AI 重采样:hi_mpi_ai_enable_resample
  • 5、使能AI的声音质量增强功能:sample_comm_audio_start_ai_vqe

sample_comm_audio_start_ai函数代码如下:

/* start ai */
hi_s32 sample_comm_audio_start_ai(hi_audio_dev ai_dev_id, hi_u32 ai_chn_cnt, hi_aio_attr *aio_attr,
    const sample_comm_ai_vqe_param *ai_vqe_param, hi_audio_dev ao_dev_id)
{
    hi_s32 i;
    hi_s32 ret;
    hi_u32 chn_cnt;

    ret = hi_mpi_ai_set_pub_attr(ai_dev_id, aio_attr);
    if (ret) {
        printf("%s: hi_mpi_ai_set_pub_attr(%d) failed with %#x\n", __FUNCTION__, ai_dev_id, ret);
        return ret;
    }

    ret = hi_mpi_ai_enable(ai_dev_id);
    if (ret) {
        printf("%s: hi_mpi_ai_enable(%d) failed with %#x\n", __FUNCTION__, ai_dev_id, ret);
        return ret;
    }

    chn_cnt = ai_chn_cnt >> ((hi_u32)aio_attr->snd_mode);
    for (i = 0; i < (hi_s32)chn_cnt; i++) {
        ret = hi_mpi_ai_enable_chn(ai_dev_id, i);
        if (ret) {
            printf("%s: hi_mpi_ai_enable_chn(%d,%d) failed with %#x\n", __FUNCTION__, ai_dev_id, i, ret);
            return ret;
        }

        if (ai_vqe_param->resample_en == HI_TRUE) {
            ret = hi_mpi_ai_enable_resample(ai_dev_id, i, ai_vqe_param->out_sample_rate);
            if (ret) {
                printf("%s: hi_mpi_ai_enable_re_smp(%d,%d) failed with %#x\n", __FUNCTION__, ai_dev_id, i, ret);
                return ret;
            }
        }

        ret = sample_comm_audio_start_ai_vqe(ai_dev_id, i, ai_vqe_param, ao_dev_id);
        if (ret != HI_SUCCESS) {
            return ret;
        }
    }

    return HI_SUCCESS;
}

✨4.5 sample_audio_ai_ao 的 sample_comm_audio_start_ao函数

sample_comm_audio_start_ao函数介绍了AO设备启用流程,可以学习后,应用到自己代码。基本流程如下:

  • 1、设置 AO 设备属性:hi_mpi_ao_set_pub_attr,参数在sample_audio_ai_ao_init_param已设置好。
  • 2、使能 AO 设备:hi_mpi_ao_enable,该函数只需要传入AO设备号;
  • 3、启用 AO 通道:hi_mpi_ao_enable_chn,启用指定设备的AO通道;
  • 4、根据标志,判断是否启用 AO 重采样:hi_mpi_ao_enable_resample

sample_comm_audio_start_ao 函数代码:

/* start ao */
hi_s32 sample_comm_audio_start_ao(hi_audio_dev ao_dev_id, hi_u32 ao_chn_cnt, hi_aio_attr *aio_attr,
    hi_audio_sample_rate in_sample_rate, hi_bool resample_en)
{
    hi_s32 i;
    hi_s32 ret;
    hi_u32 chn_cnt;

    if (ao_dev_id == SAMPLE_AUDIO_INNER_HDMI_AO_DEV) {
#ifdef OT_ACODEC_TYPE_HDMI
        aio_attr->clk_share = 0;

        sample_comm_audio_start_hdmi(aio_attr);
#endif
    }

    ret = hi_mpi_ao_set_pub_attr(ao_dev_id, aio_attr);
    if (ret != HI_SUCCESS) {
        printf("%s: hi_mpi_ao_set_pub_attr(%d) failed with %#x!\n", __FUNCTION__, ao_dev_id, ret);
        return HI_FAILURE;
    }

    ret = hi_mpi_ao_enable(ao_dev_id);
    if (ret != HI_SUCCESS) {
        printf("%s: hi_mpi_ao_enable(%d) failed with %#x!\n", __FUNCTION__, ao_dev_id, ret);
        return HI_FAILURE;
    }

    chn_cnt = ao_chn_cnt >> ((hi_u32)aio_attr->snd_mode);
    for (i = 0; i < (hi_s32)chn_cnt; i++) {
        ret = hi_mpi_ao_enable_chn(ao_dev_id, i);
        if (ret != HI_SUCCESS) {
            printf("%s: hi_mpi_ao_enable_chn(%d) failed with %#x!\n", __FUNCTION__, i, ret);
            return HI_FAILURE;
        }

        if (resample_en == HI_TRUE) {
            ret = hi_mpi_ao_disable_resample(ao_dev_id, i);
            if (ret != HI_SUCCESS) {
                printf("%s: hi_mpi_ao_disable_resample (%d,%d) failed with %#x!\n", __FUNCTION__, ao_dev_id, i, ret);
                return HI_FAILURE;
            }

            ret = hi_mpi_ao_enable_resample(ao_dev_id, i, in_sample_rate);
            if (ret != HI_SUCCESS) {
                printf("%s: hi_mpi_ao_enable_resample(%d,%d) failed with %#x!\n", __FUNCTION__, ao_dev_id, i, ret);
                return HI_FAILURE;
            }
        }
    }

    ret = hi_mpi_ao_enable_chn(ao_dev_id, HI_AO_SYS_CHN_ID);
    if (ret != HI_SUCCESS) {
        printf("%s: hi_mpi_ao_enable_chn(%d) failed with %#x!\n", __FUNCTION__, i, ret);
        return HI_FAILURE;
    }

    return HI_SUCCESS;
}

✨4.6 sample_audio_ai_ao 的 sample_audio_ai_ao_inner 函数

sample_audio_ai_ao_inner函数的内容是将AI绑定到AO,使音频帧从AI到AO,这个是本节主要想讲的;还有就是音量控制的,这个暂时用不到;下面就是使用getchar函数等待用户来结束进程。代码如下:

static hi_void sample_audio_ai_ao_inner(hi_audio_dev ai_dev, hi_ai_chn ai_chn,
   hi_audio_dev ao_dev, hi_ao_chn ao_chn)
{
   hi_s32 ret;

   /* bind AI to AO channel */
   ret = sample_audio_ao_bind_ai(ai_dev, ai_chn, ao_dev, ao_chn);
   if (ret != HI_SUCCESS) {
       return;
   }

   if (g_ao_volume_ctrl == HI_TRUE) {
       ret = sample_comm_audio_creat_trd_ao_vol_ctrl(ao_dev);
       if (ret != HI_SUCCESS) {
           sample_dbg(ret);
           goto ai_ao_err0;
       }
   }

   printf("\nplease press twice ENTER to exit this sample\n");
   smaple_audio_getchar();
   smaple_audio_getchar();

   if (g_ao_volume_ctrl == HI_TRUE) {
       ret = sample_comm_audio_destory_trd_ao_vol_ctrl(ao_dev);
       if (ret != HI_SUCCESS) {
           sample_dbg(ret);
       }
   }

ai_ao_err0:
   sample_audio_ao_unbind_ai(ai_dev, ai_chn, ao_dev, ao_chn);
}

接下来,看看 sample_audio_ao_bind_ai 函数,它主要是绑定AI到AO,分两种情况:

  • 1、用户模式:sample_comm_audio_creat_trd_ai_ao,创建一个线程sample_comm_audio_ai_proc实现从AI取帧,然后发送到AO
  • 2、系统绑定:sample_comm_audio_ao_bind_ai,调用 hi_mpi_sys_bind 绑定后,AI 的帧会自动发送到 AO。

sample_audio_ao_bind_ai 函数代码:

static hi_s32 sample_audio_ao_bind_ai(hi_audio_dev ai_dev, hi_ai_chn ai_chn,
   hi_audio_dev ao_dev, hi_ao_chn ao_chn)
{
   hi_s32 ret;
   if (g_user_get_mode == HI_TRUE) {
       ret = sample_comm_audio_creat_trd_ai_ao(ai_dev, ai_chn, ao_dev, ao_chn);
       if (ret != HI_SUCCESS) {
           sample_dbg(ret);
           return HI_FAILURE;
       }
   } else {
       ret = sample_comm_audio_ao_bind_ai(ai_dev, ai_chn, ao_dev, ao_chn);
       if (ret != HI_SUCCESS) {
           sample_dbg(ret);
           return HI_FAILURE;
       }
   }

   printf("ai(%d,%d) bind to ao(%d,%d) ok\n", ai_dev, ai_chn, ao_dev, ao_chn);
   return HI_SUCCESS;
}

在这里插入图片描述

🎄五、总结

本文主要介绍了 sample_audio.c 的 sample_audio_ai_ao 函数,我觉得关于这个函数的流程,以及重要子函数已经讲得足够清晰了。看完文章,结合自己的sample代码,可以对理解 sample_audio.c 音频例程有一定了解。
在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wkd_007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值