QCC51xx学习笔记:KSE简介和跑通官方仿真例程

为了方便大家学习,现与思度科技联合推出QCC300x/CSR867x/QCC30xx/QCC51xx学习板【思度科技蓝牙开发板】。

技术交流QQ群号:743434463
开发板会员QQ群号:725398389(凭订单号入群,赠PPT、项目源码、视频教程)
——————————正文分割线———————————–

1. 引言

高通以CSR867X系列为基础,开发了下一代高性能蓝牙SOC系列QCC30XX/QCC51XX。新系列整体的硬软件能力都有了显著的提升,包括全新的音频仿真环境KSE。

从开始阅读KSE描述文档,到第一次跑通例程,再到分析定位仿真误差,经历了漫长的三个月。为了让大家少走一些弯路,将过程中遇到的问题和解决方案分享出来(文中例程和文档都可以在思度开发板QCC512X资料库中下载)。

2. KSE简介

官方介绍文档如下文档:

  • 80-cg060-1_ab_kymera_introduction.pdf
  • 80-cg335-1_aa_kymera_simulation_environment_(kse)_training.pdf
  • 80-cg887-1_ab_kymera_simulation_environment_(kse)_user_guide.pdf

详细内容建议读者阅读原版手册,在此只抓几个重点概念简单讲一下。

首先我们需要了解KSE的系统框架,示意图如下:

QCC51xx学习笔记:KSE简介和跑通官方仿真例程_第1张图片

  • KSE: kymera simulation environment,即kymera仿真环境。包含Kalsim和kymera两部分。
  • Filesystem:仿真数据输入和输出以文件形式存在,KSE从文件中读取数据,经过处理后再写入文件。文件可以是raw/wav/sbc等各种格式,方便与算法开发工具(如matlab)对接,构建从算法原型开发到嵌入式平台仿真的全链路环境。
  • Kalsim:高通kalimba DSP的PC平台软核版本,即芯片模拟器,可以真实模拟kalimba dsp的处理结果,并提供调试接口。
  • Kymera:运行在kalimba dsp内的专用于音频处理的操作系统,C语音和汇编语言混合编程。负责进程管理、内存分配、消息队列、音频接口管理等,需要开发者重点关注。
    QCC51xx学习笔记:KSE简介和跑通官方仿真例程_第2张图片
  • Python Shell:一套Python语言编写的脚本,可以方便地运行、调试、测试KSE环境,包含uut, kymera, endpoint, capability, stream, graph, kalcmd, acat等工具。Python工具集可以实现仿真过程的高度自动化,同时兼容各种第三方插件,值得投入时间精力重点学习。

对于算法移植工程师来说,KSE中的kymera API、Python工具集API和文件接口是需要重点关注的,kalsim对我们来说是个黑盒,不用过多关注。软件开发需要掌握C语言、汇编语言,熟悉matlab脚本、python脚本和json格式。

3. 跑通官方仿真例程

3.1. 获取仿真结果

高通官方提供了loopback_download_test例程作为最简的算法移植验证环境,其音频链路框图如下:
QCC51xx学习笔记:KSE简介和跑通官方仿真例程_第3张图片
官方文档给出了各个部分的详细描述,可以参考如下文档:

  • 80-ct522-1_ae_kymera_api_reference.pdf
  • 80-cf424-1_ac_kymera_utility_api_reference.pdf
  • 80-ct243-1_ag_kymera_capability_api_document.pdf
  • 80-cf976-1_ac_kymera_capability_library_user_guide.pdf
  • 80-cg461-1_aa_kymera_endpoint_library_user_guide.pdf
  • 80-cg063-1_aa_debugging_with_acat.pdf
  • 80-cg879-1_aa_qualcomm_kymera_voice_assistant_graph_setup_user_guide.pdf

每个模块都有对应的文档,可见高通为了让开发者掌握这套新架构是投入了很多精力。建议多花时间在阅读上述文档,对理解和运用kymera有很大帮助。

跑通此例程的方法在文档“80-cg335-1_aa_kymera_simulation_environment_(kse)_training.pdf”中有详细描述,这里给出仿真的结果示意:
QCC51xx学习笔记:KSE简介和跑通官方仿真例程_第4张图片
matlab脚本读取仿真的输入输出文件后,以坐标图形式显示,代码如下:

system('run_pass_through.bat > run_log.txt');
figure(1);
fd = fopen('.\resource\153_Prompts_176.4_kHz_Music_Detected_8k.raw','r');
data1 = fread(fd, 'int16');
subplot(2,1,1);plot(data1);xlabel('aud in');
fclose(fd);

fd = fopen('.\tmp\153_Prompts_176.4_kHz_Music_Detected_8k.raw','r');
data2 = fread(fd, 'int16');
subplot(2,1,2);plot(data2);xlabel('aud out');
fclose(fd);

3.2. 加速仿真

