using System; using System.Collections.Generic; using System.IO; using System.Text; namespace wavcontrolsample { public class wavedump { static void Main(string[] args) { WaveFileParser wfp; string wavefile; if (args.Length == 1) { wfp = new WaveFileParser(); wavefile = args[0]; } else if (args.Length == 3) { if (args[0] == "-o") { wfp = new WaveFileParser(args[1]); wavefile = args[2]; } else { help(); return; } } else { help(); return; } wfp.ParseWavFile(wavefile); wfp.ListChunkIndex(); wfp.FlushWriteLine(); } static void help() { Console.WriteLine("usage: wavdump [-o dumpfile] wavefile"); Console.WriteLine(""); } } public class WaveFileParser { private List ChunkLocation = new List(); private StreamWriter sw; public WaveFileParser() { sw = new StreamWriter(Console.OpenStandardOutput(), Encoding.Default); } public WaveFileParser(string outputFilePath) { sw = new StreamWriter(outputFilePath, false, Encoding.Default); } public void FlushWriteLine() { sw.Flush(); } public void ListChunkIndex() { WriteLine("-- Chunk Index --"); foreach (string arg in ChunkLocation) { WriteLine(arg); } } public void ParseWavFile(string wavFileName) { byte[] buff = new byte[4]; UInt32 size; string ChunkID; UInt32 ChunkSize; string FormType; UInt32 Location = 0; using (FileStream fs = new FileStream(wavFileName, FileMode.Open)) { WriteLine("======== ================================================"); // RIFFチャンク、WAVEフォームでないなら終了 fs.Read(buff, 0, 4); ChunkID = Encoding.ASCII.GetString(buff); printData(Location, buff, "Chunk ID", ChunkID); Location += 4; fs.Read(buff, 0, 4); ChunkSize = (UInt32)((buff[3] << 24) + (buff[2] << 16) + (buff[1] << 8) + buff[0]); printData(Location, buff, "Chunk Size", ChunkSize); Location += 4; fs.Read(buff, 0, 4); FormType = Encoding.ASCII.GetString(buff); printData(Location, buff, "Form Type", FormType); Location += 4; if (ChunkID != "RIFF") throw new Exception(string.Format("Can't read except \"RIFF\" format. this is \"{0}\" format.", ChunkID)); if (FormType != "WAVE") throw new Exception(string.Format("Can't read except \"WAVE\" form. this is \"{0}\" form.", FormType)); size = (UInt32)(ChunkSize - 4); if ((size % 2) == 1) size += 1; ChunkLocation.Add(string.Format("{0:X08},{1},{2}", 0, ChunkID, ChunkSize)); // 子チャンクの読み取り UInt32 size2 = 0; UInt32 paddingSize; byte[] subBuff; while (size2 < size) { if (4 > fs.Read(buff, 0, 4)) break; WriteLine("======== ================================================"); ChunkID = Encoding.ASCII.GetString(buff); printData(Location, buff, "Chunk ID", ChunkID); Location += 4; fs.Read(buff, 0, 4); ChunkSize = (UInt32)((buff[3] << 24) + (buff[2] << 16) + (buff[1] << 8) + buff[0]); printData(Location, buff, "Chunk Size", ChunkSize); Location += 4; paddingSize = ((ChunkSize % 2) == 1) ? ChunkSize + 1 : ChunkSize; ChunkLocation.Add(string.Format("{0:X08},{1},{2}", Location - 8, ChunkID, ChunkSize)); subBuff = new byte[paddingSize]; fs.Read(subBuff, 0, (Int32)ChunkSize); switch (ChunkID) { case "fmt ": ParseFmt(Location, subBuff, ChunkSize); break; case "data": printData(Location, subBuff); break; case "LIST": ParseList(Location, subBuff); break; case "id3 ": ParseID3(Location, subBuff); break; case "fact": break; case "cue ": ParseCue(Location, subBuff); break; default: printData(Location, subBuff); break; } Location += paddingSize; size2 += (8 + paddingSize); } } } private void ParseFmt(UInt32 Location, byte[] data, UInt32 chunkSize) { int pos = 0; byte[] buff4 = new byte[4]; byte[] buff2 = new byte[2]; Array.Copy(data, pos, buff2, 0, 2); printData(Location + (UInt32)pos, buff2, "Format", Size2Calc(buff2)); pos += 2; Array.Copy(data, pos, buff2, 0, 2); printData(Location + (UInt32)pos, buff2, "Channels", Size2Calc(buff2)); pos += 2; Array.Copy(data, pos, buff4, 0, 4); printData(Location + (UInt32)pos, buff4, "SampleRate", Size4Calc(buff4)); pos += 4; Array.Copy(data, pos, buff4, 0, 4); printData(Location + (UInt32)pos, buff4, "ByteRate", Size4Calc(buff4)); pos += 4; Array.Copy(data, pos, buff2, 0, 2); printData(Location + (UInt32)pos, buff2, "BlockAlign", Size2Calc(buff2)); pos += 2; Array.Copy(data, pos, buff2, 0, 2); printData(Location + (UInt32)pos, buff2, "BitPerSample", Size2Calc(buff2)); pos += 2; if (chunkSize > 16) { Array.Copy(data, pos, buff2, 0, 2); UInt16 ExtendedSize = Size2Calc(buff2); printData(Location + (UInt32)pos, buff2, "ExtendedSize", ExtendedSize); pos += 2; byte[] ExtendedData = new byte[ExtendedSize]; Array.Copy(data, pos, ExtendedData, 0, ExtendedSize); printData(Location + (UInt32)pos, ExtendedData, "ExtendedData", ExtendedSize+" byte"); //pos += ExtendedSize; } } private void ParseList(UInt32 Location, byte[] data) { int pos = 0; string ChunkID; UInt32 ChunkSize; UInt32 paddingSize; byte[] buff4 = new byte[4]; byte[] buff2 = new byte[2]; byte[] subBuff; byte[] subBuff2; Array.Copy(data, pos, buff4, 0, 4); printData(Location + (UInt32)pos, buff4, "ListType", Encoding.ASCII.GetString(buff4)); pos += 4; while (pos < data.Length) { WriteLine("-------- ------------------------------------------------"); Array.Copy(data, pos, buff4, 0, 4); ChunkID = Encoding.ASCII.GetString(buff4); printData(Location + (UInt32)pos, buff4, "Chunk ID", ChunkID); pos += 4; Array.Copy(data, pos, buff4, 0, 4); ChunkSize = Size4Calc(buff4); printData(Location + (UInt32)pos, buff4, "Chunk Size", ChunkSize); pos += 4; ChunkLocation.Add(string.Format("{0:X08},{1},{2}", Location + pos - 8, ChunkID, ChunkSize)); paddingSize = ((ChunkSize % 2) == 1) ? ChunkSize + 1 : ChunkSize; subBuff = new byte[paddingSize]; Array.Copy(data, pos, subBuff, 0, ChunkSize); switch (ChunkID) { // INFO Chunk case "IARL": case "IART": case "ICMS": case "ICMT": case "ICOT": case "ICRD": case "ICRP": case "IDIM": case "IDPI": case "IENG": case "IGNR": case "IKEY": case "ILGT": case "IMED": case "INAM": case "IPLT": case "IPRD": case "ISBJ": case "ISFT": case "ISHP": case "ISRC": case "ISRF": case "ITCH": case "ITRK": // Application original? printData(Location + 0 + (UInt32)pos, subBuff, "TEXT", Encoding.ASCII.GetString(subBuff)); break; // adtl Chunk case "labl": case "note": Array.Copy(subBuff, 0, buff4, 0, 4); printData(Location + 0 + (UInt32)pos, subBuff, "Cue Point Name", Size4Calc(buff4)); subBuff2 = new byte[subBuff.Length - 4]; Array.Copy(subBuff, 4, subBuff2, 0, subBuff.Length - 4); printData(Location + 4 + (UInt32)pos, subBuff2, "TEXT", Encoding.ASCII.GetString(subBuff2)); break; case "file": Array.Copy(subBuff, 0, buff4, 0, 4); printData(Location + 0 + (UInt32)pos, subBuff, "Cue Point Name", Size4Calc(buff4)); Array.Copy(subBuff, 4, buff4, 0, 4); printData(Location + 4 + (UInt32)pos, buff2, "MedType"); subBuff2 = new byte[subBuff.Length - 8]; Array.Copy(subBuff, 8, subBuff2, 0, subBuff.Length - 8); printData(Location + 8 + (UInt32)pos, subBuff2, "file data"); break; case "ltxt": Array.Copy(subBuff, 0, buff4, 0, 4); printData(Location + 0 + (UInt32)pos, subBuff, "Cue Point Name", Size4Calc(buff4)); Array.Copy(subBuff, 4, buff4, 0, 4); printData(Location + 4 + (UInt32)pos, subBuff, "Sample Length", Size4Calc(buff4)); Array.Copy(subBuff, 8, buff4, 0, 4); printData(Location + 8 + (UInt32)pos, buff4, "Purpose", Encoding.ASCII.GetString(buff4)); Array.Copy(subBuff, 12, buff2, 0, 2); printData(Location + 12 + (UInt32)pos, buff2, "Country", Encoding.ASCII.GetString(buff2)); Array.Copy(subBuff, 14, buff2, 0, 2); printData(Location + 14 + (UInt32)pos, buff2, "Language", Encoding.ASCII.GetString(buff2)); Array.Copy(subBuff, 16, buff2, 0, 2); printData(Location + 16 + (UInt32)pos, buff2, "Dialect"); Array.Copy(subBuff, 18, buff2, 0, 2); printData(Location + 18 + (UInt32)pos, buff2, "CodePage", Size2Calc(buff2)); if (ChunkSize > 20) { subBuff2 = new byte[subBuff.Length - 20]; Array.Copy(subBuff, 20, subBuff2, 0, subBuff.Length - 20); printData(Location + 20 + (UInt32)pos, subBuff2, "TEXT", Encoding.ASCII.GetString(subBuff2)); } break; default: printData(Location + (UInt32)pos, subBuff); break; } pos += (Int32)paddingSize; } } private void ParseFact(UInt32 Location, byte[] data) { int pos = 0; byte[] buff4 = new byte[4]; Array.Copy(data, pos + 0, buff4, 0, 4); printData(Location + (UInt32)pos, buff4, "Samples", Size4Calc(buff4)); pos += 4; } private void ParseCue(UInt32 Location, byte[] data) { int pos = 0; UInt32 CuePointCount; byte[] buff4 = new byte[4]; Array.Copy(data, pos, buff4, 0, 4); CuePointCount = Size4Calc(buff4); printData(Location + (UInt32)pos, buff4, "CuePointCount", CuePointCount); pos += 4; if (CuePointCount != 0) { for (UInt32 idx = 0; idx < CuePointCount; idx++) { WriteLine("-------- ------------------------------------------------"); Array.Copy(data, pos, buff4, 0, 4); printData(Location + (UInt32)pos, buff4, "CueNumer", Size4Calc(buff4)); pos += 4; Array.Copy(data, pos, buff4, 0, 4); printData(Location + (UInt32)pos, buff4, "Position", Size4Calc(buff4)); pos += 4; Array.Copy(data, pos, buff4, 0, 4); printData(Location + (UInt32)pos, buff4, "Chunk", Encoding.ASCII.GetString(buff4)); pos += 4; Array.Copy(data, pos, buff4, 0, 4); printData(Location + (UInt32)pos, buff4, "ChunkStart", Size4Calc(buff4)); pos += 4; Array.Copy(data, pos, buff4, 0, 4); printData(Location + (UInt32)pos, buff4, "BlockStart", Size4Calc(buff4)); pos += 4; Array.Copy(data, pos, buff4, 0, 4); printData(Location + (UInt32)pos, buff4, "SampleOffset", Size4Calc(buff4)); pos += 4; } } } private void ParseID3(UInt32 Location, byte[] data) { int pos = 0; string identifier; UInt32 frameSize; string frameId; string dataText; byte[] buff4 = new byte[4]; byte[] buff3 = new byte[3]; byte[] buff2 = new byte[2]; byte[] buff1 = new byte[1]; byte[] flags = new byte[1]; byte[] subBuff; string flagStr; /////////// ID3 ヘッダのパース Array.Copy(data, pos, buff3, 0, 3); identifier = Encoding.ASCII.GetString(buff3); printData(Location + (UInt32)pos, buff3, "IDENTIFIRE", identifier); pos += 3; if ("ID3" != identifier) { subBuff = new byte[data.Length - 3]; printData(Location + (UInt32)pos, subBuff); return; } Array.Copy(data, pos, buff2, 0, 2); printData(Location + (UInt32)pos, buff2, "VERSION", string.Format("{0:D02}-{1:D02}", buff2[0], buff2[1])); pos += 2; Array.Copy(data, pos, flags, 0, 1); flagStr = ((flags[0] & 0x80) != 0) ? "Unsynchronisation, " : ""; flagStr += ((flags[0] & 0x40) != 0) ? "ExtentedHeader, " : ""; flagStr += ((flags[0] & 0x20) != 0) ? "Experimental" : ""; flagStr = flagStr.Trim(','); printData(Location + (UInt32)pos, flags, "FLAGS", flagStr); pos++; Array.Copy(data, pos, buff4, 0, 4); printData(Location + (UInt32)pos, buff4, "Frame Size", ID3Size4Calc(buff4)); pos += 4; // 拡張ヘッダがある if ((flags[0] & 0x40) != 0) { Array.Copy(data, pos, buff4, 0, 4); printData(Location + (UInt32)pos, buff4, "Extended Header Size", ID3Size4Calc(buff4)); pos += 4; Array.Copy(data, pos, buff2, 0, 2); flagStr = ((buff2[0] & 0x80) != 0) ? "CRC" : ""; printData(Location + (UInt32)pos, buff2, "Extened Header Flags", flagStr); pos += 2; Array.Copy(data, pos, buff4, 0, 4); printData(Location + (UInt32)pos, buff4, "Extended Header Size", ID3Size4Calc(buff4)); pos += 4; Array.Copy(data, pos, buff4, 0, 4); printData(Location + (UInt32)pos, buff4, "Extended Padding Size", ID3Size4Calc(buff4)); pos += 4; // CRCがある場合 if ((buff2[0] & 0x80) != 0) { Array.Copy(data, pos, buff4, 0, 4); printData(Location + (UInt32)pos, buff4, "Extended Header CRC"); pos += 4; } } /////////// ID3 フレームのパース while (pos < data.Length) { if (10 > (data.Length - pos)) break; // フレームヘッダサイズは10バイト WriteLine("-------- ------------------------------------------------"); Array.Copy(data, pos, buff4, 0, 4); frameId = Encoding.ASCII.GetString(buff4); printData(Location + (UInt32)pos, buff4, "FrameID", frameId); pos += 4; Array.Copy(data, pos, buff4, 0, 4); frameSize = ID3Size4Calc(buff4); printData(Location + (UInt32)pos, buff4, "Frame Size", frameSize); pos += 4; ChunkLocation.Add(string.Format("{0:X08},{1},{2}", Location + pos - 8, frameId, frameSize)); Array.Copy(data, pos, buff2, 0, 2); printData(Location + (UInt32)pos, buff2, "Frame Flags"); pos += 2; if (frameId.StartsWith("T") || ("WXXX" == frameId)) { Array.Copy(data, pos, buff1, 0, 1); printData(Location + (UInt32)pos, buff1, "TextEncoding"); pos ++; subBuff = new byte[frameSize - 1]; Array.Copy(data, pos, subBuff, 0, frameSize - 1); if (buff1[0] == 0x00) { dataText = Encoding.ASCII.GetString(subBuff); } else { dataText = Encoding.Unicode.GetString(subBuff); } dataText = dataText.TrimEnd('\0'); printData(Location + (UInt32)pos, subBuff, "text", dataText); pos += (Int32)(frameSize - 1); } else if (frameId.StartsWith("W")) { subBuff = new byte[frameSize]; Array.Copy(data, pos, subBuff, 0, frameSize); dataText = Encoding.ASCII.GetString(subBuff); dataText = dataText.TrimEnd('\0'); printData(Location + (UInt32)pos, subBuff, "url", dataText); pos += (Int32)(frameSize); } else { switch (frameId) { case "COMM": Array.Copy(data, pos, buff1, 0, 1); printData(Location + (UInt32)pos, buff1, "TextEncoding"); pos++; Array.Copy(data, pos, buff3, 0, 3); dataText = Encoding.ASCII.GetString(buff3); dataText = dataText.TrimEnd('\0'); printData(Location + (UInt32)pos, buff3, "Language", dataText); pos += 3; subBuff = new byte[frameSize - 4]; Array.Copy(data, pos, subBuff, 0, frameSize - 4); if (buff1[0] == 0x00) { dataText = Encoding.ASCII.GetString(subBuff); } else { dataText = Encoding.Unicode.GetString(subBuff); } dataText = dataText.TrimEnd('\0'); printData(Location + (UInt32)pos, subBuff, "text", dataText); pos += (Int32)(frameSize - 4); break; default: subBuff = new byte[frameSize]; Array.Copy(data, pos, subBuff, 0, frameSize); printData(Location + (UInt32)pos, subBuff); pos += (Int32)(frameSize); break; } } } } private UInt32 ID3Size4Calc(byte[] data) { // ID3タグ用4バイト28ビット(32ビット違うよ)の算出 return (UInt32)((data[0] << 23) | (data[1] << 15) | (data[2] << 7) | data[3]); } private UInt32 Size4Calc(byte[] data) { return (UInt32)((data[3] << 24) | (data[2] << 16) | (data[1] << 8) | data[0]); } private UInt16 Size2Calc(byte[] data) { return (UInt16)((data[1] << 8) | data[0]); } private string[] makeHexString(UInt32 Location, byte[] data, int width) { List line = new List(); StringBuilder sb = new StringBuilder(); for (int pos = 0; pos < data.Length; pos += width) { sb.Clear(); sb.Append(string.Format("{0:X08}", Location + pos)); for (int idx = 0; idx < width; idx++) { if ((pos + idx) == data.Length) break; sb.Append(string.Format(" {0:X02}", data[pos + idx])); } line.Add(sb.ToString()); } return line.ToArray(); } private void printData(UInt32 Location, byte[] data, string caption, object obj) { string[] line = makeHexString(Location, data, 8); for (int idx = 0; idx < line.Length; idx++) { if (idx != 0) { WriteLine(line[idx]); } else { StringBuilder sb = new StringBuilder(); sb.Append(line[idx]); sb.Append(' ', (8 + (3 * 8)) - line[idx].Length); WriteLine("{0} > {1} : \"{2}\"", sb.ToString(), caption, obj); } if ((idx % 50) == 0) FlushWriteLine(); } FlushWriteLine(); } private void printData(UInt32 Location, byte[] data, string caption) { string[] line = makeHexString(Location, data, 8); for (int idx = 0; idx < line.Length; idx++) { if (idx != 0) { WriteLine(line[idx]); } else { StringBuilder sb = new StringBuilder(); sb.Append(line[idx]); sb.Append(' ', (8 + (3 * 8)) - line[idx].Length); WriteLine("{0} > {1}", sb.ToString(), caption); } if ((idx % 50) == 0) FlushWriteLine(); } FlushWriteLine(); } private void printData(UInt32 Location, byte[] data) { string[] line = makeHexString(Location, data, 16); for (int idx = 0; idx < line.Length; idx++) { WriteLine(line[idx]); if ((idx % 50) == 0) FlushWriteLine(); } FlushWriteLine(); } public void WriteLine(string str) { sw.WriteLine(str); } public void WriteLine(string fmt, params object[] args) { sw.WriteLine(fmt, args); } } }