Merge pull request #4314 from MartijnKaijser/beta1
[vuplus_xbmc] / xbmc / cores / omxplayer / OMXPlayerAudio.cpp
1 /*
2  *      Copyright (C) 2005-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 #if (defined HAVE_CONFIG_H) && (!defined TARGET_WINDOWS)
22   #include "config.h"
23 #elif defined(TARGET_WINDOWS)
24 #include "system.h"
25 #endif
26
27 #include "OMXPlayerAudio.h"
28
29 #include <stdio.h>
30 #include <unistd.h>
31 #include <iomanip>
32
33 #include "linux/XMemUtils.h"
34 #include "utils/BitstreamStats.h"
35
36 #include "DVDDemuxers/DVDDemuxUtils.h"
37 #include "cores/AudioEngine/Utils/AEUtil.h"
38 #include "utils/MathUtils.h"
39 #include "settings/AdvancedSettings.h"
40 #include "settings/Settings.h"
41 #include "utils/TimeUtils.h"
42
43 #include "OMXPlayer.h"
44 #include "linux/RBP.h"
45 #include "cores/AudioEngine/AEFactory.h"
46
47 #include <iostream>
48 #include <sstream>
49
50 class COMXMsgAudioCodecChange : public CDVDMsg
51 {
52 public:
53   COMXMsgAudioCodecChange(const CDVDStreamInfo &hints, COMXAudioCodecOMX* codec)
54     : CDVDMsg(GENERAL_STREAMCHANGE)
55     , m_codec(codec)
56     , m_hints(hints)
57   {}
58  ~COMXMsgAudioCodecChange()
59   {
60     delete m_codec;
61   }
62   COMXAudioCodecOMX   *m_codec;
63   CDVDStreamInfo      m_hints;
64 };
65
66 OMXPlayerAudio::OMXPlayerAudio(OMXClock *av_clock, CDVDMessageQueue& parent)
67 : CThread("OMXPlayerAudio")
68 , m_messageQueue("audio")
69 , m_messageParent(parent)
70 {
71   m_av_clock      = av_clock;
72   m_pAudioCodec   = NULL;
73   m_speed         = DVD_PLAYSPEED_NORMAL;
74   m_started       = false;
75   m_stalled       = false;
76   m_audioClock    = DVD_NOPTS_VALUE;
77   m_buffer_empty  = false;
78   m_nChannels     = 0;
79   m_DecoderOpen   = false;
80   m_bad_state     = false;
81   m_hints_current.Clear();
82
83   bool small_mem = g_RBP.GetArmMem() < 256;
84   m_messageQueue.SetMaxDataSize((small_mem ? 3:6) * 1024 * 1024);
85
86   m_messageQueue.SetMaxTimeSize(8.0);
87   m_passthrough = false;
88   m_use_hw_decode = false;
89   m_hw_decode = false;
90   m_silence = false;
91   m_flush = false;  
92 }
93
94
95 OMXPlayerAudio::~OMXPlayerAudio()
96 {
97   CloseStream(false);
98 }
99
100 bool OMXPlayerAudio::OpenStream(CDVDStreamInfo &hints)
101 {
102   m_bad_state = false;
103
104   COMXAudioCodecOMX *codec = new COMXAudioCodecOMX();
105
106   if(!codec || !codec->Open(hints))
107   {
108     CLog::Log(LOGERROR, "Unsupported audio codec");
109     delete codec; codec = NULL;
110     return false;
111   }
112
113   if(m_messageQueue.IsInited())
114     m_messageQueue.Put(new COMXMsgAudioCodecChange(hints, codec), 0);
115   else
116   {
117     OpenStream(hints, codec);
118     m_messageQueue.Init();
119     CLog::Log(LOGNOTICE, "Creating audio thread");
120     Create();
121   }
122
123   return true;
124 }
125
126 void OMXPlayerAudio::OpenStream(CDVDStreamInfo &hints, COMXAudioCodecOMX *codec)
127 {
128   SAFE_DELETE(m_pAudioCodec);
129
130   m_hints           = hints;
131   m_pAudioCodec     = codec;
132
133   if(m_hints.bitspersample == 0)
134     m_hints.bitspersample = 16;
135
136   m_speed           = DVD_PLAYSPEED_NORMAL;
137   m_audioClock      = DVD_NOPTS_VALUE;
138   m_hw_decode       = false;
139   m_silence         = false;
140   m_started         = false;
141   m_flush           = false;
142   m_nChannels       = 0;
143   m_stalled         = m_messageQueue.GetPacketCount(CDVDMsg::DEMUXER_PACKET) == 0;
144   m_use_hw_decode   = g_advancedSettings.m_omxHWAudioDecode;
145   m_format.m_dataFormat    = GetDataFormat(m_hints);
146   m_format.m_sampleRate    = 0;
147   m_format.m_channelLayout = 0;
148 }
149
150 bool OMXPlayerAudio::CloseStream(bool bWaitForBuffers)
151 {
152   // wait until buffers are empty
153   if (bWaitForBuffers && m_speed > 0) m_messageQueue.WaitUntilEmpty();
154
155   m_messageQueue.Abort();
156
157   if(IsRunning())
158     StopThread();
159
160   m_messageQueue.End();
161
162   if (m_pAudioCodec)
163   {
164     m_pAudioCodec->Dispose();
165     delete m_pAudioCodec;
166     m_pAudioCodec = NULL;
167   }
168
169   CloseDecoder();
170
171   m_speed         = DVD_PLAYSPEED_NORMAL;
172   m_started       = false;
173
174   return true;
175 }
176
177 void OMXPlayerAudio::OnStartup()
178 {
179 }
180
181 void OMXPlayerAudio::OnExit()
182 {
183   CLog::Log(LOGNOTICE, "thread end: OMXPlayerAudio::OnExit()");
184 }
185
186 bool OMXPlayerAudio::CodecChange()
187 {
188   unsigned int old_bitrate = m_hints.bitrate;
189   unsigned int new_bitrate = m_hints_current.bitrate;
190
191   if(m_pAudioCodec)
192   {
193     m_hints.channels = m_pAudioCodec->GetChannels();
194     m_hints.samplerate = m_pAudioCodec->GetSampleRate();
195   }
196
197   /* only check bitrate changes on AV_CODEC_ID_DTS, AV_CODEC_ID_AC3, AV_CODEC_ID_EAC3 */
198   if(m_hints.codec != AV_CODEC_ID_DTS && m_hints.codec != AV_CODEC_ID_AC3 && m_hints.codec != AV_CODEC_ID_EAC3)
199     new_bitrate = old_bitrate = 0;
200
201   // for passthrough we only care about the codec and the samplerate
202   bool minor_change = m_hints_current.channels       != m_hints.channels ||
203                       m_hints_current.bitspersample  != m_hints.bitspersample ||
204                       old_bitrate                    != new_bitrate;
205
206   if(m_hints_current.codec          != m_hints.codec ||
207      m_hints_current.samplerate     != m_hints.samplerate ||
208      (!m_passthrough && minor_change) || !m_DecoderOpen)
209   {
210     m_hints_current = m_hints;
211     return true;
212   }
213
214   return false;
215 }
216
217 bool OMXPlayerAudio::Decode(DemuxPacket *pkt, bool bDropPacket)
218 {
219   if(!pkt || m_bad_state || !m_pAudioCodec)
220     return false;
221
222   if(pkt->dts != DVD_NOPTS_VALUE)
223     m_audioClock = pkt->dts;
224
225   const uint8_t *data_dec = pkt->pData;
226   int            data_len = pkt->iSize;
227
228   if(!OMX_IS_RAW(m_format.m_dataFormat) && !bDropPacket)
229   {
230     while(!m_bStop && data_len > 0)
231     {
232       int len = m_pAudioCodec->Decode((BYTE *)data_dec, data_len);
233       if( (len < 0) || (len >  data_len) )
234       {
235         m_pAudioCodec->Reset();
236         break;
237       }
238
239       data_dec+= len;
240       data_len -= len;
241
242       uint8_t *decoded;
243       int decoded_size = m_pAudioCodec->GetData(&decoded);
244
245       if(decoded_size <=0)
246         continue;
247
248       int ret = 0;
249
250       m_audioStats.AddSampleBytes(decoded_size);
251
252       if(CodecChange())
253       {
254         m_DecoderOpen = OpenDecoder();
255         if(!m_DecoderOpen)
256           return false;
257       }
258
259       while(!m_bStop)
260       {
261         // discard if flushing as clocks may be stopped and we'll never submit it
262         if(m_flush)
263           break;
264
265         if(m_omxAudio.GetSpace() < (unsigned int)decoded_size)
266         {
267           Sleep(10);
268           continue;
269         }
270         
271         if(!bDropPacket)
272         {
273           // Zero out the frame data if we are supposed to silence the audio
274           if(m_silence)
275             memset(decoded, 0x0, decoded_size);
276
277           ret = m_omxAudio.AddPackets(decoded, decoded_size, m_audioClock, m_audioClock);
278
279           if(ret != decoded_size)
280           {
281             CLog::Log(LOGERROR, "error ret %d decoded_size %d\n", ret, decoded_size);
282           }
283         }
284
285         break;
286
287       }
288     }
289   }
290   else if(!bDropPacket)
291   {
292     if(CodecChange())
293     {
294       m_DecoderOpen = OpenDecoder();
295       if(!m_DecoderOpen)
296         return false;
297     }
298
299     while(!m_bStop)
300     {
301       if(m_flush)
302         break;
303
304       if(m_omxAudio.GetSpace() < (unsigned int)pkt->iSize)
305       {
306         Sleep(10);
307         continue;
308       }
309         
310       if(!bDropPacket)
311       {
312         if(m_silence)
313           memset(pkt->pData, 0x0, pkt->iSize);
314
315         m_omxAudio.AddPackets(pkt->pData, pkt->iSize, m_audioClock, m_audioClock);
316       }
317
318       m_audioStats.AddSampleBytes(pkt->iSize);
319
320       break;
321     }
322   }
323
324   if(bDropPacket)
325     m_stalled = false;
326
327   // signal to our parent that we have initialized
328   if(m_started == false)
329   {
330     m_started = true;
331     m_messageParent.Put(new CDVDMsgInt(CDVDMsg::PLAYER_STARTED, DVDPLAYER_AUDIO));
332   }
333
334   return true;
335 }
336
337 void OMXPlayerAudio::Process()
338 {
339   m_audioStats.Start();
340
341   while(!m_bStop)
342   {
343     CDVDMsg* pMsg;
344     int priority = (m_speed == DVD_PLAYSPEED_PAUSE && m_started) ? 1 : 0;
345     int timeout = 1000;
346
347     MsgQueueReturnCode ret = m_messageQueue.Get(&pMsg, timeout, priority);
348
349     if (ret == MSGQ_TIMEOUT)
350     {
351       Sleep(10);
352       continue;
353     }
354
355     if (MSGQ_IS_ERROR(ret) || ret == MSGQ_ABORT)
356     {
357       Sleep(10);
358       continue;
359     }
360
361     if (pMsg->IsType(CDVDMsg::DEMUXER_PACKET))
362     {
363       DemuxPacket* pPacket = ((CDVDMsgDemuxerPacket*)pMsg)->GetPacket();
364       bool bPacketDrop     = ((CDVDMsgDemuxerPacket*)pMsg)->GetPacketDrop();
365
366       #ifdef _DEBUG
367       CLog::Log(LOGINFO, "Audio: dts:%.0f pts:%.0f size:%d (s:%d f:%d d:%d l:%d) s:%d %d/%d late:%d,%d", pPacket->dts, pPacket->pts,
368            (int)pPacket->iSize, m_started, m_flush, bPacketDrop, m_stalled, m_speed, 0, 0, (int)m_omxAudio.GetAudioRenderingLatency(), (int)m_hints_current.samplerate);
369       #endif
370       if(Decode(pPacket, m_speed > DVD_PLAYSPEED_NORMAL || m_speed < 0 || bPacketDrop))
371       {
372         // we are not running until something is cached in output device
373         if(m_stalled && m_omxAudio.GetCacheTime() > 0.0)
374         {
375           CLog::Log(LOGINFO, "COMXPlayerAudio - Switching to normal playback");
376           m_stalled = false;
377         }
378       }
379     }
380     else if (pMsg->IsType(CDVDMsg::GENERAL_SYNCHRONIZE))
381     {
382       if(((CDVDMsgGeneralSynchronize*)pMsg)->Wait( 100, SYNCSOURCE_AUDIO ))
383         CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::GENERAL_SYNCHRONIZE");
384       else
385         m_messageQueue.Put(pMsg->Acquire(), 1); /* push back as prio message, to process other prio messages */
386     }
387     else if (pMsg->IsType(CDVDMsg::GENERAL_RESYNC))
388     { //player asked us to set internal clock
389       CDVDMsgGeneralResync* pMsgGeneralResync = (CDVDMsgGeneralResync*)pMsg;
390
391       if (pMsgGeneralResync->m_clock && pMsgGeneralResync->m_timestamp != DVD_NOPTS_VALUE)
392       {
393         CLog::Log(LOGDEBUG, "CDVDPlayerAudio - CDVDMsg::GENERAL_RESYNC(%f, %f, 1)", m_audioClock, pMsgGeneralResync->m_timestamp);
394         m_av_clock->Discontinuity(pMsgGeneralResync->m_timestamp);
395       }
396       else
397         CLog::Log(LOGDEBUG, "CDVDPlayerAudio - CDVDMsg::GENERAL_RESYNC(%f, 0)", m_audioClock);
398
399       m_flush = false;
400       m_audioClock = DVD_NOPTS_VALUE;
401     }
402     else if (pMsg->IsType(CDVDMsg::GENERAL_RESET))
403     {
404       CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::GENERAL_RESET");
405       if (m_pAudioCodec)
406         m_pAudioCodec->Reset();
407       m_omxAudio.Flush();
408       m_started = false;
409       m_audioClock = DVD_NOPTS_VALUE;
410     }
411     else if (pMsg->IsType(CDVDMsg::GENERAL_FLUSH))
412     {
413       CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::GENERAL_FLUSH");
414       m_omxAudio.Flush();
415       m_stalled   = true;
416       m_started   = false;
417
418       if (m_pAudioCodec)
419         m_pAudioCodec->Reset();
420       m_audioClock = DVD_NOPTS_VALUE;
421     }
422     else if (pMsg->IsType(CDVDMsg::PLAYER_STARTED))
423     {
424       CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::PLAYER_STARTED %d", m_started);
425       if(m_started)
426         m_messageParent.Put(new CDVDMsgInt(CDVDMsg::PLAYER_STARTED, DVDPLAYER_AUDIO));
427     }
428     else if (pMsg->IsType(CDVDMsg::PLAYER_DISPLAYTIME))
429     {
430       COMXPlayer::SPlayerState& state = ((CDVDMsgType<COMXPlayer::SPlayerState>*)pMsg)->m_value;
431       double pts = m_audioClock;
432       double stamp = m_av_clock->OMXMediaTime();
433
434       if(state.time_src == COMXPlayer::ETIMESOURCE_CLOCK)
435         state.time      = stamp == 0.0 ? state.time : DVD_TIME_TO_MSEC(stamp + state.time_offset);
436       else
437         state.time      = stamp == 0.0 || pts == DVD_NOPTS_VALUE ? state.time : state.time + DVD_TIME_TO_MSEC(stamp - pts);
438       state.timestamp = m_av_clock->GetAbsoluteClock();
439       state.player    = DVDPLAYER_AUDIO;
440       m_messageParent.Put(pMsg->Acquire());
441     }
442     else if (pMsg->IsType(CDVDMsg::GENERAL_EOF))
443     {
444       CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::GENERAL_EOF");
445       SubmitEOS();
446     }
447     else if (pMsg->IsType(CDVDMsg::GENERAL_DELAY))
448     {
449       double timeout = static_cast<CDVDMsgDouble*>(pMsg)->m_value;
450       CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::GENERAL_DELAY(%f)", timeout);
451     }
452     else if (pMsg->IsType(CDVDMsg::PLAYER_SETSPEED))
453     {
454       if (m_speed != static_cast<CDVDMsgInt*>(pMsg)->m_value)
455       {
456         m_speed = static_cast<CDVDMsgInt*>(pMsg)->m_value;
457         CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::PLAYER_SETSPEED %d", m_speed);
458       }
459     }
460     else if (pMsg->IsType(CDVDMsg::AUDIO_SILENCE))
461     {
462       m_silence = static_cast<CDVDMsgBool*>(pMsg)->m_value;
463       if (m_silence)
464         CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::AUDIO_SILENCE(%f, 1)", m_audioClock);
465       else
466         CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::AUDIO_SILENCE(%f, 0)", m_audioClock);
467     }
468     else if (pMsg->IsType(CDVDMsg::GENERAL_STREAMCHANGE))
469     {
470       COMXMsgAudioCodecChange* msg(static_cast<COMXMsgAudioCodecChange*>(pMsg));
471       CLog::Log(LOGDEBUG, "COMXPlayerAudio - CDVDMsg::GENERAL_STREAMCHANGE");
472       OpenStream(msg->m_hints, msg->m_codec);
473       msg->m_codec = NULL;
474     }
475
476     pMsg->Release();
477   }
478 }
479
480 void OMXPlayerAudio::Flush()
481 {
482   m_flush = true;
483   m_messageQueue.Flush();
484   m_messageQueue.Put( new CDVDMsg(CDVDMsg::GENERAL_FLUSH), 1);
485 }
486
487 void OMXPlayerAudio::WaitForBuffers()
488 {
489   // make sure there are no more packets available
490   m_messageQueue.WaitUntilEmpty();
491
492   // make sure almost all has been rendered
493   // leave 500ms to avound buffer underruns
494   double delay = GetCacheTime();
495   if(delay > 0.5)
496     Sleep((int)(1000 * (delay - 0.5)));
497 }
498
499 bool OMXPlayerAudio::Passthrough() const
500 {
501   return m_passthrough;
502 }
503
504 AEDataFormat OMXPlayerAudio::GetDataFormat(CDVDStreamInfo hints)
505 {
506   AEDataFormat dataFormat = AE_FMT_S16NE;
507
508   m_passthrough = false;
509   m_hw_decode   = false;
510
511   /* check our audio capabilties */
512
513   /* pathrought is overriding hw decode*/
514   if(hints.codec == AV_CODEC_ID_AC3 && CAEFactory::SupportsRaw(AE_FMT_AC3) && !CSettings::Get().GetBool("audiooutput.dualaudio"))
515   {
516     dataFormat = AE_FMT_AC3;
517     m_passthrough = true;
518   }
519   if(hints.codec == AV_CODEC_ID_DTS && CAEFactory::SupportsRaw(AE_FMT_DTS) && !CSettings::Get().GetBool("audiooutput.dualaudio"))
520   {
521     dataFormat = AE_FMT_DTS;
522     m_passthrough = true;
523   }
524
525   /* hw decode */
526   if(m_use_hw_decode && !m_passthrough)
527   {
528     if(hints.codec == AV_CODEC_ID_AC3 && COMXAudio::CanHWDecode(m_hints.codec))
529     {
530       dataFormat = AE_FMT_AC3;
531       m_hw_decode = true;
532     }
533     if(hints.codec == AV_CODEC_ID_DTS && COMXAudio::CanHWDecode(m_hints.codec))
534     {
535       dataFormat = AE_FMT_DTS;
536       m_hw_decode = true;
537     }
538   }
539
540   /* software path */
541   if(!m_passthrough && !m_hw_decode)
542   {
543     if (m_pAudioCodec && m_pAudioCodec->GetBitsPerSample() == 16)
544       dataFormat = AE_FMT_S16NE;
545     else
546       dataFormat = AE_FMT_FLOAT;
547   }
548
549   return dataFormat;
550 }
551
552 bool OMXPlayerAudio::OpenDecoder()
553 {
554   m_nChannels   = m_hints.channels;
555   m_passthrough = false;
556   m_hw_decode   = false;
557
558   if(m_DecoderOpen)
559   {
560     m_omxAudio.Deinitialize();
561     m_DecoderOpen = false;
562   }
563
564   /* setup audi format for audio render */
565   m_format.m_sampleRate    = m_hints.samplerate;
566   /* GetDataFormat is setting up evrything */
567   m_format.m_dataFormat = GetDataFormat(m_hints);
568
569   uint64_t channelMap = 0;
570   if (m_pAudioCodec && !m_passthrough)
571     channelMap = m_pAudioCodec->GetChannelMap();
572   else if (m_passthrough)
573     // we just want to get the channel count right to stop OMXAudio.cpp rejecting stream
574     // the actual layout is not used
575     channelMap = (1<<m_nChannels)-1;
576   bool bAudioRenderOpen = m_omxAudio.Initialize(m_format, m_av_clock, m_hints, channelMap, m_passthrough, m_hw_decode);
577
578   m_codec_name = "";
579   m_bad_state  = !bAudioRenderOpen;
580   
581   if(!bAudioRenderOpen)
582   {
583     CLog::Log(LOGERROR, "OMXPlayerAudio : Error open audio output");
584     m_omxAudio.Deinitialize();
585   }
586   else
587   {
588     CLog::Log(LOGINFO, "Audio codec %s channels %d samplerate %d bitspersample %d\n",
589       m_codec_name.c_str(), m_nChannels, m_hints.samplerate, m_hints.bitspersample);
590   }
591
592   m_started = false;
593
594   return bAudioRenderOpen;
595 }
596
597 void OMXPlayerAudio::CloseDecoder()
598 {
599   m_omxAudio.Deinitialize();
600   m_DecoderOpen = false;
601 }
602
603 double OMXPlayerAudio::GetDelay()
604 {
605   return m_omxAudio.GetDelay();
606 }
607
608 double OMXPlayerAudio::GetCacheTime()
609 {
610   return m_omxAudio.GetCacheTime();
611 }
612
613 double OMXPlayerAudio::GetCacheTotal()
614 {
615   return m_omxAudio.GetCacheTotal();
616 }
617
618 void OMXPlayerAudio::SubmitEOS()
619 {
620   if(!m_bad_state)
621     m_omxAudio.SubmitEOS();
622 }
623
624 bool OMXPlayerAudio::IsEOS()
625 {
626   return m_bad_state || m_omxAudio.IsEOS();
627 }
628
629 void OMXPlayerAudio::WaitCompletion()
630 {
631   unsigned int nTimeOut = AUDIO_BUFFER_SECONDS * 1000;
632   while(nTimeOut)
633   {
634     if(IsEOS())
635     {
636       CLog::Log(LOGDEBUG, "%s::%s - got eos\n", CLASSNAME, __func__);
637       break;
638     }
639
640     if(nTimeOut == 0)
641     {
642       CLog::Log(LOGERROR, "%s::%s - wait for eos timed out\n", CLASSNAME, __func__);
643       break;
644     }
645     Sleep(50);
646     nTimeOut -= 50;
647   }
648 }
649
650 void OMXPlayerAudio::SetSpeed(int speed)
651 {
652   if(m_messageQueue.IsInited())
653     m_messageQueue.Put( new CDVDMsgInt(CDVDMsg::PLAYER_SETSPEED, speed), 1 );
654   else
655     m_speed = speed;
656 }
657
658 int OMXPlayerAudio::GetAudioBitrate()
659 {
660   return (int)m_audioStats.GetBitrate();
661 }
662
663 std::string OMXPlayerAudio::GetPlayerInfo()
664 {
665   std::ostringstream s;
666   s << "aq:"     << setw(2) << min(99,m_messageQueue.GetLevel() + MathUtils::round_int(100.0/8.0*GetCacheTime())) << "%";
667   s << ", Kb/s:" << fixed << setprecision(2) << (double)GetAudioBitrate() / 1024.0;
668
669   return s.str();
670 }