Example #1
0
        private void ParseRiff(Stream st)
        {
            // Whenever parse the RIFF, reset everything.
            Init();
            // The first three blocks do not follow the rest.
            // (WAVE is not followed by size, and there is no hard definition of "WAVEfmt " are always together)

            // The smallest wave is 44 bytes.
            Sanity.Requires(st.Length >= 44, $"The stream length {st.Length} is too short, should be at least 44.");
            // For safety, 2G file will not be supported.
            Sanity.Requires(st.Length <= int.MaxValue, $"The stream length {st.Length} is too long, at most 2G.");

            // 0-3: RIFF.
            st.Read(ChunkNameBytes, 0, 4);
            Sanity.Requires(GetName() == "RIFF", "Invalid header, should be RIFF.");

            // 4-7: int 32 for RIFF size.
            st.Read(Int32Bytes, 0, 4);
            Sanity.Requires(GetInt32() + 8 == st.Length, "Stream length mismatch, in RIFF chunk.");

            // 8-11: WAVE.
            st.Read(ChunkNameBytes, 0, 4);
            Sanity.Requires(GetName() == "WAVE", "Invalid header, should be WAVE");

            // Clear the format chunk and data chunk.
            FormatChunk = InitEmptyChunk();
            DataChunk   = InitEmptyChunk();

            // Recursively parse the remaining chunks.
            ParseChunks(st);

            // Validate the rest values.
            PostCheck(st);
        }
Example #2
0
        private void ParseRecursively(Stream st)
        {
            if (st.Position == st.Length)
            {
                return;
            }
            Sanity.Requires(st.Position + 8 <= st.Length, $"Audio length too short, at 0x{st.Position:x}.");
            string chunkName = st.ReadBytesToString(4, Encoding.ASCII);
            int    chunkSize = st.ReadBytesToInt32();
            Chunk  chunk     = new Chunk {
                Name = chunkName, Size = chunkSize, Offset = (int)st.Position
            };

            switch (chunkName)
            {
            case "fmt ":
                Sanity.Requires(FormatChunk.Name != "", "Dupelicate format chunk.");
                FormatChunk = chunk;
                return;

            case "data":
                Sanity.Requires(DataChunk.Name != "", "Duplicate data chunk.");
                DataChunk = chunk;
                return;

            default:
                break;
            }
            Sanity.Requires(st.Position + chunkSize <= st.Length, $"Audio length too short, at {chunkName}, at 0x{st.Position:x}.");
            st.Seek(chunkSize, SeekOrigin.Current);
            ParseRecursively(st);
        }
Example #3
0
 private void ParseHeader(Stream st)
 {
     Sanity.Requires(st.Length >= 44, $"Audio size is too small. Expected at least 44, which is actually {st.Length} ");
     Sanity.Requires(st.Length <= int.MaxValue, $"Audio size is too large, more than 2GB.");
     Sanity.Requires(st.ReadBytesToString(4, Encoding.ASCII) == "RIFF", $"File type error, not RIFF.");
     Sanity.Requires(st.ReadBytesToInt32() + 8 == st.Length, $"File length error, in RIFF chunk.");
     Sanity.Requires(st.ReadBytesToString(4, Encoding.ASCII) == "WAVE", $"File type error, not WAVE");
 }
Example #4
0
 /// <summary>
 /// Shallow parse the audio from a certain path.
 /// </summary>
 /// <param name="filePath">The path of the audio file.</param>
 public void ShallowParse(string filePath)
 {
     Sanity.Requires(File.Exists(filePath), $"File not exist: {filePath}.");
     using (FileStream st = new FileStream(filePath, FileMode.Open, FileAccess.Read))
     {
         // Call the shallow parse stream to keep the core algorithm identical.
         ShallowParse(st);
     }
 }
Example #5
0
 private void CheckData(Stream st)
 {
     Sanity.Requires(DataChunk.Name != "", "No data chunk.");
     if (IsDeep)
     {
         DataBytes = new byte[DataChunk.Size];
         st.Seek(DataChunk.Offset, 0);
         st.Read(DataBytes, 0, DataChunk.Size);
     }
 }
