Audio Codec的必要性
在理想状况下,对于录音过程,只需要将麦克风获取到的analog信号通过ADC转换为digital信号并存储即可,对于播放音过程,只需要将digital信号通过DAC转换为analog并输出到speaker播放即可。
但在实际的过程中,对于录音过程而言,会受到外界声源的干扰,麦克风自身对信号的衰减以及物理链路接口上引入的杂音等因素的影响,对于放音,可能会受digital数据本身的问题等因素的影响。
举个简单的例子,拿着手机或者固定电话和别人讲话的时候,虽然一边自己说话,一边听电话另外一端的人讲话,但是从听筒中并没有非常明显的听到自己的讲话声音。这中间就是一些Audio Codec在起作用,它们可以实现回音消除,噪音抵消,以及ALC/Limiter等,当然它也实现了最重要的AD和DA功能。
ALC(automatic level control)
不知道怎么来翻译这个词语,姑且称之为动态电压控制。
通过检测系统中的analog信号来判断是否超过Codec中设定的Max/Min值,如果超过的话,则去根据设定的attack rate和release rate来调整analog的电压,以达到使其介于一个合理范围内的目的。
所谓的attach rate其作用的过程就是增益增大(电压降低)的过程,而release rate则是增益减小(电压增高)的过程,如下图所示:
实际使用过程中,ALC功能所能够达到的效果如下图:
从图中可以看到,对于超过Max强度的电压,通过调整attach过程将其降低,反之则通过release将其增大。
对于一般的Codec芯片而言,使用ALC功能的时候都要去配置Max/Min的threshold(db)值,以及Attach rate和Release Rate(快or慢)。
Limiter
不知道应该怎么翻译这个词。
与ALC类似,Limiter也是去检测analog信号的强度,当大于用于设置的threshold的时候通过直接对信号进行截断处理,也有的Codec不是野蛮的进行截断处理,而是采用类似ALC调整Gain的方法来调整analog信号的强度。
例如max9756中Output Limiter的效果图如下:
注:
个人理解,Limiter的效果比起ALC来说相对差一些,可能是内部硬件实现的决定了这点吧,前者可能是一种相对Cost-down的方案。
本文接着前面一篇来简单说明Audio Codec中常见的一些概念
1. HPF(Digital High Pass Filter)
即为数字高通滤波的意思,实现的功能就是使高频率信号通过,而低频率信号被衰减。引文描述为:
A high-pass filter, or HPF, is an LTI filter that passes high frequencies well but attenuates (i.e., reduces the amplitude of) frequencies lower than the filter's cutoff frequency. The actual amount of attenuation for each frequency is a design parameter of the filter. It is sometimes called a low-cut filter or bass-cut filter.[1]
那么有什么好处呢,用英文来进行描述就是High-pass filters have many applications. They are used as part of an audio crossover to direct high frequencies to a tweeter while attenuating bass signals which could interfere with, or damage, the speaker.
更加全面的解释请参照:
http://en.wikipedia.org/wiki/High-pass_filter2. AIN+与AIN-
这就是所谓的差分输入形式,下面来简单的介绍一下单端输入与差分输入。
在单端方式工作时,ADC转换的是单输入引脚对地的电压值。当增益为1时,测量的值就是输入的电压值;范围是0V到VREF;当增益增加时,输入的范围要相应的减小;
在差分方式工作时;ADC转换的是AIN+与AIN-两个引脚的差值;在增益为1时,测量的值等于(AIN+)-(AIN-),范围是-VREF到+VREF;当增益增加时,输入的范围要相应的减小。
注意:在差分方式时所提的负压是指AIN-引脚的电压大于AIN+引脚的电压,实际输入到两个引脚的电压对地都必需是正的;例如:如果AIN+引脚输入的电压为0V,AIN-引脚的输入电压为1/2VREF时,差分的输入电压为(0V-1/2VREF) = -1/2VREF
下面列举了一个两路单端analog输入的例子:
3. zero-crossing detection
即过零检测。指的是当交流系统中,当波形从正半周向负半周转换时,经过零位时,系统作出的检测,可作开关电路或者频率检测。
1. AGC(Automatic Gain Control)
即自动增益控制; 对一个输入信号进行放大,为了方便处理,应该保证输出有一定的幅度同时又不会饱和,但是由于输入信号的幅度通常变化很大,所以不能采用一个简单的单一放大倍数,AGC就是根据输入信号调整放大倍数,是输出信号幅度一致。
2. 音频数据的格式WAVE_FORMAT
音频格式包括Sample rate,Mono/Stereo等内容,具体的类型包括下面的几种:
#define WAVE_FORMAT_1M08 0x00000001 /* 11.025 kHz, Mono, 8-bit */
#define WAVE_FORMAT_1S08 0x00000002 /* 11.025 kHz, Stereo, 8-bit */
#define WAVE_FORMAT_1M16 0x00000004 /* 11.025 kHz, Mono, 16-bit */
#define WAVE_FORMAT_1S16 0x00000008 /* 11.025 kHz, Stereo, 16-bit */
#define WAVE_FORMAT_2M08 0x00000010 /* 22.05 kHz, Mono, 8-bit */
#define WAVE_FORMAT_2S08 0x00000020 /* 22.05 kHz, Stereo, 8-bit */
#define WAVE_FORMAT_2M16 0x00000040 /* 22.05 kHz, Mono, 16-bit */
#define WAVE_FORMAT_2S16 0x00000080 /* 22.05 kHz, Stereo, 16-bit */
#define WAVE_FORMAT_4M08 0x00000100 /* 44.1 kHz, Mono, 8-bit */
#define WAVE_FORMAT_4S08 0x00000200 /* 44.1 kHz, Stereo, 8-bit */
#define WAVE_FORMAT_4M16 0x00000400 /* 44.1 kHz, Mono, 16-bit */
#define WAVE_FORMAT_4S16 0x00000800 /* 44.1 kHz, Stereo, 16-bit */
通过Wave API waveInGetDevCaps()可以获取Audio驱动所支持的音频格式,如下所示:
mmRtn=waveInGetDevCaps(g_dwDeviceNum-1,&woc,sizeof(woc));
CheckMMRESULT(mmRtn,"ERROR: Failed to get device caps. waveInGetDevCaps",TPR_FAIL,"Driver responded incorrectly");
WinCE下面标准的Audio驱动中采样率为48000(48K),软件上在通过插值运算来支持各个各样的采样率,这部分实现可以在类StreamContext的继承类诸如OutputStreamContext1M8/OutputStreamContext1M16等中的方法Rend2中实现。
当我们使用Wave API waveInOpen来创建音频Stream的时候,需要通过实参向驱动传送结构体WAVEFORMATEX以告知驱动待创建的Stream的种类,如下:
WAVEFORMATEX wfx;
WAVEHDR wh;
ZeroMemory( &wfx, sizeof( wfx ) );
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = 1;
wfx.nSamplesPerSec = 11025;
wfx.wBitsPerSample = 8;
wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8;
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
wfx.cbSize = 0;
//---- Open the Wave Input Stream
mmRtn = waveInOpen( &hwi, g_dwDeviceNum-1, &wfx, NULL, 0, NULL );
if( MMSYSERR_NOERROR != mmRtn )
{
LOG( TEXT( "FAIL in %s @ line %u:" ), TEXT( __FILE__ ), __LINE__ );
LOG( TEXT( "\twaveInOpen returned error code, #%d." ), mmRtn );
return TPR_FAIL;
}
驱动中会依次调用WAV_IOControlà HandleWaveMessageà DeviceContext::OpenStream()来创建音频流。在函数DeviceContext::OpenStream的实现过程中,首先会去调用DeviceContext::IsSupportedFormat()来判断是否为支持的音频格式流,如果不支持的话则创建失败,这部分代码如下:
BOOL DeviceContext::IsSupportedFormat(LPWAVEFORMATEX lpFormat)
{
if (lpFormat->wFormatTag != WAVE_FORMAT_PCM)
return FALSE;
if ( (lpFormat->nChannels!=1) && (lpFormat->nChannels!=2) )
return FALSE;
if ( (lpFormat->wBitsPerSample!=8) && (lpFormat->wBitsPerSample!=16) )
return FALSE;
if (lpFormat->nSamplesPerSec < 100 || lpFormat->nSamplesPerSec > 192000)
return FALSE;
return TRUE;
}
对于支持的音频流格式,则会调用虚函数CreateStream()来创建对应的流,具体的实现将在类DeviceContext的继承中找到,以继承类OutputDeviceContext的实现为例,代码如下:
StreamContext *OutputDeviceContext::CreateStream(LPWAVEOPENDESC lpWOD)
{
LPWAVEFORMATEX lpFormat = lpWOD->lpFormat;
#if ENABLE_MIDI
if (lpFormat->wFormatTag == WAVE_FORMAT_MIDI)
{
return new CMidiStream;
}
#endif
// 应用层可以通过api的参数来告诉driver有几个channel
// 下面会根据具体的通道数和采样来决定创建什么样的output device stream 类实例
switch (lpFormat->nChannels)
{
case 1:
switch (g_pHWContext->m_dwWavOutChnum)
{
case 1:
return (lpFormat->wBitsPerSample == 8) ?
(StreamContext *)new OutputStreamContext1M8 :
(StreamContext *)new OutputStreamContext1M16;
case 2:
return (lpFormat->wBitsPerSample == 8) ?
(StreamContext *)new OutputStreamContext2M8 :
(StreamContext *)new OutputStreamContext2M16;
case 6:
return (lpFormat->wBitsPerSample == 8) ?
(StreamContext *)new OutputStreamContext6M8 :
(StreamContext *)new OutputStreamContext6M16;
default:
break;
}
break;
case 2:
switch (g_pHWContext->m_dwWavOutChnum)
{
case 1:
return (lpFormat->wBitsPerSample == 8) ?
(StreamContext *)new OutputStreamContext1S8 :
(StreamContext *)new OutputStreamContext1S16;
case 2:
return (lpFormat->wBitsPerSample == 8) ?
(StreamContext *)new OutputStreamContext2S8 :
(StreamContext *)new OutputStreamContext2S16;
case 6:
return (lpFormat->wBitsPerSample == 8) ?
(StreamContext *)new OutputStreamContext6S8 :
(StreamContext *)new OutputStreamContext6S16;
default:
break;
}
break;
default:
switch (g_pHWContext->m_dwWavOutChnum)
{
case 6:
return new OutputStreamContext6X16;
}
break;
}
return NULL;
}
其实,驱动中可以选择去支持那些格式,没有必要去支持每一种音频Stream格式。
利用Adobe Audition工具来产生正弦波
在开发Audio驱动的过程中,经常会需要去录制信号,然后比较录制后的信号和原信号的差异。这种情况下,大部分人会选择一个标准的正弦波信号来作为外部音源。
本文就来讲述制作正选波音源的一种方法,即使用Adobe Audition来制作正弦波信号。
Adobe Audition 的前身为 Cool Edit。2003 年 Adobe 公司收购了 Syntrillium 公司的全部产品都,用于充实其阵容强大的视频处理软件系列。该软件功能强大,控制灵活,使用它可以录制、混合、编辑和控制数字音频文件,也可轻松创建音乐、制作广播短片、修复录制缺陷。
本文中利用该软件的信号发生器来产生正弦波。
Adobe Audition本身很贵,但是聪明的国人把它给破解了,可以从位置
http://www.tangzhehao.com.cn/saint/article.asp?id=16找到其破解汉化版,而且体积(50MB左右)相对于官方版(250MB左右)堪称迷你。
下载并安装之后可以看到如下的主界面:
下面我们用它来产生正弦波,步骤如下:
>> 以此选择菜单:文件à新建,将看到如下的框图:
这几个选项就不解释了吧,点击确定完成即可。
>> 点击工作区左侧的效果选项卡,如下图所示:
将看到如下的框图:
稍微解释一下,“基准频率”那里用来指定调制前的信号频率,信号的特性可以在“正弦波”和“方波”等中间进行选择,而右上角的dB音量用来定义音量幅度,“相位调整”一栏中用来定义调制信号(这里要有结合数字信号处理方面的知识来理解),右下角“时值”用来定义产生信号的时常。
例如,我们可以通过修改上图中的配置来产生时长为128S/左声道幅度为-6db/右声道为-12dB的正弦波,最终波形如下图所示:
当然了,Adobe Audition的功能远不止此,这里我们仅仅是用来产生一个正弦波音频数据,方便测试Audio Driver使用。
IIS总线的几句经典说明
在飞利浦公司的I2S标准(其实还有其它的一些标准,如日本标准等)中,既规定了硬件接口规范,也规定了数字音频数据的格式。I2S有3个主要信号:1.串行时钟SCLK,也叫位时钟(BCLK),即对应数字音频的每一位数据,SCLK都有1个脉冲。SCLK的频率=2×采样频率×采样位数(很多的时候,我们也会用倒推的方式算出来采样频率,即采样频率=SCLK的频率/(2×采样位数)) 2. 帧时钟LRCK,用于切换左右声道的数据。LRCK为“1”表示正在传输的是左声道的数据,为“0”则表示正在传输的是右声道的数据。LRCK的频率等于采样频率(示波器上一看就能够算得出来采样频率是多少,另外还可以数的出来采样位数是多少,即在LRCK为高或者低的状态下SCLK的周期个数-1)。3.串行数据SDATA,就是用二进制补码表示的音频数据。
有时为了使系统间能够更好地同步,还需要另外传输一个信号MCLK(有些控制器中称之为sys clk,前面别和MCU的系统时钟混淆了),称为主时钟,也叫系统时钟(Sys Clock),是采样频率的256倍或384倍(注意这里是采样频率而不是SCLK,如MCLK为24MHz,SCLK为48KHz,中间大概是MCLK=256×SCLK的关系)。
音频设备的3种硬件接口--PCM,IIS和AC97
一些Audio Codec中总是出现PCM字样,一直以为它是一种音频数据编码格式,但是越看Spec觉得越不像,赶紧到网上查了下,发现它是一种类似与IIS的数据传输格式。
1. PCM接口
针对不同的数字音频子系统,出现了几种微处理器或DSP与音频器件间用于数字转换的接口。最简单的音频接口是PCM(脉冲编码调制)接口,该接口由时钟脉冲(BCLK)、帧同步信号(FS)及接收数据(DR)和发送数据(DX)组成。在FS信号的上升沿,数据传输从MSB(Most Significant Bit)字开始,FS频率等于采样率。FS信号之后开始数据字的传输,单个的数据位按顺序进行传输,1个时钟周期传输1个数据字。发送MSB时,信号的等级首先降到最低,以避免在不同终端的接口使用不同的数据方案时造成MSB的丢失。
PCM接口很容易实现,原则上能够支持任何数据方案和任何采样率,但需要每个音频通道获得一个独立的数据队列(为什么?)。
2. IIS接口
IIS接口(Inter-IC Sound)在20世纪80年代首先被飞利浦用于消费音频,并在一个称为LRCLK(Left/Right CLOCK)的信号机制中经过多路转换,将两路音频信号变成单一的数据队列。当LRCLK为高时,左声道数据被传输;LRCLK为低时,右声道数据被传输。与PCM相比,IIS更适合于立体声系统(因为可以传送多个声道的数据,所以显然更加适合Stereo Single,其实所谓的立体声,也就是多个声道可以发出不一样的声音)。对于多通道系统,在同样的BCLK和LRCLK条件下,并行执行几个数据队列也是可能的。
3. AC97接口
AC'97(Audio Codec 1997)是以Intel为首的五个PC厂商Intel、Creative Labs、NS、Analog Device与Yamaha共同提出的规格标准。与PCM和IIS不同,AC'97不只是一种数据格式,用于音频编码的内部架构规格,它还具有控制功能。AC'97采用AC-Link与外部的编解码器相连,AC-Link接口包括位时钟(BITCLK)、同步信号校正(SYNC)和从编码到处理器及从处理器中解码(SDATDIN与SDATAOUT)的数据队列。AC'97数据帧以SYNC脉冲开始,包括12个20位时间段(时间段为标准中定义的不同的目的服务)及16位“tag”段,共计256个数据序列。例如,时间段“1”和“2”用于访问编码的控制寄存器,而时间段“3”和“4”分别负载左、右两个音频通道。“tag”段表示其他段中哪一个包含有效数据。把帧分成时间段使传输控制信号和音频数据仅通过4根线到达9个音频通道或转换成其他数据流成为可能。与具有分离控制接口的IIS方案相比,AC'97明显减少了整体管脚数。一般来说,AC'97 编解码器采用TQFP48封装。
声音强度的定义
声音强度的定义dB是从电学中而来的。在实际的电路应用中,会存在很多的放大器,尤其是对模拟信号而言,而且放大器的放大倍数可能很大。
例如,一个收音机从天线收到的信号至送入喇叭放音输出,一共要放大2万倍左右,如果直接用2万这样的概念来形容放大倍数的话,数值过于大。另外,直接用放大倍数声音来表示用户的直观感受的话,也不够直观,例如当电功率从0.1瓦增长到1.1瓦时,听到的声音就响了很多;而从1瓦增强到2瓦时,响度就差不太多;再从10瓦增强到11瓦时,没有人能听出响度的差别来(这里同样是增大1W)。
因此,引入了dB来形容声音的强度,也可以说是增益。
回顾一下电学中计算电压/电流/功率强度的方法:
电流增益:XdB = 20log(Io/Ii)
电压增益:XdB = 20log(Vo/Vi)
功率增益:XdB=10log(Po/Pi)
其实,功率增益的公式可以从上面电流增益上推导出来,方法如下:
à首先,Po/Pi = POW(Io, 2)*R/(POW(Ii, 2)*R) = POW(Io, 2)/ POW(Ii, 2) = POW(Io/Ii, 2),
à然后,由电流增益XdB = 20log(Io/Ii)得到Io/Ii = POW(10, XdB/20),
两边同时平方得到:POW(Io/Ii, 2) = POW(POW(10, XdB/20), 2) = POW(10, XdB*2/20) = POW(10, XdB/10)
也即:POW(Io/Ii, 2) = POW(10, XdB/10)
也就是:XdB/10 = log(POW(Io/Ii, 2))
à用第一步的计算结果替换第二部的计算结果中左边
XdB/10 = log(POW(Io/Ii, 2))à XdB/10 = log(Po/Pi)à XdB = 10 log(Po/Pi)
à最终XdB = 10 log(Po/Pi)
在实际的Wave Driver和Audio Codec中,无论DA采样还是AD转换都是基于电压的,也即电压增益,实际上使用的公式就是XdB = 20log(Vo/Vi)。
假设Vo/Vi增大一倍,也即变为20log(2*Vo/Vi)=20*(log2+log(Vo/Vi))=6+20log(Vo/Vi),所以我们可以得到一个常用的结论,当放大倍数增大一倍的时候,增益值增加6dB,相应的当放大倍数减少50%的时候,增益值减少6dB(增加-6dB)。