only call IPlayerCallback::OnPlayBackSpeedChanged if the speed has actually changed
[vuplus_xbmc] / xbmc / cores / AudioRenderers / IOSAudioRenderer.cpp
1 #ifdef __APPLE__
2 /*
3  *      Copyright (C) 2010 Team XBMC
4  *      http://www.xbmc.org
5  *
6  *  This Program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2, or (at your option)
9  *  any later version.
10  *
11  *  This Program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with XBMC; see the file COPYING.  If not, write to
18  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
19  *  http://www.gnu.org/copyleft/gpl.html
20  *
21  */
22
23 #include "IOSAudioRenderer.h"
24 #include "IOSAudioRingBuffer.h"
25 #include "AudioContext.h"
26 #include "GUISettings.h"
27 #include "Settings.h"
28 #include "utils/log.h"
29
30 //***********************************************************************************************
31 // Contruction/Destruction
32 //***********************************************************************************************
33 CIOSAudioRenderer::CIOSAudioRenderer() :
34   m_Pause(false),
35   m_Initialized(false),
36   m_CurrentVolume(0),
37   m_OutputBufferIndex(0),
38   m_BytesPerSec(0),
39   m_NumChunks(0),
40   m_PacketSize(0),
41   m_Passthrough(false),
42   m_SamplesPerSec(0),
43   m_DoRunout(0)
44 {
45   m_Buffer = new IOSAudioRingBuffer();
46 }
47
48 CIOSAudioRenderer::~CIOSAudioRenderer()
49 {
50   Deinitialize();
51   delete m_Buffer;
52 }
53
54 //***********************************************************************************************
55 // Initialization
56 //***********************************************************************************************
57
58 bool CIOSAudioRenderer::Initialize(IAudioCallback* pCallback, const CStdString& device, int iChannels, enum PCMChannels *channelMap, unsigned int uiSamplesPerSec, unsigned int uiBitsPerSample, bool bResample, bool bIsMusic /*Useless Legacy Parameter*/, bool bPassthrough)
59 {
60   // Limit to 2.0. It is only used for anloge audio.
61   static enum PCMChannels IOSChannelMap[2] =
62   {PCM_FRONT_LEFT, PCM_FRONT_RIGHT};
63
64   m_Passthrough = bPassthrough;
65
66   g_audioContext.SetActiveDevice(CAudioContext::DIRECTSOUND_DEVICE);
67
68   bool bAudioOnAllSpeakers(false);
69   g_audioContext.SetupSpeakerConfig(iChannels, bAudioOnAllSpeakers, bIsMusic);
70
71   if(bPassthrough)
72   {
73     g_audioContext.SetActiveDevice(CAudioContext::DIRECTSOUND_DEVICE_DIGITAL);
74   } else {
75     g_audioContext.SetActiveDevice(CAudioContext::DIRECTSOUND_DEVICE);
76   }
77
78   m_DataChannels = iChannels;
79   m_remap.Reset();
80
81   if (!m_Passthrough && channelMap)
82   {
83     enum PCMChannels *outLayout;
84
85     /* set the input format, and get the channel layout so we know what we need to open */
86     outLayout = m_remap.SetInputFormat (iChannels, channelMap, uiBitsPerSample / 8, uiSamplesPerSec);
87     unsigned int outChannels = 0;
88     unsigned int ch = 0, map;
89     while(outLayout[ch] != PCM_INVALID)
90     {
91       for(map = 0; map < 8; ++map)
92       {
93         if (outLayout[ch] == IOSChannelMap[map])
94         {
95           if (map > outChannels)
96             outChannels = map;
97           break;
98         }
99       }
100       ++ch;
101     }
102
103     m_remap.SetOutputFormat(++outChannels, IOSChannelMap);
104     if (m_remap.CanRemap())
105     {
106       iChannels = outChannels;
107       if (m_DataChannels != (unsigned int)iChannels)
108         CLog::Log(LOGDEBUG, "CIOSAudioRenderer::InitializePCM: Requested channels changed from %i to %i", m_DataChannels, iChannels);
109     }    
110
111   }
112
113   m_Channels = iChannels;
114
115   // Set the input stream format for the AudioUnit
116   // We use the default DefaultOuput AudioUnit, so we only can set the input stream format.
117   // The autput format is automaticaly set to the input format.
118   AudioStreamBasicDescription audioFormat;
119   audioFormat.mFormatID = kAudioFormatLinearPCM;              //        Data encoding format
120
121   audioFormat.mFormatFlags = kAudioFormatFlagsCanonical;
122   audioFormat.mChannelsPerFrame = iChannels;                  // Number of interleaved audiochannels
123   audioFormat.mSampleRate = (Float64)uiSamplesPerSec;         //        the sample rate of the audio stream
124   audioFormat.mBitsPerChannel = uiBitsPerSample;              // Number of bits per sample, per channel
125   audioFormat.mBytesPerFrame = (uiBitsPerSample>>3) * iChannels; // Size of a frame == 1 sample per channel             
126   audioFormat.mFramesPerPacket = 1;                           // The smallest amount of indivisible data. Always 1 for uncompressed audio       
127   audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame * audioFormat.mFramesPerPacket;
128   audioFormat.mReserved = 0;
129
130   // Attach our output object to the device
131   if(!m_AudioDevice.Init(/*m_Passthrough*/ true, &audioFormat, RenderCallback, this))
132   {
133     CLog::Log(LOGDEBUG, "CIOSAudioRenderer::Init failed");
134     return false;
135   }
136
137   m_PacketSize = iChannels * (uiBitsPerSample / 8) * 512;
138
139   m_BufferFrames = m_AudioDevice.FramesPerSlice(m_PacketSize);
140   if(!m_BufferFrames) 
141   {
142     CLog::Log(LOGDEBUG, "CIOSAudioRenderer::FramesPerSlice bufferFrames == 0\n");
143     //return false;
144   }
145
146   m_BytesPerFrame = audioFormat.mBytesPerFrame;
147   m_BitsPerChannel = audioFormat.mBitsPerChannel;
148   m_BytesPerSec = uiSamplesPerSec * (uiBitsPerSample / 8) * iChannels;
149   m_SamplesPerSec = uiSamplesPerSec;
150   m_BufferLen = m_PacketSize * 96;
151   if(m_BufferLen < m_PacketSize || m_BufferLen == 0)
152     m_BufferLen = m_PacketSize;
153
154   bool success = m_Buffer->Create(m_BufferLen);
155   if(!success || !m_BufferLen)
156   {
157     CLog::Log(LOGDEBUG, "CIOSAudioRenderer::Initialize: Error allocation audio buffer size %d.", m_BufferLen);
158     return false;
159   }
160
161   m_EnableVolumeControl = true;
162
163   /*
164   if (!m_AudioDevice.SetSessionListener(kAudioSessionProperty_AudioRouteChange, PropertyChangeCallback, this))
165     return false;
166   */
167
168   // Start the audio device
169   if (!m_AudioDevice.Open())
170     return false;
171
172   // Suspend rendering. We will start once we have some data.
173   m_Pause = true;
174   m_Initialized = true;
175
176   CLog::Log(LOGDEBUG, "CIOSAudioRenderer::Initialize: Renderer Configuration - Chunk Len: %u, Max Cache: %u (%0.0fms).", m_PacketSize, m_BufferLen, 1000.0 *(float)m_BufferLen/(float)m_BytesPerSec);
177   CLog::Log(LOGINFO, "CIOSAudioRenderer::Initialize: Successfully configured audio output.");
178
179   m_DoRunout = 0;
180
181   return true;
182 }
183
184 bool CIOSAudioRenderer::Deinitialize()
185 {
186
187   if(m_Initialized)
188     WaitCompletion();
189
190   // Stop rendering
191   Stop();
192
193   Sleep(10);
194   m_AudioDevice.Close();
195   m_Initialized = false;
196   m_BytesPerSec = 0;
197   m_BufferLen   = 0;
198   m_NumChunks   = 0;
199   m_PacketSize  = 0;
200   m_SamplesPerSec = 0;
201   m_DoRunout    = 0;
202
203   CLog::Log(LOGINFO, "CIOSAudioRenderer::Deinitialize: Renderer has been shut down.");
204
205   return true;
206 }
207
208 void CIOSAudioRenderer::Flush()
209 {
210   Pause();
211
212   // IOSAudioRingBuffer::Reset is not threadsafe but we have
213   // paused here so renderer is not reading from m_Buffer and
214   // we can reset with confidence.
215   m_Buffer->Reset();
216 }
217
218 //***********************************************************************************************
219 // Transport control methods
220 //***********************************************************************************************
221 bool CIOSAudioRenderer::Pause()
222 {
223   if (!m_Pause)
224   {
225     m_AudioDevice.Stop();
226     m_Pause = true;
227   }
228   return true;
229 }
230
231 bool CIOSAudioRenderer::Resume()
232 {
233   if (m_Pause)
234   {
235     m_AudioDevice.Start();
236     m_Pause = false;
237   }
238   return true;
239 }
240
241 bool CIOSAudioRenderer::Stop()
242 {
243   m_AudioDevice.Stop();
244
245   m_Pause = true;
246
247   Flush();
248   return true;
249 }
250
251 //***********************************************************************************************
252 // Volume control methods
253 //***********************************************************************************************
254 LONG CIOSAudioRenderer::GetCurrentVolume() const
255 {
256   return m_CurrentVolume;
257 }
258
259 void CIOSAudioRenderer::Mute(bool bMute)
260 {
261 }
262
263 bool CIOSAudioRenderer::SetCurrentVolume(LONG nVolume)
264 {
265   return true;
266 }
267
268 //***********************************************************************************************
269 // Data management methods
270 //***********************************************************************************************
271 unsigned int CIOSAudioRenderer::GetSpace()
272 {
273   int free = m_Buffer->GetWriteSize();
274   return (free / m_Channels) * m_DataChannels;
275 }
276
277 unsigned int CIOSAudioRenderer::AddPackets(const void* data, DWORD len)
278 {
279   int status;
280
281   // call channel remapping routine if available and required
282   if (m_remap.CanRemap() && !m_Passthrough)
283   {
284     int length, frames;
285
286     // we might be up or down converting, so convert to number of bytes
287     // that we will get out of remapping the channels and see if that fits. 
288     length = (len / m_DataChannels) * m_Channels;
289     if (length > GetSpace())
290       return 0;
291
292     // check buffer fit, we can only accept unit frames, so if less than
293     // a complete frame, we punt.
294     frames = length / m_Channels / (m_BitsPerChannel >> 3);
295     if (frames == 0)
296     {
297       CLog::Log(LOGINFO, "IOSAudioRenderer::AddPackets() - Need complete frame.");
298       return 0;
299     }
300
301     uint8_t outData[length];
302     // remap the audio channels using the frame count
303     m_remap.Remap((void*)data, outData, frames);
304
305     status = m_Buffer->Write(outData, length);
306     // return the number of input bytes we accepted
307     len = (length / m_Channels) * m_DataChannels;
308   }
309   else
310   {
311     // simple case, not remaping or passthough, only have to check 
312     // that we have free space in our buffer.
313     status = m_Buffer->Write((unsigned char *)data, len);
314   }
315
316   Resume();
317   //only return the length if buffer accepted the data
318   return status == 0 ? len : 0;
319 }
320
321 float CIOSAudioRenderer::GetDelay()
322 {
323   return (float)m_Buffer->GetReadSize() / (float)m_BytesPerSec;
324 }
325
326 float CIOSAudioRenderer::GetCacheTime()
327 {
328   return (float)(m_BufferLen - GetSpace()) / (float)m_BytesPerSec;
329 }
330
331 float CIOSAudioRenderer::GetCacheTotal()
332 {
333   return (float)m_BufferLen / (float)m_BytesPerSec;
334 }
335
336 unsigned int CIOSAudioRenderer::GetChunkLen()
337 {
338   return (m_PacketSize / m_Channels) * m_DataChannels;
339 }
340
341 void CIOSAudioRenderer::WaitCompletion()
342 {
343   // we don't lock here as we are just checking for zero or non-zero.
344
345   // The cache is already empty. There is nothing to wait for.
346   if (m_Buffer->GetReadSize() == 0)
347     return;
348
349   m_DoRunout = 1;
350   
351   UInt32 delay =  (UInt32)(GetDelay() * 1000.0f) + 10;
352   if (!delay)
353   {
354     bool ret = m_RunoutEvent.WaitMSec(delay);
355     if (!ret && m_Buffer->GetReadSize() )
356     {
357       //See if there is still some data left in the cache that didn't get played
358       CLog::Log(LOGERROR, "CIOSAudioRenderer::WaitCompletion: Timed-out waiting for runout. Remaining data will be truncated.");
359     }
360   }
361
362   Stop();
363 }
364
365 //***********************************************************************************************
366 // Rendering Methods
367 //***********************************************************************************************
368 OSStatus CIOSAudioRenderer::OnRender(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
369 {
370   if (!m_Initialized)
371   {
372     CLog::Log(LOGERROR, "CIOSAudioRenderer::OnRender: Callback to de/unitialized renderer.");
373     ioData->mBuffers[m_OutputBufferIndex].mDataByteSize = 0;
374     return noErr;
375   }
376
377   if(m_Pause)
378   {
379     ioData->mBuffers[m_OutputBufferIndex].mDataByteSize = 0;
380     return noErr;
381   }
382
383   UInt32 bytesRead = m_Buffer->GetReadSize();
384   UInt32 bytesRequested = inNumberFrames * m_BytesPerFrame;
385
386   if (bytesRead < bytesRequested)
387   {
388     m_RunoutEvent.Set(); // Tell anyone who cares that the cache is empty
389     if (m_DoRunout) // We were waiting for a runout. This is not an error.
390     {
391       m_DoRunout = 0;
392     }
393     ioData->mBuffers[m_OutputBufferIndex].mDataByteSize = 0;
394     return noErr;
395   }
396
397   m_Buffer->Read((unsigned char *)ioData->mBuffers[m_OutputBufferIndex].mData, bytesRequested);
398
399   if (!m_EnableVolumeControl && m_CurrentVolume <= VOLUME_MINIMUM)
400     ioData->mBuffers[m_OutputBufferIndex].mDataByteSize = 0;
401   else
402     ioData->mBuffers[m_OutputBufferIndex].mDataByteSize = bytesRequested;
403
404   return noErr;
405 }
406
407 OSStatus CIOSAudioRenderer::RenderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
408 {
409   return ((CIOSAudioRenderer*)inRefCon)->OnRender(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData);
410 }
411
412 // Static Callback from AudioUnit
413 void CIOSAudioRenderer::PropertyChanged(AudioSessionPropertyID inID, UInt32 inDataSize, const void* inPropertyValue)
414 {
415   CLog::Log(LOGERROR, "CIOSAudioRenderer::PropertyChanged: inID %d.", (int)inID);
416 }
417
418 void  CIOSAudioRenderer::PropertyChangeCallback(void* inClientData, AudioSessionPropertyID inID, UInt32 inDataSize, const void* inPropertyValue)
419 {
420   ((CIOSAudioRenderer*)inClientData)->PropertyChanged(inID, inDataSize, inPropertyValue);
421 }
422
423 #endif