Example #6
0
        private void PostCheck(Stream st)
        {
            // The format chunk and data chunk have to exist.
            Sanity.Requires(FormatChunk.ChunkName != null, "Format error, missing format chunk.");
            Sanity.Requires(DataChunk.ChunkName != null, "Format error, missing data chunk.");

            // Go to the format chunk.
            st.Seek(FormatChunk.ChunkOffset + 8, SeekOrigin.Begin);

            // First two bytes for wave type.
            st.Read(Int16Bytes, 0, 2);
            TypeId = GetInt16();

            // Following two bytes for the number of channels.
            st.Read(Int16Bytes, 0, 2);
            NumChannels = GetInt16();
            Sanity.Requires(NumChannels != 0, "Channel cannot be zero.");

            // Following four bytes for the sample rate.
            st.Read(Int32Bytes, 0, 4);
            SampleRate = GetInt32();
            Sanity.Requires(SampleRate != 0, "Sample rate cannot be zeror.");

            // Following four bytes for the byte rate(bytes per second).
            // Audio length(seconds) can be calculated by this value and the length of data bytes directly.
            st.Read(Int32Bytes, 0, 4);
            ByteRate = GetInt32();
            Sanity.Requires(ByteRate != 0, "Byte rate cannot be zero.");
            AudioTime = (double)DataChunk.ChunkLength / ByteRate;

            // Block align, how many bytes for every "block", e.g. a single sample from all channels.
            st.Read(Int16Bytes, 0, 2);
            BlockAlign = GetInt16();
            Sanity.Requires(BlockAlign != 0, "Block align cannot be zero.");

            // Bits per sample, the audio precision, e.g. 8 bit or 16 bit.
            st.Read(Int16Bytes, 0, 2);
            BitsPerSample = GetInt16();
            Sanity.Requires(BitsPerSample != 0, "Bits per sample cannot be zero.");

            // The values of all the wave format are redundant, two extra equations should be followed.
            Sanity.Requires(ByteRate == BitsPerSample / 8 * SampleRate * NumChannels, "Error in byte rate equation.");
            Sanity.Requires(BlockAlign == BitsPerSample / 8 * NumChannels, "Error in block align equation.");

            // In practical, invalid format is seldom seen.
            // So just let it be.

            // Set the wave type string.
            SetWaveType();
        }
Example #7
0
 private void CheckFormat(Stream st)
 {
     Sanity.Requires(FormatChunk.Name != "", "No format chunk.");
     Sanity.Requires(FormatChunk.Size >= 16, $"Format chunk too small. Expected at least 16, which is actually {FormatChunk.Size}.");
     st.Seek(FormatChunk.Offset, SeekOrigin.Begin);
     WaveTypeId    = st.ReadBytesToInt16();
     NumChannels   = st.ReadBytesToInt16();
     SampleRate    = st.ReadBytesToInt32();
     ByteRate      = st.ReadBytesToInt32();
     BlockAlign    = st.ReadBytesToInt16();
     BitsPerSample = st.ReadBytesToInt16();
     Sanity.Requires(ByteRate == SampleRate * BlockAlign, $"Wave format error: byte rate: {ByteRate}, sample rate: {SampleRate}, block align: {BlockAlign}.");
     Sanity.Requires(BitsPerSample * NumChannels == 8 * BlockAlign, $"Wave format error: bits per sample: {BitsPerSample}, channel: {NumChannels}, block align: {BlockAlign}.");
     AudioLength = (double)DataChunk.Size / ByteRate;
 }
Example #8
0
        private void ParseChunks(Stream st)
        {
            // When reaches the end, stop.
            if (st.Position == st.Length)
            {
                return;
            }

            // If it is a valid chunk, it has to be at least 8
            Sanity.Requires(st.Position + 8 <= st.Length, $"Stream length mismatch, at position {st.Position}.");

            // Offset is the current position.
            int offset = (int)st.Position;

            // The first four bytes(of the 8 bytes) is the name.
            st.Read(ChunkNameBytes, 0, 4);
            string name = GetName();

            // The next four bytes(of the 8 bytes) is the chunk siz/length.
            st.Read(Int32Bytes, 0, 4);
            int length = GetInt32();

            // The chunk length should not exceed the end of the file.
            Sanity.Requires(st.Position + length <= st.Length, $"Stream length mismatch, at position {st.Position}, in chunk {name}.");
            // Move forward.
            st.Seek(length, SeekOrigin.Current);

            // Set the chunk.
            WaveChunk chunk = new WaveChunk
            {
                ChunkName   = name,
                ChunkLength = length,
                ChunkOffset = offset
            };

            // Format chunk is special.
            if (chunk.ChunkName == "fmt ")
            {
                // There should be only 1 format chunk.
                Sanity.Requires(FormatChunk.ChunkName == null, $"Format error, at most 1 format chunk.");
                // The smallest format chunk for PCM is 16.
                Sanity.Requires(chunk.ChunkLength >= 16, $"Format error, format chunk length {FormatChunk.ChunkLength}, should be at least 16.");
                FormatChunk = chunk;
            }

            // Data chunk is special.
            if (chunk.ChunkName == "data")
            {
                // There should be only 1 data chunk.
                // NOTE: I'M NOT SURE ABOUT THIS, THERE IS NOWHERE SAY ONLY 1 DATA CHUNK IS ALLOWED.
                // JUST FOR SIMPLE HERE.
                Sanity.Requires(DataChunk.ChunkName == null, $"Format error, at most 1 data chunk.");
                DataChunk = chunk;
            }

            // Add this chunk into the list.
            ChunkList.Add(chunk);


            // Parse the next chunk recursively.
            ParseChunks(st);
        }