.NET オーディオ/MIDIライブラリ「NAudio」 を使って、C#言語で音楽ファイルをリアルタイムに再生するソフトを作ります。なお、開発環境はVisual Studio 2013 ExpressでWindows 7 Proです。
NAudioライブラリのインストールおよび設定
NAudioライブラリは「naudio/NAudio」から「NAudio-Release.zip」をダウンロードし、解凍したライブラリを、Visual Studio 2013 Expressの「参照の追加」によりプロジェクトに追加します。
リアルタイム再生ソフトの作成
リアルタイム再生ソフトは、デモコード「NAudio/NAudioDemo/Mp3StreamingDemo/」を参照して作成します。デモソフトではメニュー形式で様々なソフトをロードして実行しますが、今回はMp3StreamingDemoを直接動作させます。
StreamingPanel.cs
using NAudio.Wave;
public partial class StreamingPanel : Form
{
enum StreamingPlaybackState
{
Stopped,
Playing,
Buffering,
Paused
}
public StreamingPanel()
{
InitializeComponent();
volumeSlider1.VolumeChanged += OnVolumeSliderChanged;
Disposed += MP3StreamingPanel_Disposing;
}
void OnVolumeSliderChanged(object sender, EventArgs e)
{
if (volumeProvider != null)
{
volumeProvider.Volume = volumeSlider1.Volume;
}
}
private BufferedWaveProvider bufferedWaveProvider;
private IWavePlayer waveOut;
private volatile StreamingPlaybackState playbackState;
private volatile bool fullyDownloaded;
private HttpWebRequest webRequest;
private VolumeWaveProvider16 volumeProvider;
delegate void ShowErrorDelegate(string message);
private void ShowError(string message)
{
if (InvokeRequired)
{
BeginInvoke(new ShowErrorDelegate(ShowError), message);
}
else
{
MessageBox.Show(message);
}
}
private void StreamMp3(object state)
{
bufferedWaveProvider = new BufferedWaveProvider(new WaveFormat(44100, 16, 2));
bufferedWaveProvider.BufferDuration =
TimeSpan.FromSeconds(20); // allow us to get well ahead of ourselves
IMp3FrameDecompressor decompressor = null;
try
{
//外部入力のダミーとして適当な音声データを用意して使う
string wavFilePath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
"sample.wav"
);
//mp3を使うならこう。
string mp3FilePath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
"sample.mp3"
);
if (!(File.Exists(wavFilePath) || File.Exists(mp3FilePath)))
{
Console.WriteLine("Target sound files were not found. Wav file or MP3 file is needed for this program.");
Console.WriteLine("expected wav file: {wavFilePath}");
Console.WriteLine("expected mp3 file: {wavFilePath}");
Console.WriteLine("(note: ONE file is enough, two files is not needed)");
return;
}
//mp3しかない場合、先にwavへ変換を行う
if (!File.Exists(wavFilePath))
{
using (var mp3reader = new Mp3FileReader(mp3FilePath))
using (var pcmStream = WaveFormatConversionStream.CreatePcmStream(mp3reader))
{
WaveFileWriter.CreateWaveFile(wavFilePath, pcmStream);
}
}
byte[] data = File.ReadAllBytes(wavFilePath);
//若干効率が悪いがヘッダのバイト数を確実に割り出して削る
using (var r = new WaveFileReader(wavFilePath))
{
int headerLength = (int)(data.Length - r.Length);
data = data.Skip(headerLength).ToArray();
}
// int bufsize = 16000;
int bufsize = 20000;
for (int i = 0; i + bufsize < data.Length; i += bufsize)
{
bufferedWaveProvider.AddSamples(data, i, bufsize);
while (bufferedWaveProvider.BufferedDuration.Seconds > 5)
{
Thread.Sleep(100);
}
}
// was doing this in a finally block, but for some reason
// we are hanging on response stream .Dispose so never get there
// decompressor.Dispose();
}
finally
{
if (decompressor != null)
{
decompressor.Dispose();
}
}
}
private static IMp3FrameDecompressor CreateFrameDecompressor(Mp3Frame frame)
{
WaveFormat waveFormat = new Mp3WaveFormat(frame.SampleRate, frame.ChannelMode == ChannelMode.Mono ? 1 : 2,
frame.FrameLength, frame.BitRate);
return new AcmMp3FrameDecompressor(waveFormat);
}
private bool IsBufferNearlyFull
{
get
{
return bufferedWaveProvider != null &&
bufferedWaveProvider.BufferLength - bufferedWaveProvider.BufferedBytes
< bufferedWaveProvider.WaveFormat.AverageBytesPerSecond / 4;
}
}
private void buttonPlay_Click(object sender, EventArgs e)
{
if (playbackState == StreamingPlaybackState.Stopped)
{
playbackState = StreamingPlaybackState.Buffering;
bufferedWaveProvider = null;
ThreadPool.QueueUserWorkItem(StreamMp3, textBoxStreamingUrl.Text);
timer1.Enabled = true;
}
else if (playbackState == StreamingPlaybackState.Paused)
{
playbackState = StreamingPlaybackState.Buffering;
}
}
private void StopPlayback()
{
if (playbackState != StreamingPlaybackState.Stopped)
{
if (!fullyDownloaded)
{
}
playbackState = StreamingPlaybackState.Stopped;
if (waveOut != null)
{
waveOut.Stop();
waveOut.Dispose();
waveOut = null;
}
timer1.Enabled = false;
// n.b. streaming thread may not yet have exited
Thread.Sleep(500);
ShowBufferState(0);
}
}
private void ShowBufferState(double totalSeconds)
{
labelBuffered.Text = String.Format("{0:0.0}s", totalSeconds);
progressBarBuffer.Value = (int)(totalSeconds * 1000);
}
private void timer1_Tick(object sender, EventArgs e)
{
if (playbackState != StreamingPlaybackState.Stopped)
{
if (waveOut == null && bufferedWaveProvider != null)
{
Debug.WriteLine("Creating WaveOut Device");
waveOut = CreateWaveOut();
waveOut.PlaybackStopped += OnPlaybackStopped;
volumeProvider = new VolumeWaveProvider16(bufferedWaveProvider);
volumeProvider.Volume = volumeSlider1.Volume;
waveOut.Init(volumeProvider);
progressBarBuffer.Maximum = (int)bufferedWaveProvider.BufferDuration.TotalMilliseconds;
}
else if (bufferedWaveProvider != null)
{
var bufferedSeconds = bufferedWaveProvider.BufferedDuration.TotalSeconds;
ShowBufferState(bufferedSeconds);
// make it stutter less if we buffer up a decent amount before playing
if (bufferedSeconds < 0.5 && playbackState == StreamingPlaybackState.Playing && !fullyDownloaded)
{
Pause();
}
else if (bufferedSeconds > 4 && playbackState == StreamingPlaybackState.Buffering)
{
Play();
}
else if (fullyDownloaded && bufferedSeconds == 0)
{
Debug.WriteLine("Reached end of stream");
StopPlayback();
}
}
}
}
private void Play()
{
waveOut.Play();
Debug.WriteLine(String.Format("Started playing, waveOut.PlaybackState={0}", waveOut.PlaybackState));
playbackState = StreamingPlaybackState.Playing;
}
private void Pause()
{
playbackState = StreamingPlaybackState.Buffering;
waveOut.Pause();
Debug.WriteLine(String.Format("Paused to buffer, waveOut.PlaybackState={0}", waveOut.PlaybackState));
}
private IWavePlayer CreateWaveOut()
{
return new WaveOut();
}
private void MP3StreamingPanel_Disposing(object sender, EventArgs e)
{
StopPlayback();
}
private void buttonPause_Click(object sender, EventArgs e)
{
if (playbackState == StreamingPlaybackState.Playing || playbackState == StreamingPlaybackState.Buffering)
{
waveOut.Pause();
Debug.WriteLine(String.Format("User requested Pause, waveOut.PlaybackState={0}", waveOut.PlaybackState));
playbackState = StreamingPlaybackState.Paused;
}
}
private void buttonStop_Click(object sender, EventArgs e)
{
StopPlayback();
}
private void OnPlaybackStopped(object sender, StoppedEventArgs e)
{
Debug.WriteLine("Playback Stopped");
if (e.Exception != null)
{
MessageBox.Show(String.Format("Playback Error {0}", e.Exception.Message));
}
}
}
リアルタイム再生ソフトの実行
リアルタイム再生ソフトを実行すると、次の画面が表示されます。
