[release] version bump to 13.0 beta1
[vuplus_xbmc] / xbmc / cores / AudioEngine / Engines / CoreAudio / CoreAudioAEHALOSX.cpp
1 /*
2  *      Copyright (C) 2011-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 #ifdef TARGET_DARWIN_OSX
22
23 #include "system.h"
24
25 #include "CoreAudioAEHALOSX.h"
26
27 #include "CoreAudioAE.h"
28 #include "CoreAudioAEHAL.h"
29 #include "CoreAudioUnit.h"
30 #include "CoreAudioDevice.h"
31 #include "CoreAudioGraph.h"
32 #include "CoreAudioMixMap.h"
33 #include "CoreAudioHardware.h"
34 #include "CoreAudioChannelLayout.h"
35
36 #include "cores/AudioEngine/Utils/AEUtil.h"
37 #include "utils/log.h"
38 #include "settings/Settings.h"
39
40 CCoreAudioAEHALOSX::CCoreAudioAEHALOSX() :
41   m_audioGraph        (NULL   ),
42   m_Initialized       (false  ),
43   m_Passthrough       (false  ),
44   m_allowMixing       (false  ),
45   m_encoded           (false  ),
46   m_initVolume        (1.0f   ),
47   m_NumLatencyFrames  (0      ),
48   m_OutputBufferIndex (0      ),
49   m_ae                (NULL   )
50 {
51   m_AudioDevice   = new CCoreAudioDevice();
52   m_OutputStream  = new CCoreAudioStream();
53
54   SInt32 major, minor;
55   Gestalt(gestaltSystemVersionMajor, &major);
56   Gestalt(gestaltSystemVersionMinor, &minor);
57
58   // By default, kAudioHardwarePropertyRunLoop points at the process's main thread on SnowLeopard,
59   // If your process lacks such a run loop, you can set kAudioHardwarePropertyRunLoop to NULL which
60   // tells the HAL to run it's own thread for notifications (which was the default prior to SnowLeopard).
61   // So tell the HAL to use its own thread for similar behavior under all supported versions of OSX.
62   if (major == 10 && minor >= 6)
63   {
64     CFRunLoopRef theRunLoop = NULL;
65     AudioObjectPropertyAddress theAddress = {
66       kAudioHardwarePropertyRunLoop,
67       kAudioObjectPropertyScopeGlobal,
68       kAudioObjectPropertyElementMaster
69     };
70     OSStatus theError = AudioObjectSetPropertyData(kAudioObjectSystemObject,
71       &theAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
72     if (theError != noErr)
73     {
74       CLog::Log(LOGERROR, "CCoreAudioAE::constructor: kAudioHardwarePropertyRunLoop error.");
75     }
76   }
77 }
78
79 CCoreAudioAEHALOSX::~CCoreAudioAEHALOSX()
80 {
81   Deinitialize();
82
83   delete m_audioGraph;
84   delete m_AudioDevice;
85   delete m_OutputStream;
86 }
87
88 bool CCoreAudioAEHALOSX::InitializePCM(ICoreAudioSource *pSource, AEAudioFormat &format, bool allowMixing, AudioDeviceID outputDevice)
89 {
90   if (m_audioGraph)
91     m_audioGraph->Close(), delete m_audioGraph;
92   m_audioGraph = new CCoreAudioGraph();
93   if (!m_audioGraph)
94     return false;
95
96   AudioChannelLayoutTag layout = g_LayoutMap[ CSettings::Get().GetInt("audiooutput.channels") ];
97   // force optical/coax to 2.0 output channels
98   if (!m_Passthrough && CSettings::Get().GetInt("audiooutput.channels") ==  AE_CH_LAYOUT_2_0)
99     layout = g_LayoutMap[1];
100
101   if (!m_audioGraph->Open(pSource, format, outputDevice, allowMixing, layout, m_initVolume ))
102   {
103     CLog::Log(LOGDEBUG, "CCoreAudioAEHALOSX::Initialize: "
104       "Unable to initialize audio due a missconfiguration. Try 2.0 speaker configuration.");
105     return false;
106   }
107
108   m_NumLatencyFrames = m_AudioDevice->GetNumLatencyFrames();
109
110   m_allowMixing = allowMixing;
111
112   return true;
113 }
114
115 bool CCoreAudioAEHALOSX::InitializePCMEncoded(ICoreAudioSource *pSource, AEAudioFormat &format, AudioDeviceID outputDevice)
116 {
117   // Prevent any other application from using this device.
118   m_AudioDevice->SetHogStatus(true);
119   // Try to disable mixing support. Effectiveness depends on the device.
120   m_AudioDevice->SetMixingSupport(false);
121   // Set the Sample Rate as defined by the spec.
122   m_AudioDevice->SetNominalSampleRate((float)format.m_sampleRate);
123
124   if (!InitializePCM(pSource, format, false, outputDevice))
125     return false;
126
127   return true;
128 }
129
130 bool CCoreAudioAEHALOSX::InitializeEncoded(AudioDeviceID outputDevice, AEAudioFormat &format)
131 {
132   std::string formatString;
133   AudioStreamID outputStream = 0;
134   AudioStreamBasicDescription outputFormat = {0};
135
136   // Fetch a list of the streams defined by the output device
137   UInt32 streamIndex = 0;
138   AudioStreamIdList streams;
139   m_AudioDevice->GetStreams(&streams);
140
141   m_OutputBufferIndex = 0;
142
143   while (!streams.empty())
144   {
145     // Get the next stream
146     CCoreAudioStream stream;
147     stream.Open(streams.front());
148     streams.pop_front(); // We copied it, now we are done with it
149
150     // Probe physical formats
151     StreamFormatList physicalFormats;
152     stream.GetAvailablePhysicalFormats(&physicalFormats);
153     while (!physicalFormats.empty())
154     {
155       AudioStreamRangedDescription& desc = physicalFormats.front();
156       CLog::Log(LOGDEBUG, "CCoreAudioAEHALOSX::InitializeEncoded:    "
157         "Considering Physical Format: %s", StreamDescriptionToString(desc.mFormat, formatString));
158
159       if (m_rawDataFormat == AE_FMT_LPCM   || m_rawDataFormat == AE_FMT_DTSHD ||
160           m_rawDataFormat == AE_FMT_TRUEHD || m_rawDataFormat == AE_FMT_EAC3)
161       {
162         // check pcm output formats
163         unsigned int bps = CAEUtil::DataFormatToBits(AE_FMT_S16NE);
164         if (desc.mFormat.mChannelsPerFrame == m_initformat.m_channelLayout.Count() &&
165             desc.mFormat.mBitsPerChannel == bps &&
166             desc.mFormat.mSampleRate == m_initformat.m_sampleRate )
167         {
168           outputFormat = desc.mFormat; // Select this format
169           m_OutputBufferIndex = streamIndex;
170           outputStream = stream.GetId();
171           break;
172         }
173       }
174       else
175       {
176         // check encoded formats
177         if (desc.mFormat.mFormatID == kAudioFormat60958AC3 || desc.mFormat.mFormatID == 'IAC3')
178         {
179           if (desc.mFormat.mChannelsPerFrame == m_initformat.m_channelLayout.Count() &&
180               desc.mFormat.mSampleRate == m_initformat.m_sampleRate )
181           {
182             outputFormat = desc.mFormat; // Select this format
183             m_OutputBufferIndex = streamIndex;
184             outputStream = stream.GetId();
185             break;
186           }
187         }
188       }
189       physicalFormats.pop_front();
190     }
191
192     // TODO: How do we determine if this is the right stream (not just the right format) to use?
193     if (outputFormat.mFormatID)
194       break; // We found a suitable format. No need to continue.
195     streamIndex++;
196   }
197
198   if (!outputFormat.mFormatID) // No match found
199   {
200     CLog::Log(LOGDEBUG, "CCoreAudioAEHALOSX::InitializeEncoded: "
201       "Unable to identify suitable output format.");
202     return false;
203   }
204
205   CLog::Log(LOGDEBUG, "CCoreAudioAEHALOSX::InitializeEncoded: "
206     "Selected stream[%u] - id: 0x%04X, Physical Format: %s",
207     m_OutputBufferIndex, (uint)outputStream, StreamDescriptionToString(outputFormat, formatString));
208
209   // TODO: Auto hogging sets this for us. Figure out how/when to turn it off or use it
210   // It appears that leaving this set will aslo restore the previous stream format when the
211   // Application exits. If auto hogging is set and we try to set hog mode, we will deadlock
212   // 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."
213
214   // Lock down the device.  This MUST be done PRIOR to switching to a non-mixable format, if it is done at all
215   // If it is attempted after the format change, there is a high likelihood of a deadlock
216   // We may need to do this sooner to enable mix-disable (i.e. before setting the stream format)
217
218   // Auto-Hog does not always un-hog the device when changing back to a mixable mode.
219   // Handle this on our own until it is fixed.
220   CCoreAudioHardware::SetAutoHogMode(false);
221   bool autoHog = CCoreAudioHardware::GetAutoHogMode();
222   CLog::Log(LOGDEBUG, " CoreAudioRenderer::InitializeEncoded: "
223     "Auto 'hog' mode is set to '%s'.", autoHog ? "On" : "Off");
224   if (!autoHog) // Try to handle this ourselves
225   {
226     // Hog the device if it is not set to be done automatically
227     m_AudioDevice->SetHogStatus(true);
228     // Try to disable mixing. If we cannot, it may not be a problem
229     m_AudioDevice->SetMixingSupport(false);
230   }
231
232   m_NumLatencyFrames = m_AudioDevice->GetNumLatencyFrames();
233
234   // Configure the output stream object, this is the one we will keep
235   m_OutputStream->Open(outputStream);
236
237   AudioStreamBasicDescription virtualFormat;
238   m_OutputStream->GetVirtualFormat(&virtualFormat);
239   CLog::Log(LOGDEBUG, "CCoreAudioAEHALOSX::InitializeEncoded: "
240     "Previous Virtual Format: %s", StreamDescriptionToString(virtualFormat, formatString));
241
242   AudioStreamBasicDescription previousPhysicalFormat;
243   m_OutputStream->GetPhysicalFormat(&previousPhysicalFormat);
244   CLog::Log(LOGDEBUG, "CCoreAudioAEHALOSX::InitializeEncoded: "
245     "Previous Physical Format: %s", StreamDescriptionToString(previousPhysicalFormat, formatString));
246
247   // Set the active format (the old one will be reverted when we close)
248   m_OutputStream->SetPhysicalFormat(&outputFormat);
249   m_NumLatencyFrames += m_OutputStream->GetNumLatencyFrames();
250
251   m_OutputStream->GetVirtualFormat(&virtualFormat);
252   CLog::Log(LOGDEBUG, "CCoreAudioAEHALOSX::InitializeEncoded: "
253     "New Virtual Format: %s", StreamDescriptionToString(virtualFormat, formatString));
254   CLog::Log(LOGDEBUG, "CCoreAudioAEHALOSX::InitializeEncoded: "
255     "New Physical Format: %s", StreamDescriptionToString(outputFormat, formatString));
256
257   m_allowMixing = false;
258
259   return true;
260 }
261
262 bool CCoreAudioAEHALOSX::Initialize(ICoreAudioSource *ae, bool passThrough, AEAudioFormat &format, AEDataFormat rawDataFormat, std::string &device, float initVolume)
263 {
264   // Reset all the devices to a default 'non-hog' and mixable format.
265   // If we don't do this we may be unable to find the Default Output device.
266   // (e.g. if we crashed last time leaving it stuck in AC-3 mode)
267
268   CCoreAudioHardware::ResetAudioDevices();
269
270   m_ae = (CCoreAudioAE*)ae;
271   if (!m_ae)
272     return false;
273
274   m_initformat          = format;
275   m_rawDataFormat       = rawDataFormat;
276   m_Passthrough         = passThrough;
277   m_encoded             = false;
278   m_OutputBufferIndex   = 0;
279   m_initVolume          = initVolume;
280
281   if (format.m_channelLayout.Count() == 0)
282   {
283     CLog::Log(LOGERROR, "CCoreAudioAEHALOSX::Initialize - "
284       "Unable to open the requested channel layout");
285     return false;
286   }
287
288   if (device.find("CoreAudio:") != std::string::npos)
289     device.erase(0, strlen("CoreAudio:"));
290
291   AudioDeviceID outputDevice = CCoreAudioHardware::FindAudioDevice(device);
292   if (!outputDevice)
293   {
294     // Fall back to the default device if no match is found
295     CLog::Log(LOGWARNING, "CCoreAudioAEHALOSX::Initialize: "
296       "Unable to locate configured device, falling-back to the system default.");
297     outputDevice = CCoreAudioHardware::GetDefaultOutputDevice();
298     if (!outputDevice) // Not a lot to be done with no device. TODO: Should we just grab the first existing device?
299       return false;
300   }
301
302   // Attach our output object to the device
303   m_AudioDevice->Open(outputDevice);
304   m_AudioDevice->SetHogStatus(false);
305   m_AudioDevice->SetMixingSupport(true);
306
307   // If this is a passthrough (AC3/DTS) stream, attempt to handle it natively
308   if (m_Passthrough)
309     m_encoded = InitializeEncoded(outputDevice, format);
310
311   // If this is a PCM stream, or we failed to handle a passthrough stream natively,
312   // prepare the standard interleaved PCM interface
313   if (!m_encoded)
314   {
315     // If we are here and this is a passthrough stream, native handling failed.
316     // Try to handle it as IEC61937 data over straight PCM (DD-Wav)
317     bool configured = false;
318     if (m_Passthrough)
319     {
320       CLog::Log(LOGDEBUG, "CCoreAudioAEHALOSX::Initialize: "
321         "No suitable AC3 output format found. Attempting DD-Wav.");
322       configured = InitializePCMEncoded(ae, format, outputDevice);
323     }
324     else
325     {
326       // Standard PCM data
327       configured = InitializePCM(ae, format, true, outputDevice);
328     }
329
330     // No suitable output format was able to be configured
331     if (!configured)
332       return false;
333   }
334
335   m_Initialized = true;
336
337   return true;
338 }
339
340 CAUOutputDevice *CCoreAudioAEHALOSX::DestroyUnit(CAUOutputDevice *outputUnit)
341 {
342   if (m_audioGraph && outputUnit)
343     return m_audioGraph->DestroyUnit(outputUnit);
344
345   return NULL;
346 }
347
348 CAUOutputDevice *CCoreAudioAEHALOSX::CreateUnit(ICoreAudioSource *pSource, AEAudioFormat &format)
349 {
350   CAUOutputDevice *outputUnit = NULL;
351
352   // when HAL is using a mixer, the input is routed through converter units.
353   // therefore we create a converter unit attach the source and give it back.
354   if (m_allowMixing && m_audioGraph)
355   {
356     outputUnit = m_audioGraph->CreateUnit(format);
357
358     if (pSource && outputUnit)
359       outputUnit->SetInputSource(pSource);
360   }
361
362   return outputUnit;
363 }
364
365 void CCoreAudioAEHALOSX::Deinitialize()
366 {
367   if (!m_Initialized)
368     return;
369
370   Stop();
371
372   if (m_encoded)
373     m_AudioDevice->SetInputSource(NULL, 0, 0);
374
375   if (m_audioGraph)
376     m_audioGraph->SetInputSource(NULL);
377
378   m_OutputStream->Close();
379   m_AudioDevice->Close();
380
381   if (m_audioGraph)
382   {
383     //m_audioGraph->Close();
384     delete m_audioGraph;
385   }
386   m_audioGraph = NULL;
387
388   m_NumLatencyFrames = 0;
389   m_OutputBufferIndex = 0;
390
391   m_Initialized = false;
392   m_Passthrough = false;
393 }
394
395 void CCoreAudioAEHALOSX::EnumerateOutputDevices(AEDeviceList &devices, bool passthrough)
396 {
397   CoreAudioDeviceList deviceList;
398   CCoreAudioHardware::GetOutputDevices(&deviceList);
399
400   devices.push_back(AEDevice("Default", "CoreAudio:default"));
401
402   std::string deviceName;
403   for (int i = 0; !deviceList.empty(); i++)
404   {
405     CCoreAudioDevice device(deviceList.front());
406     deviceName = device.GetName();
407
408     std::string deviceName_Internal = std::string("CoreAudio:");
409     deviceName_Internal.append(deviceName);
410     devices.push_back(AEDevice(deviceName, deviceName_Internal));
411
412     deviceList.pop_front();
413   }
414 }
415
416 void CCoreAudioAEHALOSX::Stop()
417 {
418   if (!m_Initialized)
419     return;
420
421   if (m_encoded)
422     m_AudioDevice->Stop();
423   else
424     m_audioGraph->Stop();
425 }
426
427 bool CCoreAudioAEHALOSX::Start()
428 {
429   if (!m_Initialized)
430     return false;
431
432   if (m_encoded)
433     m_AudioDevice->Start();
434   else
435     m_audioGraph->Start();
436
437   return true;
438 }
439
440 void CCoreAudioAEHALOSX::SetDirectInput(ICoreAudioSource *pSource, AEAudioFormat &format)
441 {
442   if (!m_Initialized)
443     return;
444
445   // when HAL is initialized encoded we use directIO
446   // when HAL is not in encoded mode and there is no mixer attach source the audio unit
447   // when mixing is allowed in HAL, HAL is working with converter units where we attach the source.
448
449   if (m_encoded)
450   {
451     // register directcallback for the audio HAL
452     // direct render callback need to know the framesize and buffer index
453     if (pSource)
454       m_AudioDevice->SetInputSource(pSource, format.m_frameSize, m_OutputBufferIndex);
455     else
456       m_AudioDevice->SetInputSource(pSource, 0, 0);
457   }
458   else if (!m_encoded && !m_allowMixing)
459   {
460     // register render callback for the audio unit
461     m_audioGraph->SetInputSource(pSource);
462   }
463 }
464
465 double CCoreAudioAEHALOSX::GetDelay()
466 {
467   return (double)(m_NumLatencyFrames) / (m_initformat.m_sampleRate);
468 }
469
470 void CCoreAudioAEHALOSX::SetVolume(float volume)
471 {
472   if (m_encoded || m_Passthrough)
473     return;
474
475   m_audioGraph->SetCurrentVolume(volume);
476 }
477
478 unsigned int CCoreAudioAEHALOSX::GetBufferIndex()
479 {
480   return m_OutputBufferIndex;
481 }
482
483 #endif