官方例程中需要用MDE开发环境运行KSE,每次仿真都需手动执行连接仿真器等操作。在此给出一种运用windows bat脚本结合python脚本实现一键自动化仿真的方法。

首先需要按照官方文档运行一次MDE的例程loopback_download_test,目的是用MDE模板工程创建基础仿真工程。

然后我们可以抓取MDE部署仿真工程的命令行,以windows bat脚本的方式启动python KSE shell:

cmd.exe /C "D:\qtil\ADK_QCC512X_QCC302X_WIN_6.3.2.24\tools\python27\python.exe -m kse.kalsim.kalsim_shell --ks_path D:\qtil\ADK_QCC512X_QCC302X_WIN_6.3.2.24\tools\kalsim\kalsim_qcc512x_audio.exe --log_level 20 --ks_firmware E:\___Product\0_INP\TWSearphone\target\qcc512x\loopback_download_test\audio\kalimba\kymera\output\stre_rom_kalsim_kalcmd2_release\build\debugbin\kymera_stre_audio --acat_path D:\qtil\ADK_QCC512X_QCC302X_WIN_6.3.2.24\audio\kalimba\kymera\tools\ACAT\ACAT --acat_use --acat_bundle E:\___Product\0_INP\TWSearphone\target\qcc512x\loopback_download_test\audio\kalimba\kymera\output_bundles\stre_rom_kalsim_kalcmd2_release\self_test_and_passthrough\self_test_and_passthrough --ka_path D:\qtil\ADK_QCC512X_QCC302X_WIN_6.3.2.24\tools\pythontools --platform stre  --hydra_ftp_server_directory E:\___Product\0_INP\TWSearphone\target\qcc512x\loopback_download_test\audio\kalimba\kymera\output\stre_rom_kalsim_kalcmd2_release\build\patchbin --script "script/kalsim/pass_through.py script/kalsim/self_test_and_passthrough.dkcs""

windows bat脚本的最后一行指明了KSE shell启动时自动加载的仿真脚本pass_through.py,其描述了kymera的输入输出关系、内部音频链路、各capability的设置等,代码如下:

import os
import time
import sys
import argparse

if __name__ == '__main__':

    from kats.kalsim.stream.stream_base import CALLBACK_EOF

    INPUT_FILE_RAW = 'resource/153_Prompts_176.4_kHz_Music_Detected_8k.raw'
    OUTPUT_FILE_RAW = 'tmp/153_Prompts_176.4_kHz_Music_Detected_8k.raw'
	
    print('removing output files')
    try:
        os.remove(OUTPUT_FILE_RAW)
    except Exception:
        pass

    print('creating source stream')
    config = {
        'hydra_type': 'audio_slot',
        'hydra_bac_handle': 0,
        'hydra_audioslot': 0,
        'stream_flow_control_drive': 'kalsim',
        'stream_flow_control_block_size': 1,
        'stream_format': 16,
        'stream_backing': 'file',
        'stream_filename': INPUT_FILE_RAW,
        'stream_flow_control_rate': 8000,
    }
    st_source = stream.get_instance('hydra', 'source', **config)
    st_source.create()
    st_source.config()

    print('creating sink stream')
    config = {
        'hydra_type': 'audio_slot',
        'hydra_bac_handle': 0,
        'hydra_audioslot': 0,
        'stream_flow_control_drive': 'kalsim',
        'stream_flow_control_block_size': 1,
        'stream_format': 16,
        'stream_backing': 'file',
        'stream_filename': OUTPUT_FILE_RAW,
        'stream_flow_control_rate': 8000,
    }
    st_sink = stream.get_instance('hydra', 'sink', **config)
    st_sink.create()
    st_sink.config()

    print('creating endpoints')
    ep_source = kymera.stream_if_get_source('pcm', [0, 0])
    kymera.stream_if_configure_sid(ep_source, 'pcm_sample_rising_edge', 0)
    kymera.stream_if_configure_sid(ep_source, 'pcm_master_clock_rate', 512000)
    kymera.stream_if_configure_sid(ep_source, 'pcm_master_mode', 1)
    kymera.stream_if_configure_sid(ep_source, 'pcm_sample_format', 1)
    kymera.stream_if_configure_sid(ep_source, 'pcm_sync_rate', 8000)
    kymera.stream_if_configure_sid(ep_source, 'pcm_slot_count', 4)
    ep_sink = kymera.stream_if_get_sink('pcm', [0, 0])
	
    print('creating pass through operator')
    op = kymera.opmgr_create_operator(1)
    kymera.opmgr_operator_message(op, [10, 1])
    kymera.opmgr_operator_message(op, [11, 1])

    print('connecting endpoints')
    tr1 = kymera.stream_if_connect(ep_source, op | 0xa000)
    tr2 = kymera.stream_if_connect(op | 0x2000, ep_sink)

    print('start streaming')
    st_sink.start()
    kymera.opmgr_start_operators([op])
    st_source.start()

    for _ in range(12500):
        if not st_source.check_active():
            print('end of file detected')
            break
        time.sleep(0.005)
    else:
        raise RuntimeError('timeout streaming the file')

    print('stop streaming')
    st_source.stop()
    st_source.destroy()
    st_sink.stop()
    st_sink.destroy()

    print('release resources')
    kymera.opmgr_stop_operators([op])
    kymera.stream_if_transform_disconnect([tr1, tr2])
    kymera.opmgr_destroy_operators([op])

    kymera.stream_if_close_source(ep_source)
    kymera.stream_if_close_sink(ep_sink)

    exit()

