Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 392 lines (317 sloc) 11.184 kb
01499fe @renaudbedard Ogg streamingp playback implementation for OpenTK
authored
1 using System;
2 using System.Diagnostics;
9a14370 @renaudbedard Stream constructor instead of (or in addition to) filename
authored
3 using System.IO;
01499fe @renaudbedard Ogg streamingp playback implementation for OpenTK
authored
4 using System.Threading;
5 using NVorbis;
6 using OpenTK.Audio.OpenAL;
7
8 namespace OggStream
9 {
10 class OggStream : IDisposable
11 {
12 // 2 buffers of 0.5 second (stereo) ready at all times
13 const int BufferCount = 3;
14 const int BufferSize = 44100;
15
16 // in times per second, at most
17 const float StreamingThreadUpdateRate = 10;
18
19 readonly float[] readSampleBuffer = new float[BufferSize];
20 readonly short[] castBuffer = new short[BufferSize];
21
22 readonly int[] alBufferIds;
23 readonly int alSourceId;
24 readonly int alFilterId;
25
26 static readonly XRamExtension xre;
27 static readonly EffectsExtension fxe;
28
29 class ThreadFlow : IDisposable
30 {
31 public bool Cancelled;
32 public bool Finished;
33 public readonly ManualResetEventSlim PauseEvent;
34
35 public ThreadFlow()
36 {
37 PauseEvent = new ManualResetEventSlim(true);
38 }
39
40 public void Dispose()
41 {
42 PauseEvent.Dispose();
43 }
44 }
45
46 ThreadFlow currentFlow;
47 Thread currentThread;
48
9a14370 @renaudbedard Stream constructor instead of (or in addition to) filename
authored
49 Stream underlyingStream;
01499fe @renaudbedard Ogg streamingp playback implementation for OpenTK
authored
50 VorbisReader reader;
51 bool ready;
52
53 public bool IsLooped { get; set; }
54
55 static OggStream()
56 {
57 xre = new XRamExtension();
58 fxe = new EffectsExtension();
59 }
60
9a14370 @renaudbedard Stream constructor instead of (or in addition to) filename
authored
61 public OggStream(string filename) : this(File.OpenRead(filename)) { }
62 public OggStream(Stream stream)
01499fe @renaudbedard Ogg streamingp playback implementation for OpenTK
authored
63 {
64 alBufferIds = AL.GenBuffers(BufferCount);
65 alSourceId = AL.GenSource();
66 Volume = 1;
67 Check();
68
69 if (xre.IsInitialized)
70 {
71 Trace.WriteLine("hardware buffers will be used");
72 xre.SetBufferMode(BufferCount, ref alBufferIds[0], XRamExtension.XRamStorage.Hardware);
73 Check();
74 }
75
76 if (fxe.IsInitialized)
77 {
78 Trace.WriteLine("effects will be used");
79 alFilterId = fxe.GenFilter();
80 fxe.Filter(alFilterId, EfxFilteri.FilterType, (int)EfxFilterType.Lowpass);
81 fxe.Filter(alFilterId, EfxFilterf.LowpassGain, 1);
82 LowPassHFGain = 1;
83 }
84
9a14370 @renaudbedard Stream constructor instead of (or in addition to) filename
authored
85 underlyingStream = stream;
86 Open(precache: true);
01499fe @renaudbedard Ogg streamingp playback implementation for OpenTK
authored
87 }
88
89 void Open(bool precache = false)
90 {
9a14370 @renaudbedard Stream constructor instead of (or in addition to) filename
authored
91 underlyingStream.Seek(0, SeekOrigin.Begin);
92 reader = new VorbisReader(underlyingStream, false);
01499fe @renaudbedard Ogg streamingp playback implementation for OpenTK
authored
93
94 if (precache)
95 {
96 foreach (var buffer in alBufferIds)
97 FillBuffer(buffer);
98 AL.SourceQueueBuffers(alSourceId, BufferCount, alBufferIds);
99 Check();
100 }
101
102 ready = true;
103
104 Trace.WriteLine("streaming is ready");
105 }
106
107 public void Play()
108 {
109 if (AL.GetSourceState(alSourceId) == ALSourceState.Playing)
110 {
111 Trace.WriteLine("stream is already playing");
112 return;
113 }
114
115 if (AL.GetSourceState(alSourceId) == ALSourceState.Paused)
116 {
117 Resume();
118 return;
119 }
120
121 if (currentFlow != null && currentFlow.Finished)
122 Stop();
123
124 if (!ready)
125 Open(precache: true);
126
127 Trace.WriteLine("starting playback");
128 AL.SourcePlay(alSourceId);
129 Check();
130
131 currentThread = new Thread(() => EnsureBuffersFilled(currentFlow = new ThreadFlow()));
132 currentThread.Priority = ThreadPriority.Lowest;
133 currentThread.Start();
134 }
135
136 public void Pause()
137 {
138 if (AL.GetSourceState(alSourceId) != ALSourceState.Playing)
139 {
140 Trace.WriteLine("stream is not playing");
141 return;
142 }
143
144 currentFlow.PauseEvent.Reset();
145
146 Trace.WriteLine("pausing playback");
147 AL.SourcePause(alSourceId);
148 Check();
149 }
150
151 public void Resume()
152 {
153 if (AL.GetSourceState(alSourceId) != ALSourceState.Paused)
154 {
155 Trace.WriteLine("stream is not paused");
156 return;
157 }
158
159 currentFlow.PauseEvent.Set();
160
161 Trace.WriteLine("resuming playback");
162 AL.SourcePlay(alSourceId);
163 Check();
164 }
165
166 public void Stop()
167 {
168 var state = AL.GetSourceState(alSourceId);
169 if (state == ALSourceState.Playing || state == ALSourceState.Paused)
170 {
171 Trace.WriteLine("stopping playback");
172 StopPlayback();
173 }
174
175 if (currentFlow != null)
176 StopStreaming();
177
178 if (state != ALSourceState.Initial)
179 Empty();
180 Close();
181 }
182
183 public float LowPassHFGain
184 {
185 set
186 {
187 if (fxe.IsInitialized)
188 {
189 fxe.Filter(alFilterId, EfxFilterf.LowpassGainHF, value);
190 fxe.BindFilterToSource(alSourceId, alFilterId);
191 Check();
192 }
193 }
194 }
195
196 float volume;
197 public float Volume
198 {
199 get { return volume; }
200 set { AL.Source(alSourceId, ALSourcef.Gain, volume = value); }
201 }
202
203 public void Dispose()
204 {
205 Trace.WriteLine("disposing stream");
206
207 var state = AL.GetSourceState(alSourceId);
208 if (state == ALSourceState.Playing || state == ALSourceState.Paused)
209 StopPlayback();
210
211 if (currentFlow != null)
212 StopStreaming(join: true);
213
214 if (state != ALSourceState.Initial)
215 Empty();
216
217 Close();
218
9a14370 @renaudbedard Stream constructor instead of (or in addition to) filename
authored
219 underlyingStream.Dispose();
220
01499fe @renaudbedard Ogg streamingp playback implementation for OpenTK
authored
221 AL.DeleteSource(alSourceId);
222 AL.DeleteBuffers(alBufferIds);
223
224 if (fxe.IsInitialized)
225 fxe.DeleteFilter(alFilterId);
226
227 Check();
228 }
229
230 void StopPlayback()
231 {
232 AL.SourceStop(alSourceId);
233 Check();
234 }
235
236 void StopStreaming(bool join = false)
237 {
238 currentFlow.Cancelled = true;
239 currentFlow.PauseEvent.Set();
240 currentFlow = null;
241
242 if (join)
243 currentThread.Join();
244
245 currentThread = null;
246 }
247
248 void Empty()
249 {
250 int queued;
251
252 AL.GetSource(alSourceId, ALGetSourcei.BuffersQueued, out queued);
253 if (queued > 0)
254 {
255 AL.SourceUnqueueBuffers(alSourceId, queued);
256 Check();
257 }
258 }
259
260 void Close()
261 {
262 if (reader != null)
263 {
264 reader.Dispose();
265 reader = null;
266 }
267 ready = false;
268 }
269
270 bool FillBuffer(int bufferId)
271 {
272 var readSamples = reader.ReadSamples(readSampleBuffer, 0, BufferSize);
273 CastBuffer(readSampleBuffer, castBuffer, readSamples);
274 AL.BufferData(bufferId, reader.Channels == 1 ? ALFormat.Mono16 : ALFormat.Stereo16, castBuffer,
275 readSamples * sizeof (short), reader.SampleRate);
276 Check();
277
278 if (readSamples != BufferSize)
279 Trace.WriteLine("eos detected; only " + readSamples + " samples found");
280 TraceMemoryUsage();
281
282 return readSamples != BufferSize;
283 }
284
285 [Conditional("TRACE")]
286 static void TraceMemoryUsage()
287 {
288 var usedHeap = (double)GC.GetTotalMemory(true);
289
290 string[] sizes = { "B", "KB", "MB", "GB" };
291 int order = 0;
292 while (usedHeap >= 1024 && order + 1 < sizes.Length)
293 {
294 order++;
295 usedHeap = usedHeap / 1024;
296 }
297
298 Trace.WriteLine(String.Format("memory used : {0:0.###} {1}", usedHeap, sizes[order]));
299 }
300
301 static void CastBuffer(float[] inBuffer, short[] outBuffer, int length)
302 {
303 for (int i = 0; i < length; i++)
304 {
305 var temp = (int) (32767f * inBuffer[i]);
306 if (temp > short.MaxValue) temp = short.MaxValue;
307 else if (temp < short.MinValue) temp = short.MinValue;
308 outBuffer[i] = (short)temp;
309 }
310 }
311
312 static void Check()
313 {
314 ALError error;
315 if ((error = AL.GetError()) != ALError.NoError)
316 throw new InvalidOperationException(AL.GetErrorString(error));
317 }
318
319 void EnsureBuffersFilled(ThreadFlow flow)
320 {
321 bool finished = false;
322
323 while (!finished)
324 {
325 flow.PauseEvent.Wait();
326 if (flow.Cancelled)
327 {
328 Trace.WriteLine("streaming cancelled");
329 flow.Dispose();
330 return;
331 }
332
333 Thread.Sleep((int) (1000 / StreamingThreadUpdateRate));
334
335 flow.PauseEvent.Wait();
336 if (flow.Cancelled)
337 {
338 Trace.WriteLine("streaming cancelled");
339 flow.Dispose();
340 return;
341 }
342
343 try
344 {
345 int processed;
346 AL.GetSource(alSourceId, ALGetSourcei.BuffersProcessed, out processed);
347 Check();
348
349 if (processed == 0)
350 continue;
351
352 var tempBuffers = AL.SourceUnqueueBuffers(alSourceId, processed);
353
354 for (int i = 0; i < processed; i++)
355 {
356 finished |= FillBuffer(tempBuffers[i]);
357
358 if (finished && IsLooped)
359 {
360 finished = false;
361 Close();
362 Open();
363 }
364
365 Trace.WriteLine("buffer " + tempBuffers[i] + " refilled");
366 }
367
368 AL.SourceQueueBuffers(alSourceId, processed, tempBuffers);
369 Check();
370
371 var state = AL.GetSourceState(alSourceId);
372 if (state == ALSourceState.Stopped)
373 {
374 Trace.WriteLine("buffer underrun detected! restarting playback");
375 AL.SourcePlay(alSourceId);
376 Check();
377 }
378 }
379 catch (Exception ex)
380 {
381 if (!flow.Cancelled)
382 throw ex;
383 Trace.WriteLine("cancellation caused an error : " + ex.Message);
384 }
385 }
386
387 flow.Finished = true;
388 Trace.WriteLine("streaming complete");
389 }
390 }
391 }
Something went wrong with that request. Please try again.