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