一、基础知识
语音信号有四个重要的参数:声道数、采样频率、量化位数(位深)和比特率。
声道数: 可以是单声道、双声道 …
采样率(Sample rate): 声音是模拟信号,物理信号转化为数字信号的过程我们称之为采样,采样率则表示 每秒对声音信号(模拟信号)抽取的总采样点数,44100Hz采样频率意味着每秒钟信号被均匀抽取44100次。采样频率越高,即采样的间隔时间越短,则在单位时间内得到的样本数据就越多,对信号波形的表示也越精确。
量化位数(Bit depth): 也称为“位深”,每个采样点中信息的比特(bit)数。1 byte等于8 bit。通常有8bit、16bit、24bit、32bit…,位深越大,每个采样点能够表示的信息越多,精度越高。
比特率(Bit rate): 每秒处理多少个Bit。计算公式为:采样率(kHz) * 位深(bit) * 声道数。比如一个单声道,用44.1KHz/16Bit的配置来说,它的比特率就为44100161=705600,单位是bit/s(或者bps),因为通常计算出来的数字都比较大,大家就用kbit/s了,也就是705.6kbit/s。在对音频进行压缩时,比特率就成为了我们的一个要选的选项了,越高的比特率,其音质也就越好。一些常用的比特率有:16kbps:为可视电话质量、32kbit/s: 一般只适用于语音、96kbit/s: 一般用于语音或低质量流媒体、128或160kbit/s: 中等比特率质量、192kbit/s: 中等质量比特率、256kbit/s: 常用的高质量比特率、320kbit/s: MP3标准支持的最高水平
音频从业人员用的两款查看音频软件:
Adobe Audition: 他是Adobe公司开发专门处理音频的专业软件,可以快速看语音波形和语谱图,微博关注vposy,下载地址见置顶。他破解了很多adobe公司的软件,包括PS、PR…
Audacity: 它是一款开源的、跨平台、多声道的录音编辑软件。在我的工作中经常使用Audacity进行声音信号的录制,然后再输出成WAV文件供Python程序处理。
音频在网络中传播,主要分为如下几个阶段:录制-编码-传输-解码-播放,音频编解码发挥这不可忽视的作用,能够优化数据包大小,通常通过媒体采集设备采集的音频数据被编码后,可以极大的压缩数据,并且音质影响不大。本节主要介绍常见的音频编解码格式。
音频编码是为了将 PCM 音频采样数据转换为音频码流, 减少存储来提升网络传输效率。常见的格式有:FLAC、APE、WAV、Opus、MP3、WMA、AAC。
FLAC、APE、WAV 是属于无损编码格式,压缩率低,通常用于音质要求较高的音乐等内容; Opus、MP3、WMA、AAC 属于有损压缩格式,压缩率高利于网络传输; 其中 Opus、OGG 属于完全免费开源的编码格式。不同的编码格式特点也不尽相同,不同编码面向的使用场景不同,没有绝对的优劣。
二、PCM、RAW、SAM
RAW、PCM(Pulse Code Modulation)、SAM 都是一种存储 原始数据 的音频文件格式,未经过任何编码和压缩处理,他们的本质一样,只是文件扩展名不同,也可以没有扩展名。与WAV或AIFF的大小相比,这音频文件不包含任何头信息(采样率、位深度、通道数)。
如果在PCM文件的前面添加WAV文件头,就可以生成WAV格式文件。
如果是16位的话,pcm每个采样点的值在0~ 2^15,因为第一位是符号位。所以我们有时候用librosa读取的音频每个采样点都是0~1之间的,如果该音频是16bit的,如果想将他换成short型应该乘以2 ^ 15。
1.pcm转wav
版本一:代码参考自:https://github.com/pliu6/pcm2wav
/**
* https://github.com/pliu6/pcm2wav
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
typedef struct {
unsigned char chunk_id[4]; /*{'R', 'I', 'F', 'F'}*/
unsigned int chunk_size;
unsigned char format[4];
} FIFFChunk;
typedef struct {
unsigned char chunk_id[4]; /* {'f', 'm', 't', ' '} */
unsigned int chunk_size;
unsigned short audio_format; // 2字节
unsigned short channels; // 4字节
unsigned int sample_rate; // 4字节
unsigned int byte_rate; // 4字节
unsigned short block_align; // 2字节
unsigned short bits_per_sample; // 2字节
} FormatChunk;
typedef struct {
unsigned char chunk_id[4]; /* {'d', 'a', 't', 'a'} */
unsigned int chunk_size;
} DataChunk;
// pcm2wav ***.pcm ***.wav 通道 采样率 量化位数
int main(int argc, char *argv[]) {
FILE *pcmfile, *wavfile;
long pcmfile_size;
FIFFChunk fiffchunk;
FormatChunk formatchunk;
DataChunk datachunk;
int read_len;
char buf[1024];
if (argc != 6) {
printf("usage:\n"
"\t%s pcmfile wavfile channel samplerate bitspersample\n", argv[0]);
return 1;
}
pcmfile = fopen(argv[1], "rb");
if (pcmfile == NULL) {
printf("!Error: Can't open pcmfile.\n");
return 1;
}
fseek(pcmfile, 0, SEEK_END); // 将文件指针移动到文件最后
pcmfile_size = ftell(pcmfile); // 返回给定流 stream 的当前文件位置(字节)
fseek(pcmfile, 0, SEEK_SET); // 将文件指针移动到文件开头
wavfile = fopen(argv[2], "wb");
if (wavfile == NULL) {
printf("!Error: Can't create wavfile.\n");
return 1;
}
/* *********** RIFF区块 ********************* */
strncpy(fiffchunk.chunk_id,"RIFF", 4);
fiffchunk.chunk_size = pcmfile_size+36;
strncpy(fiffchunk.format,"WAVE",4);
fwrite(&fiffchunk, sizeof(fiffchunk), 1, wavfile);
/* *********** FORMAT区块 ********************* */
strncpy(formatchunk.chunk_id,"fmt ", 4);
formatchunk.chunk_size = sizeof(FormatChunk) - 8; // 不包含该区块ID和Size的长度
formatchunk.audio_format = 1; /* 未压缩的 */
formatchunk.channels = atoi(argv[3]); // 通道数,字符串转换成整型
formatchunk.sample_rate = atoi(argv[4]); // 采样率
formatchunk.bits_per_sample = atoi(argv[5]); // 量化位数
formatchunk.byte_rate = formatchunk.sample_rate * formatchunk.channels * (formatchunk.bits_per_sample >> 3); // 每秒数据字节数=SampleRate * NumChannels * BitsPerSample/8
formatchunk.block_align = formatchunk.channels * (formatchunk.bits_per_sample >> 3); // 每个样本需要的字节数
fwrite(&formatchunk, 1, sizeof(formatchunk), wavfile);
/* *********** DATA区块 ********************* */
strncpy(datachunk.chunk_id, "data",4);
datachunk.chunk_size = pcmfile_size;
fwrite(&datachunk, 1, sizeof(datachunk.chunk_id) + sizeof(datachunk.chunk_size), wavfile);
while ((read_len = fread(buf, 1, sizeof(buf), pcmfile)) != 0) {
fwrite(buf, 1, read_len, wavfile);
}
fclose(pcmfile);
fclose(wavfile);
}
pcm2wav.c
版本二:代码参考自:https://github.com/jwhu1024/pcm-to-wav
/**
* https://github.com/jwhu1024/pcm-to-wav
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
unsigned char chunk_id[4]; // RIFF string
unsigned int chunk_size; // overall size of file in bytes (36 + data_size)
unsigned char sub_chunk1_id[8]; // WAVEfmt string with trailing null char
unsigned int sub_chunk1_size; // 16 for PCM. This is the size of the rest of the Subchunk which follows this number.
unsigned short audio_format; // format type. 1-PCM, 3- IEEE float, 6 - 8bit A law, 7 - 8bit mu law
unsigned short num_channels; // Mono = 1, Stereo = 2
unsigned int sample_rate; // 8000, 16000, 44100, etc. (blocks per second)
unsigned int byte_rate; // SampleRate * NumChannels * BitsPerSample/8
unsigned short block_align; // NumChannels * BitsPerSample/8
unsigned short bits_per_sample; // bits per sample, 8- 8bits, 16- 16 bits etc
unsigned char sub_chunk2_id[4]; // Contains the letters "data"
unsigned int sub_chunk2_size; // NumSamples * NumChannels * BitsPerSample/8 - size of the next chunk that will be read
} wav_header_t;
char *dummy_get_raw_pcm(char *p, int *bytes_read) {
long lSize;
char *pcm_buf;
size_t result;
FILE *fp_pcm;
fp_pcm = fopen(p, "rb");
if (fp_pcm == NULL) {
printf("File error");
exit(1);
}
// obtain file size:
fseek(fp_pcm, 0, SEEK_END); // 将文件指针移动到文件最后
lSize = ftell(fp_pcm); // 返回给定流 stream 的当前文件位置(字节)
rewind(fp_pcm); // 将文件指针移动到文件开头
// 分配内存来包含整个文件
pcm_buf = (char *) malloc(sizeof(char) * lSize);
if (pcm_buf == NULL) {
printf("Memory error");
exit(2);
}
// 将文件复制到pcm_buf中:
result = fread(pcm_buf, 1, lSize, fp_pcm);
if (result != lSize) {
printf("Reading error");
exit(3);
}
*bytes_read = (int) lSize;
return pcm_buf;
}
void get_wav_header(int raw_sz, wav_header_t *wh) {
// RIFF chunk
strcpy(wh->chunk_id, "RIFF");
wh->chunk_size = 36 + raw_sz;
// fmt sub-chunk (to be optimized)
strncpy(wh->sub_chunk1_id, "WAVEfmt ", strlen("WAVEfmt "));
wh->sub_chunk1_size = 16;
wh->audio_format = 1;
wh->num_channels = 1;
wh->sample_rate = 16000;
wh->bits_per_sample = 16;
wh->block_align = wh->num_channels * wh->bits_per_sample / 8;
wh->byte_rate = wh->sample_rate * wh->num_channels * wh->bits_per_sample / 8;
// data sub-chunk
strncpy(wh->sub_chunk2_id, "data", strlen("data"));
wh->sub_chunk2_size = raw_sz;
}
void dump_wav_header(wav_header_t *wh) {
printf("=========================================\n");
printf("chunk_id:\t\t\t%s\n", wh->chunk_id);
printf("chunk_size:\t\t\t%d\n", wh->chunk_size);
printf("sub_chunk1_id:\t\t\t%s\n", wh->sub_chunk1_id);
printf("sub_chunk1_size:\t\t%d\n", wh->sub_chunk1_size);
printf("audio_format:\t\t\t%d\n", wh->audio_format);
printf("num_channels:\t\t\t%d\n", wh->num_channels);
printf("sample_rate:\t\t\t%d\n", wh->sample_rate);
printf("bits_per_sample:\t\t%d\n", wh->bits_per_sample);
printf("block_align:\t\t\t%d\n", wh->block_align);
printf("byte_rate:\t\t\t%d\n", wh->byte_rate);
printf("sub_chunk2_id:\t\t\t%s\n", wh->sub_chunk2_id);
printf("sub_chunk2_size:\t\t%d\n", wh->sub_chunk2_size);
printf("=========================================\n");
}
// pcm-to-wav ./time.pcm ./***.wav
int main(int argc, char *argv[]) {
int raw_sz = 0;
FILE *fwav;
wav_header_t wheader; // 文件头 结构体变量声明
memset(&wheader, '\0', sizeof(wav_header_t)); // 清除内存位置
// check argument
if (argc != 2)
return -1;
// dummy raw pcm data
char *pcm_buf = dummy_get_raw_pcm("./time.pcm", &raw_sz);
// construct wav header
get_wav_header(raw_sz, &wheader); // 给文件头赋 初值
dump_wav_header(&wheader); // 打印文件头 信息
// write out the .wav file
fwav = fopen(argv[1], "wb");
fwrite(&wheader, 1, sizeof(wheader), fwav);
fwrite(pcm_buf, 1, raw_sz, fwav);
fclose(fwav);
if (pcm_buf)
free(pcm_buf);
return 0;
}
pcm2wav.c
版本三:使用python的wave库
def pcm2wav(pcm_file, wav_file, channels=1, bits=16, sample_rate=16000):
f = open(pcm_file, 'rb')
pcmdata = f.read()
f.close()
if bits % 8 != 0:
raise ValueError("bits % 8 must == 0. now bits:" + str(bits))
wavfile = wave.open(wav_file, 'wb')
wavfile.setnchannels(channels) # 通道数
wavfile.setsampwidth(bits // 8) # 位深
wavfile.setframerate(sample_rate) # 采样率
wavfile.writeframes(pcmdata) # 数据
wavfile.close()
还有一个github开源代码:wavutils
三、FLAC
FLAC 全称 Free Lossless Audio Codec,中文直译为自由无损音频压缩编码。无损也就是说当你将从音频 CD 上读取的音频数据文件压缩成 FLAC 格式后,你还可以再将 FLAC 格式的文件还原,而还原后的音频文件与压缩前的一模一样,没有任何损失。
FLAC 是一款的自由音频压缩编码,其特点是可以对音频文件无损压缩。不同于其他有损压缩编码,如 MP3 、AAC,压缩后不会有任何音质损失,现在已被很多软件及硬件音频产品所支持,很多流行的音乐播放器默认的无损音频格式都是 FLAC。
特点:无损压缩格式,体积较大,但是兼容性好,编码速度快,播放器支持广。
四、APE
APE 是一种常见的无损音频压缩编码格式,APE 是其编码后的文件扩展名,该编码格式的名称是 Monkey’s Audio。
Monkey’s Audio 压缩比高于其他常见的无损音频压缩格式,约在 55%上下,但编解码速度略慢。在搜寻回放位置时,如果文件压缩比过高,在配备较差的计算机会有延迟的现象。另外,由于它没有提供错误处理的功能,若发生文件损坏,损坏位置之后的数据有可能会丢失。
Monkey’s Audio 是开放源代码的免费软件,但因其授权协议并非自由软件而是准自由软件(Semi-free Software)而受到排挤。因为这意味着许多基于 GNU/Linux 的 Linux 发行包或是其他只能基于自由软件的操作系统不能将其收入。较之其他使用更自由的许可证的无损音频编码器(如 FLAC),受其他软件的支持也更少。
特点:无损压缩格式,其体积比其它无损压缩格式较小,编码速度偏慢。
五、WAVE
WAV 全称 Waveform Audio File Format,是微软公司开发的一种无损声音文件格式,也叫波形声音文件,是最早的数字音频格式,被 Windows 平台及其应用程序广泛支持。WAV格式支持多种压缩算法、音频位数、采样频率和声道。
WAV 符合 RIFF(Resource Interchange File Format) 规范,所有的WAV都由 44字节 头文件 和 PCM文件 组成,这个文件头包含语音信号的所有参数信息(声道数、采样率、量化位数、比特率…)
44个字节的 头文件由 3个区块组成:
RIFF chunk:WAV文件标识
Format chunk: 声道数、采样率、量化位数、等信息
Data chunk:存放数据
相反的,在PCM文件头部添加44个字节的WAV文件头,就可以生成WAV格式文件
1.RIFF区块
规范的WAVE格式遵循RIFF头
2.FORMAT区块
描述声音数据的格式
3.DATA区块
包含数据的大小和实际声音
4.文件实例:
RIFF区块
ChunkID(4字节 52 49 46 46):对应ASCII中的 RIFF,这里是ASCII码对照表。
ChunkSize(4字节 76 01 03 00):表示WAV文件的大小,不包含了前面8个字节,所以真正的大小等于文件总字节减去8。76 01 03 00 对应的正序16进制为 00 03 01 76大小为196982
Format(4字节 57 41 56 45):对应ASCII中的WAVE
FORMAT区块
Subchunkl ID(4字节 66 6d 74 20):对应ASCII中的fmt
Subchunkl Size(4字节 10 00 00 00):正序16进制 00 00 00 10 对应16
AudioFormat(2字节 01 00):正序16进制 00 01,对应数字1,表示编码格式“WAVE_FORMAT_PCM”
NumChannels(2字节 01 00):正序16进制 00 01,对应数字1,表示声道数为1
SampleRate(4字节 80 bb 00 00):正序16进制 00 00 bb 80,表示采样率为48000
ByteRate(4字节 00 77 01 00):正序16进制 00 01 77 00,表示传输速率为96000
BlockAlign(2字节 02 00):正序16进制 00 02,每个采样所需的2字节数
BitsPerSample(2字节 10 00):正序16进制 00 10,采样大小为16 Bits
DATA区块
Subchunk2ID(4字节 64 61 74 61):表示为ASCII的data,开始数据区
Subchunk2 Size(4字节 52 01 03 00):正序16进制 00 03 01 52,PCM字节数,大小为196946
wav文件(wav字节-44字节):pcm音频数据
通常WAV文件的大小不能超过4GB,但是WAV文件的大小通常不直接受限于WAV文件格式本身,而是受到多种因素的影响,特别是文件系统的限制。
FAT32文件系统:FAT32文件系统(常用于早期的存储设备)有一个文件大小限制,通常是4GB(2^32 - 1字节,因为FAT32使用32位来存储文件大小)。如果一个WAV文件存储在FAT32文件系统的分区上,它的大小可能不能超过这个限制。
其他文件系统:现代的文件系统(如NTFS、exFAT、HFS+、APFS等)通常没有这样的限制,或者限制要大得多。
5.WAV转PCM
因为wav比pcm多44个字节的文件头,也就是说44字节后的信息,就是pcm数据
版本1:C语言实现wave to pcm
#include <stdio.h>
/**
* wav2pcm ***.wav **.pcm
* @param argc 命令行参数的长度
* @param argv 命令行参数,argv[0]是程序名称
* @return
*/
int main(int argc, char *argv[]) {
FILE *wavfile;
FILE *pcmfile;
char buf[1024];
int read_len;
if (argc != 3) {
printf("usage:\n"
"\t wav2pcm ***.wav **.pcm\n");
}
wavfile = fopen(argv[1], "rb");
if (wavfile == NULL) {
printf("!Error: Can't open wavfile.\n");
return 1;
}
pcmfile = fopen(argv[2], "wb");
if (pcmfile == NULL) {
printf("!Error: Can't open pcmfile.\n");
return 1;
}
fseek(wavfile, 44, SEEK_SET); // 将文件指针移动到文件开头,后移44字节
while ((read_len = fread(buf, 1, sizeof(buf), wavfile)) != 0) {
fwrite(buf, 1, read_len, pcmfile);
}
fclose(pcmfile);
fclose(wavfile);
return 0;
}
版本2:shell实现 wave to pcm
dd if=1.wav of=1.pcm bs=1 skip=44
版本3:使用python的
def wav2pcm(wavfile, pcmfile, data_type=np.int16):
f = open(wavfile, "rb")
f.seek(0) # 移动文件读取指针到指定位置
data = np.fromfile(f, dtype=data_type, offset=44) # 从文本或二进制文件中的数据构造一个数组
data.tofile(pcmfile)
f.close()
当我们读取pcm数据的时候,我们需要弄清楚语音每个采样点的位深是多少bit,一般来说是16bit,那么我们去pcm数据的时候就应该2个字节的去取,应该创建short的buf。
读取pcm数据
#include <stdio.h>
int main() {
FILE *pcmfile;
int frame_len = 480; // 帧长
short buf[frame_len]; // 每个采样点2字节
int read_len;
char pcmpath[]="../p225_001.pcm";
pcmfile = fopen(pcmpath, "rb");
if (pcmfile == NULL) {
printf("!Error: Can't open wavfile.\n");
return 1;
}
while (feof(pcmfile)==0){
read_len = fread(buf, sizeof(short), frame_len, pcmfile);
for (int i = 0; i < read_len; i++) {
printf("%d ", buf[i]);
}
}
fclose(pcmfile);
return 0;
}
六、Opus
Opus 是一个有损声音编码的格式,由 Xiph.Org 基金会开发,之后由 IETF 互联网工程任务组进行标准化,适用于网上低延迟的即时声音传输,标准格式定义于 RFC 6716 文件。Opus 格式是一个开放格式,使用上没有任何专利或限制。
Opus 集成了两种声音编码的技术:以语音编码为导向的 SILK 和低延迟的 CELT。Opus 可以无缝调节高低比特率。在编码器内部它在较低比特率时使用线性预测编码在高比特率时候使用变换编码(在高低比特率交界处也使用两者结合的编码方式)。Opus 具有非常低的算法延迟(默认为 22.5 ms),非常适合用于低延迟语音通话的编码,像是网上上的即时声音流、即时同步声音旁白等等,此外 Opus 也可以透过降低编码码率,达成更低的算法延迟,最低可以到 5 ms。在多个听觉盲测中,Opus 都比 MP3、AAC 等常见格式,有更低的延迟和更好的声音压缩率。
在 WebRTC 实现中,强制要求支持 Opus,也是其默认的音频编码格式。
特点:有损压缩;可动态调节比特率,音频带宽和帧大小;开放免费、没有专利限制。
七、MP3
MP3利用MPEG Audio Layer3 压缩方式进行压缩,所以简称为MP3,是一种有损压缩格式。 MPEG Audio Layer 3 压缩技术可以将音乐以1:10 甚至 1:12 的压缩率,能够在音质丢失很小的情况下把文件压缩到更小的程度。由于MP3体积小,音质高互联网上音乐几乎都是这种格式。但Mp3最高比特率320K,高频部分一刀切是他的缺点,对音质要求高的话还是建议wav格式。
正是因为 MP3 体积小,音质高的特点使得 MP3 格式几乎成为网上音乐的代名词。
特点:有损压缩,压缩率高,文件体积小,最高比特率 320K
八、WMA
WMA 的全称是 Windows Media Audio,是微软力开发的一种音频编码格式。一般情况下相同音质的 WMA 和 MP3 音频,前者文件体积较小,并且可以通过 DRM(Digital Rights Management)方案加入防止拷贝,或者加入限制播放时间和播放次数,甚至是播放机器的限制,可有力地防止盗版。
特点:支持 DRM 防盗版,相比 MP3 格式压缩率更高
九、AAC
AAC 全称 Advanced Audio Coding,是一种基于 MPEG-2 的有损数字音频压缩的专利音频编码标准,由 Fraunhofer IIS、杜比实验室、AT&T、Sony、Nokia 等公司共同开发。MPEG-4 标准 出台后,在原本的基础上加上了 PNS(Perceptual Noise Substitution)等技术,并提供了多种扩展工具。为了区别于传统的 MPEG-2 AAC 又称为 MPEG-4 AAC。其作为 MP3 的后继者而被设计出来,在相同的位元率之下,AAC 相较于 MP3 通常可以达到更好的声音质量。
AAC 提供了低至 8 kHz 高至 96 kHz 的多种采样率、更高的比特深度(8, 16, 24, 32 bit),并且支持 1 到 48 之间的任何声道数
特点:目前最好的有损格式之一,压缩率高
ARM格式全称Adaptive Multi-Rate 和 Adaptive Multi-Rate Wideband,主要用于移动设备的音频,压缩比比较大,但相对其他的压缩格式质量比较差,多用于人声,通话,是一种有损压缩格式。
Ogg全称应该是OGG Vobis(ogg Vorbis) 是一种新的音频压缩格式,类似于MP3等现有的音乐格式。相对于MP3压缩技术它是完全免费、开放和没有专利限制的,是一种有损压缩格式。
LAC即是Free Lossless Audio Codec的缩写,为无损音频压缩编码,由于不会丢失任何音频信息可以利用算法恢复原始编码,前景广阔。