背景介绍:随着电车的发展势头迅猛,国内车载音频也成为电车火热宣称的势头,要想深入了解车载音频,那首先还是得从最为普通的音频文件WAV开始。
我们都知道,计算机只能存储数字,声音确实靠不同频率的波组成,那么这些波的数据是如何存储在wav文件中的呢
首先我们用notepad++打开一首简单的wav文件 一堆看不懂的乱码
然后借助插件看起hex格式
这些一个个数字和字母就是该文件的全部内容了,当然,这么看还是很迷,那么我们就用专门的音频软件audition来解析这些数据,看看wav到底长什么样
用audition打开可以看到该wav有两个声道,每个声道对应着一段波形图
我们放大了看,每个波箱图上有非常多的小点,这些点就被称为采样点,只要采样点足够密,那么将这些点按先后顺序连起来,那就成了波形数据
当然,实际的wav文件的内容远不止这些,要像真正读懂wav还要接着往下看
我们先来百度一下有关wav的词条
词条上显示有三个关键词:采样频率,采样位数,声道数
采样频率:简单理解就是一秒钟有多少个小点
采样位数:简单理解就是每个小点在计算机上由多少个 0/1 来记录
声道数:就是这个这个wav有多少个声道 (除了我们大多熟悉的立体音,还有5.1,7.1.4,等其他各种各样的声道数的音源,当我们了解了wav的格式后,想要多少个声道就能有多少个声道)
接下来,我们应该知道的是 采样频率,采样位数,声道数 这些信息我们要如何知道,到底又怎么用,那我们就要去在百度再搜一搜wav文件头
知乎上说的很明白,以下就是文件头的内容
那么知道了这些,作为一个程序员就可以开始展现我们自己的专业啦
先建一个头文件的结构体
cpp
struct WavHeader {
char chunkId[4]; //"RIFF"
uint32_t chunkSize; //totalsize - 8
char format[4]; //"WAVE"
char subchunk1Id[4]; //"fmt"
uint32_t subchunk1Size; //16:Normal; 18:Non_PCM; 40:Extensible;
uint16_t audioFormat; //wav 格式 1;int型 3:float型 65534:未知
uint16_t numChannels; //声道数
uint32_t sampleRate; //采样率
uint32_t byteRate; //比特率:采样率 * 采样位宽
uint16_t blockAlign; //采样深度:
uint16_t bitsPerSample; //采样位宽:采样深度 * 8
char subchunk2Id[4]; //"data"
uint32_t subchunk2Size; //datasize
};
通过IO读取文件的数据可得到如下内容
读出了头文件的信息后,剩下的便是读取其所有数据
根据头文件信息我们知道该文件有两声道,采样率是48000,采样位数是16
那接下来我们每16 位也就是每两个字节读一次数据
数据量太大,我们就截取看一部分
这里有正有负,
对应上这里纵坐标为dB 值,那么我把把这些数据转化一下,算出每个数据对应的dB值
cpp
memcpy(&WavDate16, buffer.data() + i * headera.numChannels * perdatasize + j * perdatasize, perdatasize);
printf(" %10d , %.2f db ,", WavDate16, 20 * log10f(abs((float)WavDate16) / 32768));
得到以下结果
接下来我们就还可以完全用代码把所有wav的数据读出来了
以下是优化好的c++代码
cpp
#include <iostream>
#include <fstream>
#include <vector>
#include <cstring>
#include <cmath>
using namespace std;
struct WavHeader {
char chunkId[4]; //"RIFF"
uint32_t chunkSize; //totalsize - 8
char format[4]; //"WAVE"
char subchunk1Id[4]; //"fmt"
uint32_t subchunk1Size; //16:Normal; 18:Non_PCM; 40:Extensible;
};
struct type40_header {
uint16_t audioFormat; //wav 格式 1;int型 3:float型 65534:未知
uint16_t numChannels; //声道数
uint32_t sampleRate; //采样率
uint32_t byteRate; //比特率:采样率 * 采样位宽
uint16_t blockAlign; //采样深度:
uint16_t bitsPerSample; //采样位宽:采样深度 * 8
uint16_t cbSize;
uint16_t wValidBitsPerSample;
uint32_t dwChannelMask;
char SubFormat[4];
char ckID[4];
uint32_t cksize;
uint32_t dwSampleLength;
};
struct data_header {
char subchunk2Id[4]; //"data"
uint32_t subchunk2Size; //datasize
};
//查看头部数据
int header_check(const char* filename)
{
WavHeader header = {};
type40_header headera = {};
data_header headerb = {};
ifstream inputFile(filename, ios::binary);
if (!inputFile.is_open()) {
cerr << "无法打开文件" << endl;
return -1;
}
inputFile.read(reinterpret_cast<char*>(&header), sizeof(header));
inputFile.read(reinterpret_cast<char*>(&headera), header.subchunk1Size);
inputFile.read(reinterpret_cast<char*>(&headerb), sizeof(headerb));
//printf("\ntotalsize : \t%u \n", header.chunkSize + 8);
printf("chunkId : ,%c%c%c%c\n", header.chunkId[0], header.chunkId[1], header.chunkId[2], header.chunkId[3]);
cout << "chunkSize : ," << header.chunkSize << endl;
printf("format : ,%c%c%c%c\n", header.format[0], header.format[1], header.format[2], header.format[3]);
printf("subchunk1Id : ,%c%c%c%c\n", header.subchunk1Id[0], header.subchunk1Id[1], header.subchunk1Id[2], header.subchunk1Id[3]);
cout << "subchunk1Size : ," << header.subchunk1Size << endl;
cout << "audioFormat : ," << headera.audioFormat << endl;
cout << "numChannels : ," << headera.numChannels << endl;
cout << "sampleRate : ," << headera.sampleRate << endl;
cout << "byteRate : ," << headera.byteRate << endl;
cout << "blockAlign : ," << headera.blockAlign << endl;
cout << "bitsPerSample : ," << headera.bitsPerSample << endl;
printf("subchunk2Id : ,%c%c%c%c\n", headerb.subchunk2Id[0], headerb.subchunk2Id[1], headerb.subchunk2Id[2], headerb.subchunk2Id[3]);
cout << "subchunk2Size : ," << headerb.subchunk2Size << endl;
inputFile.close();
return 0;
}
int data_check(const char* filename)
{
WavHeader header = {};
type40_header headera = {};
data_header headerb = {};
ifstream inputFile(filename, ios::binary);
if (!inputFile.is_open()) {
cerr << "无法打开文件" << endl;
return -1;
}
inputFile.read(reinterpret_cast<char*>(&header), sizeof(header));
inputFile.read(reinterpret_cast<char*>(&headera), header.subchunk1Size);
inputFile.read(reinterpret_cast<char*>(&headerb), sizeof(headerb));
const size_t dataSize = headerb.subchunk2Size;
vector<char> buffer(dataSize);
inputFile.read(buffer.data(), dataSize);
uint16_t perdatasize = headera.bitsPerSample / 8;
float wavdate;
int32_t WavDate = 0;
int16_t WavDate16 = 0;
printf(",");
for (int j = 0; j < headera.numChannels; j++) {
printf("channel[%d] , ,", j + 1);
}
printf("\n");
printf("data,");
for (int j = 0; j < headera.numChannels; j++) {
printf("Frequency , Response ,");
}
printf("\n");
for (size_t i = 0; i < dataSize / perdatasize/ headera.numChannels; i++) {
//for (size_t i = 0; i < 100; i++) {
printf("data[%8d] : ,",(unsigned int)i);
for (size_t j = 0; j < headera.numChannels; j++) {
if (16 == headera.bitsPerSample)
{
memcpy(&WavDate16, buffer.data() + i * headera.numChannels * perdatasize + j * perdatasize, perdatasize);
printf(" %10d , %.2f db ,", WavDate16, 20 * log10f(abs((float)WavDate16) / 32768));
}
else if (24 == headera.bitsPerSample)
{
memcpy(&WavDate, buffer.data() + i * headera.numChannels * perdatasize + j * perdatasize, perdatasize);
printf(" %10d , %.2f db ,", (WavDate << 8) / 256, 20 * log10f(abs((float)(WavDate << 8)) / 256 / 8388608));
}
else if (32 == headera.bitsPerSample)
{
if (1 != headera.audioFormat) {
memcpy(&wavdate, buffer.data() + i * headera.numChannels * perdatasize + j * perdatasize, perdatasize);
printf(" %.6f , %.2f db ,", wavdate, 20 * log10f(abs(wavdate)));
}
else {
memcpy(&WavDate, buffer.data() + i * headera.numChannels * perdatasize + j * perdatasize, perdatasize);
printf(" %16d , %.2f db ,", WavDate, 20 * log10f(abs((float)WavDate) / 2147483648));
}
}
}
cout << endl;
}
buffer.clear();
inputFile.close();
return 0;
}
int main(int argc, const char* argv[]) {
int wavnum = argc - 1;
if (1 == wavnum) {
header_check(argv[1]);
printf("\n");
data_check(argv[1]);
}
return 0;
}