项目需要用到音频信号输出,学习一下基本的3.5mm耳机孔接线、信号形式,以及所用到WAV文件格式以及PCM音频编码方式。
1.3.5mm耳机孔
3.5mm耳机孔插座有2~7引脚不等的,最常用的4引脚包括:GND microphone L R
在B站找到个视频,用示波器看信号输出:
https://www.bilibili.com/video/BV1BE411i7iE?share_source=copy_web
手机接口输出信号,信号幅值随着输出音量变化,在10mV~300mV间。信号整体比较杂乱,最高频在40kHz左右。
3.5mm耳机接口输出信号为模拟信号,在手机内置模块中进行DAC转换以及放大。
2.PCM编码
PCM作用是将时域模拟信号转换成二进制编码流存储,其实就是采样-量化。人耳能听到的极限频率是20kHz,通常人声、乐器的频率不会超过10kHz。
见: 乐器及人声重要频率范围表 - 音 响 论 坛 -耳机俱乐部论坛
根据采样定理,采样频率高于信号最高频两倍才不会发生混叠。PCM编码音频CD的标准采样率是44.1kHz。
量化位数指的是编码时每次采样的值ADC的位数,一般为16bit。也可以进行多声道采样,1-2.
3.WAV文件格式
最常见WAV编码就是在PCM数据格式的前面加上44字节,分别用来描述PCM的采样率、声道数、数据格式等信息。
保存格式如上图,区块从上至下依次是:RIFF fmt data,前44字节存储文件ID SIZE byterate等信息。
具体的“头文件”信息:
ChunkID:'RIFF'标识
CHUNKSize: 是整个文件的长度减去ID和Size的长度
Format: Type是WAVE,后面需要两个子块:Format和Data
Subchunk1ID: 以'fmt '为标识
Subchunk1Size: 该区块-fmt-数据的长度(不包含ID和Size)
AudioFormat: Data区块存储的音频数据的格式,PCM格式值为1
NumChannels: 音频数据的声道数:1-单声道,2-双声道
SampleRate:音频采样率
ByteRate: 每秒数据字节数 = 采样率* 通道数 * 转换位数 / 8
BlockAlign: 每次采样字节数 = 通道数 * 转换位数 / 8
BitsPerSample:每个采样存储的bit数,8-16-32
所以,在读取WAV文件时,可以依次写出读取代码,再此以C#为例:
class WAVReader //wav 文件读取
{
private string Id; //文件标识
private double Size; //文件大小
private string Type; //文件类型
private string formatId;
private double formatSize;
private int formatTag;
private int num_Channels; //声道数目
private int SamplesPerSec; //采样率
private int AvgBytesPerSec; //每秒所需字节数
private int BlockAlign; //数据块对齐单位(每个采样需要的字节数)
private int BitsPerSample; //每个采样需要的bit数
private string additionalInfo; //附加信息
private string dataId;
private int dataSize;
public List<double> wavdata = new List<double>();
public void ReadWAVFile(string filePath) //读取波形文件并显示
{
if (filePath == "") return;
byte[] id = new byte[4];
byte[] size = new byte[4];
byte[] type = new byte[4];
byte[] formatid = new byte[4];
byte[] formatsize = new byte[4];
byte[] formattag = new byte[2];
byte[] numchannels = new byte[2];
byte[] samplespersec = new byte[4];
byte[] avgbytespersec = new byte[4];
byte[] blockalign = new byte[2];
byte[] bitspersample = new byte[2];
byte[] additionalinfo = new byte[2]; //可选
byte[] factid = new byte[4];
byte[] factsize = new byte[4];
byte[] factdata = new byte[4];
byte[] dataid = new byte[4];
byte[] datasize = new byte[4];
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
using (BinaryReader br = new BinaryReader(fs, Encoding.UTF8))
{
#region RIFF WAVE Chunk
br.Read(id, 0, 4);
br.Read(size, 0, 4);
br.Read(type, 0, 4);
Id = getString(id, 4);
long longsize = bytArray2Int(size);//十六进制转为十进制
Size = longsize * 1.0;
Type = getString(type, 4);
#endregion
#region Format Chunk
br.Read(formatid, 0, 4);
br.Read(formatsize, 0, 4);
br.Read(formattag, 0, 2);
br.Read(numchannels, 0, 2);
br.Read(samplespersec, 0, 4);
br.Read(avgbytespersec, 0, 4);
br.Read(blockalign, 0, 2);
br.Read(bitspersample, 0, 2);
if (getString(formatsize, 2) == "18")
{
br.Read(additionalinfo, 0, 2);
additionalInfo = getString(additionalinfo, 2); //附加信息
}
formatId = getString(formatid, 4);
formatSize = bytArray2Int(formatsize);
byte[] tmptag = composeByteArray(formattag);
formatTag = bytArray2Int(tmptag);
byte[] tmpchanels = composeByteArray(numchannels);
num_Channels = bytArray2Int(tmpchanels); //声道数目
SamplesPerSec = bytArray2Int(samplespersec); //采样率
AvgBytesPerSec = bytArray2Int(avgbytespersec); //每秒所需字节数
byte[] tmpblockalign = composeByteArray(blockalign);
BlockAlign = bytArray2Int(tmpblockalign); //数据块对齐单位(每个采样需要的字节数)
byte[] tmpbitspersample = composeByteArray(bitspersample);
BitsPerSample = bytArray2Int(tmpbitspersample); // 每个采样需要的bit数
#endregion
#region Data Chunk
byte[] d_flag = new byte[1];
while (true)
{
br.Read(d_flag, 0, 1);
if (getString(d_flag, 1) == "d")
{
break;
}
}
byte[] dt_id = new byte[4];
dt_id[0] = d_flag[0];
br.Read(dt_id, 1, 3);
dataId = getString(dt_id, 4);
br.Read(datasize, 0, 4);
dataSize = bytArray2Int(datasize);
List<string> testl = new List<string>();
if (BitsPerSample == 8)
{
for (int i = 0; i < dataSize; i++)
{
byte wavdt = br.ReadByte();
wavdata.Add(wavdt);
}
}
else if (BitsPerSample == 16)
{
for (int i = 0; i < dataSize / 2; i++)
{
short wavdt = br.ReadInt16();
wavdata.Add(wavdt);
}
}
#endregion
}
} //wavdata
}
此代码参考自:http://t.csdn.cn/hgMb3
至此,文件信息被存在了各个定义的变量中,data数据存在wavdata中。注意存储时采用线性存储,所以使用时需要注意根据不同的文件 量化位数、声道数 来进行输出等操作。
参考: