1 /****************************************************************************
3 * Copyright (C) 2014, Andrew Ward <afward@gmail.com> *
5 * See COPYING for license terms (Ms-PL). *
7 ***************************************************************************/
9 using System.Collections.Generic;
13 using System.Diagnostics;
17 public class VorbisReader : IDisposable
21 IContainerReader _containerReader;
22 List<VorbisStreamDecoder> _decoders;
29 _decoders = new List<VorbisStreamDecoder>();
30 _serials = new List<int>();
34 public VorbisReader(string fileName)
35 : this(File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read), true)
39 public VorbisReader(Stream stream, bool closeStreamOnDispose)
42 var bufferedStream = new BufferedReadStream(stream);
43 bufferedStream.CloseBaseStream = closeStreamOnDispose;
46 var oggContainer = new Ogg.ContainerReader(bufferedStream, closeStreamOnDispose);
47 if (!LoadContainer(oggContainer))
50 // we don't support any other container types yet, so error out
51 // TODO: Add Matroska fallback
52 bufferedStream.Close();
53 throw new InvalidDataException("Could not determine container type!!");
55 _containerReader = oggContainer;
57 if (_decoders.Count == 0) throw new InvalidDataException("No Vorbis data found!");
60 public VorbisReader(IContainerReader containerReader)
63 if (!LoadContainer(containerReader))
65 throw new InvalidDataException("Container did not initialize!");
67 _containerReader = containerReader;
69 if (_decoders.Count == 0) throw new InvalidDataException("No Vorbis data found!");
72 public VorbisReader(IPacketProvider packetProvider)
75 var ea = new NewStreamEventArgs(packetProvider);
77 if (ea.IgnoreStream) throw new InvalidDataException("No Vorbis data found!");
80 bool LoadContainer(IContainerReader containerReader)
82 containerReader.NewStream += NewStream;
83 if (!containerReader.Init())
85 containerReader.NewStream -= NewStream;
91 void NewStream(object sender, NewStreamEventArgs ea)
93 var packetProvider = ea.PacketProvider;
94 var decoder = new VorbisStreamDecoder(packetProvider);
95 if (decoder.TryInit())
97 _decoders.Add(decoder);
98 _serials.Add(packetProvider.StreamSerial);
102 // This is almost certainly not a Vorbis stream
103 ea.IgnoreStream = true;
107 public void Dispose()
109 if (_decoders != null)
111 foreach (var decoder in _decoders)
119 if (_containerReader != null)
121 _containerReader.NewStream -= NewStream;
122 _containerReader.Dispose();
123 _containerReader = null;
127 VorbisStreamDecoder ActiveDecoder
131 if (_decoders == null) throw new ObjectDisposedException("VorbisReader");
132 return _decoders[_streamIdx];
136 #region Public Interface
139 /// Gets the number of channels in the current selected Vorbis stream
141 public int Channels { get { return ActiveDecoder._channels; } }
144 /// Gets the sample rate of the current selected Vorbis stream
146 public int SampleRate { get { return ActiveDecoder._sampleRate; } }
149 /// Gets the encoder's upper bitrate of the current selected Vorbis stream
151 public int UpperBitrate { get { return ActiveDecoder._upperBitrate; } }
154 /// Gets the encoder's nominal bitrate of the current selected Vorbis stream
156 public int NominalBitrate { get { return ActiveDecoder._nominalBitrate; } }
159 /// Gets the encoder's lower bitrate of the current selected Vorbis stream
161 public int LowerBitrate { get { return ActiveDecoder._lowerBitrate; } }
164 /// Gets the encoder's vendor string for the current selected Vorbis stream
166 public string Vendor { get { return ActiveDecoder._vendor; } }
169 /// Gets the comments in the current selected Vorbis stream
171 public string[] Comments { get { return ActiveDecoder._comments; } }
174 /// Gets whether the previous short sample count was due to a parameter change in the stream.
176 public bool IsParameterChange { get { return ActiveDecoder.IsParameterChange; } }
179 /// Gets the number of bits read that are related to framing and transport alone
181 public long ContainerOverheadBits { get { return ActiveDecoder.ContainerBits; } }
184 /// Gets or sets whether to automatically apply clipping to samples returned by <see cref="VorbisReader.ReadSamples"/>.
186 public bool ClipSamples { get; set; }
189 /// Gets stats from each decoder stream available
191 public IVorbisStreamStatus[] Stats
193 get { return _decoders.Select(d => d).Cast<IVorbisStreamStatus>().ToArray(); }
197 /// Gets the currently-selected stream's index
199 public int StreamIndex
201 get { return _streamIdx; }
205 /// Reads decoded samples from the current logical stream
207 /// <param name="buffer">The buffer to write the samples to</param>
208 /// <param name="offset">The offset into the buffer to write the samples to</param>
209 /// <param name="count">The number of samples to write</param>
210 /// <returns>The number of samples written</returns>
211 public int ReadSamples(float[] buffer, int offset, int count)
213 if (offset < 0) throw new ArgumentOutOfRangeException("offset");
214 if (count < 0 || offset + count > buffer.Length) throw new ArgumentOutOfRangeException("count");
216 count = ActiveDecoder.ReadSamples(buffer, offset, count);
220 var decoder = _decoders[_streamIdx];
221 for (int i = 0; i < count; i++, offset++)
223 buffer[offset] = Utils.ClipValue(buffer[offset], ref decoder._clipped);
231 /// Clears the parameter change flag so further samples can be requested.
233 public void ClearParameterChange()
235 ActiveDecoder.IsParameterChange = false;
239 /// Returns the number of logical streams found so far in the physical container
241 public int StreamCount
243 get { return _decoders.Count; }
247 /// Searches for the next stream in a concatenated file
249 /// <returns><c>True</c> if a new stream was found, otherwise <c>false</c>.</returns>
250 public bool FindNextStream()
252 if (_containerReader == null) return false;
253 return _containerReader.FindNextStream();
257 /// Switches to an alternate logical stream.
259 /// <param name="index">The logical stream index to switch to</param>
260 /// <returns><c>True</c> if the properties of the logical stream differ from those of the one previously being decoded. Otherwise, <c>False</c>.</returns>
261 public bool SwitchStreams(int index)
263 if (index < 0 || index >= StreamCount) throw new ArgumentOutOfRangeException("index");
265 if (_decoders == null) throw new ObjectDisposedException("VorbisReader");
267 if (_streamIdx == index) return false;
269 var curDecoder = _decoders[_streamIdx];
271 var newDecoder = _decoders[_streamIdx];
273 return curDecoder._channels != newDecoder._channels || curDecoder._sampleRate != newDecoder._sampleRate;
277 /// Gets or Sets the current timestamp of the decoder. Is the timestamp before the next sample to be decoded
279 public TimeSpan DecodedTime
283 return TimeSpan.FromSeconds((double)ActiveDecoder.CurrentPosition / SampleRate);
287 ActiveDecoder.SeekTo((long)(value.TotalSeconds * SampleRate));
293 /// Gets or Sets the current position of the next sample to be decoded.
295 public long DecodedPosition
299 return ActiveDecoder.CurrentPosition;
303 ActiveDecoder.SeekTo(value);
308 /// Gets the total length of the current logical stream
310 public TimeSpan TotalTime
314 var decoder = ActiveDecoder;
317 return TimeSpan.FromSeconds((double)decoder.GetLastGranulePos() / decoder._sampleRate);
321 return TimeSpan.MaxValue;
326 public long TotalSamples
330 var decoder = ActiveDecoder;
333 return decoder.GetLastGranulePos();
337 return long.MaxValue;