NAudio ライブラリでオーディオを再生する(2)


子どもの頃は、クリスマスが終わると近所のおじいちゃんちに親戚一同集まって餅つき。いくつものかまどに火が入り、次々と蒸籠でもち米を蒸しあげては、こねる、搗く、丸める、ちょっと食べるの繰り返し。親戚一同のぶんですから、高校生ぐらいになると次の日に響くほど手伝わされたもんです。今は昔。さあ昔話じゃなく音話、また少ししましょうか。

その1でNAudio ライブラリで wav ファイルを再生する簡単なプログラムを紹介しました。今回は、レベルメーター、音量ボリューム、再生時間表示を追加してみようと思います。

その1を読んでない方は、「NAudio ライブラリでオーディオを再生する(1)」をどうぞ。

さて、今回新しく使用するクラス SampleChannel を紹介しましょう。これは、読みこんだ WaveStrem を入力にしてオーディオサンプルを扱うストリームです。SampleChannel は様々なフォーマットのオーディオデータをNAudioの内部表現形式に変換して扱うものです。SampleChannel では、音量のコントロールができます。それから、メーターを扱う MeteringSampleProvider というのも使います。こちらは、一定回数ごとに音の最大値をお知らせしてくれるものです。図に書いてみると、こんな風にオーディオファイルから始まってさまざまなクラスが必要な処理をして、最後に出力デバイスに渡すというような感じでしょうか。まるで、電子ブロックで回路を作っている感じです。電子ブロックなんて、だれも知らんか?

NAudio-Stream

コードもお見せしましょう。肝心の音を再生するところだけです。エラー処理など何もしていないコードなので、すっきりした飲みごたえ、じゃなくて読みやすいでしょう。

private void buttonPlay_Click(object sender, EventArgs e)
{
	// インスタンスを初期化しておく(再生中ストリームの停止、前回の再生の後始末)
	ClearAll();

	// ファイルが存在するか調べて、拡張子を fileExt に
	string fileName = textBoxSoundFile.Text;
	FileInfo fi = new FileInfo(fileName);
	if (!fi.Exists) return;
	string fileExt = fi.Extension;

	// 拡張子に応じて FileReader を生成
	if (fileExt == ".wav") wavStream = new WaveFileReader(textBoxSoundFile.Text);
	else if (fileExt == ".mp3") wavStream = new Mp3FileReader(fileName);
	else return;

	// 出力デバイスの選択と初期化
	WaveOutDevice = CreateDevice(comboBoxAudioIf.SelectedIndex, 250);

	// WaveStrem を SampleChannel へ流し込む
	sampleChannel = new SampleChannel(wavStream);

	// Volume delegate 定義して、動的に音量を変える
	// setVolumeDelegateflot value) を呼び出すと音量が変わる
	setVolumeDelegate = (float vol) => sampleChannel.Volume = vol;

	// n 回ごとに最大値を調べてお知らせする MeteringSampleProvider に流し込む
	MeteringSampleProvider postVolumeMeter = new MeteringSampleProvider(sampleChannel);
	// メーター表示のイベントハンドラを割り当て、0.1秒に1回
	postVolumeMeter.SamplesPerNotification=postVolumeMeter.WaveFormat.SampleRate/10; // 0.1秒
	postVolumeMeter.StreamVolume += OnPostVolumeMeter;

	// MeteringSampleProvider を SampleProvider に変換してデバイスに流し込む
	WaveOutDevice.Init(new SampleToWaveProvider(postVolumeMeter as ISampleProvider));

	// 再生
	WaveOutDevice.Play();

}

この表記が分かりにくいですね。引数に float を持つ関数 setVolumeDelegate Actionデリゲート関数を宣言して、その実態を下のコードで書いています。

setVolumeDelegate = (float vol) => sampleChannel.Volume = vol;

べつのところで、こういう宣言をしています。

private Action setVolumeDelegate;

これは、SampleChannel のボリュームコントロールです。SampleChannel のインスタンスをローカル変数にすれば不要なコードですが、SampleChannelは WaveStream から作られたもので、次に渡してしまいます。そのため、ボリュームコントロールだけを関数として取り出しています。次の段の、postVolumeMeter.StreamVolume イベントハンドラも似たような境遇にあります。WaveStremが破棄されれば、運命を共にします。

なお、レベルメーターは 0.1 秒ごとに通知をもらって PictureBox に FillRectangle を書いているだけです。

private void pictureBoxMeterDraw(PictureBox pb, float flevel)
{
	int level = Convert.ToInt32((float)pictureBoxMeterL.Width * flevel);
	using (Graphics g = pb.CreateGraphics())
	{
		g.FillRectangle(Brushes.Coral, 0, 0, level, pb.Height);
		g.FillRectangle(SystemBrushes.Control, level + 1, 0, pb.Width - level, pb.Height);
		g.Dispose();
	}
}
private void OnPostVolumeMeter(object sender, StreamVolumeEventArgs e)
{
	float[] maxLevel = e.MaxSampleValues; // 0:L、1:R
	pictureBoxMeterDraw(pictureBoxMeterL, maxLevel[0]);
	pictureBoxMeterDraw(pictureBoxMeterR, maxLevel[1]);
}

実行中の画面はこんな感じです。mp3 ファイルの読み込みやドラッグ&ドロップも付けてあります。いかがでしたでしょうか?意外なほど簡単にボリュームやメーターが実現できました。

NAudio-volmeter

 

もちろん、このコードにはたくさんの問題があります。エラー処理していませんし、それ以外にも実行してみるとこんなことに気がつくでしょう。

  1. 再生状態の表示がない、再生が終わってもそのまま
  2. レベルメーターがちらちらする
  3. 音量もメーターもリニア表示
  4. 他のアプリが動くと音が途切れる
  5. などなど・・・・

あえて、できるだけエラー処理コードを書かずにシンプルに仕組みを理解してもらうためのコードです。(うそです、サボってるだけ)NAudio を使った音声再生プログラムできそうな気がしてきたでしょう?細かいところは、ソースを見てくださいね。

load_downloadNAudioWavePlayTest2 ダウンロード

参考

  • Introduction to Using NAudio (OpenSebJ by DSebJ)
    http://opensebj.blogspot.jp/2009/02/introduction-to-using-naudio.html
印刷
You can skip to the end and leave a response. Pinging is currently not allowed.
Subscribe to RSS Feed Twitter は Ume108 だよ