1 感知音频编码的设计思想
感知音频编码的设计,主要是依托于人耳的听觉系统的感知特性来达成的。有些声音并不能被人听到,因此可以对于这部分不编码。
1.1 等响度曲线
两个声音响度级相同,但强度不一定相同,还与频率有关,这便是人耳对于响度的感知特性。
图中的曲线是人耳感知的响度相同的曲线。 最下方一条曲线代表最小可听阈,其下方区域的声音人耳无法察觉,因此可以不编码。
1.2 频域掩蔽效应
一个高强度纯音会使得该频率附近的最小可听阈曲线提升,掩蔽掉在其下的(原本能听到的)声音。这个纯音称为掩蔽声,掩蔽声存在时,音调音刚刚能被听到时的阈值称为掩蔽阈值。
2 心理声学模型的实现过程
2.1 临界频带
当某个纯音被以它为中心频率、且具有一定带宽的连续噪声所掩蔽时,如果该纯音刚好被听到时的功率等于这一频带内的噪声功率,这个带宽为临界频带宽度。
2.2 掩蔽值计算的思路(?)
3 码率分配的实现思路
完成码率分配需要借助一个噪声-掩蔽比的公式:
N
M
R
=
S
M
R
−
S
N
R
(
d
B
)
NMR=SMR-SNR (dB)
NMR=SMR−SNR(dB)
意义:噪掩比是信掩比减去信噪比。我们的最终目标,是把噪掩比降低,最好降低到0。
其中,根据量化的估计,每提升1比特,信噪比就可以提升6dB。因此,使用这种方法,我们就可以设计算法:
- 对最高NMR的子带分配比特,使获益最大的子带的量化级别增加一级;
- 重新计算分配了更多比特子带的NMR
- 循环上述步骤,直到所有NMR都降到0或者没有比特可供分配。
4 程序设计的整体框架
main函数中重要内容的解读:
int main (int argc, char **argv)
{
#if TRACE
tracefile = fopen(TRACE_FILE, "w");
#endif
typedef double SBS[2][3][SCALE_BLOCK][SBLIMIT];
SBS *sb_sample;
typedef double JSBS[3][SCALE_BLOCK][SBLIMIT];
JSBS *j_sample;
typedef double IN[2][HAN_SIZE];
IN *win_que;
typedef unsigned int SUB[2][3][SCALE_BLOCK][SBLIMIT];
SUB *subband;
frame_info frame; //该结构体内包含头信息、比特分配表、声道数、子带数等内容
frame_header header; //头信息
char original_file_name[MAX_NAME_SIZE]; //输入文件名
char encoded_file_name[MAX_NAME_SIZE]; //编码后输出文件名
short **win_buf;
static short buffer[2][1152];
static unsigned int bit_alloc[2][SBLIMIT], scfsi[2][SBLIMIT]; //存放2声道各个子带的比特分配表,SBLIMIT=32,也就是32个子带
static unsigned int scalar[2][3][SBLIMIT], j_scale[3][SBLIMIT];//存放2声道,3组,每组12个样值的各子带的比例因子
static double smr[2][SBLIMIT], lgmin[2][SBLIMIT], max_sc[2][SBLIMIT];
// FLOAT snr32[32];
short sam[2][1344]; /* was [1056]; */
int model, nch, error_protection;
static unsigned int crc;
int sb, ch, adb;
unsigned long frameBits, sentBits = 0;
unsigned long num_samples;
int lg_frame;
int i;
/* Used to keep the SNR values for the fast/quick psy models */
static FLOAT smrdef[2][32];
static int psycount = 0;
extern int minimum;
time_t start_time, end_time;
int total_time;
sb_sample = (SBS *) mem_alloc (sizeof (SBS), "sb_sample");
j_sample = (JSBS *) mem_alloc (sizeof (JSBS), "j_sample");
win_que = (IN *) mem_alloc (sizeof (IN), "Win_que");
subband = (SUB *) mem_alloc (sizeof (SUB), "subband");
win_buf = (short **) mem_alloc (sizeof (short *) * 2, "win_buf");
/* clear buffers */
memset ((char *) buffer, 0, sizeof (buffer));
memset ((char *) bit_alloc, 0, sizeof (bit_alloc));
memset ((char *) scalar, 0, sizeof (scalar));
memset ((char *) j_scale, 0, sizeof (j_scale));
memset ((char *) scfsi, 0, sizeof (scfsi));
memset ((char *) smr, 0, sizeof (smr));
memset ((char *) lgmin, 0, sizeof (lgmin));
memset ((char *) max_sc, 0, sizeof (max_sc));
//memset ((char *) snr32, 0, sizeof (snr32));
memset ((char *) sam, 0, sizeof (sam));
global_init (); //全局初始化
header.extension = 0;
frame.header = &header;
frame.tab_num = -1; /* no table loaded */
frame.alloc = NULL;
header.version = MPEG_AUDIO_ID; /* Default: MPEG-1 */
total_time = 0;
time(&start_time);
programName = argv[0];
if (argc == 1) /* no command-line args */
short_usage ();
else
parse_args (argc, argv, &frame, &model, &num_samples, original_file_name,
encoded_file_name);
print_config (&frame, &model, original_file_name, encoded_file_name);//输出配置信息到窗口
/* this will load the alloc tables and do some other stuff */
hdr_to_frps (&frame);
nch = frame.nch;
error_protection = header.error_protection;
while (get_audio (musicin, buffer, num_samples, nch, &header) > 0) { //从数据流获取音频
if (glopts.verbosity > 1)
if (++frameNum % 10 == 0)
fprintf (stderr, "[%4u]\r", frameNum);
fflush (stderr);
win_buf[0] = &buffer[0][0];
win_buf[1] = &buffer[1][0];
adb = available_bits (&header, &glopts); //计算比特预算
lg_frame = adb / 8;
if (header.dab_extension) {
/* in 24 kHz we always have 4 bytes */
if (header.sampling_frequency == 1)
header.dab_extension = 4;
/* You must have one frame in memory if you are in DAB mode */
/* in conformity of the norme ETS 300 401 http://www.etsi.org */
/* see bitstream.c */
if (frameNum == 1)
minimum = lg_frame + MINIMUM;
adb -= header.dab_extension * 8 + header.dab_length * 8 + 16;
}
{
int gr, bl, ch;
/* New polyphase filter
Combines windowing and filtering. Ricardo Feb'03 */
for( gr = 0; gr < 3; gr++ ) //将36个样值分为3组
for ( bl = 0; bl < 12; bl++ ) //每组做12次子带分解
for ( ch = 0; ch < nch; ch++ )
WindowFilterSubband( &buffer[ch][gr * 12 * 32 + 32 * bl], ch,
&(*sb_sample)[ch][gr][bl][0] );
}
我们可以看到,在函数中主要做的操作有这些:
global_init ();//全局初始化
print_config (&frame, &model, original_file_name, encoded_file_name);//输出配置信息到窗口
while (get_audio (musicin, buffer, num_samples, nch, &header) > 0) { //从数据流获取音频
adb = available_bits (&header, &glopts); //计算比特预算
5 输出某一数据帧的信息
使用与JPEG类似的方法,我们开辟一个TRACE文件,并使用条件编译对于调试信息进行输出。
令在.h
文件内添加,令输出内容为trace.txt,控制信号为TRACE,并添加文件指针:
# define TRACE 1
# define TRACE_FILE trace_file.txt
FILE* tracefile;
然后在.c main函数
内设置文件,打开文件:
#if TRACE
tracefile = fopen(TRACE_FILE, "w");
#endif
我们进入print_config()
函数进行查看,发现内含音频的采样频率和目标码率:
fprintf (stderr, "--------------------------------------------\n");
fprintf (stderr, "Input File : '%s' %.1f kHz\n",
(strcmp (inPath, "-") ? inPath : "stdin"), //输入文件path
s_freq[header->version][header->sampling_frequency]); //采样频率
fprintf (stderr, "Output File: '%s'\n",
(strcmp (outPath, "-") ? outPath : "stdout")); //输出文件path
fprintf (stderr, "%d kbps ", bitrate[header->version][header->bitrate_index]); //目标码率
因此,我们在这个函数里加上条件编译的代码,将内容输出到tracefile中:
#if TRACE
fprintf(tracefile,"-------------------------\n");
fprintf(tracefile, "-------音频基本信息------\n");
fprintf(tracefile, "输入文件名:%s\n",inPath);
fprintf(tracefile, "输出文件名:%s\n", outPath);
fprintf(tracefile, "音频采样率:%.1f kHz\n", s_freq[header->version][header->sampling_frequency]);
fprintf(tracefile, "目标码率:%d kbps\n", bitrate[header->version][header->bitrate_index]);
#endif
并输出比例因子和比特分配表:
#if TRACE
//代码参考S.Z.Zheng
fprintf(tracefile, "---------音频信息---------\n", nch);
fprintf(tracefile, "该音频声道数:%d\n", nch);
fprintf(tracefile, "观测第 %d 帧\n", frameNum);
fprintf(tracefile, "本帧比特预算:%d bits\n", adb);
fprintf(tracefile, "-----------------------");
fprintf(tracefile, "---------比例因子---------\n");
for (ch = 0; ch < nch; ch++) // 每个声道单独输出
{
fprintf(tracefile, "--- 声道%2d ----\n", ch + 1);
for (sb = 0; sb < frame.sblimit; sb++) // 每个子带
{
fprintf(tracefile, "子带[%2d]:\t", sb + 1);
for (int gr = 0; gr < 3; gr++) {
fprintf(tracefile, "%2d\t", scalar[ch][gr][sb]);
}
fprintf(tracefile, "\n");
}
}
fprintf(tracefile, "\n");
fprintf(tracefile, "--------- 比特分配表 ---------\n"); //输出比特分配结果
for (ch = 0; ch < nch; ch++) {
fprintf(tracefile, "--- 声道%2d ---\n", ch + 1); //按声道分配
for (sb = 0; sb < frame.sblimit; sb++) {
fprintf(tracefile, "子带[%2d]:\t%2d\n", sb + 1, bit_alloc[ch][sb]);
}
fprintf(tracefile, "\n");
}
#endif
添加完成后,即可得到对应的信息。我们抽取其中的一帧进行查看:
------音频基本信息------
输入文件名:test.mp2
输出文件名:output.mp2
音频采样率:44.1 kHz
目标码率:192 kbps
-------------------------
---------音频信息---------
该音频声道数:2
观测第 3 帧
本帧比特预算:5016 bits
----------比例因子---------
--- 声道 1 ----
子带[ 1]: 8 9 7
子带[ 2]: 8 9 9
子带[ 3]: 8 9 9
子带[ 4]: 9 8 11
子带[ 5]: 10 9 10
子带[ 6]: 9 12 8
子带[ 7]: 12 11 9
子带[ 8]: 9 9 9
子带[ 9]: 9 10 10
子带[10]: 10 11 11
子带[11]: 8 8 10
子带[12]: 10 10 10
子带[13]: 10 10 9
子带[14]: 10 7 9
子带[15]: 9 7 9
子带[16]: 8 10 9
子带[17]: 10 9 9
子带[18]: 9 10 8
子带[19]: 9 12 9
子带[20]: 13 9 10
子带[21]: 9 7 12
子带[22]: 9 10 10
子带[23]: 9 11 8
子带[24]: 9 11 10
子带[25]: 8 9 10
子带[26]: 11 9 10
子带[27]: 9 9 11
子带[28]: 10 10 9
子带[29]: 9 10 10
子带[30]: 9 12 8
--- 声道 2 ----
子带[ 1]: 10 9 11
子带[ 2]: 9 10 10
子带[ 3]: 7 9 9
子带[ 4]: 9 9 8
子带[ 5]: 10 11 10
子带[ 6]: 9 9 9
子带[ 7]: 11 11 8
子带[ 8]: 9 8 9
子带[ 9]: 8 9 7
子带[10]: 9 8 10
子带[11]: 10 10 10
子带[12]: 10 7 9
子带[13]: 8 9 8
子带[14]: 12 10 10
子带[15]: 9 10 10
子带[16]: 8 10 9
子带[17]: 11 9 9
子带[18]: 12 11 9
子带[19]: 8 11 9
子带[20]: 10 9 9
子带[21]: 10 9 10
子带[22]: 12 9 11
子带[23]: 9 9 10
子带[24]: 11 12 10
子带[25]: 9 10 11
子带[26]: 8 10 10
子带[27]: 9 9 11
子带[28]: 9 9 8
子带[29]: 9 12 8
子带[30]: 10 11 12
--------- 比特分配表 ---------
--- 声道 1 ---
子带[ 1]: 5
子带[ 2]: 4
子带[ 3]: 3
子带[ 4]: 5
子带[ 5]: 5
子带[ 6]: 4
子带[ 7]: 5
子带[ 8]: 3
子带[ 9]: 5
子带[10]: 4
子带[11]: 3
子带[12]: 4
子带[13]: 3
子带[14]: 4
子带[15]: 4
子带[16]: 2
子带[17]: 2
子带[18]: 3
子带[19]: 2
子带[20]: 1
子带[21]: 1
子带[22]: 1
子带[23]: 2
子带[24]: 2
子带[25]: 2
子带[26]: 1
子带[27]: 1
子带[28]: 1
子带[29]: 1
子带[30]: 1
--- 声道 2 ---
子带[ 1]: 4
子带[ 2]: 3
子带[ 3]: 3
子带[ 4]: 4
子带[ 5]: 5
子带[ 6]: 4
子带[ 7]: 3
子带[ 8]: 5
子带[ 9]: 5
子带[10]: 4
子带[11]: 3
子带[12]: 4
子带[13]: 3
子带[14]: 4
子带[15]: 4
子带[16]: 2
子带[17]: 2
子带[18]: 3
子带[19]: 2
子带[20]: 1
子带[21]: 1
子带[22]: 1
子带[23]: 2
子带[24]: 2
子带[25]: 2
子带[26]: 1
子带[27]: 1
子带[28]: 1
子带[29]: 1
子带[30]: 1
至此,完成了对于所需内容的输出。
6 对比不同特性的音频文件输出结果
6.1 音乐
选择test.mp2
作为音乐的音频,进行编码。可以看出,
------音频基本信息------
输入文件名:test.mp2
输出文件名:output.mp2
音频采样率:44.1 kHz
目标码率:192 kbps
-------------------------
---------音频信息---------
该音频声道数:2
观测第 3 帧
本帧比特预算:5016 bits
----------比例因子---------
--- 声道 1 ----
子带[ 1]: 8 9 7
子带[ 2]: 8 9 9
子带[ 3]: 8 9 9
子带[ 4]: 9 8 11
子带[ 5]: 10 9 10
子带[ 6]: 9 12 8
子带[ 7]: 12 11 9
子带[ 8]: 9 9 9
子带[ 9]: 9 10 10
子带[10]: 10 11 11
子带[11]: 8 8 10
子带[12]: 10 10 10
子带[13]: 10 10 9
子带[14]: 10 7 9
子带[15]: 9 7 9
子带[16]: 8 10 9
子带[17]: 10 9 9
子带[18]: 9 10 8
子带[19]: 9 12 9
子带[20]: 13 9 10
子带[21]: 9 7 12
子带[22]: 9 10 10
子带[23]: 9 11 8
子带[24]: 9 11 10
子带[25]: 8 9 10
子带[26]: 11 9 10
子带[27]: 9 9 11
子带[28]: 10 10 9
子带[29]: 9 10 10
子带[30]: 9 12 8
--- 声道 2 ----
子带[ 1]: 10 9 11
子带[ 2]: 9 10 10
子带[ 3]: 7 9 9
子带[ 4]: 9 9 8
子带[ 5]: 10 11 10
子带[ 6]: 9 9 9
子带[ 7]: 11 11 8
子带[ 8]: 9 8 9
子带[ 9]: 8 9 7
子带[10]: 9 8 10
子带[11]: 10 10 10
子带[12]: 10 7 9
子带[13]: 8 9 8
子带[14]: 12 10 10
子带[15]: 9 10 10
子带[16]: 8 10 9
子带[17]: 11 9 9
子带[18]: 12 11 9
子带[19]: 8 11 9
子带[20]: 10 9 9
子带[21]: 10 9 10
子带[22]: 12 9 11
子带[23]: 9 9 10
子带[24]: 11 12 10
子带[25]: 9 10 11
子带[26]: 8 10 10
子带[27]: 9 9 11
子带[28]: 9 9 8
子带[29]: 9 12 8
子带[30]: 10 11 12
--------- 比特分配表 ---------
--- 声道 1 ---
子带[ 1]: 5
子带[ 2]: 4
子带[ 3]: 3
子带[ 4]: 5
子带[ 5]: 5
子带[ 6]: 4
子带[ 7]: 5
子带[ 8]: 3
子带[ 9]: 5
子带[10]: 4
子带[11]: 3
子带[12]: 4
子带[13]: 3
子带[14]: 4
子带[15]: 4
子带[16]: 2
子带[17]: 2
子带[18]: 3
子带[19]: 2
子带[20]: 1
子带[21]: 1
子带[22]: 1
子带[23]: 2
子带[24]: 2
子带[25]: 2
子带[26]: 1
子带[27]: 1
子带[28]: 1
子带[29]: 1
子带[30]: 1
--- 声道 2 ---
子带[ 1]: 4
子带[ 2]: 3
子带[ 3]: 3
子带[ 4]: 4
子带[ 5]: 5
子带[ 6]: 4
子带[ 7]: 3
子带[ 8]: 5
子带[ 9]: 5
子带[10]: 4
子带[11]: 3
子带[12]: 4
子带[13]: 3
子带[14]: 4
子带[15]: 4
子带[16]: 2
子带[17]: 2
子带[18]: 3
子带[19]: 2
子带[20]: 1
子带[21]: 1
子带[22]: 1
子带[23]: 2
子带[24]: 2
子带[25]: 2
子带[26]: 1
子带[27]: 1
子带[28]: 1
子带[29]: 1
子带[30]: 1
6.2 噪声
-------音频基本信息------
输入文件名:噪声.mp2
输出文件名:output噪声.mp2
音频采样率:44.1 kHz
目标码率:192 kbps
-------------------------
---------音频信息---------
该音频声道数:2
观测第 3 帧
本帧比特预算:5016 bits
--------------------------------比例因子---------
--- 声道 1 ----
子带[ 1]: 8 8 9
子带[ 2]: 9 10 10
子带[ 3]: 10 8 10
子带[ 4]: 12 10 9
子带[ 5]: 10 7 10
子带[ 6]: 12 10 8
子带[ 7]: 8 8 10
子带[ 8]: 8 10 9
子带[ 9]: 7 9 9
子带[10]: 10 11 10
子带[11]: 8 10 9
子带[12]: 9 9 11
子带[13]: 7 10 9
子带[14]: 10 9 9
子带[15]: 11 10 10
子带[16]: 10 7 9
子带[17]: 9 9 10
子带[18]: 9 10 11
子带[19]: 12 10 9
子带[20]: 9 11 12
子带[21]: 10 11 8
子带[22]: 9 10 9
子带[23]: 10 9 7
子带[24]: 9 10 9
子带[25]: 11 10 9
子带[26]: 9 9 10
子带[27]: 10 10 10
子带[28]: 9 10 10
子带[29]: 9 12 9
子带[30]: 9 9 11
--- 声道 2 ----
子带[ 1]: 9 8 8
子带[ 2]: 9 9 10
子带[ 3]: 8 9 10
子带[ 4]: 8 10 9
子带[ 5]: 9 8 10
子带[ 6]: 10 7 10
子带[ 7]: 9 11 9
子带[ 8]: 10 11 9
子带[ 9]: 9 7 10
子带[10]: 9 11 10
子带[11]: 10 10 9
子带[12]: 8 9 9
子带[13]: 8 10 10
子带[14]: 12 9 9
子带[15]: 11 9 12
子带[16]: 10 9 9
子带[17]: 10 11 9
子带[18]: 9 9 10
子带[19]: 10 9 10
子带[20]: 9 11 8
子带[21]: 8 9 9
子带[22]: 9 8 9
子带[23]: 9 8 9
子带[24]: 11 10 10
子带[25]: 9 10 11
子带[26]: 10 10 10
子带[27]: 10 10 8
子带[28]: 9 10 9
子带[29]: 10 10 10
子带[30]: 8 9 8
--------- 比特分配表 ---------
--- 声道 1 ---
子带[ 1]: 4
子带[ 2]: 4
子带[ 3]: 3
子带[ 4]: 5
子带[ 5]: 4
子带[ 6]: 4
子带[ 7]: 3
子带[ 8]: 3
子带[ 9]: 4
子带[10]: 3
子带[11]: 2
子带[12]: 3
子带[13]: 3
子带[14]: 3
子带[15]: 2
子带[16]: 2
子带[17]: 1
子带[18]: 3
子带[19]: 3
子带[20]: 1
子带[21]: 1
子带[22]: 1
子带[23]: 1
子带[24]: 1
子带[25]: 1
子带[26]: 1
子带[27]: 0
子带[28]: 1
子带[29]: 1
子带[30]: 1
--- 声道 2 ---
子带[ 1]: 4
子带[ 2]: 3
子带[ 3]: 3
子带[ 4]: 4
子带[ 5]: 4
子带[ 6]: 5
子带[ 7]: 4
子带[ 8]: 3
子带[ 9]: 4
子带[10]: 3
子带[11]: 3
子带[12]: 2
子带[13]: 3
子带[14]: 3
子带[15]: 2
子带[16]: 2
子带[17]: 1
子带[18]: 3
子带[19]: 3
子带[20]: 1
子带[21]: 1
子带[22]: 1
子带[23]: 1
子带[24]: 1
子带[25]: 1
子带[26]: 1
子带[27]: 0
子带[28]: 1
子带[29]: 1
子带[30]: 1
6.3 噪声&音乐
-------音频基本信息------
输入文件名:音乐&噪声.WAV
输出文件名:output噪声&音乐.mp2
音频采样率:22.1 kHz
目标码率:96 kbps
-------------------------
---------音频信息---------
该音频声道数:1
观测第 3 帧
本帧比特预算:5016 bits
--------------------------------比例因子---------
--- 声道 1 ----
子带[ 1]: 12 14 12
子带[ 2]: 12 11 11
子带[ 3]: 12 12 13
子带[ 4]: 11 13 10
子带[ 5]: 11 12 12
子带[ 6]: 13 12 10
子带[ 7]: 13 10 12
子带[ 8]: 10 11 12
子带[ 9]: 12 9 10
子带[10]: 12 10 11
子带[11]: 9 9 8
子带[12]: 10 13 11
子带[13]: 12 11 11
子带[14]: 9 10 12
子带[15]: 14 12 13
子带[16]: 10 11 12
子带[17]: 10 9 10
子带[18]: 12 12 10
子带[19]: 10 12 12
子带[20]: 11 9 10
子带[21]: 10 12 10
子带[22]: 9 10 8
子带[23]: 11 11 12
子带[24]: 12 10 10
子带[25]: 13 11 11
子带[26]: 11 9 11
子带[27]: 10 11 13
子带[28]: 15 12 13
子带[29]: 12 10 11
子带[30]: 10 11 12
--------- 比特分配表 ---------
--- 声道 1 ---
子带[ 1]: 8
子带[ 2]: 8
子带[ 3]: 7
子带[ 4]: 8
子带[ 5]: 5
子带[ 6]: 6
子带[ 7]: 6
子带[ 8]: 6
子带[ 9]: 5
子带[10]: 5
子带[11]: 5
子带[12]: 3
子带[13]: 3
子带[14]: 3
子带[15]: 3
子带[16]: 3
子带[17]: 3
子带[18]: 3
子带[19]: 3
子带[20]: 3
子带[21]: 3
子带[22]: 3
子带[23]: 3
子带[24]: 3
子带[25]: 3
子带[26]: 3
子带[27]: 3
子带[28]: 3
子带[29]: 3
子带[30]: 3
6.4 结论
通过观察,我们可以发现,对于含有噪声的音乐来说,每个子带都分配到了较多的比特数,这说明了这种音源声音的分布比较均匀,对于每种频率上的音频都超过掩蔽曲线不少,说明了信号分布的均匀;而对于音乐来说,在后分配的比特数较少。这主要是因为人发声的语音范围有限,高频分量较少。