2 * Copyright (C) 2011-2013 Team XBMC
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)
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.
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/>.
21 #ifdef TARGET_DARWIN_OSX
25 #include "CoreAudioAEHALOSX.h"
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"
36 #include "cores/AudioEngine/Utils/AEUtil.h"
37 #include "utils/log.h"
38 #include "settings/Settings.h"
40 CCoreAudioAEHALOSX::CCoreAudioAEHALOSX() :
42 m_Initialized (false ),
43 m_Passthrough (false ),
44 m_allowMixing (false ),
47 m_NumLatencyFrames (0 ),
48 m_OutputBufferIndex (0 ),
51 m_AudioDevice = new CCoreAudioDevice();
52 m_OutputStream = new CCoreAudioStream();
55 Gestalt(gestaltSystemVersionMajor, &major);
56 Gestalt(gestaltSystemVersionMinor, &minor);
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)
64 CFRunLoopRef theRunLoop = NULL;
65 AudioObjectPropertyAddress theAddress = {
66 kAudioHardwarePropertyRunLoop,
67 kAudioObjectPropertyScopeGlobal,
68 kAudioObjectPropertyElementMaster
70 OSStatus theError = AudioObjectSetPropertyData(kAudioObjectSystemObject,
71 &theAddress, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
72 if (theError != noErr)
74 CLog::Log(LOGERROR, "CCoreAudioAE::constructor: kAudioHardwarePropertyRunLoop error.");
79 CCoreAudioAEHALOSX::~CCoreAudioAEHALOSX()
85 delete m_OutputStream;
88 bool CCoreAudioAEHALOSX::InitializePCM(ICoreAudioSource *pSource, AEAudioFormat &format, bool allowMixing, AudioDeviceID outputDevice)
91 m_audioGraph->Close(), delete m_audioGraph;
92 m_audioGraph = new CCoreAudioGraph();
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];
101 if (!m_audioGraph->Open(pSource, format, outputDevice, allowMixing, layout, m_initVolume ))
103 CLog::Log(LOGDEBUG, "CCoreAudioAEHALOSX::Initialize: "
104 "Unable to initialize audio due a missconfiguration. Try 2.0 speaker configuration.");
108 m_NumLatencyFrames = m_AudioDevice->GetNumLatencyFrames();
110 m_allowMixing = allowMixing;
115 bool CCoreAudioAEHALOSX::InitializePCMEncoded(ICoreAudioSource *pSource, AEAudioFormat &format, AudioDeviceID outputDevice)
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);
124 if (!InitializePCM(pSource, format, false, outputDevice))
130 bool CCoreAudioAEHALOSX::InitializeEncoded(AudioDeviceID outputDevice, AEAudioFormat &format)
132 std::string formatString;
133 AudioStreamID outputStream = 0;
134 AudioStreamBasicDescription outputFormat = {0};
136 // Fetch a list of the streams defined by the output device
137 UInt32 streamIndex = 0;
138 AudioStreamIdList streams;
139 m_AudioDevice->GetStreams(&streams);
141 m_OutputBufferIndex = 0;
143 while (!streams.empty())
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
150 // Probe physical formats
151 StreamFormatList physicalFormats;
152 stream.GetAvailablePhysicalFormats(&physicalFormats);
153 while (!physicalFormats.empty())
155 AudioStreamRangedDescription& desc = physicalFormats.front();
156 CLog::Log(LOGDEBUG, "CCoreAudioAEHALOSX::InitializeEncoded: "
157 "Considering Physical Format: %s", StreamDescriptionToString(desc.mFormat, formatString));
159 if (m_rawDataFormat == AE_FMT_LPCM || m_rawDataFormat == AE_FMT_DTSHD ||
160 m_rawDataFormat == AE_FMT_TRUEHD || m_rawDataFormat == AE_FMT_EAC3)
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 )
168 outputFormat = desc.mFormat; // Select this format
169 m_OutputBufferIndex = streamIndex;
170 outputStream = stream.GetId();
176 // check encoded formats
177 if (desc.mFormat.mFormatID == kAudioFormat60958AC3 || desc.mFormat.mFormatID == 'IAC3')
179 if (desc.mFormat.mChannelsPerFrame == m_initformat.m_channelLayout.Count() &&
180 desc.mFormat.mSampleRate == m_initformat.m_sampleRate )
182 outputFormat = desc.mFormat; // Select this format
183 m_OutputBufferIndex = streamIndex;
184 outputStream = stream.GetId();
189 physicalFormats.pop_front();
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.
198 if (!outputFormat.mFormatID) // No match found
200 CLog::Log(LOGDEBUG, "CCoreAudioAEHALOSX::InitializeEncoded: "
201 "Unable to identify suitable output format.");
205 CLog::Log(LOGDEBUG, "CCoreAudioAEHALOSX::InitializeEncoded: "
206 "Selected stream[%u] - id: 0x%04X, Physical Format: %s",
207 m_OutputBufferIndex, (uint)outputStream, StreamDescriptionToString(outputFormat, formatString));
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."
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)
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
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);
232 m_NumLatencyFrames = m_AudioDevice->GetNumLatencyFrames();
234 // Configure the output stream object, this is the one we will keep
235 m_OutputStream->Open(outputStream);
237 AudioStreamBasicDescription virtualFormat;
238 m_OutputStream->GetVirtualFormat(&virtualFormat);
239 CLog::Log(LOGDEBUG, "CCoreAudioAEHALOSX::InitializeEncoded: "
240 "Previous Virtual Format: %s", StreamDescriptionToString(virtualFormat, formatString));
242 AudioStreamBasicDescription previousPhysicalFormat;
243 m_OutputStream->GetPhysicalFormat(&previousPhysicalFormat);
244 CLog::Log(LOGDEBUG, "CCoreAudioAEHALOSX::InitializeEncoded: "
245 "Previous Physical Format: %s", StreamDescriptionToString(previousPhysicalFormat, formatString));
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();
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));
257 m_allowMixing = false;
262 bool CCoreAudioAEHALOSX::Initialize(ICoreAudioSource *ae, bool passThrough, AEAudioFormat &format, AEDataFormat rawDataFormat, std::string &device, float initVolume)
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)
268 CCoreAudioHardware::ResetAudioDevices();
270 m_ae = (CCoreAudioAE*)ae;
274 m_initformat = format;
275 m_rawDataFormat = rawDataFormat;
276 m_Passthrough = passThrough;
278 m_OutputBufferIndex = 0;
279 m_initVolume = initVolume;
281 if (format.m_channelLayout.Count() == 0)
283 CLog::Log(LOGERROR, "CCoreAudioAEHALOSX::Initialize - "
284 "Unable to open the requested channel layout");
288 if (device.find("CoreAudio:") != std::string::npos)
289 device.erase(0, strlen("CoreAudio:"));
291 AudioDeviceID outputDevice = CCoreAudioHardware::FindAudioDevice(device);
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?
302 // Attach our output object to the device
303 m_AudioDevice->Open(outputDevice);
304 m_AudioDevice->SetHogStatus(false);
305 m_AudioDevice->SetMixingSupport(true);
307 // If this is a passthrough (AC3/DTS) stream, attempt to handle it natively
309 m_encoded = InitializeEncoded(outputDevice, format);
311 // If this is a PCM stream, or we failed to handle a passthrough stream natively,
312 // prepare the standard interleaved PCM interface
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;
320 CLog::Log(LOGDEBUG, "CCoreAudioAEHALOSX::Initialize: "
321 "No suitable AC3 output format found. Attempting DD-Wav.");
322 configured = InitializePCMEncoded(ae, format, outputDevice);
327 configured = InitializePCM(ae, format, true, outputDevice);
330 // No suitable output format was able to be configured
335 m_Initialized = true;
340 CAUOutputDevice *CCoreAudioAEHALOSX::DestroyUnit(CAUOutputDevice *outputUnit)
342 if (m_audioGraph && outputUnit)
343 return m_audioGraph->DestroyUnit(outputUnit);
348 CAUOutputDevice *CCoreAudioAEHALOSX::CreateUnit(ICoreAudioSource *pSource, AEAudioFormat &format)
350 CAUOutputDevice *outputUnit = NULL;
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)
356 outputUnit = m_audioGraph->CreateUnit(format);
358 if (pSource && outputUnit)
359 outputUnit->SetInputSource(pSource);
365 void CCoreAudioAEHALOSX::Deinitialize()
373 m_AudioDevice->SetInputSource(NULL, 0, 0);
376 m_audioGraph->SetInputSource(NULL);
378 m_OutputStream->Close();
379 m_AudioDevice->Close();
383 //m_audioGraph->Close();
388 m_NumLatencyFrames = 0;
389 m_OutputBufferIndex = 0;
391 m_Initialized = false;
392 m_Passthrough = false;
395 void CCoreAudioAEHALOSX::EnumerateOutputDevices(AEDeviceList &devices, bool passthrough)
397 CoreAudioDeviceList deviceList;
398 CCoreAudioHardware::GetOutputDevices(&deviceList);
400 devices.push_back(AEDevice("Default", "CoreAudio:default"));
402 std::string deviceName;
403 for (int i = 0; !deviceList.empty(); i++)
405 CCoreAudioDevice device(deviceList.front());
406 deviceName = device.GetName();
408 std::string deviceName_Internal = std::string("CoreAudio:");
409 deviceName_Internal.append(deviceName);
410 devices.push_back(AEDevice(deviceName, deviceName_Internal));
412 deviceList.pop_front();
416 void CCoreAudioAEHALOSX::Stop()
422 m_AudioDevice->Stop();
424 m_audioGraph->Stop();
427 bool CCoreAudioAEHALOSX::Start()
433 m_AudioDevice->Start();
435 m_audioGraph->Start();
440 void CCoreAudioAEHALOSX::SetDirectInput(ICoreAudioSource *pSource, AEAudioFormat &format)
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.
451 // register directcallback for the audio HAL
452 // direct render callback need to know the framesize and buffer index
454 m_AudioDevice->SetInputSource(pSource, format.m_frameSize, m_OutputBufferIndex);
456 m_AudioDevice->SetInputSource(pSource, 0, 0);
458 else if (!m_encoded && !m_allowMixing)
460 // register render callback for the audio unit
461 m_audioGraph->SetInputSource(pSource);
465 double CCoreAudioAEHALOSX::GetDelay()
467 return (double)(m_NumLatencyFrames) / (m_initformat.m_sampleRate);
470 void CCoreAudioAEHALOSX::SetVolume(float volume)
472 if (m_encoded || m_Passthrough)
475 m_audioGraph->SetCurrentVolume(volume);
478 unsigned int CCoreAudioAEHALOSX::GetBufferIndex()
480 return m_OutputBufferIndex;