C#实现WAV音频单声道提取.docx
- 文档编号:6217971
- 上传时间:2023-01-04
- 格式:DOCX
- 页数:11
- 大小:240.78KB
C#实现WAV音频单声道提取.docx
《C#实现WAV音频单声道提取.docx》由会员分享,可在线阅读,更多相关《C#实现WAV音频单声道提取.docx(11页珍藏版)》请在冰豆网上搜索。
C#实现WAV音频单声道提取
C#实现WAV音频单声道提取
作者:
JosephPan(转载请注明出处)
喜欢买碟或者卡拉ok的朋友一定不会对声道这个术语陌生。
通常我们在音像店买回来的VCD或者DVD都是双声道的形式,唱片商在录制唱片时往往提供了两个或多个声道,以保存不同的音频内容,以形成立体声效。
左声道保存的大多为一些背景声效,如卡拉OK的消音伴唱。
而右声道保存的往往是较为主要的声音,比如主唱的歌声。
利用多声道技术,听众可以清晰地分辨出各种乐器来自的方向,从而使音乐更富想象力,更加接近于临场感受。
有时候我们只需要音频里的单声道内容,比如喜欢用电脑录制卡拉ok的朋友就经常为了找歌曲的伴唱而流连于各大伴奏网站。
现在的网络翻唱非常流行,很多网络歌手就是先搜索喜欢的歌曲的伴奏,然后利用AdobeAudition(前身就是大名鼎鼎的CoolEdit)录制自己的演唱,然后加一些简单的降噪和压限处理,最后mix到伴奏的音轨里面。
尽管利用Audition也可以完成单声道的提取工作,但是操作起来比较复杂。
其实对WAV的单声道提取并不困难。
关键在于对WAV文件格式的理解。
一、WAV的文件头
WAV为微软公司(Microsoft)开发的一种声音文件格式,它符合RIFF(ResourceInterchangeFileFormat)文件规范。
所有的WAV都有一个文件头,这个文件头包含了音频流的编码参数。
偏移地址
字节数
类型
内容
00H~03H
4
字符
资源交换文件标志(RIFF)
04H~07H
4
长整数
从下个地址开始到文件尾的总字节数
08H~0BH
4
字符
WAV文件标志(WAVE)
0CH~0FH
4
字符
波形格式标志(FMT)
10H~13H
4
整数
过滤字节(一般为00000010H)
14H~15H
2
整数
格式种类(值为1,表示数据PCMμ律编码的数据)
16H~17H
2
整数
通道数,单声道为1,双声道为2
18H~1BH
4
长整数
采样频率
1CH~1FH
4
长整数
波形数据传输速率(每秒平均字节数)
20H~21H
2
整数
数据的调整数(按字节计算)
22H~23H
2
整数
样本数据位数
表1 WAV的文件头
由表1我们可以得到以下几个重要的信息:
116H~17H处记录通道数,当值为1时,表示文件为单声道;当值为2时,表示文件为双声道。
218H~1BH处记录采样频率。
它的取值与声卡的支持情况有关。
常见的有8000、11025、22050、44100、48000、96000等。
其中,44100是大多数歌曲文件采用的标准采样频率。
322H~23H处记录样本数据位数。
即每一个采样的长度。
常见的有8位和16位。
这里还包含了另外一个信息:
若样本的数据位数为n,对于双声道文件,则低n/2位用于存放左声道;高n/2位用于存放右声道。
根据这三点信息,我们可以自己编程实现单声道的提取。
下面我们就来一步步动手实现。
由于程序涉及的只是简单的二进制文件读写操作,因此这里只举C#作简单示例,其他语言的处理与之大同小异。
二、文件读取类的编写
为了方便以后对WAV文件的研究,我们可以先单独写一个WAV文件读取类,专门获取文件头的每一块信息:
WaveAccess1/*WaveAccess
2*提供wav文件头的访问
3*和文件写入相关操作
4*/
5
6classWaveAccess
7{
8
9privatebyte[]riff;//4
10privatebyte[]riffSize;//4
11privatebyte[]waveID;//4
12privatebyte[]fmtID;//4
13privatebyte[]notDefinition;//4
14privatebyte[]waveType;//2
15privatebyte[]channel;//2
16privatebyte[]sample;//4
17privatebyte[]send;//4
18privatebyte[]blockAjust;//2
19privatebyte[]bitNum;//2
20privatebyte[]unknown;//2
21privatebyte[]dataID;//4
22privatebyte[]dataLength;//4
23
24short[]data;
25privatestringlongFileName;
26
27publicstringLongFileName
28{
29get{returnlongFileName;}
30}
31
32publicstringShortFileName
33{
34get
35{
36intpos=LongFileName.LastIndexOf("\\");
37returnLongFileName.Substring(pos+1);
38}
39}
40
41publicshort[]Data
42{
43get{returndata;}
44set{data=value;}
45}
46
47publicstringRiff
48{
49get{returnEncoding.Default.GetString(riff);}
50set{riff=Encoding.Default.GetBytes(value);}
51}
52
53publicuintRiffSize
54{
55get{returnBitConverter.ToUInt32(riffSize,0);}
56set{riffSize=BitConverter.GetBytes(value);}
57}
58
59
60publicstringWaveID
61{
62get{returnEncoding.Default.GetString(waveID);}
63set{waveID=Encoding.Default.GetBytes(value);}
64}
65
66
67publicstringFmtID
68{
69get{returnEncoding.Default.GetString(fmtID);}
70set{fmtID=Encoding.Default.GetBytes(value);}
71}
72
73
74publicintNotDefinition
75{
76get{returnBitConverter.ToInt32(notDefinition,0);}
77set{notDefinition=BitConverter.GetBytes(value);}
78}
79
80
81publicshortWaveType
82{
83get{returnBitConverter.ToInt16(waveType,0);}
84set{waveType=BitConverter.GetBytes(value);}
85}
86
87
88publicushortChannel
89{
90get{returnBitConverter.ToUInt16(channel,0);}
91set{channel=BitConverter.GetBytes(value);}
92}
93
94
95publicuintSample
96{
97get{returnBitConverter.ToUInt32(sample,0);}
98set{sample=BitConverter.GetBytes(value);}
99}
100
101
102publicuintSend
103{
104get{returnBitConverter.ToUInt32(send,0);}
105set{send=BitConverter.GetBytes(value);}
106}
107
108
109publicushortBlockAjust
110{
111get{returnBitConverter.ToUInt16(blockAjust,0);;}
112set{blockAjust=BitConverter.GetBytes(value);}
113}
114
115
116publicushortBitNum
117{
118get{returnBitConverter.ToUInt16(bitNum,0);}
119set{bitNum=BitConverter.GetBytes(value);}
120}
121
122
123publicushortUnknown
124{
125get
126{
127if(unknown==null)
128{
129return1;
130}
131else
132returnBitConverter.ToUInt16(unknown,0);
133}
134
135set{unknown=BitConverter.GetBytes(value);}
136}
137
138
139publicstringDataID
140{
141get{returnEncoding.Default.GetString(dataID);}
142set{dataID=Encoding.Default.GetBytes(value);}
143}
144
145publicuintDataLength
146{
147get{returnBitConverter.ToUInt32(dataLength,0);}
148set{dataLength=BitConverter.GetBytes(value);}
149}
150
151
152publicWaveAccess(){}
153
154publicWaveAccess(stringfilepath)
155{
156try
157{
158riff=newbyte[4];
159riffSize=newbyte[4];
160waveID=newbyte[4];
161fmtID=newbyte[4];
162notDefinition=newbyte[4];
163waveType=newbyte[2];
164channel=newbyte[2];
165sample=newbyte[4];
166send=newbyte[4];
167blockAjust=newbyte[2];
168bitNum=newbyte[2];
169unknown=newbyte[2];
170dataID=newbyte[4];//52
171dataLength=newbyte[4];//56个字节
172
173longFileName=filepath;
174
175
176FileStreamfs=newFileStream(filepath,FileMode.Open);
177BinaryReaderbread=newBinaryReader(fs);
178riff=bread.ReadBytes(4);
179riffSize=bread.ReadBytes(4);
180waveID=bread.ReadBytes(4);
181fmtID=bread.ReadBytes(4);
182notDefinition=bread.ReadBytes(4);
183waveType=bread.ReadBytes
(2);
184channel=bread.ReadBytes
(2);
185sample=bread.ReadBytes(4);
186send=bread.ReadBytes(4);
187blockAjust=bread.ReadBytes
(2);
188bitNum=bread.ReadBytes
(2);
189if(BitConverter.ToUInt32(notDefinition,0)==18)
190{
191unknown=bread.ReadBytes
(2);
192}
193dataID=bread.ReadBytes(4);
194dataLength=bread.ReadBytes(4);
195uintlength=DataLength/2;
196data=newshort[length];
197for(inti=0;i 198{ 199data[i]=bread.ReadInt16();//读入2字节有符号整数 200} 201fs.Close(); 202bread.Close(); 203} 204catch(System.Exceptionex) 205{ 206Console.Write(ex.Message); 207} 208} 209 210publicshort[]GetData(uintbegin,uintend) 211{ 212if((end-begin)>=Data.Length) 213returnData; 214else 215{ 216uinttemp=end-begin+1; 217short[]dataTemp=newshort[temp]; 218uintj=begin; 219for(inti=0;i 220{ 221dataTemp[i]=Data[j]; 222j++; 223} 224returndataTemp; 225} 226 227} 228 229/// 230///生成wav文件到系统 231/// 232/// 233/// 234publicboolbulidWave(stringfileName) 235{ 236try 237{ 238FileInfofi=newFileInfo(fileName); 239if(fi.Exists) 240fi.Delete(); 241FileStreamfs=newFileStream(fileName,FileMode.CreateNew); 242BinaryWriterbwriter=newBinaryWriter(fs);//二进制写入 243bwriter.Seek(0,SeekOrigin.Begin); 244bwriter.Write(Encoding.Default.GetBytes(this.Riff));//不可以直接写入string类型的字符串,字符串会有串结束符,比原来的bytes多一个字节 245bwriter.Write(this.RiffSize); 246bwriter.Write(Encoding.Default.GetBytes(this.WaveID)); 247bwriter.Write(Encoding.Default.GetBytes(this.FmtID)); 248bwriter.Write(this.NotDefinition); 249bwriter.Write(this.WaveType); 250bwriter.Write(this.Channel); 251bwriter.Write(this.Sample); 252bwriter.Write(this.Send); 253bwriter.Write(this.BlockAjust); 254bwriter.Write(this.BitNum); 255if(this.Unknown! =0) 256bwriter.Write(this.Unknown); 257bwriter.Write(Encoding.Default.GetBytes(this.DataID)); 258bwriter.Write(this.DataLength); 259 260for(inti=0;i 261{ 262bwriter.Write(this.Data[i]); 263} 264 265 266bwriter.Flush(); 267fs.Close(); 268bwriter.Close(); 269fi=null; 270returntrue; 271} 272catch(System.Exceptionex) 273{ 274Console.Write(ex.Message); 275returnfalse; 276} 277} 278 279} 三、单声道提取 前面提到,若样本的数据位数为n,则对单声道的提取,其实就是提取出n/2的数据。 对于任意一位数据,其在新的数据队列中的索引k’与其在源数据队列中的索引k满足如下的映射关系: k=2*k’–k’mod(n/2)+n/2 但这里有个问题,加入只是将高或者低n/2的数据提取出来合为一个新的文件,则样本的数据位数和文件长度都需要修改为原先的一半,如果没有进行修改,播放速度将变为原来的两倍。 另外一种解决思路是将我们需要的那n/2的数据提取出来,然后覆盖另外n/2的数据。 这样,头文件就不需要进行修改,因为没有任何属性发生了改变,只是文件的内容发生了变化。 根据上面的思想,对整个WAV文件作一次遍历,每次读入n位数据,如果是要提取左声道,则取出低n/2位数,覆盖高n/2位数;如果是要提取右声道,则取出高n/2位数,覆盖低n/2位数。 据此编写singleChannelExtract函数如下: singleChannelExtract1/// 2///singleChannelExtract 3///用于提取单声道 4/// 5/// 6/// 7/// 提取左声道;1: 提取右声道 8privatevoidsingleChannelExtract(stringfrom,stringto,intflag) 9{ 10//判断是否为双声道 11WaveAccesswa=newWaveAccess(from); 12 13if(wa.Channel==2) 14{ 15//获取位数 16ushortbitNum=wa.BitNum; 17intpos1=0,pos2=0; 18intlth=(int)(wa.Data.Length*2/bitNum); 19 20for(inti=0;i 21{ 22for(intj=0;j<(int)(bitNum);j++) 23{ 24for(intk=0;k 25{ 26//判断要提取的声道类型 27switch(flag) 28{ 29case0: //提取左声道 30wa.Data[(int)(k+bitNum/2)]=wa.Data[k]; 31break; 32case1: //提取右声道 33wa.Data[k]=wa.Data[(int)(k+bitNum/2)]; 34break; 35default: 36MessageBox.Show("Sorry.Onlyleftorrightchannelissupportedfornow! "); 37return; 38} 39} 40} 41} 42 43/***写入文件***/ 44wa.bulidWave(to); 45wa=null; 46MessageBox.Show("Done! "); 47return; 48} 49else 50{ 51MessageBox.Show("Thesongalreadyhassinglechannel! "); 52return; 53} 四、实验结果 根据上面的分析,完成单声道提取器如图1所示。 图1 单声道提取器 运行该程序后,点击“open...”按钮,打开文件打开对话框,选中要进行单声道提取的文件。 完成后,FormatInfo栏将显示该wav文件的信息。 之后,单击“Extract! ”按钮,弹出一个文件保存对话框,选择要保存的路径点确定,开始提取单声道,完成后将提示“Done! ”。 图2和图3分别给出了对一段音频进行右声道提取前和提取后的结果。 图2 提取右声道前 图3 提取右声道后 注意图3中原来的左声道内容已经被右声道的内容覆盖,因此此时虽然还是双声道,但两个声道的内容是一样的,因此在使用
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C# 实现 WAV 音频 单声道 提取