changed: Add logic to properly handle subtitles for stacked files
[vuplus_xbmc] / xbmc / cores / AudioEngine / Engines / PulseAE / PulseAEStream.cpp
1 /*
2  *      Copyright (C) 2010-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "system.h"
22 #ifdef HAS_PULSEAUDIO
23
24 #include "PulseAEStream.h"
25 #include "AEFactory.h"
26 #include "Utils/AEUtil.h"
27 #include "utils/log.h"
28 #include "utils/MathUtils.h"
29 #include "threads/SingleLock.h"
30
31 static const char *StreamStateToString(pa_stream_state s)
32 {
33   switch(s)
34   {
35     case PA_STREAM_UNCONNECTED:
36       return "unconnected";
37     case PA_STREAM_CREATING:
38       return "creating";
39     case PA_STREAM_READY:
40       return "ready";
41     case PA_STREAM_FAILED:
42       return "failed";
43     case PA_STREAM_TERMINATED:
44       return "terminated";
45     default:
46       return "none";
47   }
48 }
49
50 CPulseAEStream::CPulseAEStream(pa_context *context, pa_threaded_mainloop *mainLoop, enum AEDataFormat format, unsigned int sampleRate, CAEChannelInfo channelLayout, unsigned int options) : m_fader(this)
51 {
52   ASSERT(channelLayout.Count());
53   m_Destroyed = false;
54   m_Initialized = false;
55   m_Paused = false;
56   m_ResumeCallback = false;
57
58   m_Stream = NULL;
59   m_Context = context;
60   m_MainLoop = mainLoop;
61
62   m_format = format;
63   m_sampleRate = sampleRate;
64   m_channelLayout = channelLayout;
65   m_options = options;
66
67   m_DrainOperation = NULL;
68   m_slave = NULL;
69
70   pa_threaded_mainloop_lock(m_MainLoop);
71
72   m_SampleSpec.channels = channelLayout.Count();
73   m_SampleSpec.rate = m_sampleRate;
74
75   switch (m_format)
76   {
77     case AE_FMT_U8    : m_SampleSpec.format = PA_SAMPLE_U8; break;
78     case AE_FMT_S16NE : m_SampleSpec.format = PA_SAMPLE_S16NE; break;
79     case AE_FMT_S16LE : m_SampleSpec.format = PA_SAMPLE_S16LE; break;
80     case AE_FMT_S16BE : m_SampleSpec.format = PA_SAMPLE_S16BE; break;
81     case AE_FMT_S24NE3: m_SampleSpec.format = PA_SAMPLE_S24NE; break;
82     case AE_FMT_S24NE4: m_SampleSpec.format = PA_SAMPLE_S24_32NE; break;
83     case AE_FMT_S32NE : m_SampleSpec.format = PA_SAMPLE_S32NE; break;
84     case AE_FMT_S32LE : m_SampleSpec.format = PA_SAMPLE_S32LE; break;
85     case AE_FMT_S32BE : m_SampleSpec.format = PA_SAMPLE_S32BE; break;
86     case AE_FMT_FLOAT : m_SampleSpec.format = PA_SAMPLE_FLOAT32NE; break;
87 #if PA_CHECK_VERSION(1,0,0)
88     case AE_FMT_DTS   :
89     case AE_FMT_EAC3  :
90     case AE_FMT_AC3   : m_SampleSpec.format = PA_SAMPLE_S16NE; break;
91 #endif
92
93     default:
94       CLog::Log(LOGERROR, "PulseAudio: Invalid format %i", format);
95       pa_threaded_mainloop_unlock(m_MainLoop);
96       m_format = AE_FMT_INVALID;
97       return;
98   }
99
100   if (!pa_sample_spec_valid(&m_SampleSpec))
101   {
102     CLog::Log(LOGERROR, "PulseAudio: Invalid sample spec");
103     pa_threaded_mainloop_unlock(m_MainLoop);
104     Destroy();
105     return /*false*/;
106   }
107
108   m_frameSize = pa_frame_size(&m_SampleSpec);
109
110   struct pa_channel_map map;
111   map.channels = m_channelLayout.Count();
112
113   for (unsigned int ch = 0; ch < m_channelLayout.Count(); ++ch)
114     switch(m_channelLayout[ch])
115     {
116       case AE_CH_NULL: break;
117       case AE_CH_MAX : break;
118       case AE_CH_RAW : break;
119       case AE_CH_FL  : map.map[ch] = PA_CHANNEL_POSITION_FRONT_LEFT           ; break;
120       case AE_CH_FR  : map.map[ch] = PA_CHANNEL_POSITION_FRONT_RIGHT          ; break;
121       case AE_CH_FC  : map.map[ch] = PA_CHANNEL_POSITION_FRONT_CENTER         ; break;
122       case AE_CH_BC  : map.map[ch] = PA_CHANNEL_POSITION_REAR_CENTER          ; break;
123       case AE_CH_BL  : map.map[ch] = PA_CHANNEL_POSITION_REAR_LEFT            ; break;
124       case AE_CH_BR  : map.map[ch] = PA_CHANNEL_POSITION_REAR_RIGHT           ; break;
125       case AE_CH_LFE : map.map[ch] = PA_CHANNEL_POSITION_LFE                  ; break;
126       case AE_CH_FLOC: map.map[ch] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER ; break;
127       case AE_CH_FROC: map.map[ch] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; break;
128       case AE_CH_SL  : map.map[ch] = PA_CHANNEL_POSITION_SIDE_LEFT            ; break;
129       case AE_CH_SR  : map.map[ch] = PA_CHANNEL_POSITION_SIDE_RIGHT           ; break;
130       case AE_CH_TC  : map.map[ch] = PA_CHANNEL_POSITION_TOP_CENTER           ; break;
131       case AE_CH_TFL : map.map[ch] = PA_CHANNEL_POSITION_TOP_FRONT_LEFT       ; break;
132       case AE_CH_TFR : map.map[ch] = PA_CHANNEL_POSITION_TOP_FRONT_RIGHT      ; break;
133       case AE_CH_TFC : map.map[ch] = PA_CHANNEL_POSITION_TOP_CENTER           ; break;
134       case AE_CH_TBL : map.map[ch] = PA_CHANNEL_POSITION_TOP_REAR_LEFT        ; break;
135       case AE_CH_TBR : map.map[ch] = PA_CHANNEL_POSITION_TOP_REAR_RIGHT       ; break;
136       case AE_CH_TBC : map.map[ch] = PA_CHANNEL_POSITION_TOP_REAR_CENTER      ; break;
137       default: break;
138     }
139
140   m_MaxVolume     = CAEFactory::GetEngine()->GetVolume();
141   m_Volume        = 1.0f;
142   pa_volume_t paVolume = pa_sw_volume_from_linear((double)(m_Volume * m_MaxVolume));
143   pa_cvolume_set(&m_ChVolume, m_SampleSpec.channels, paVolume);
144
145 #if PA_CHECK_VERSION(1,0,0)
146   pa_format_info *info[1];
147   info[0] = pa_format_info_new();
148   switch(m_format)
149   {
150     case AE_FMT_DTS : info[0]->encoding = PA_ENCODING_DTS_IEC61937 ; break;
151     case AE_FMT_EAC3: info[0]->encoding = PA_ENCODING_EAC3_IEC61937; break;
152     case AE_FMT_AC3 : info[0]->encoding = PA_ENCODING_AC3_IEC61937 ; break;
153     default:          info[0]->encoding = PA_ENCODING_PCM          ; break;
154   }
155   pa_format_info_set_rate         (info[0], m_SampleSpec.rate);
156   pa_format_info_set_channels     (info[0], m_SampleSpec.channels);
157   pa_format_info_set_channel_map  (info[0], &map);
158   pa_format_info_set_sample_format(info[0], m_SampleSpec.format);
159   m_Stream = pa_stream_new_extended(m_Context, "audio stream", info, 1, NULL);
160   pa_format_info_free(info[0]);
161 #else
162   m_Stream = pa_stream_new(m_Context, "audio stream", &m_SampleSpec, &map);
163 #endif
164
165   if (m_Stream == NULL)
166   {
167     CLog::Log(LOGERROR, "PulseAudio: Could not create a stream");
168     pa_threaded_mainloop_unlock(m_MainLoop);
169     Destroy();
170     return /*false*/;
171   }
172
173   pa_stream_set_state_callback(m_Stream, CPulseAEStream::StreamStateCallback, this);
174   pa_stream_set_write_callback(m_Stream, CPulseAEStream::StreamRequestCallback, this);
175   pa_stream_set_latency_update_callback(m_Stream, CPulseAEStream::StreamLatencyUpdateCallback, this);
176   pa_stream_set_underflow_callback(m_Stream, CPulseAEStream::StreamUnderflowCallback, this);
177
178   int flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE;
179   if (options && AESTREAM_FORCE_RESAMPLE)
180     flags |= PA_STREAM_VARIABLE_RATE;
181
182   if (pa_stream_connect_playback(m_Stream, NULL, NULL, (pa_stream_flags)flags, &m_ChVolume, NULL) < 0)
183   {
184     CLog::Log(LOGERROR, "PulseAudio: Failed to connect stream to output");
185     pa_threaded_mainloop_unlock(m_MainLoop);
186     Destroy();
187     return /*false*/;
188   }
189
190   /* Wait until the stream is ready */
191   do
192   {
193     pa_threaded_mainloop_wait(m_MainLoop);
194     CLog::Log(LOGDEBUG, "PulseAudio: Stream %s", StreamStateToString(pa_stream_get_state(m_Stream)));
195   }
196   while (pa_stream_get_state(m_Stream) != PA_STREAM_READY && pa_stream_get_state(m_Stream) != PA_STREAM_FAILED);
197
198   if (pa_stream_get_state(m_Stream) == PA_STREAM_FAILED)
199   {
200     CLog::Log(LOGERROR, "PulseAudio: Waited for the stream but it failed");
201     pa_threaded_mainloop_unlock(m_MainLoop);
202     Destroy();
203     return /*false*/;
204   }
205
206   const pa_buffer_attr *streamBuffer;
207   streamBuffer = pa_stream_get_buffer_attr(m_Stream);
208   m_cacheSize = streamBuffer->maxlength;
209
210   pa_threaded_mainloop_unlock(m_MainLoop);
211
212   m_Initialized = true;
213
214   CLog::Log(LOGINFO, "PulseAEStream::Initialized");
215   CLog::Log(LOGINFO, "  Sample Rate   : %d", m_sampleRate);
216   CLog::Log(LOGINFO, "  Sample Format : %s", CAEUtil::DataFormatToStr(m_format));
217   CLog::Log(LOGINFO, "  Channel Count : %d", m_channelLayout.Count());
218   CLog::Log(LOGINFO, "  Channel Layout: %s", ((std::string)m_channelLayout).c_str());
219   CLog::Log(LOGINFO, "  Frame Size    : %d", m_frameSize);
220   CLog::Log(LOGINFO, "  Cache Size    : %d", m_cacheSize);
221
222   Resume();
223
224   return /*true*/;
225 }
226
227 CPulseAEStream::~CPulseAEStream()
228 {
229   Destroy();
230 }
231
232 /*
233   this method may be called inside the pulse main loop,
234   so be VERY careful with locking
235 */
236 void CPulseAEStream::Destroy()
237 {
238   if (!m_Initialized)
239     return;
240
241   if (m_Destroyed)
242     return;
243
244   m_fader.StopThread(true);
245
246   pa_threaded_mainloop_lock(m_MainLoop);
247
248   if (m_DrainOperation)
249   {
250     pa_operation_cancel(m_DrainOperation);
251     pa_operation_unref(m_DrainOperation);
252     m_DrainOperation = NULL;
253   }
254
255   if (m_Stream)
256   {
257     pa_stream_set_state_callback(m_Stream, NULL, NULL);
258     pa_stream_set_write_callback(m_Stream, NULL, NULL);
259     pa_stream_set_latency_update_callback(m_Stream, NULL, NULL);
260     pa_stream_set_underflow_callback(m_Stream, NULL, NULL);
261     pa_stream_disconnect(m_Stream);
262     pa_stream_unref(m_Stream);
263     m_Stream = NULL;
264   }
265
266   /* signal CPulseAE to free us */
267   m_Destroyed = true;
268   m_Initialized = false;
269
270   pa_threaded_mainloop_unlock(m_MainLoop);
271 }
272
273 unsigned int CPulseAEStream::GetSpace()
274 {
275   if (!m_Initialized)
276     return 0;
277
278   pa_threaded_mainloop_lock(m_MainLoop);
279   unsigned int size = pa_stream_writable_size(m_Stream);
280   pa_threaded_mainloop_unlock(m_MainLoop);
281
282   if(size > m_cacheSize)
283     m_cacheSize = size;
284
285   return size;
286 }
287
288 unsigned int CPulseAEStream::AddData(void *data, unsigned int size)
289 {
290   if (!m_Initialized)
291     return size;
292
293   pa_threaded_mainloop_lock(m_MainLoop);
294
295   int length = std::min((int)pa_stream_writable_size(m_Stream), (int)size);
296   if (length == 0)
297   {
298     pa_threaded_mainloop_unlock(m_MainLoop);
299     return 0;
300   }
301
302   int written = pa_stream_write(m_Stream, data, length, NULL, 0, PA_SEEK_RELATIVE);
303   pa_threaded_mainloop_unlock(m_MainLoop);
304
305   if (written < 0)
306   {
307     CLog::Log(LOGERROR, "PulseAudio: AddPackets - pa_stream_write failed\n");
308     return 0;
309   }
310
311   return length;
312 }
313
314 double CPulseAEStream::GetDelay()
315 {
316   if (!m_Initialized)
317     return 0.0;
318
319   pa_usec_t latency = 0;
320   pa_threaded_mainloop_lock(m_MainLoop);
321
322   if (pa_stream_get_latency(m_Stream, &latency, NULL) == PA_ERR_NODATA)
323     CLog::Log(LOGERROR, "PulseAudio: pa_stream_get_latency() failed");
324
325   pa_threaded_mainloop_unlock(m_MainLoop);
326   return (double)((double)latency / 1000000.0);
327 }
328
329 double CPulseAEStream::GetCacheTime()
330 {
331   if (!m_Initialized)
332     return 0.0;
333
334   return (double)(m_cacheSize - GetSpace()) / (double)(m_sampleRate * m_frameSize);
335 }
336
337 double CPulseAEStream::GetCacheTotal()
338 {
339   if (!m_Initialized)
340     return 0.0;
341
342   return (double)m_cacheSize / (double)(m_sampleRate * m_frameSize);
343 }
344
345 bool CPulseAEStream::IsPaused()
346 {
347   return m_Paused;
348 }
349
350 bool CPulseAEStream::IsDraining()
351 {
352   if (m_DrainOperation)
353   {
354     if (pa_operation_get_state(m_DrainOperation) == PA_OPERATION_RUNNING)
355       return true;
356
357     pa_operation_unref(m_DrainOperation);
358     m_DrainOperation = NULL;
359   }
360   ProcessCallbacks();
361   return false;
362 }
363
364 bool CPulseAEStream::IsDrained()
365 {
366   bool ret = (m_DrainOperation == NULL);
367   ProcessCallbacks();
368
369   return ret;
370 }
371
372 bool CPulseAEStream::IsDestroyed()
373 {
374   return m_Destroyed;
375 }
376
377 void CPulseAEStream::Pause()
378 {
379   if (m_Initialized)
380     m_Paused = Cork(true);
381 }
382
383 void CPulseAEStream::Resume()
384 {
385   if (m_Initialized)
386     m_Paused = Cork(false);
387 }
388
389 void CPulseAEStream::Drain(bool wait)
390 {
391   if (!m_Initialized)
392     return;
393
394   if (m_DrainOperation)
395     return;
396
397   pa_threaded_mainloop_lock(m_MainLoop);
398   m_DrainOperation = pa_stream_drain(m_Stream, CPulseAEStream::StreamDrainComplete, this);
399   pa_threaded_mainloop_unlock(m_MainLoop);
400 }
401
402 void CPulseAEStream::Flush()
403 {
404   if (!m_Initialized)
405     return;
406
407   pa_threaded_mainloop_lock(m_MainLoop);
408   pa_operation_unref(pa_stream_flush(m_Stream, NULL, NULL));
409   pa_threaded_mainloop_unlock(m_MainLoop);
410 }
411
412 float CPulseAEStream::GetVolume()
413 {
414   return m_Volume;
415 }
416
417 float CPulseAEStream::GetReplayGain()
418 {
419   return 0.0f;
420 }
421
422 void CPulseAEStream::SetVolume(float volume)
423 {
424   if (!m_Initialized)
425     return;
426
427   if (!pa_threaded_mainloop_in_thread(m_MainLoop))
428     pa_threaded_mainloop_lock(m_MainLoop);
429
430   if (volume > 0.f)
431   {
432     m_Volume = volume;
433     pa_volume_t paVolume = pa_sw_volume_from_linear((double)(m_Volume * m_MaxVolume));
434
435     pa_cvolume_set(&m_ChVolume, m_SampleSpec.channels, paVolume);
436   } 
437   else
438     pa_cvolume_mute(&m_ChVolume,m_SampleSpec.channels);
439
440   pa_operation *op = pa_context_set_sink_input_volume(m_Context, pa_stream_get_index(m_Stream), &m_ChVolume, NULL, NULL);
441
442   if (op == NULL)
443     CLog::Log(LOGERROR, "PulseAudio: Failed to set volume");
444   else
445     pa_operation_unref(op);
446
447   if (!pa_threaded_mainloop_in_thread(m_MainLoop))
448     pa_threaded_mainloop_unlock(m_MainLoop);
449 }
450
451 void CPulseAEStream::UpdateVolume(float max)
452 {
453    if (!m_Initialized)
454     return;
455
456   m_MaxVolume = max;
457   SetVolume(m_Volume);
458 }
459
460 void CPulseAEStream::SetMute(const bool mute)
461 {
462   if (mute)
463     SetVolume(-1.f);
464   else
465     SetVolume(m_Volume);
466 }
467
468 void CPulseAEStream::SetReplayGain(float factor)
469 {
470 }
471
472 const unsigned int CPulseAEStream::GetFrameSize() const
473 {
474   return m_frameSize;
475 }
476
477 const unsigned int CPulseAEStream::GetChannelCount() const
478 {
479   return m_channelLayout.Count();
480 }
481
482 const unsigned int CPulseAEStream::GetSampleRate() const
483 {
484   return m_sampleRate;
485 }
486
487 const enum AEDataFormat CPulseAEStream::GetDataFormat() const
488 {
489   return m_format;
490 }
491
492 double CPulseAEStream::GetResampleRatio()
493 {
494   return 1.0;
495 }
496
497 bool CPulseAEStream::SetResampleRatio(double ratio)
498 {
499   return false;
500 }
501
502 void CPulseAEStream::RegisterAudioCallback(IAudioCallback* pCallback)
503 {
504   m_AudioCallback = pCallback;
505 }
506
507 void CPulseAEStream::UnRegisterAudioCallback()
508 {
509   m_AudioCallback = NULL;
510 }
511
512 void CPulseAEStream::FadeVolume(float from, float target, unsigned int time)
513 {
514   if (!m_Initialized)
515     return;
516
517   m_fader.SetupFader(from, target, time);
518 }
519
520 bool CPulseAEStream::IsFading()
521 {
522   return m_fader.IsRunning();
523 }
524
525 void CPulseAEStream::StreamRequestCallback(pa_stream *s, size_t length, void *userdata)
526 {
527   CPulseAEStream *stream = (CPulseAEStream *)userdata;
528   pa_threaded_mainloop_signal(stream->m_MainLoop, 0);
529 }
530
531 void CPulseAEStream::StreamLatencyUpdateCallback(pa_stream *s, void *userdata)
532 {
533   CPulseAEStream *stream = (CPulseAEStream *)userdata;
534   pa_threaded_mainloop_signal(stream->m_MainLoop, 0);
535 }
536
537 void CPulseAEStream::StreamStateCallback(pa_stream *s, void *userdata)
538 {
539   CPulseAEStream *stream = (CPulseAEStream *)userdata;
540   pa_stream_state_t state = pa_stream_get_state(s);
541
542   switch (state)
543   {
544     case PA_STREAM_UNCONNECTED:
545     case PA_STREAM_CREATING:
546     case PA_STREAM_READY:
547     case PA_STREAM_FAILED:
548     case PA_STREAM_TERMINATED:
549       pa_threaded_mainloop_signal(stream->m_MainLoop, 0);
550       break;
551   }
552 }
553
554 void CPulseAEStream::StreamUnderflowCallback(pa_stream *s, void *userdata)
555 {
556   CPulseAEStream *stream = (CPulseAEStream *)userdata;
557   CLog::Log(LOGWARNING, "PulseAudio: Stream underflow");
558   pa_threaded_mainloop_signal(stream->m_MainLoop, 0);
559 }
560
561 void CPulseAEStream::StreamDrainComplete(pa_stream *s, int success, void *userdata)
562 {
563   CPulseAEStream *stream = (CPulseAEStream *)userdata;
564   if(stream)
565   {
566     stream->SetDrained();
567     pa_threaded_mainloop_signal(stream->m_MainLoop, 0);
568   }
569 }
570
571 void CPulseAEStream::ProcessCallbacks()
572 {
573   if(m_ResumeCallback && m_slave)
574     m_slave->Resume();
575
576   m_ResumeCallback = false;
577 }
578
579 inline bool CPulseAEStream::WaitForOperation(pa_operation *op, pa_threaded_mainloop *mainloop, const char *LogEntry = "")
580 {
581   if (op == NULL)
582     return false;
583
584   bool sucess = true;
585   ASSERT(!pa_threaded_mainloop_in_thread(mainloop));
586
587   while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
588     pa_threaded_mainloop_wait(mainloop);
589
590   if (pa_operation_get_state(op) != PA_OPERATION_DONE)
591   {
592     CLog::Log(LOGERROR, "PulseAudio: %s Operation failed", LogEntry);
593     sucess = false;
594   }
595
596   pa_operation_unref(op);
597   return sucess;
598 }
599
600 bool CPulseAEStream::Cork(bool cork)
601 {
602   pa_threaded_mainloop_lock(m_MainLoop);
603
604   pa_operation *op = pa_stream_cork(m_Stream, cork ? 1 : 0, NULL, NULL);
605   if (!WaitForOperation(op, m_MainLoop, cork ? "Pause" : "Resume"))
606     cork = !cork;
607
608   pa_threaded_mainloop_unlock(m_MainLoop);
609   return cork;
610 }
611
612 void CPulseAEStream::RegisterSlave(IAEStream *stream)
613 {
614   m_slave = stream;
615 }
616
617 CPulseAEStream::CLinearFader::CLinearFader(IAEStream *stream) : CThread("AEStream"), m_stream(stream)
618 {
619   m_from = 0;
620   m_target = 0;
621   m_time = 0;
622   m_isRunning = false;
623 }
624
625 void CPulseAEStream::CLinearFader::SetupFader(float from, float target, unsigned int time)
626 {
627   StopThread(true);
628
629   m_from = from;
630   m_target = target;
631   m_time = time;
632
633   if (m_time > 0)
634     Create();
635   else
636     m_stream->SetVolume(m_target);
637 }
638
639 void CPulseAEStream::CLinearFader::Process()
640 {
641   if (m_stream == NULL)
642     return;
643
644   m_isRunning = true;
645   m_stream->SetVolume(m_from);
646   float k = m_target - m_from;
647
648   unsigned int begin = XbmcThreads::SystemClockMillis();
649   unsigned int end = begin + m_time;
650   unsigned int current = begin;
651   unsigned int step = std::max(1u, m_time / 100);
652
653   do
654   {
655     float x = ((float)current - (float)begin) / (float)m_time;
656
657     m_stream->SetVolume(m_from + k * x);
658     usleep(step * 1000);
659     current = XbmcThreads::SystemClockMillis();
660   } while (current <= end && !m_bStop);
661
662   m_stream->SetVolume(m_target);
663   m_isRunning = false;
664 }
665
666 bool CPulseAEStream::CLinearFader::IsRunning()
667 {
668   return !m_isRunning;
669 }
670 #endif