C#中使用NAudio做自定义播放器

目的:
利用NAudio中提供的WaveProvider编写出自定义的声音播放器,能够播放自己想要的声音数据。

一、自定义的WaveProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class SelfPlayer: WaveProvider16 //继承自WaveProvider16
{
public SelfPlayer(): base() { }
public SelfPlayer(int sampleRate): base(sampleRate, 1) { }

public int audioRate = 1000; //测试音频的频率
double Amplitude = 0.5; //测试音频的振幅

public short[] waveData = new short[2000]; //模拟的音频数据点数
public int playPos = 0; //播放点的位置
double t = 0; //起始时刻

public WaveOut selfWaveout = null;

public override int Read(short[] buffer, int offset, int sampleCount)
{//通过该函数给播放数据 buffer 提供数据

short dataTemp = 0;
for (int index = 0; index < sampleCount; index++)
{
t += 1/(double)base.WaveFormat.SampleRate;
dataTemp = (short)(short.MaxValue * Amplitude * Math.Sin(2 * Math.PI * audioRate * t));
buffer[offset + index] = dataTemp;
waveData[playPos] = dataTemp;
playPos++;
if (playPos == 2000)
playPos = 0;
}
return sampleCount;
}
}

在主函数中使用如下方式播放测试音频:

1
2
3
4
5
6
7
8
9
10
11
12
public partial class MainWindow : Window
{
SelfPlayer player=null;
public MainWindow()
{

InitializeComponent();
player = new SelfPlayer();
player.selfWaveout = new NAudio.Wave.WaveOut();
player.selfWaveout.Init(player);
player.selfWaveout.Play();
}
}

二、独立线程的播放

通常声音数据的获取和播放会使用独立的线程,这是如果采用下面的方式,则不能获得连续的声音:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public partial class MainWindow : Window
{
SelfPlayer player=null;
public MainWindow()
{

InitializeComponent();
player = new SelfPlayer();
Thread th = new Thread(threadPlay); //在新线程中播放声音
th.Start();
}

private void threadPlay()
{

player.selfWaveout = new NAudio.Wave.WaveOut();
player.selfWaveout.Init(player);
player.selfWaveout.Play();
}
}

只听到首次较短时间的声音,之后就没有声音播出了。暂时还不清楚原因,可能的原因之一是不能用Player自身的Waveout来播放自身,于是编写如下测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public partial class MainWindow : Window
{
SelfPlayer player=null;
WaveOut waveout = null;
public MainWindow()
{

InitializeComponent();
player = new SelfPlayer();
Thread th = new Thread(threadPlay);
th.Start();
}

private void threadPlay()
{

waveout = new NAudio.Wave.WaveOut();
waveout.Init(player);
waveout.Play();
}
}

结果发现依然只能播出短暂的声音。可能的原因:Waveout在Init之后需要一定的延迟,但经测试仍然不对。最后将初始化的语句放到线程之外,就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public partial class MainWindow : Window
{
SelfPlayer player=null;
WaveOut waveout = null;
public MainWindow()
{

InitializeComponent();
player = new SelfPlayer();
waveout = new NAudio.Wave.WaveOut();
waveout.Init(player);
Thread th = new Thread(threadPlay);
th.Start();
}

private void threadPlay()
{

waveout.Play();
}
}

类似地,下面的代码也是可行的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public partial class MainWindow : Window
{
SelfPlayer player=null;
public MainWindow()
{

InitializeComponent();
player = new SelfPlayer();
player.selfWaveout = new NAudio.Wave.WaveOut();
player.selfWaveout.Init(player);
Thread th = new Thread(threadPlay);
th.Start();
}

private void threadPlay()
{

player.selfWaveout.Play();
}
}
```
事实上,可以把许多播放的细节都隐藏到自定义的Player中:
``` csharp
class SelfPlayer: WaveProvider16
{
public SelfPlayer(): base() { }
public SelfPlayer(int sampleRate): base(sampleRate, 1) { }

public int audioRate = 1000;
double Amplitude = 0.5;

public short[] waveData = new short[2000];
public int playPos = 0;
double t = 0;

public WaveOut selfWaveout = null;
public void Init()//自初始化
{

this.selfWaveout = new WaveOut();
this.selfWaveout.Init(this);
}

public void ThPlay()//线程自播放
{

Thread thplay = new Thread(ThPlaying);
thplay.Start();
}

private void ThPlaying()
{

this.Play();
}

public void Play()//自播放
{

this.selfWaveout.Play();
}

public override int Read(short[] buffer, int offset, int sampleCount)
{

short dataTemp = 0;
for (int index = 0; index < sampleCount; index++)
{
t += 1/(double)base.WaveFormat.SampleRate;
dataTemp = (short)(short.MaxValue * Amplitude * Math.Sin(2 * Math.PI * audioRate * t));
buffer[offset + index] = dataTemp;
waveData[playPos] = dataTemp;
playPos++;
if (playPos == 2000)
playPos = 0;
}
return sampleCount;
}
}

这样在调用函数中就可以比较简洁:

1
2
3
4
5
6
7
8
9
10
11
public partial class MainWindow : Window
{
SelfPlayer player=null;
public MainWindow()
{

InitializeComponent();
player = new SelfPlayer();
player.Init();
player.ThPlay();
}
}

总结

使用NAudio时应注意,Waveout的初始化(Init)和播放(Play)不能在同一个进程中。

热评文章