查看: 1344|回复: 7
收起左侧

第一篇 数字音频处理系统的基本原理

[复制链接]
     

144

主题

742

帖子

2674

积分

坛主

 楼主| 发表于 2017-3-16 09:50 | 显示全部楼层 |返回版面||阅读模式

写在前面:

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



......查看原文:第一篇 数字音频处理系统的基本原理



     

144

主题

742

帖子

2674

积分

坛主

 楼主| 发表于 2017-3-16 09:52 | 显示全部楼层 |返回版面
大家对第一篇有任何疑问都可以回帖@zhanzr来解答~
     

89

主题

651

帖子

2867

积分

初级工程师

发表于 2017-3-16 10:34 | 显示全部楼层 |返回版面
巧克力娃娃 发表于 2017-3-16 09:52
大家对第一篇有任何疑问都可以回帖@zhanzr来解答~

网页中的代码与文件中的还是有区别的,文件中的更全些!粘贴的报错无法运行,文件的可以。
网页中的代码:
#!/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()

文件中的代码:
#!/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
import random

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)

PULSE_HZ = 5
PULSE_CYCLE = (TEST_SAMPLE_RATE/PULSE_HZ)

test_amp_gain = 0.75
INT16_MAX = 32767
INT16_MIN = -32768

f=open('test.raw',mode='wb')

for i in range(0, TEST_SAMPLE_NUM):
# Sine Wave
    #test_sample = int(INT16_MAX * test_amp_gain * (math.sin(math.pi*2*(i%AUDIO_CYCLE)/AUDIO_CYCLE)))
# Square Wave
    #test_sample = int(test_amp_gain * (INT16_MAX if ((i%AUDIO_CYCLE)>(AUDIO_CYCLE/2)) else INT16_MIN))
# Saw wave
    #test_sample = int(test_amp_gain * (INT16_MIN + (i%AUDIO_CYCLE)*((INT16_MAX-INT16_MIN)/AUDIO_CYCLE)))
# Triangle wave
    #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)))
# Random Number
    #test_sample = random.randint(INT16_MIN, INT16_MAX)
# Pulse Sine Wave
    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_ba = bytearray()
    test_ba.append(test_sample&0x00ff)
    test_ba.append((test_sample>>8)&0x00ff)
    f.write(test_ba)

f.close()

     

144

主题

742

帖子

2674

积分

坛主

 楼主| 发表于 2017-3-16 10:50 | 显示全部楼层 |返回版面
jinglixixi 发表于 2017-3-16 10:34
网页中的代码与文件中的还是有区别的,文件中的更全些!粘贴的报错无法运行,文件的可以。
网页中的代码 ...

多谢  jinglixixi
     

89

主题

651

帖子

2867

积分

初级工程师

发表于 2017-3-16 12:19 | 显示全部楼层 |返回版面

初次学习,又长知识了,又收获!!!
      

54

主题

702

帖子

2345

积分

版主

发表于 2017-3-17 00:19 | 显示全部楼层 |返回版面
jinglixixi 发表于 2017-3-16 10:34
网页中的代码与文件中的还是有区别的,文件中的更全些!粘贴的报错无法运行,文件的可以。
网页中的代码 ...

是这样的, Python对Tab很严格,
因为Python就是靠Tab来分程序的层次的,
这一点跟C/C++等其他靠符号来区分程序的层次有显著不同
比如C/C++程序
  1. for(; ; ;)
  2. {
  3. Func();
  4. }
复制代码
  1. for(; ; ;)
  2. {
  3.         Func();
  4. }
复制代码
  1. for(; ; ;)
  2. {Func();
  3. }
复制代码
  1. for(; ; ;){Func();
  2. }
复制代码
等等写法都可以,通过;号来标记语句的结尾, {}来区分层次, 只是某些写法更好看些
而Python类似的结构只能写成这样:
  1. for i in range(3):
  2.     print(i)
  3. AnotherStatement()
复制代码
建议使用下载版本的py原文件直接修改. 网页上的Python代码中的Tab可能会被破坏格式.这一点写作的时候忘记提醒了. 我的错.

每天都進步
     

89

主题

651

帖子

2867

积分

初级工程师

发表于 2017-3-17 08:49 | 显示全部楼层 |返回版面
zhanzr21 发表于 2017-3-17 00:19
是这样的, Python对Tab很严格,
因为Python就是靠Tab来分程序的层次的,
这一点跟C/C++等其他靠符号来区分程 ...

多谢你的讲解!
     

45

主题

1765

帖子

5287

积分

高级工程师

发表于 2017-4-7 14:50 | 显示全部楼层 |返回版面
Python 生成波形那个语句怎么来的
*滑动验证:
您需要登录后才可以回帖 登录 | 注册 手机登录

本版积分规则

关闭

热门推荐上一条 /2 下一条

分享 快速回复 返回顶部 返回列表