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


すこしおなかが減りました。なんかたべたいな。

前回の NAudio ライブラリでオーディオを再生する(2)では、大きな問題として次のような課題がありました。そこで、そのいくつかを修正していこうと思います。

  • 再生状態の表示がない、再生が終わってもそのまま
  • レベルメーターがちらちらする
  • 音量もメーターもリニア表示
  • 他のアプリが動くと音が途切れる
  • 音量を調整するとブツブツになる

音が止まってはオーディオじゃないよ

一番大きな問題は、やはり音が止まってブツブツになってしまうことです。NAudio当然ながらUIスレッドとは別のスレッドで音を再生しているはずですが、どこかで UIスレッドが邪魔をしている可能性があります。というのも、他の大きなウィンドウを動かしたり、プレイヤーのウィンドウを最小化したりとUIスレッドに負荷をかけると音が止まるからです。

では、いったいどこでしょうか?探すところは、NAudio のスレッドと通信しているところのはず。それは、音量を調整する setVolumeDelegate か、MeteringSampleProvider postVolumeMeter で OnPostVolumeMeter をコールバックしてもらってレベルメーターを書いているところかどちらかに絞り込めます。

setVolumeDelegate はただ値を渡しているだけです。そこで、OnPostVolumeMeter を見てみると、こんなコードでした。

private void OnPostVolumeMeter(object sender, StreamVolumeEventArgs e)
{
	float[] maxLevel = e.MaxSampleValues;
	pictureBoxMeterDraw(pictureBoxMeterL, maxLevel[0]);
	pictureBoxMeterDraw(pictureBoxMeterR, maxLevel[1]);
}
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();
	}
}

別の実験で試したように、FillRectangle はけっこう時間がかかるのでした。そこで、OnPostVolumeMeter の中では値を保存するだけにして、その値をUIスレッドのタイマーで適当な間隔で pictureBoxMeterDraw で書くようにしましょう。大事なのは、次のことです。

NAudio のスレッドからコールバックされるメソッドは軽くしろ!

maxLevel はフォームのメンバ変数として定義しておきます。

float[] maxLevel = new float[2]{0,0};

OnPostVolumeMeter では、その値を保存するだけにします。

private void OnPostVolumeMeter(object sender, StreamVolumeEventArgs e)
{
	maxLevel[0] = e.MaxSampleValues[0];
	maxLevel[1] = e.MaxSampleValues[1];
}

デシベル表示に対応する

どうせですから直すついでに dB に変換して表示するように変えてしまいましょう。レベルメーターは、-60dBから0dBまでということにして、値を 20*log(level) で計算して表示します。pictureBoxMeterDraw はこんな風になり、タイマーから一定時間ごとに呼ばれます。

private void pictureBoxMeterDraw(PictureBox pb, float flevel)
{
	int level_width = 0;
	double dbLevel = 20 * Math.Log10(flevel);
	if (dbLevel > -60.0)
	{
		// -60~0
		level_width = Convert.ToInt32(Math.Floor((60 + dbLevel) / 60 * pb.Width));
	}
	using (Graphics g = pb.CreateGraphics())
	{
		g.FillRectangle(Brushes.Coral, 0, 0, level_width, pb.Height);
		g.FillRectangle(SystemBrushes.Control, level_width + 1, 0, pb.Width - level_width, pb.Height);
		string s = String.Format("{0:f1}", dbLevel);
		g.DrawString(s, this.Font, SystemBrushes.ControlText,0, 0);
		g.Dispose();
	}
}

本来のコードなら、前回表示状態を覚えておいて、時間のかかる FillRectangle を実行するのをできるだけ控えておくほうがいいかもしれませんね。でも、このあたりは別にやっているレベルメーターコントロールを作る作業が進んでくれば解決しますから、ここまでにしておきましょう。

ボリューム調整も dB でできるようにします。trackBarVolume は最小値を-60、最大値を0 に変えました。dBをテキストでも表示するために、labelVoldB というラベルを使いました。dBの計算は、

dB=20\log(level)

です。ただし、level は 0~1 の範囲です。

private void trackBarVolume_ValueChanged(object sender, EventArgs e)
{
	if (WaveOutDevice != null && wavStream != null)
	{
		TrackBar tbar = sender as TrackBar;
		float db_volume = 0;
		if (tbar.Value > -60)
		{
			db_volume = (float)Math.Pow(10.0, (double)tbar.Value / 20);
		}
		labelVoldB.Text = tbar.Value.ToString() + "dB";
		setVolumeDelegate(db_volume);
	}
}

さらに、dB目盛が欲しいので、 dBLabel ユーザコントロールを作りました。dBの目盛が出るだけのコントロールです。-60dBから0dBまで表示します。

wasapiOutを使う

それから、waveOutwasapiOut に変えてありますが、コードはさほど変わりません。CreateDevice を少し変えました。デバイスの選択が、id 番号ではなく、MMDevice になっていますのでコンボボックス回リも変えていますが、これは NAudio の NAudioDemo プロジェクトにある WasapiOutSettingsPanel.cs から借りてきたものです。

private IWavePlayer CreateDevice(int id)
{
	int latency = (int)numericUpDownLatency.Value* 100;
	WasapiOut outputDevice = new WasapiOut((MMDevice)comboBoxAudioIf.SelectedValue,AudioClientShareMode.Shared,false,latency);
	return outputDevice as IWavePlayer;
}

他にも細かいところを直しましたが、今回はコードをつけますのでそれをご覧ください。

うまく動くか確認ーちゃんと動くよ

NAudio-3png

実際に動かしてみます。音は途切れなくなりました。1KHzの正弦波のファイルで試してみると、レベルメーターの表示も、音量調整も dB で動いています。

  • 音量もメーターもリニア表示
  • 他のアプリが動くと音が途切れる

このあたりは解決しました。音が途切れなくなったのは大事な発見です。

あとは、

  1. レベルメーターがちらちらする
  2. 音量を調整するとブツブツになる
  3. 再生状態の表示がない、再生が終わってもそのまま

このあたりです。2つ目の音量を調整するときにプツプツ音が出るのは致命的ですね。あとの2つは、それほどでもありません。音量をスムーズに変えるには、ボリューム値を一気に変えるのではなく、徐々に動かす仕組みが必要ですね。でも、だいぶアプリケーションらしくなってきました。次は、Mixer を実装して複数の音を同時再生しようと思います。今回から、WASAPIの共有モードを使っているので、複数走らせればいいだけではありますが、全体が見通せる本物のオーディオミキサーっぽいインタフェースにしたいなんて思っています。そうそう、入力をミキサーを通して再生するしくみもできれば、ライブ配信にももっと便利になりそうですし。

餅を2つ食いました。何の報告だよ!

 

load_downloadNAudioWavePlayTest3 ソースをダウンロード

参考

  • Sound Code
    http://mark-dot-net.blogspot.jp/2011/05/naudio-audio-output-devices.html
  • NAudio Souce Code
    http://naudio.codeplex.com/SourceControl/changeset/view/8a937910ee9c

You can skip to the end and leave a response. Pinging is currently not allowed.
Subscribe to RSS Feed Twitter は Ume108 だよ