Merge pull request #4441 from Memphiz/osxdevicereset
[vuplus_xbmc] / xbmc / cores / AudioEngine / Sinks / AESinkDARWINOSX.cpp
1 /*
2  *      Copyright (C) 2005-2014 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 "cores/AudioEngine/AEFactory.h"
22 #include "cores/AudioEngine/Sinks/AESinkDARWINOSX.h"
23 #include "cores/AudioEngine/Utils/AEUtil.h"
24 #include "cores/AudioEngine/Utils/AERingBuffer.h"
25 #include "cores/AudioEngine/Sinks/osx/CoreAudioHelpers.h"
26 #include "cores/AudioEngine/Sinks/osx/CoreAudioHardware.h"
27 #include "osx/DarwinUtils.h"
28 #include "utils/log.h"
29 #include "utils/StringUtils.h"
30 #include "threads/Condition.h"
31 #include "threads/CriticalSection.h"
32
33 #include <sstream>
34
35 #define CA_MAX_CHANNELS 8
36 static enum AEChannel CAChannelMap[CA_MAX_CHANNELS + 1] = {
37   AE_CH_FL , AE_CH_FR , AE_CH_BL , AE_CH_BR , AE_CH_FC , AE_CH_LFE , AE_CH_SL , AE_CH_SR ,
38   AE_CH_NULL
39 };
40
41 static bool HasSampleRate(const AESampleRateList &list, const unsigned int samplerate)
42 {
43   for (size_t i = 0; i < list.size(); ++i)
44   {
45     if (list[i] == samplerate)
46       return true;
47   }
48   return false;
49 }
50
51 static bool HasDataFormat(const AEDataFormatList &list, const enum AEDataFormat format)
52 {
53   for (size_t i = 0; i < list.size(); ++i)
54   {
55     if (list[i] == format)
56       return true;
57   }
58   return false;
59 }
60
61 typedef std::vector< std::pair<AudioDeviceID, CAEDeviceInfo> > CADeviceList;
62
63 static void EnumerateDevices(CADeviceList &list)
64 {
65   CAEDeviceInfo device;
66
67   std::string defaultDeviceName;
68   CCoreAudioHardware::GetOutputDeviceName(defaultDeviceName);
69
70   CoreAudioDeviceList deviceIDList;
71   CCoreAudioHardware::GetOutputDevices(&deviceIDList);
72   while (!deviceIDList.empty())
73   {
74     AudioDeviceID deviceID = deviceIDList.front();
75     CCoreAudioDevice caDevice(deviceID);
76
77     device.m_channels.Reset();
78     device.m_dataFormats.clear();
79     device.m_sampleRates.clear();
80
81     device.m_deviceType = AE_DEVTYPE_PCM;
82     device.m_deviceName = caDevice.GetName();
83     device.m_displayName = device.m_deviceName;
84     device.m_displayNameExtra = "";
85
86     // flag indicating that passthroughformats where added throughout the stream enumeration
87     bool hasPassthroughFormats = false;
88     // the maximum number of channels found in the streams
89     UInt32 numMaxChannels = 0;
90     // the terminal type as reported by ca
91     UInt32 caTerminalType = 0;
92       
93     bool isDigital = caDevice.IsDigital(caTerminalType);
94
95
96     CLog::Log(LOGDEBUG, "EnumerateDevices:Device(%s)" , device.m_deviceName.c_str());
97     AudioStreamIdList streams;
98     if (caDevice.GetStreams(&streams))
99     {
100       for (AudioStreamIdList::iterator j = streams.begin(); j != streams.end(); ++j)
101       {
102         StreamFormatList streams;
103         if (CCoreAudioStream::GetAvailablePhysicalFormats(*j, &streams))
104         {
105           for (StreamFormatList::iterator i = streams.begin(); i != streams.end(); ++i)
106           {
107             AudioStreamBasicDescription desc = i->mFormat;
108             std::string formatString;
109             CLog::Log(LOGDEBUG, "EnumerateDevices:Format(%s)" ,
110                                 StreamDescriptionToString(desc, formatString));
111
112             // add stream format info
113             switch (desc.mFormatID)
114             {
115               case kAudioFormatAC3:
116               case kAudioFormat60958AC3:
117                 if (!HasDataFormat(device.m_dataFormats, AE_FMT_AC3))
118                   device.m_dataFormats.push_back(AE_FMT_AC3);
119                 if (!HasDataFormat(device.m_dataFormats, AE_FMT_DTS))
120                   device.m_dataFormats.push_back(AE_FMT_DTS);
121                 hasPassthroughFormats = true;
122                 isDigital = true;// sanity - those are always digital devices!
123                 break;
124               default:
125                 AEDataFormat format = AE_FMT_INVALID;
126                 switch(desc.mBitsPerChannel)
127                 {
128                   case 16:
129                     if (desc.mFormatFlags & kAudioFormatFlagIsBigEndian)
130                       format = AE_FMT_S16BE;
131                     else
132                     {
133                       // if it is no digital stream per definition
134                       // check if the device name suggests that it is digital
135                       // (some hackintonshs are not so smart in announcing correct
136                       // ca devices ...
137                       if (!isDigital)
138                       {
139                         std::string devNameLower = device.m_deviceName;
140                         StringUtils::ToLower(devNameLower);                       
141                         isDigital = devNameLower.find("digital") != std::string::npos;
142                       }
143
144                       /* Passthrough is possible with a 2ch digital output */
145                       if (desc.mChannelsPerFrame == 2 && isDigital)
146                       {
147                         if (desc.mSampleRate == 48000)
148                         {
149                           if (!HasDataFormat(device.m_dataFormats, AE_FMT_AC3))
150                             device.m_dataFormats.push_back(AE_FMT_AC3);
151                           if (!HasDataFormat(device.m_dataFormats, AE_FMT_DTS))
152                             device.m_dataFormats.push_back(AE_FMT_DTS);
153                           hasPassthroughFormats = true;
154                         }
155                         else if (desc.mSampleRate == 192000)
156                         {
157                           if (!HasDataFormat(device.m_dataFormats, AE_FMT_EAC3))
158                             device.m_dataFormats.push_back(AE_FMT_EAC3);
159                           hasPassthroughFormats = true;
160                         }
161                       }
162                       format = AE_FMT_S16LE;
163                     }
164                     break;
165                   case 24:
166                     if (desc.mFormatFlags & kAudioFormatFlagIsBigEndian)
167                       format = AE_FMT_S24BE3;
168                     else
169                       format = AE_FMT_S24LE3;
170                     break;
171                   case 32:
172                     if (desc.mFormatFlags & kAudioFormatFlagIsFloat)
173                       format = AE_FMT_FLOAT;
174                     else
175                     {
176                       if (desc.mFormatFlags & kAudioFormatFlagIsBigEndian)
177                         format = AE_FMT_S32BE;
178                       else
179                         format = AE_FMT_S32LE;
180                     }
181                     break;
182                 }
183                 
184                 if (numMaxChannels < desc.mChannelsPerFrame)
185                   numMaxChannels = desc.mChannelsPerFrame;
186                 
187                 if (format != AE_FMT_INVALID && !HasDataFormat(device.m_dataFormats, format))
188                   device.m_dataFormats.push_back(format);
189                 break;
190             }
191
192             // add channel info
193             CAEChannelInfo channel_info;
194             for (UInt32 chan = 0; chan < CA_MAX_CHANNELS && chan < desc.mChannelsPerFrame; ++chan)
195             {
196               if (!device.m_channels.HasChannel(CAChannelMap[chan]))
197                 device.m_channels += CAChannelMap[chan];
198               channel_info += CAChannelMap[chan];
199             }
200
201             // add sample rate info
202             if (!HasSampleRate(device.m_sampleRates, desc.mSampleRate))
203               device.m_sampleRates.push_back(desc.mSampleRate);
204           }
205         }
206       }
207     }
208
209     
210     // flag indicating that the device name "sounds" like HDMI
211     bool hasHdmiName = device.m_deviceName.find("HDMI") != std::string::npos;
212     // flag indicating that the device name "sounds" like DisplayPort
213     bool hasDisplayPortName = device.m_deviceName.find("DisplayPort") != std::string::npos;
214     
215     // decide the type of the device based on the discovered information
216     // in the streams
217     // device defaults to PCM (see start of the while loop)
218     // it can be HDMI, DisplayPort or Optical
219     // for all of those types it needs to support
220     // passthroughformats and needs to be a digital port
221     if (hasPassthroughFormats && isDigital)
222     {
223       // if the max number of channels was more then 2
224       // this can be HDMI or DisplayPort or Thunderbolt
225       if (numMaxChannels > 2)
226       {
227         // either the devicename suggests its HDMI
228         // or CA reported the terminalType as HDMI
229         if (hasHdmiName || caTerminalType == kIOAudioDeviceTransportTypeHdmi)
230           device.m_deviceType = AE_DEVTYPE_HDMI;
231
232         // either the devicename suggests its DisplayPort
233         // or CA reported the terminalType as DisplayPort or Thunderbolt
234         if (hasDisplayPortName || caTerminalType == kIOAudioDeviceTransportTypeDisplayPort || caTerminalType == kIOAudioDeviceTransportTypeThunderbolt)
235           device.m_deviceType = AE_DEVTYPE_DP;
236       }
237       else// treat all other digital passthrough devices as optical
238         device.m_deviceType = AE_DEVTYPE_IEC958;
239     }
240
241     // devicename based overwrites from former code - maybe FIXME at some point when we
242     // are sure that the upper detection does its job in all[tm] use cases
243     if (hasHdmiName)
244       device.m_deviceType = AE_DEVTYPE_HDMI;
245     if (hasDisplayPortName)
246       device.m_deviceType = AE_DEVTYPE_DP;
247     
248     
249     list.push_back(std::make_pair(deviceID, device));
250     //in the first place of the list add the default device
251     //with name "default" - if this is selected
252     //we will output to whatever osx claims to be default
253     //(allows transition from headphones to speaker and stuff
254     //like that
255     if(defaultDeviceName == device.m_deviceName)
256     {
257       device.m_deviceName = "default";
258       device.m_displayName = "Default";
259       list.insert(list.begin(), std::make_pair(deviceID, device));
260     }
261
262     deviceIDList.pop_front();
263   }
264 }
265
266 /* static, threadsafe access to the device list */
267 static CADeviceList     s_devices;
268 static CCriticalSection s_devicesLock;
269
270 static void EnumerateDevices()
271 {
272   CADeviceList devices;
273   EnumerateDevices(devices);
274   {
275     CSingleLock lock(s_devicesLock);
276     s_devices = devices;
277   }
278 }
279
280 static CADeviceList GetDevices()
281 {
282   CADeviceList list;
283   {
284     CSingleLock lock(s_devicesLock);
285     list = s_devices;
286   }
287   return list;
288 }
289
290 OSStatus deviceChangedCB(AudioObjectID                       inObjectID,
291                          UInt32                              inNumberAddresses,
292                          const AudioObjectPropertyAddress    inAddresses[],
293                          void*                               inClientData)
294 {
295   CLog::Log(LOGDEBUG, "CoreAudio: audiodevicelist changed - reenumerating");
296   CAEFactory::DeviceChange();
297   CLog::Log(LOGDEBUG, "CoreAudio: audiodevicelist changed - done");
298   return noErr;
299 }
300
301 void RegisterDeviceChangedCB(bool bRegister, void *ref)
302 {
303   OSStatus ret = noErr;
304   const AudioObjectPropertyAddress inAdr =
305   {
306     kAudioHardwarePropertyDevices,
307     kAudioObjectPropertyScopeGlobal,
308     kAudioObjectPropertyElementMaster
309   };
310
311   if (bRegister)
312     ret = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &inAdr, deviceChangedCB, ref);
313   else
314     ret = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &inAdr, deviceChangedCB, ref);
315
316   if (ret != noErr)
317     CLog::Log(LOGERROR, "CCoreAudioAE::Deinitialize - error %s a listener callback for device changes!", bRegister?"attaching":"removing");
318 }
319
320
321 ////////////////////////////////////////////////////////////////////////////////////////////
322 CAESinkDARWINOSX::CAESinkDARWINOSX()
323 : m_latentFrames(0), m_outputBitstream(false), m_outputBuffer(NULL), m_buffer(NULL)
324 {
325   // By default, kAudioHardwarePropertyRunLoop points at the process's main thread on SnowLeopard,
326   // If your process lacks such a run loop, you can set kAudioHardwarePropertyRunLoop to NULL which
327   // tells the HAL to run it's own thread for notifications (which was the default prior to SnowLeopard).
328   // So tell the HAL to use its own thread for similar behavior under all supported versions of OSX.
329   CFRunLoopRef theRunLoop = NULL;
330   AudioObjectPropertyAddress theAddress = {
331     kAudioHardwarePropertyRunLoop,
332     kAudioObjectPropertyScopeGlobal,
333     kAudioObjectPropertyElementMaster
334   };
335   OSStatus theError = AudioObjectSetPropertyData(kAudioObjectSystemObject,
336                                                  &theAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
337   if (theError != noErr)
338   {
339     CLog::Log(LOGERROR, "CCoreAudioAE::constructor: kAudioHardwarePropertyRunLoop error.");
340   }
341   RegisterDeviceChangedCB(true, this);
342   m_started = false;
343 }
344
345 CAESinkDARWINOSX::~CAESinkDARWINOSX()
346 {
347   RegisterDeviceChangedCB(false, this);
348 }
349
350 float ScoreStream(const AudioStreamBasicDescription &desc, const AEAudioFormat &format)
351 {
352   float score = 0;
353   if (format.m_dataFormat == AE_FMT_AC3 ||
354       format.m_dataFormat == AE_FMT_DTS)
355   {
356     if (desc.mFormatID == kAudioFormat60958AC3 ||
357         desc.mFormatID == 'IAC3' ||
358         desc.mFormatID == kAudioFormatAC3)
359     {
360       if (desc.mSampleRate == format.m_sampleRate &&
361           desc.mBitsPerChannel == CAEUtil::DataFormatToBits(format.m_dataFormat) &&
362           desc.mChannelsPerFrame == format.m_channelLayout.Count())
363       {
364         // perfect match
365         score = FLT_MAX;
366       }
367     }
368   }
369   if (format.m_dataFormat == AE_FMT_AC3 ||
370       format.m_dataFormat == AE_FMT_DTS ||
371       format.m_dataFormat == AE_FMT_EAC3)
372   { // we should be able to bistreaming in PCM if the samplerate, bitdepth and channels match
373     if (desc.mSampleRate       == format.m_sampleRate                            &&
374         desc.mBitsPerChannel   == CAEUtil::DataFormatToBits(format.m_dataFormat) &&
375         desc.mChannelsPerFrame == format.m_channelLayout.Count()                 &&
376         desc.mFormatID         == kAudioFormatLinearPCM)
377     {
378       score = FLT_MAX / 2;
379     }
380   }
381   else
382   { // non-passthrough, whatever works is fine
383     if (desc.mFormatID == kAudioFormatLinearPCM)
384     {
385       if (desc.mSampleRate == format.m_sampleRate)
386         score += 10;
387       else if (desc.mSampleRate > format.m_sampleRate)
388         score += 1;
389       if (desc.mChannelsPerFrame == format.m_channelLayout.Count())
390         score += 5;
391       else if (desc.mChannelsPerFrame > format.m_channelLayout.Count())
392         score += 1;
393       if (format.m_dataFormat == AE_FMT_FLOAT)
394       { // for float, prefer the highest bitdepth we have
395         if (desc.mBitsPerChannel >= 16)
396           score += (desc.mBitsPerChannel / 8);
397       }
398       else
399       {
400         if (desc.mBitsPerChannel == CAEUtil::DataFormatToBits(format.m_dataFormat))
401           score += 5;
402         else if (desc.mBitsPerChannel == CAEUtil::DataFormatToBits(format.m_dataFormat))
403           score += 1;
404       }
405     }
406   }
407   return score;
408 }
409
410 bool CAESinkDARWINOSX::Initialize(AEAudioFormat &format, std::string &device)
411 {
412   AudioDeviceID deviceID = 0;
413   CADeviceList devices = GetDevices();
414   if (StringUtils::EqualsNoCase(device, "default"))
415   {
416     CCoreAudioHardware::GetOutputDeviceName(device);
417     deviceID = CCoreAudioHardware::GetDefaultOutputDevice();
418     CLog::Log(LOGNOTICE, "%s: Opening default device %s", __PRETTY_FUNCTION__, device.c_str());
419   }
420   else
421   {
422     for (size_t i = 0; i < devices.size(); i++)
423     {
424       if (device.find(devices[i].second.m_deviceName) != std::string::npos)
425       {
426         deviceID = devices[i].first;
427         break;
428       }
429     }
430   }
431
432   if (!deviceID)
433   {
434     CLog::Log(LOGERROR, "%s: Unable to find device %s", __FUNCTION__, device.c_str());
435     return false;
436   }
437
438   m_device.Open(deviceID);
439
440   // Fetch a list of the streams defined by the output device
441   AudioStreamIdList streams;
442   m_device.GetStreams(&streams);
443
444   CLog::Log(LOGDEBUG, "%s: Finding stream for format %s", __FUNCTION__, CAEUtil::DataFormatToStr(format.m_dataFormat));
445
446   bool                        passthrough  = false;
447   UInt32                      outputIndex  = 0;
448   float                       outputScore  = 0;
449   AudioStreamBasicDescription outputFormat = {0};
450   AudioStreamID               outputStream = 0;
451
452   /* The theory is to score based on
453    1. Matching passthrough characteristics (i.e. passthrough flag)
454    2. Matching sample rate.
455    3. Matching bits per channel (or higher).
456    4. Matching number of channels (or higher).
457    */
458   UInt32 index = 0;
459   for (AudioStreamIdList::const_iterator i = streams.begin(); i != streams.end(); ++i)
460   {
461     // Probe physical formats
462     StreamFormatList formats;
463     CCoreAudioStream::GetAvailablePhysicalFormats(*i, &formats);
464     for (StreamFormatList::const_iterator j = formats.begin(); j != formats.end(); ++j)
465     {
466       const AudioStreamBasicDescription &desc = j->mFormat;
467
468       float score = ScoreStream(desc, format);
469
470       std::string formatString;
471       CLog::Log(LOGDEBUG, "%s: Physical Format: %s rated %f", __FUNCTION__, StreamDescriptionToString(desc, formatString), score);
472
473       if (score > outputScore)
474       {
475         passthrough  = score > 1000;
476         outputScore  = score;
477         outputFormat = desc;
478         outputStream = *i;
479         outputIndex  = index;
480       }
481     }
482     index++;
483   }
484
485   if (!outputFormat.mFormatID)
486   {
487     CLog::Log(LOGERROR, "%s, Unable to find suitable stream", __FUNCTION__);
488     return false;
489   }
490
491   /* Update our AE format */
492   format.m_sampleRate    = outputFormat.mSampleRate;
493   if (outputFormat.mChannelsPerFrame != format.m_channelLayout.Count())
494   { /* update the channel count.  We assume that they're layed out as given in CAChannelMap.
495        if they're not, this is plain wrong */
496     format.m_channelLayout.Reset();
497     for (unsigned int i = 0; i < outputFormat.mChannelsPerFrame; i++)
498       format.m_channelLayout += CAChannelMap[i];
499   }
500
501   m_outputBitstream   = passthrough && outputFormat.mFormatID == kAudioFormatLinearPCM;
502
503   std::string formatString;
504   CLog::Log(LOGDEBUG, "%s: Selected stream[%u] - id: 0x%04X, Physical Format: %s %s", __FUNCTION__, outputIndex, outputStream, StreamDescriptionToString(outputFormat, formatString), m_outputBitstream ? "bitstreamed passthrough" : "");
505
506   SetHogMode(passthrough);
507
508   // Configure the output stream object
509   m_outputStream.Open(outputStream);
510
511   AudioStreamBasicDescription virtualFormat, previousPhysicalFormat;
512   m_outputStream.GetVirtualFormat(&virtualFormat);
513   m_outputStream.GetPhysicalFormat(&previousPhysicalFormat);
514   CLog::Log(LOGDEBUG, "%s: Previous Virtual Format: %s", __FUNCTION__, StreamDescriptionToString(virtualFormat, formatString));
515   CLog::Log(LOGDEBUG, "%s: Previous Physical Format: %s", __FUNCTION__, StreamDescriptionToString(previousPhysicalFormat, formatString));
516
517   m_outputStream.SetPhysicalFormat(&outputFormat); // Set the active format (the old one will be reverted when we close)
518   m_outputStream.GetVirtualFormat(&virtualFormat);
519   CLog::Log(LOGDEBUG, "%s: New Virtual Format: %s", __FUNCTION__, StreamDescriptionToString(virtualFormat, formatString));
520   CLog::Log(LOGDEBUG, "%s: New Physical Format: %s", __FUNCTION__, StreamDescriptionToString(outputFormat, formatString));
521
522   m_latentFrames = m_device.GetNumLatencyFrames();
523   m_latentFrames += m_outputStream.GetNumLatencyFrames();
524
525   /* TODO: Should we use the virtual format to determine our data format? */
526   format.m_frameSize     = format.m_channelLayout.Count() * (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3);
527   format.m_frames        = m_device.GetBufferSize();
528   format.m_frameSamples  = format.m_frames * format.m_channelLayout.Count();
529
530   if (m_outputBitstream)
531   {
532     m_outputBuffer = new int16_t[format.m_frameSamples];
533     /* TODO: Do we need this? */
534     m_device.SetNominalSampleRate(format.m_sampleRate);
535   }
536
537   unsigned int num_buffers = 4;
538   m_buffer = new AERingBuffer(num_buffers * format.m_frames * format.m_frameSize);
539   CLog::Log(LOGDEBUG, "%s: using buffer size: %u (%f ms)", __FUNCTION__, m_buffer->GetMaxSize(), (float)m_buffer->GetMaxSize() / (format.m_sampleRate * format.m_frameSize));
540
541   m_format = format;
542   if (passthrough)
543     format.m_dataFormat = AE_FMT_S16NE;
544   else
545     format.m_dataFormat = AE_FMT_FLOAT;
546
547   // Register for data request callbacks from the driver and start
548   m_device.AddIOProc(renderCallback, this);
549   m_device.Start();
550   return true;
551 }
552
553 void CAESinkDARWINOSX::SetHogMode(bool on)
554 {
555   // TODO: Auto hogging sets this for us. Figure out how/when to turn it off or use it
556   // It appears that leaving this set will aslo restore the previous stream format when the
557   // Application exits. If auto hogging is set and we try to set hog mode, we will deadlock
558   // From the SDK docs: "If the AudioDevice is in a non-mixable mode, the HAL will automatically take hog mode on behalf of the first process to start an IOProc."
559
560   // Lock down the device.  This MUST be done PRIOR to switching to a non-mixable format, if it is done at all
561   // If it is attempted after the format change, there is a high likelihood of a deadlock
562   // We may need to do this sooner to enable mix-disable (i.e. before setting the stream format)
563   if (on)
564   {
565     // Auto-Hog does not always un-hog the device when changing back to a mixable mode.
566     // Handle this on our own until it is fixed.
567     CCoreAudioHardware::SetAutoHogMode(false);
568     bool autoHog = CCoreAudioHardware::GetAutoHogMode();
569     CLog::Log(LOGDEBUG, " CoreAudioRenderer::InitializeEncoded: "
570               "Auto 'hog' mode is set to '%s'.", autoHog ? "On" : "Off");
571     if (autoHog)
572       return;
573   }
574   m_device.SetHogStatus(on);
575   m_device.SetMixingSupport(!on);
576 }
577
578 void CAESinkDARWINOSX::Deinitialize()
579 {
580   m_device.Stop();
581   m_device.RemoveIOProc();
582
583   m_outputStream.Close();
584   m_device.Close();
585   if (m_buffer)
586   {
587     delete m_buffer;
588     m_buffer = NULL;
589   }
590   m_outputBitstream = false;
591
592   delete[] m_outputBuffer;
593   m_outputBuffer = NULL;
594
595   m_started = false;
596 }
597
598 bool CAESinkDARWINOSX::IsCompatible(const AEAudioFormat &format, const std::string &device)
599 {
600   return ((m_format.m_sampleRate    == format.m_sampleRate) &&
601           (m_format.m_dataFormat    == format.m_dataFormat) &&
602           (m_format.m_channelLayout == format.m_channelLayout));
603 }
604
605 double CAESinkDARWINOSX::GetDelay()
606 {
607   if (m_buffer)
608   {
609     // Calculate the duration of the data in the cache
610     double delay = (double)m_buffer->GetReadSize() / (double)m_format.m_frameSize;
611     delay += (double)m_latentFrames;
612     delay /= (double)m_format.m_sampleRate;
613     return delay;
614   }
615   return 0.0;
616 }
617
618 double CAESinkDARWINOSX::GetCacheTotal()
619 {
620   return (double)m_buffer->GetMaxSize() / (double)(m_format.m_frameSize * m_format.m_sampleRate);
621 }
622
623 CCriticalSection mutex;
624 XbmcThreads::ConditionVariable condVar;
625
626 unsigned int CAESinkDARWINOSX::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio, bool blocking)
627 {
628   if (m_buffer->GetWriteSize() < frames * m_format.m_frameSize)
629   { // no space to write - wait for a bit
630     CSingleLock lock(mutex);
631     unsigned int timeout = 900 * frames / m_format.m_sampleRate;
632     if (!m_started)
633       timeout = 500;
634
635     // we are using a timer here for beeing sure for timeouts
636     // condvar can be woken spuriously as signaled
637     XbmcThreads::EndTime timer(timeout);
638     condVar.wait(mutex, timeout);
639     if (!m_started && timer.IsTimePast())
640       return INT_MAX;    
641   }
642
643   unsigned int write_frames = std::min(frames, m_buffer->GetWriteSize() / m_format.m_frameSize);
644   if (write_frames)
645     m_buffer->Write(data, write_frames * m_format.m_frameSize);
646
647   return write_frames;
648 }
649
650 void CAESinkDARWINOSX::Drain()
651 {
652   int bytes = m_buffer->GetReadSize();
653   int totalBytes = bytes;
654   int maxNumTimeouts = 3;
655   unsigned int timeout = 900 * bytes / (m_format.m_sampleRate * m_format.m_frameSize);
656   while (bytes && maxNumTimeouts > 0)
657   {
658     CSingleLock lock(mutex);
659     XbmcThreads::EndTime timer(timeout);
660     condVar.wait(mutex, timeout);
661
662     bytes = m_buffer->GetReadSize();
663     // if we timeout and don't
664     // consum bytes - decrease maxNumTimeouts
665     if (timer.IsTimePast() && bytes == totalBytes)
666       maxNumTimeouts--;
667     totalBytes = bytes;
668   }
669 }
670
671 void CAESinkDARWINOSX::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
672 {
673   EnumerateDevices();
674   list.clear();
675   for (CADeviceList::const_iterator i = s_devices.begin(); i != s_devices.end(); ++i)
676     list.push_back(i->second);
677 }
678
679 inline void LogLevel(unsigned int got, unsigned int wanted)
680 {
681   static unsigned int lastReported = INT_MAX;
682   if (got != wanted)
683   {
684     if (got != lastReported)
685     {
686       CLog::Log(LOGWARNING, "DARWINOSX: %sflow (%u vs %u bytes)", got > wanted ? "over" : "under", got, wanted);
687       lastReported = got;
688     }    
689   }
690   else
691     lastReported = INT_MAX; // indicate we were good at least once
692 }
693
694 OSStatus CAESinkDARWINOSX::renderCallback(AudioDeviceID inDevice, const AudioTimeStamp* inNow, const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime, void* inClientData)
695 {
696   CAESinkDARWINOSX *sink = (CAESinkDARWINOSX*)inClientData;
697
698   sink->m_started = true;
699   for (unsigned int i = 0; i < outOutputData->mNumberBuffers; i++)
700   {
701     if (sink->m_outputBitstream)
702     {
703       /* HACK for bitstreaming AC3/DTS via PCM.
704        We reverse the float->S16LE conversion done in the stream or device */
705       static const float mul = 1.0f / (INT16_MAX + 1);
706
707       unsigned int wanted = std::min(outOutputData->mBuffers[i].mDataByteSize / sizeof(float), (size_t)sink->m_format.m_frameSamples)  * sizeof(int16_t);
708       if (wanted <= sink->m_buffer->GetReadSize())
709       {
710         sink->m_buffer->Read((unsigned char *)sink->m_outputBuffer, wanted);
711         int16_t *src = sink->m_outputBuffer;
712         float  *dest = (float*)outOutputData->mBuffers[i].mData;
713         for (unsigned int i = 0; i < wanted / 2; i++)
714           *dest++ = *src++ * mul;
715       }
716     }
717     else
718     {
719       /* buffers appear to come from CA already zero'd, so just copy what is wanted */
720       unsigned int wanted = outOutputData->mBuffers[i].mDataByteSize;
721       unsigned int bytes = std::min(sink->m_buffer->GetReadSize(), wanted);
722       sink->m_buffer->Read((unsigned char*)outOutputData->mBuffers[i].mData, bytes);
723       LogLevel(bytes, wanted);
724     }
725
726     // tell the sink we're good for more data
727     condVar.notifyAll();
728   }
729   return noErr;
730 }