using System; namespace NLayer { public class MpegFile : IDisposable { System.IO.Stream _stream; bool _closeStream, _eofFound; Decoder.MpegStreamReader _reader; MpegFrameDecoder _decoder; object _seekLock = new object(); long _position; /// /// Construct Mpeg file representation from filename. /// /// The file which contains Mpeg data. public MpegFile(string fileName) { Init(System.IO.File.Open(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read), true); } /// /// Construct Mpeg file representation from stream. /// /// The input stream which contains Mpeg data. public MpegFile(System.IO.Stream stream) { Init(stream, false); } void Init(System.IO.Stream stream, bool closeStream) { _stream = stream; _closeStream = closeStream; _reader = new Decoder.MpegStreamReader(_stream); _decoder = new MpegFrameDecoder(); } /// /// Implements IDisposable.Dispose. /// public void Dispose() { if (_closeStream) { _stream.Dispose(); _closeStream = false; } } /// /// Sample rate of source Mpeg, in Hertz. /// public int SampleRate { get { return _reader.SampleRate; } } /// /// Channel count of source Mpeg. /// public int Channels { get { return _reader.Channels; } } /// /// Whether the Mpeg stream supports seek operation. /// public bool CanSeek { get { return _reader.CanSeek; } } /// /// Data length of decoded data, in PCM. /// public long Length { get { return _reader.SampleCount * _reader.Channels * sizeof(float); } } /// /// Media duration of the Mpeg file. /// public TimeSpan Duration { get { var len = _reader.SampleCount; if (len == -1) return TimeSpan.Zero; return TimeSpan.FromSeconds((double)len / _reader.SampleRate); } } /// /// Current decode position, in number of sample. Calling the setter will result in a seeking operation. /// public long Position { get { return _position; } set { if (!_reader.CanSeek) throw new InvalidOperationException("Cannot Seek!"); if (value < 0L) throw new ArgumentOutOfRangeException("value"); // we're thinking in 4-byte samples, pcmStep interleaved... adjust accordingly var samples = value / sizeof(float) / _reader.Channels; var sampleOffset = 0; // seek to the frame preceding the one we want (unless we're seeking to the first frame) if (samples >= _reader.FirstFrameSampleCount) { sampleOffset = _reader.FirstFrameSampleCount; samples -= sampleOffset; } lock (_seekLock) { // seek the stream var newPos = _reader.SeekTo(samples); if (newPos == -1) throw new ArgumentOutOfRangeException("value"); _decoder.Reset(); // if we have a sample offset, decode the next frame if (sampleOffset != 0) { _decoder.DecodeFrame(_reader.NextFrame(), _readBuf, 0); // throw away a frame (but allow the decoder to resync) newPos += sampleOffset; } _position = newPos * sizeof(float) * _reader.Channels; _eofFound = false; // clear the decoder & buffer _readBufOfs = _readBufLen = 0; } } } /// /// Current decode position, represented by time. Calling the setter will result in a seeking operation. /// public TimeSpan Time { get { return TimeSpan.FromSeconds((double)_position / sizeof(float) / _reader.Channels / _reader.SampleRate); } set { Position = (long)(value.TotalSeconds * _reader.SampleRate * _reader.Channels * sizeof(float)); } } /// /// Set the equalizer. /// /// The equalizer, represented by an array of 32 adjustments in dB. public void SetEQ(float[] eq) { _decoder.SetEQ(eq); } /// /// Stereo mode used in decoding. /// public StereoMode StereoMode { get { return _decoder.StereoMode; } set { _decoder.StereoMode = value; } } /// /// Read specified samples into provided buffer. Do exactly the same as /// except that the data is written in type of byte, while still representing single-precision float (in local endian). /// /// Buffer to write. Floating point data will be actually written into this byte array. /// Writing offset on the destination buffer. /// Length of samples to be read, in bytes. /// Sample size actually reads, in bytes. public int ReadSamples(byte[] buffer, int index, int count) { if (index < 0 || index + count > buffer.Length) throw new ArgumentOutOfRangeException("index"); // make sure we're asking for an even number of samples count -= (count % sizeof(float)); return ReadSamplesImpl(buffer, index, count, 32); } /// /// Read specified samples into provided buffer, as PCM format. /// Result varies with diffirent : /// /// /// For , sample data on both two channels will occur in turn (left first). /// /// /// For and , only data on /// specified channel will occur. /// /// /// For , two channels will be down-mixed into single channel. /// /// /// /// Buffer to write. /// Writing offset on the destination buffer. /// Count of samples to be read. /// Sample count actually reads. public int ReadSamples(float[] buffer, int index, int count) { if (index < 0 || index + count > buffer.Length) throw new ArgumentOutOfRangeException("index"); // ReadSampleImpl "thinks" in bytes, so adjust accordingly return ReadSamplesImpl(buffer, index * sizeof(float), count * sizeof(float), 32) / sizeof(float); } public int ReadSamplesInt16(byte[] buffer, int index, int count) { if (index < 0 || index + count > buffer.Length * sizeof(short)) throw new ArgumentOutOfRangeException("index"); return ReadSamplesImpl(buffer, index, count, 16) * sizeof(short) / sizeof(float); } public int ReadSamplesInt8(byte[] buffer, int index, int count) { if (index < 0 || index + count > buffer.Length * sizeof(float)) throw new ArgumentOutOfRangeException("index"); return ReadSamplesImpl(buffer, index, count, 8) * sizeof(byte) / sizeof(float); } float[] _readBuf = new float[1152 * 2]; int _readBufLen, _readBufOfs; int ReadSamplesImpl(Array buffer, int index, int count, int bitDepth) { var cnt = 0; // lock around the entire read operation so seeking doesn't bork our buffers as we decode lock (_seekLock) { while (count > 0) { if (_readBufLen > _readBufOfs) { // we have bytes in the buffer, so copy them first var temp = _readBufLen - _readBufOfs; if (temp > count) temp = count; if (bitDepth == 32) { Buffer.BlockCopy(_readBuf, _readBufOfs, buffer, index, temp); } else { for (int i = 0; i < temp / sizeof(float); i++) { switch (bitDepth) { case 8: buffer.SetValue((byte)Math.Round(127.5f * _readBuf[_readBufOfs / sizeof(float) + i] + 127.5f), index / sizeof(float) + i); break; case 16: var value = (int)Math.Round(32767.5f * _readBuf[_readBufOfs / sizeof(float) + i] - 0.5f); if (value < 0) { value += 65536; } buffer.SetValue((byte)(value % 256), 2 * (index / sizeof(float) + i)); buffer.SetValue((byte)(value / 256), 2 * (index / sizeof(float) + i) + 1); break; } } } // now update our counters... cnt += temp; count -= temp; index += temp; _position += temp; _readBufOfs += temp; // finally, mark the buffer as empty if we've read everything in it if (_readBufOfs == _readBufLen) { _readBufLen = 0; } } // if the buffer is empty, try to fill it // NB: If we've already satisfied the read request, we'll still try to fill the buffer. // This ensures there's data in the pipe on the next call if (_readBufLen == 0) { if (_eofFound) { break; } // decode the next frame (update _readBufXXX) var frame = _reader.NextFrame(); if (frame == null) { _eofFound = true; break; } try { _readBufLen = _decoder.DecodeFrame(frame, _readBuf, 0) * sizeof(float); _readBufOfs = 0; } catch (System.IO.InvalidDataException) { // bad frame... try again... _decoder.Reset(); _readBufOfs = _readBufLen = 0; continue; } catch (System.IO.EndOfStreamException) { // no more frames _eofFound = true; break; } finally { frame.ClearBuffer(); } } } } return cnt; } } }