.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)); } } }
リアルタイム再生ソフトの実行
リアルタイム再生ソフトを実行すると、次の画面が表示されます。