低レベル(ハードウェア寄り)なアナログ信号を扱っていると、サクッと使えるアナログ信号のファイルフォーマットが欲しくなります。そういうときに、WAV(RIFF/PCM)は結構便利です。サンプリングレートを32bitで記録でき、チャンネル数も16bitで記録でき、ビット数もいくつか選択でき、その他にも色々と便利な点が多いです。が、「どんなデータが入っているか」を示す手段がファイル名くらいしかないのが少し困ったところです。
せっかくの音楽ファイルフォーマットなんだから、サムネにデータの中身のアイコンなりを埋め込んだら便利だと思いませんか?
ということで、C#でRIFF/PCMの生成とID3v2.3の埋め込みを行ってみました。
class Program
{
static void Main(string[] args)
{
using (FileStream fs = new FileStream("test.wav", FileMode.Create, FileAccess.Write))
{
ChunkSizeStack sizeStack = new ChunkSizeStack(fs);
Encoding encoding = Encoding.ASCII;
fs.Write("RIFF", encoding);
sizeStack.Push();
fs.Write("WAVE", encoding);
fs.Write("fmt ", encoding); // id
fs.WriteU32LE(16); // size
fs.WriteU16LE(1); // format: PCM
fs.WriteU16LE(1); // channels
fs.WriteU32LE(44100); // samplingrate
fs.WriteU32LE(0); // bytepersec
fs.WriteU16LE(0); // blockalign
fs.WriteU16LE(8); // bitwidth
//***
fs.Write("id3 ", encoding);
sizeStack.Push();
long a = fs.Position;
fs.Write("ID3", encoding);
fs.WriteByte(0x03); // major ver
fs.WriteByte(0x00); // revision
fs.WriteByte(0x00); // header flag
sizeStack.Push();
fs.Write("APIC", encoding);
sizeStack.Push();
fs.WriteU16LE(0); // tag flag
fs.WriteByte(0); // text encoding
fs.Write("image/jpeg\0", encoding);
fs.WriteByte(0x03); // picture type (0x03: front cover)
fs.WriteByte(0); // description (null terminal)
using (Bitmap bmp = new Bitmap(128, 128))
using (Graphics g = Graphics.FromImage(bmp))
using (Font font = new Font("MS ゴシック", 60, GraphicsUnit.Pixel))
using (StringFormat sf = new StringFormat())
{
sf.Alignment = sf.LineAlignment = StringAlignment.Center;
g.Clear(Color.Orange);
g.DrawString("TEST", font, Brushes.White, 64, 64, sf);
bmp.Save(fs, System.Drawing.Imaging.ImageFormat.Jpeg);
}
sizeStack.Pop(ToID3TagSize); // APIC tag size
sizeStack.Pop(ToID3size); // ID3v2 tags size
sizeStack.Pop(); // "id3 " chunk size
//***
fs.Write("data", encoding);
sizeStack.Push();
byte[] buff = Enumerable.Range(0, 44100).Select(i => (byte)(Math.Sin(i / 44100.0 * 800 * Math.PI * 2) * 127.5 + 127.5)).ToArray();
fs.Write(buff, 0, buff.Length);
sizeStack.Pop(); // "data" chunk size
sizeStack.Pop(); // RIFF size
}
}
static byte[] ToID3size(UInt32 size)
{
byte[] tmp = new byte[4]
{
(byte)((size >> 21) & 0x7F),
(byte)((size >> 14) & 0x7F),
(byte)((size >> 7) & 0x7F),
(byte)((size >> 0) & 0x7F)
};
return (tmp);
}
static byte[] ToID3TagSize(UInt32 size)
{
byte[] v = BitConverter.GetBytes((UInt32)(size - 2));
byte w;
w = v[0]; v[0] = v[3]; v[3] = w;
w = v[1]; v[1] = v[2]; v[2] = w;
return (v);
}
class ChunkSizeStack
{
private readonly Stream stream;
private readonly Stack<long> stack;
public ChunkSizeStack(Stream stream)
{
this.stream = stream;
stack = new Stack<long>();
}
public void Push()
{
stream.Write(new byte[4], 0, 4);
stack.Push(stream.Position);
}
public void Pop()
{
long a = stack.Pop();
if ((stream.Position - a) % 2 != 0) { stream.WriteByte(0); }
long b = stream.Position;
stream.Position = a - 4;
stream.Write(BitConverter.GetBytes((UInt32)(b - a)), 0, 4);
stream.Position = b;
}
public void Pop(Func<UInt32, byte[]> func)
{
long a = stack.Pop();
long b = stream.Position;
stream.Position = a - 4;
stream.Write(func((UInt32)(b - a)), 0, 4);
stream.Position = b;
}
}
}
static class MyExt
{
public static void Write(this Stream stream, string text, Encoding encoding)
{
byte[] tmp = encoding.GetBytes(text);
stream.Write(tmp, 0, tmp.Length);
}
public static void WriteU32LE(this Stream stream, UInt32 a)
{
stream.Write(BitConverter.GetBytes(a), 0, 4);
}
public static void WriteU16LE(this Stream stream, UInt16 a)
{
stream.Write(BitConverter.GetBytes(a), 0, 2);
}
}
で、うまく表示できず、いよいよわけがわからなくなってググってみたところ、Win10だとWAVのID3は表示できないみたいです。ナンテコッタ!!!
ちなみに、上記コードで作ったWAVファイルをTagKaeruで開くとちゃんとサムネが表示されるので、いちおう動いているはずです。
アナログ波形、例えばオシロスコープで取得した波形にオシロのキャプチャをサムネにして保存するとか、NTSCの波形データにNTSCをデコードした画像をサムネにして保存するとか、できると便利だなーと思っていただけに、アテが外れて残念。
某サイトで買ったMP3のサムネは600x600が入っていました。上記コードで1024x1024のJPEGを突っ込んでTagKaeruで開くとちゃんと表示できるので、常識的な範囲であれば、画像の大きさの制限は特になさそうです。他のソフトでも動くかはわかりませんが。ID3v2で16MBの制限があるみたいですが、JPEGやPNGで16MBになるような画像はかなりの大きさなので、実質無制限かな、という感じです。
// そういえば、PNGって互換性を保ったままアニメーションを設定できるけど、MP3やWAVのサムネにAPNGを入れて音楽再生中に(非同期で?)アニメーションが表示される、みたいな音楽プレイヤーとかあったりするんだろうか?
参考にしたページ
ID3タグ - Wikipedia
ID3v2 - Informal standard
ID3タグ、アートワークの仕様とiTunes4の仕様について