写在前面: 21ic打算携手资(tu)深(ding)直男癌晚期工程师zhanzr21,来给大家讲一讲嵌入式系统与音频处理的故事。每个板子都能歌唱:嵌入式系统与音频处理(缘起) 关于zhanzr21: 曾经混迹于两岸三地,摸爬滚打在前端后端,搞过学术上过班。现在创业中,欢迎各种撩 点击链接加入群【嵌入式音频信号处理】:https://jq.qq.com/?_wv=1027&k=45wk8Ks 嵌入式音频专用资料代码分享:https://pan.baidu.com/s/1dFh5pWd 活动帖见:暮春三月,火舞喊你来寻找最美波形
第一篇 数字音频处理系统的基本原理 1.声音 从物理学的角度来看,声音就是介质中传播的振动.从生理学的角度来看,声音就是听觉神经接收到的脉冲.从工程的角度来看,声音就是连续的电压变化与内存中的一组组变量.人耳朵能感知的声音频率范围为20Hz-20KHz.超出此频率的称之为超声频(波),低于此的称之次声频(波).深究声音的物理学,生理学,心理学的属性不是本文所能涵盖的内容,如同此系列的前言所言,本文只从工程的角度来理解声音.在这个角度所理解的声音也称之为音频(信号/数据). 对于工程师来说,关心的是音频的录制,处理,混合与再生.这个过程中的音频可以用两个元素来表达:幅度与时间.当然从两个元素衍生出很多其他复合元素,时域的音频也时常转到频率来处理.但是从表面来看,音频的输入与输出都可用幅度与时间这两个元素来表达. 讲得哲学一点,音频就是一个维度上的变化(区别于视频的二维变化,真3D视频的三维变化).数字音频就是这种一维变化的离散近似还原.比如任何编程语言中的一维数组可以表达一段音频.播放两个采样之间的时间间隔决定了音频采样率.下图为一段4bit采样的正弦音频数据. 图 声音的数字存在形式 上图显示的是一种最典型的音频采集的例子,最高幅度的声音强度被表示为1111,最低的为0000.实际中使用的音频采样系统,一般使用8bit至24bit的采样深度. 沈从文先生说过,电影的美不如绘画之美,绘画的美不如文字之美,文字的美不如声音之美,声音之美不如数学之美.这段话说的有点过于玄妙,有的唯心主义的色彩.大致意思是越简单的变化形式越是难以捉磨,也就显得更加神秘迷人.用工程师的语言来翻译就是:低维变化因为形式简单,而使得变化较为二维三维变化更加能表达人类的美学观点.数字音频处理可以说结合了音乐与数学,算是美学中比较高端的内容了. 2.数字音频系统的基本组成 全模拟的音频系统也是存在的,比如传统的AM/FM广播系统属于全模拟的音频系统.这里不多评论.但是只要涉及到复杂一点的数学处理,那么音频必然要先转换为数据才能进行处理.随着计算能力与带宽的飞速发展,现在说的音频系统一般都指的是数字音频系统.这样的系统的特点就是,仅仅在声音的入口出口两个点,音频作为模拟形式存在,中间都作为数字形式被进行滤波,放大,各种处理.一般而言,数字音频系统组成如此: 图 数字音频系统的组成 下面看一看ST的F769-Discovery开发板上的音频系统组成部分: 图 STM32F769-Discovery板子上的音频处理系统组成 [1] LineIn输入接口 [2] LineOut输出接口 [3] 音频Codec(主要相当于DAC,也包含一路ADC) [4],[5] SPDIF输入输出,一种特殊的信号格式,后面章节中详叙 [6] STM32F769作为处理器,虽然不是专门DSP系列,计算能力也能胜任音频DSP的功能. 也包括DFSDM接口,相当于音频ADC. [7] SD卡,存储设备 [8] 网口,可以获取数据或者上传数据 [9] MEMS Micphone作为模拟输入源 这个系统的原理图将在后续**中进行更详细的分析. 3.音频的形状,Play with it! 闲话少说,现在开始动手试着感觉一下音频形状与味道.使用代码来生成一段音频数据,比如生成一段1.5秒的600Hz的正弦波信号(Python 3.5环境): #!/bin/python #Example source code for 21ic #Default runs in Python 3.5 Environment #Author: zhanzr21 #Description: This Example demonstrates how to generate raw audio data. # import os import math TEST_SAMPLE_RATE = 22050 TEST_SAMPLE_LEN_SEC = 1.5 TEST_SAMPLE_NUM = int(TEST_SAMPLE_RATE * TEST_SAMPLE_LEN_SEC) CHAN_NO = 1 AUDIO_HZ = 600 AUDIO_CYCLE = (TEST_SAMPLE_RATE/AUDIO_HZ) test_amp_gain = 0.75 INT16_MAX = 32767 f=open('test.raw',mode='wb') for i in range(0, TEST_SAMPLE_NUM): test_sample = int(INT16_MAX * test_amp_gain * (math.sin(math.pi*2*(i%AUDIO_CYCLE)/AUDIO_CYCLE))) test_ba = bytearray() test_ba.append(test_sample&0x00ff) test_ba.append((test_sample>>8)&0x00ff) f.write(test_ba) f.close() 代码比较简单,不多介绍.只提一句,要生成的音频振动频率为AUDIO_HZ, 用频率与采样率来计算AUDIO_CYCLE,每个周期的角度变化为2*Pi. 图 生成的正弦波形状 如果要生成方波呢,把上面代码关键处改成这样就可以了: test_sample = int(test_amp_gain * (INT16_MAX if ((i%AUDIO_CYCLE)>(AUDIO_CYCLE/2)) else INT16_MIN)) 图 生成的方波形状 如果要生成锯齿波,这样改: test_sample = int(test_amp_gain * (INT16_MIN + (i%AUDIO_CYCLE)*((INT16_MAX-INT16_MIN)/AUDIO_CYCLE))) 图 生成的锯齿波形状 如果要生成三角波,这样改: test_sample = int(test_amp_gain * (INT16_MIN + (i%AUDIO_CYCLE)*(2*(INT16_MAX-INT16_MIN)/AUDIO_CYCLE)) if ((i%AUDIO_CYCLE)<(AUDIO_CYCLE/2)) else (INT16_MAX - (i%AUDIO_CYCLE)*(2*(INT16_MAX-INT16_MIN)/AUDIO_CYCLE))) 图 生成的三角波形状 上面都是连续信号的还原[非数学意义上的连续信号],要产生那种报警用的嘟嘟嘟信号,还要间歇地加一些空白区,比如要产生200ms的间歇性数据.添加两个定义: PULSE_HZ = 5 PULSE_CYCLE = (TEST_SAMPLE_RATE/PULSE_HZ) 再将数据生成代码改成: test_sample = int(INT16_MAX * test_amp_gain * (math.sin(math.pi*2*(i%AUDIO_CYCLE)/AUDIO_CYCLE))) if (0==(i//PULSE_CYCLE)%2) else 0 图 生成的间歇性报警音频形状 上面的波形幅度都是一致的,再看看幅度随着时间衰减与增加的效果. 数据生成代码改成: test_sample = int(INT16_MAX * ((2*i)/TEST_SAMPLE_NUM if i <(TEST_SAMPLE_NUM//2) else (2-(2*i)/TEST_SAMPLE_NUM)) * (math.sin(math.pi*2*(i%AUDIO_CYCLE)/AUDIO_CYCLE))) 图 生成的幅度先增强后减弱的正弦音频形状(正弦要拉长时间轴才看得出) 当然还可以生成各种形状,这里就留给大家发挥想象力了.比如将生成数据那一行改成这样: test_sample = random.randint(INT16_MIN, INT16_MAX) 会生成怎样的波形呢? 留给大家做实验. 这里顺便再提一下,本文所有代码都会有附件提供以便读者方便实验.一般数据处理性质的代码为python代码,复杂的数据处理为Matlab/Octave代码,有必要还会提供C/C++代码.嵌入式系统的将会给出硬件原理图与配套代码. 这个生成的test.raw文件的大小为66150字节.是这么计算的: raw_file_size = 采样率 * 声道数 * (声道位宽/8) * 音频持续长度 看看上面的代码就知道: 我们定义的采样率 = 22050, 声道为单声道, 位宽为16bit, 持续长度为1.5秒. 这里称之为raw文件,意思为原始格式,也有称为binaryx文件的. 对于这种数据,生成者与使用者都要知道数据的含义才行.和嵌入式开发中用来烧录的binary文件是一个道理,事实上后面的章节中会将此文件直接烧录到板子上进行播放.我们这里可以试着使用烧写工具Jlink打开这个文件: 图 Jlink打开生成的原始文件test.raw
......查看原文:第一篇 数字音频处理系统的基本原理
|