准备做个非特定的声音识别程序,卡在录音和切割一小段时间,C#下基于DirectX的声音处理真的超级麻烦,后来找到了NAudio,非常好用,安利给大家,顺带把几个小坑稍微描述下,希望帮到类似需求的人。
项目下载:
NAudio官方地址:http://naudio.codeplex.com/
GitHub地址:https://github.com/naudio/NAudio
下载后是完整的工程文件,直接运行应该没问题,官方的demo都写成自定义控件的形式,界面写的不错,我也就懒得慢慢摘出来了,照猫画虎的去掉了demo里面选择控件的部分,直接显示在tabControl里,效果如下图。
private void MainForm_Load(object sender, EventArgs e)
{
var demos = ReflectionHelper.CreateAllInstancesOf().OrderBy(d => d.Name);
var plugin1 = (INAudioDemoPlugin)demos.Last();
var control1 = plugin1.CreatePanel();
control1.Dock = DockStyle.Fill;
panel1.Controls.Add(control1);
var plugin2 = (INAudioDemoPlugin)demos.First();
var control2 = plugin2.CreatePanel();
control2.Dock = DockStyle.Fill;
panel2.Controls.Add(control2);
}
demos是项目下所有自定控件的集合,我这里删的大部分,只留下了一个录音和一个播放,所以只有last和first。
至此,录音和播放功能完全实现了,而后我的工作是希望将声音切割,判断依据是dB(分贝),
就是上图中垂直的绿色条条和黄色图中的波形,达到某个值就切割出来,静音部分不要(做监听咯,懂的,没声音就pass掉)
void OnPostVolumeMeter(object sender, StreamVolumeEventArgs e)
{
// we know it is stereo
volumeMeter1.Amplitude = e.MaxSampleValues[0];
volumeMeter2.Amplitude = e.MaxSampleValues[1];
if (e.MaxSampleValues[0] > 0.01 && StartOREnd == false)
{
StartOREnd = true;
DateTime starttime = DateTime.ParseExact(this.labelCurrentTime.Text,"mm:ss:fff",null);
int StartPoint = starttime.Minute * 60 * 1000 + starttime.Second * 1000 + starttime.Millisecond;
this.richTextBox1.Text += "\r\n开始:" + StartPoint.ToString();
CutInfo.Add(StartPoint);
}
if (e.MaxSampleValues[0] < 0.01 && StartOREnd == true)
{
StartOREnd = false;
DateTime endtime = DateTime.ParseExact(this.labelCurrentTime.Text, "mm:ss:fff", null);
int EndPoint = endtime.Minute * 60 * 1000 + endtime.Second * 1000 + endtime.Millisecond;
this.richTextBox1.Text += "\r\n结束:" + EndPoint.ToString();
CutInfo.Add(EndPoint);
}
}
我定义了一个CutInfo的list,坦白说这里的e.MaxSampleValuesde的值代表什么意思我没搞明白,但是有声音数值就大,没声音数值就小,所以我这就当时dB来用了,按照阈值定义起点和终点,用于后期的切割。
切割分两部分,mp3和wav,其中mp3直接就能按照时间进行切割,wav得换算为字节长度稍微麻烦一点点。
public static void TrimMp3File(string inputPath, string outputPath, TimeSpan? begin, TimeSpan? end)
{
if (begin.HasValue && end.HasValue && begin > end)
throw new ArgumentOutOfRangeException("end", "end should be greater than begin");
using (var reader = new Mp3FileReader(inputPath))
using (var writer = File.Create(outputPath))
{
Mp3Frame frame;
while ((frame = reader.ReadNextFrame()) != null)
if (reader.CurrentTime >= begin || !begin.HasValue)
{
if (reader.CurrentTime <= end || !end.HasValue)
writer.Write(frame.RawData, 0, frame.RawData.Length);
else break;
}
}
}
public static void TrimWavFile(string inPath, string outPath, TimeSpan cutFromStart, TimeSpan cutFromEnd)
{
using (WaveFileReader reader = new WaveFileReader(inPath))
{
using (WaveFileWriter writer = new WaveFileWriter(outPath, reader.WaveFormat))
{
int bytesPerMillisecond = reader.WaveFormat.AverageBytesPerSecond/1000;
int startPos = (int)cutFromStart.TotalMilliseconds * bytesPerMillisecond;
startPos = startPos - startPos % reader.WaveFormat.BlockAlign;
int endPos = (int)cutFromEnd.TotalMilliseconds * bytesPerMillisecond;
endPos = endPos - endPos % reader.WaveFormat.BlockAlign;
TrimWavFile(reader, writer, startPos, endPos);
}
}
}
private static void TrimWavFile(WaveFileReader reader, WaveFileWriter writer, int startPos, int endPos)
{
reader.Position = startPos;
byte[] buffer = new byte[1024];
while (reader.Position < endPos)
{
int bytesRequired = (int)(endPos - reader.Position);
if (bytesRequired > 0)
{
int bytesToRead = Math.Min(bytesRequired, buffer.Length);
int bytesRead = reader.Read(buffer, 0, bytesToRead);
if (bytesRead > 0)
{
writer.WriteData(buffer, 0, bytesRead);
}
}
}
}
我每段测试音频都念了五个音,测试MP3和WAV都成功,效果如下图:
黄色波形图前5个波是一段,后面的是切割后单独放的5个。切割的目的是识别,下一篇写识别。
新开博客,目的为学习交流,本人QQ:273651820。