/// <summary> /// Reads the header part of a WAV file from a stream /// </summary> /// <param name="stream">The stream, positioned at the start of audio data</param> /// <param name="format">The format found</param> /// <param name="dataChunkPosition">The position of the data chunk</param> /// <param name="dataChunkLength">The length of the data chunk</param> /// <param name="chunks">Additional chunks found</param> public static void ReadWaveHeader(Stream stream, out WaveFormat format, out long dataChunkPosition, out int dataChunkLength, List <RiffChunk> chunks) { dataChunkPosition = -1; format = null; BinaryReader br = new BinaryReader(stream); if (br.ReadInt32() != WaveInterop.mmioStringToFOURCC("RIFF", 0)) { throw new FormatException("Not a WAVE file - no RIFF header"); } uint fileSize = br.ReadUInt32(); // read the file size (minus 8 bytes) if (br.ReadInt32() != WaveInterop.mmioStringToFOURCC("WAVE", 0)) { throw new FormatException("Not a WAVE file - no WAVE header"); } int dataChunkID = WaveInterop.mmioStringToFOURCC("data", 0); int formatChunkId = WaveInterop.mmioStringToFOURCC("fmt ", 0); dataChunkLength = 0; // sometimes a file has more data than is specified after the RIFF header long stopPosition = Math.Min(fileSize + 8, stream.Length); // this -8 is so we can be sure that there are at least 8 bytes for a chunk id and length while (stream.Position <= stopPosition - 8) { Int32 chunkIdentifier = br.ReadInt32(); Int32 chunkLength = br.ReadInt32(); if (chunkIdentifier == dataChunkID) { dataChunkPosition = stream.Position; dataChunkLength = chunkLength; stream.Position += chunkLength; } else if (chunkIdentifier == formatChunkId) { format = WaveFormat.FromFormatChunk(br, chunkLength); } else { if (chunks != null) { chunks.Add(new RiffChunk(chunkIdentifier, chunkLength, stream.Position)); } stream.Position += chunkLength; } } if (format == null) { throw new FormatException("Invalid WAV file - No fmt chunk found"); } if (dataChunkPosition == -1) { throw new FormatException("Invalid WAV file - No data chunk found"); } }
/// <summary> /// Creates a cue list from the cue RIFF chunk and the list RIFF chunk /// </summary> /// <param name="cueChunkData">The data contained in the cue chunk</param> /// <param name="listChunkData">The data contained in the list chunk</param> internal CueList(byte[] cueChunkData, byte[] listChunkData) { int cueCount = BitConverter.ToInt32(cueChunkData, 0); Dictionary <int, int> cueIndex = new Dictionary <int, int>(); int[] positions = new int[cueCount]; int cue = 0; for (int p = 4; cueChunkData.Length - p >= 24; p += 24, cue++) { cueIndex[BitConverter.ToInt32(cueChunkData, p)] = cue; positions[cue] = BitConverter.ToInt32(cueChunkData, p + 20); } string[] labels = new string[cueCount]; int labelLength = 0; int cueID = 0; Int32 labelChunkID = WaveInterop.mmioStringToFOURCC("labl", 0); for (int p = 4; listChunkData.Length - p >= 16; p += labelLength + labelLength % 2 + 12) { if (BitConverter.ToInt32(listChunkData, p) == labelChunkID) { labelLength = BitConverter.ToInt32(listChunkData, p + 4) - 4; cueID = BitConverter.ToInt32(listChunkData, p + 8); cue = cueIndex[cueID]; labels[cue] = Encoding.Default.GetString(listChunkData, p + 12, labelLength - 1); } } for (int i = 0; i < cueCount; i++) { cues.Add(new Cue(positions[i], labels[i])); } }
/// <summary> /// Reads the header part of a WAV file from a stream /// </summary> /// <param name="stream">The stream, positioned at the start of audio data</param> /// <param name="format">The format found</param> /// <param name="dataChunkPosition">The position of the data chunk</param> /// <param name="dataChunkLength">The length of the data chunk</param> /// <param name="chunks">Additional chunks found</param> public static void ReadWaveHeader(Stream stream, out WaveFormat format, out long dataChunkPosition, out int dataChunkLength, List <RiffChunk> chunks) { dataChunkPosition = -1; format = null; BinaryReader br = new BinaryReader(stream); if (br.ReadInt32() != WaveInterop.mmioStringToFOURCC("RIFF", 0)) { throw new FormatException("Not a WAVE file - no RIFF header"); } uint fileSize = br.ReadUInt32(); // read the file size (minus 8 bytes) if (br.ReadInt32() != WaveInterop.mmioStringToFOURCC("WAVE", 0)) { throw new FormatException("Not a WAVE file - no WAVE header"); } int dataChunkID = WaveInterop.mmioStringToFOURCC("data", 0); int formatChunkId = WaveInterop.mmioStringToFOURCC("fmt ", 0); dataChunkLength = 0; // sometimes a file has more data than is specified after the RIFF header long stopPosition = Math.Min(fileSize + 8, stream.Length); // this -8 is so we can be sure that there are at least 8 bytes for a chunk id and length while (stream.Position <= stopPosition - 8) { Int32 chunkIdentifier = br.ReadInt32(); Int32 chunkLength = br.ReadInt32(); if (chunkIdentifier == dataChunkID) { dataChunkPosition = stream.Position; dataChunkLength = chunkLength; stream.Position += chunkLength; } else if (chunkIdentifier == formatChunkId) { format = WaveFormat.FromFormatChunk(br, chunkLength); } else { // check for invalid chunk length if (chunkLength < 0 || chunkLength > stream.Length - stream.Position) { Debug.Assert(false, String.Format("Invalid chunk length {0}, pos: {1}. length: {2}", chunkLength, stream.Position, stream.Length)); // an exception will be thrown further down if we haven't got a format and data chunk yet, // otherwise we will tolerate this file despite it having corrupt data at the end break; } if (chunks != null) { chunks.Add(new RiffChunk(chunkIdentifier, chunkLength, stream.Position)); } stream.Position += chunkLength; } } if (format == null) { throw new FormatException("Invalid WAV file - No fmt chunk found"); } if (dataChunkPosition == -1) { throw new FormatException("Invalid WAV file - No data chunk found"); } }
/// <summary> /// Gets the cues as the concatenated cue and list RIFF chunks. /// </summary> /// <returns>RIFF chunks containing the cue data</returns> internal byte[] GetRIFFChunks() { if (this.Count == 0) { return(null); } else { int cueChunkLength = 12 + 24 * this.Count; int listChunkLength = 12; int labelChunkLength = 0; for (int i = 0; i < this.Count; i++) { labelChunkLength = this[i].Label.Length + 1; listChunkLength += labelChunkLength + labelChunkLength % 2 + 12; } byte[] chunks = new byte[cueChunkLength + listChunkLength]; Int32 cueChunkID = WaveInterop.mmioStringToFOURCC("cue ", 0); Int32 dataChunkID = WaveInterop.mmioStringToFOURCC("data", 0); Int32 listChunkID = WaveInterop.mmioStringToFOURCC("LIST", 0); Int32 adtlTypeID = WaveInterop.mmioStringToFOURCC("adtl", 0); Int32 labelChunkID = WaveInterop.mmioStringToFOURCC("labl", 0); using (MemoryStream stream = new MemoryStream(chunks)) { using (BinaryWriter writer = new BinaryWriter(stream)) { writer.Write(cueChunkID); writer.Write(cueChunkLength - 8); writer.Write(this.Count); for (int cue = 0; cue < this.Count; cue++) { writer.Write(cue); writer.Seek(4, SeekOrigin.Current); writer.Write(dataChunkID); writer.Seek(8, SeekOrigin.Current); writer.Write(this[cue].Position); } writer.Write(listChunkID); writer.Write(listChunkLength - 8); writer.Write(adtlTypeID); for (int cue = 0; cue < this.Count; cue++) { writer.Write(labelChunkID); writer.Write(this[cue].Label.Length + 1 + 4); writer.Write(cue); writer.Write(Encoding.Default.GetBytes(this[cue].Label.ToCharArray())); if (this[cue].Label.Length % 2 == 0) { writer.Seek(2, SeekOrigin.Current); } else { writer.Seek(1, SeekOrigin.Current); } } } } return(chunks); } }