kymera的音频链路可以图形化显示,需要安装python环境下的graphviz插件。安装完插件后,在上述py脚本中的print(‘start streaming’)语句前插入acat.run(),执行脚本后会需要提示输入指令,输入stream.create_graph_img():
QCC51xx学习笔记:KSE简介和跑通官方仿真例程_第5张图片
生成html格式的graph描述文件,可以在浏览器打开,这里给出音频链路示意图:
QCC51xx学习笔记:KSE简介和跑通官方仿真例程_第6张图片

4. 验证仿真结果

loopback_download_test例程跑通后,我们需要验证KSE的仿真结果是否符合预期。此例程中的basic_pass_through capability可以看做简单的数据搬运,因此输入输出应该是bit-by-bit对应的。

回顾3.1章中给出的坐标图,肉眼可见的差异是输出相对于输入有很大的延迟。这里我的判断是KSE环境在运行时引入的延迟,即从打开输出文件,到写入仿真数据,这两个时间点之间有仿真环境的延迟。

为了能较方便地去除延迟对仿真结果结果判定的影响,我们改用白噪声作为输入信号,用find函数查询非0点的起始位置,使输入输出按样点对齐。

matlab脚本代码如下:

figure(1);
fd = fopen('.\resource\audio_in.raw','r');
data1 = fread(fd, 'int16');
data1 = data1(1:5000);
subplot(3,1,1);plot(data1);xlabel('aud in');
fclose(fd);

fd = fopen('.\tmp\audio_out.raw','r');
data2 = fread(fd, 'int16');
offset = find(data2~=0);
data2 = data2(1+offset(1)-1:5000+offset(1)-1);
subplot(3,1,2);plot(data2);xlabel('aud out');
fclose(fd);

data3=data1-data2;
subplot(3,1,3);plot(data3);xlabel('audio in - audio out');

对齐后的输入、输出、误差结果:
QCC51xx学习笔记:KSE简介和跑通官方仿真例程_第7张图片
结果出乎意料,可以看到输入输出之间是有误差存在的。对误差信号进行频谱估计,可见其频率成分主要集中在低频:
QCC51xx学习笔记:KSE简介和跑通官方仿真例程_第8张图片
这个问题困扰了我有近一个月,终于在文档“80-cg887-1_ab_kymera_simulation_environment_(kse)_user_guide.pdf”中找到了答案:

3.4.1 Hydra PCM Endpoints
On the data that is filled in their buffer PCM endpoints carry out two operations:
■ DC removal: To remove the DC component from the data on a moving window basis.
■ Rate matching: To adjust the incoming sample rate to the rate that is specified in the endpoint
configuration.
These two operations can be controlled by a configuration key named
audio_disable_endpoint_processing. A value of 0x0000 disables both functionalities and
0x0001 disables DC removal.

原来hydra PCM类型的endpoint本身隐含了DC removal和Rate matching两个功能,即去除信号中的直流成分和采样率转换。对照上文中提到的误差信号的频率成分主要是低频,猜想消除误差的方式应是关闭DC removal功能。

按照文档给出的方法在pass_through.py脚本中的"create endpoint"段插入如下代码:

    kymera.stream_if_configure_sid(ep_source, 'audio_disable_endpoint_processing', 1)
    ep_sink = kymera.stream_if_get_sink('pcm', [0, 0])
    kymera.stream_if_configure_sid(ep_sink, 'audio_disable_endpoint_processing', 1)

修改accmd.py脚本,添加“audio_disable_endpoint_processing”命令:

    # available to all interfaces
    'audio_channel_mute_enable': 0x0700,
    'audio_sample_size': 0x0701,
	'audio_disable_endpoint_processing': 0x0710,

重新运行KSE,再用matlab脚本比对结果,误差消除:
QCC51xx学习笔记:KSE简介和跑通官方仿真例程_第9张图片
至此一个高效并可靠的仿真环境已搭建完毕,可以在此基础上开始算法移植工作。

5. 总结

  • kymera能够支持C & 汇编混合编程是很有益的特性。可以先基于通用C代码评估算法误差、执行效率,后续C转汇编以提升算法效率。
  • AEC capability的仿真还未成功。很多算法需要AEC Reference作为参考输入。
  • 内存管理机制有待探索。
  • 没有涉及客制算法模块和调音工具的开发。

你可能感兴趣的:(蓝牙方案)