音声データを再生したいが、waveファイルやmp3ファイルを別ファイルで配布しないでリソースで同梱して扱いたい。というのを叶える方法を調べてみた。
- 扱う音声データはwavとmp3(NAudioが扱えるものなら何でも良いはず)
- wavはPlaySoundを使用し、mp3はNAudioを使用した
Visual Studio 2019を使っているが、昔のバージョンでもいけるはず。またusing変数宣言はC# 8.0の記法なので適宜置き換えて読んでほしい。
また、エラー処理や例外処理は端折っているので、適宜補うべし。
ソリューション構成
C#のプロジェクトを作成し、あらかじめ音声データファイルをリソースに登録しておく。
wavの場合、リソースの種別は「オーディオ」となる。
mp3の場合、リソースの種別は「ファイル」となる。
これらは勝手に設定されて、変更できなさそう。(調査不足)
ソリューション
|- SampleProj ※C#プロジェクト名
|- Properties
|- aiueo.wav ※音声データ1
|- dindon.mp3 ※音声データ2
|- Resources.resx
|- Resources.Designer.cs
...
登録したリソースは、次のように自動的に実装が作られる。リソースの名前や定義に、拡張子は含まれないようだ。
これらのリソース定義を使用しても良いし、自分でResourceManager
を使って取得しても良い。リソース定義を使った場合は、wav(オーディオ)はstream
で、mp3(ファイル)はbyte[]
で取得できる。
以下では、リソースをstring
等で選ぶことを考えて、自分でResourceManager
を使って取得する方法で記述している。
namespace SampleProj.Properties {
// ...省略...
internal static System.IO.UnmanagedMemoryStream aiueo {
get {
return ResourceManager.GetStream("aiueo", resourceCulture);
}
}
internal static byte[] dindon {
get {
object obj = ResourceManager.GetObject("dindon", resourceCulture);
return ((byte[])(obj));
}
}
}
PlaySoundによるwavの再生
winmm.dllからPlaySoundをインポートする。
引数で渡す予定のFlagも、別途enum
で定義しておく。
[DllImport("winmm.dll", CharSet = CharSet.Auto)]
private static extern bool PlaySound(byte[] sound, IntPtr hMod, PlaySoundFlags flags);
[System.Flags]
public enum PlaySoundFlags : int
{
SND_MEMORY = 0x0004, // メモリの再生の意
}
再生はPlaySoundにbyte配列を渡すことで行える。
オーディオリソースはstreamなので、いったん別のbyte配列に読み込む。
using var target = SampleProj.Properties.Resources.ResourceManager.GetStream("aiueo");
byte[] buffer = new byte[target.Length];
target.Read(buffer, 0, buffer.Length);
target.Close();
// 再生
_ = PlaySound(buffer, IntPtr.Zero, PlaySoundFlags.SND_MEMORY);
停止も同じAPIで、第一引数をnull
に、第三引数を0にする。これ以上のPlaySoundの利用方法は省略。
// 停止
_ = PlaySound(null, IntPtr.Zero, 0);
NAudioによるmp3の再生
NAudioは次で配布されているMs-PLライセンスの、.NET向け音声再生APIである。dll一つで扱える簡単さでありながら様々な音声形式に対応する。wav形式も対応しているので、PlaySoundではなくNAudioを使うこともできる。
https://github.com/naudio/NAudio
再生は、NAudioのAPIであるMp3FileReader
にMemoryStream
を渡すことで行う。
再生状態はWaveOut.PlaybackState
で判別できる。これをnowaitでポーリングすると音声再生が乱れたのでwaitを入れている。多分ちゃんと調べる方法はある。(調査不足)
// WaveOutのインスタンスはクラスフィールドで持つなりする。
var player = new NAudio.Wave.WaveOut(); // 要Dispose
// リソースからデータの取得
byte[] buffer = (byte[])Properties.Resources.ResourceManager.GetObject("dindon");
using var stream = new MemoryStream(buffer)
{
Position = 0 // 先頭位置からの再生を意味するが、streamを新規にnewするときは不要なはず
};
using WaveStream pcm = new Mp3FileReader(stream);
player.Init(pcm);
// 再生
player.Play();
// 待ち(ここ調査不足)
while (player.PlaybackState == PlaybackState.Playing)
{
await Task.Delay(10);
}
停止はWaveOut.Stop()
で行える。
// 停止
player.Stop();
まとめ
exe単体ないしはdllの同梱のみで、音声データを再生する方法を調べた。
とにかく再生できれば良かったので、再生中かどうかを考慮したり音声デバイスを選んだりなどは本記事では省略している。
- リソースで埋め込んだwave形式とmp3形式のデータを再生する
- PlaySoundとNAudioの2つの方法で再生する