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 #include "CoreAudioStream.h"
23 #include "CoreAudioHelpers.h"
24 #include "utils/log.h"
25 #include "utils/StdString.h"
27 CCoreAudioStream::CCoreAudioStream() :
30 m_OriginalVirtualFormat.mFormatID = 0;
31 m_OriginalPhysicalFormat.mFormatID = 0;
34 CCoreAudioStream::~CCoreAudioStream()
39 bool CCoreAudioStream::Open(AudioStreamID streamId)
41 m_StreamId = streamId;
42 CLog::Log(LOGDEBUG, "CCoreAudioStream::Open: Opened stream 0x%04x.", (uint)m_StreamId);
44 // watch for physical property changes.
45 AudioObjectPropertyAddress propertyAOPA;
46 propertyAOPA.mScope = kAudioObjectPropertyScopeGlobal;
47 propertyAOPA.mElement = kAudioObjectPropertyElementMaster;
48 propertyAOPA.mSelector = kAudioStreamPropertyPhysicalFormat;
49 if (AudioObjectAddPropertyListener(m_StreamId, &propertyAOPA, HardwareStreamListener, this) != noErr)
50 CLog::Log(LOGERROR, "CCoreAudioStream::Open: couldn't set up a physical property listener.");
52 // watch for virtual property changes.
53 propertyAOPA.mScope = kAudioObjectPropertyScopeGlobal;
54 propertyAOPA.mElement = kAudioObjectPropertyElementMaster;
55 propertyAOPA.mSelector = kAudioStreamPropertyVirtualFormat;
56 if (AudioObjectAddPropertyListener(m_StreamId, &propertyAOPA, HardwareStreamListener, this) != noErr)
57 CLog::Log(LOGERROR, "CCoreAudioStream::Open: couldn't set up a virtual property listener.");
62 // TODO: Should it even be possible to change both the
63 // physical and virtual formats, since the devices do it themselves?
64 void CCoreAudioStream::Close(bool restore)
69 std::string formatString;
71 // remove the physical/virtual property listeners before we make changes
72 // that will trigger callbacks that we do not care about.
73 AudioObjectPropertyAddress propertyAOPA;
74 propertyAOPA.mScope = kAudioObjectPropertyScopeGlobal;
75 propertyAOPA.mElement = kAudioObjectPropertyElementMaster;
76 propertyAOPA.mSelector = kAudioStreamPropertyPhysicalFormat;
77 if (AudioObjectRemovePropertyListener(m_StreamId, &propertyAOPA, HardwareStreamListener, this) != noErr)
78 CLog::Log(LOGDEBUG, "CCoreAudioStream::Close: Couldn't remove property listener.");
80 propertyAOPA.mScope = kAudioObjectPropertyScopeGlobal;
81 propertyAOPA.mElement = kAudioObjectPropertyElementMaster;
82 propertyAOPA.mSelector = kAudioStreamPropertyVirtualFormat;
83 if (AudioObjectRemovePropertyListener(m_StreamId, &propertyAOPA, HardwareStreamListener, this) != noErr)
84 CLog::Log(LOGDEBUG, "CCoreAudioStream::Close: Couldn't remove property listener.");
86 // Revert any format changes we made
87 if (restore && m_OriginalVirtualFormat.mFormatID && m_StreamId)
89 CLog::Log(LOGDEBUG, "CCoreAudioStream::Close: "
90 "Restoring original virtual format for stream 0x%04x. (%s)",
91 (uint)m_StreamId, StreamDescriptionToString(m_OriginalVirtualFormat, formatString));
92 AudioStreamBasicDescription setFormat = m_OriginalVirtualFormat;
93 SetVirtualFormat(&setFormat);
95 if (restore && m_OriginalPhysicalFormat.mFormatID && m_StreamId)
97 CLog::Log(LOGDEBUG, "CCoreAudioStream::Close: "
98 "Restoring original physical format for stream 0x%04x. (%s)",
99 (uint)m_StreamId, StreamDescriptionToString(m_OriginalPhysicalFormat, formatString));
100 AudioStreamBasicDescription setFormat = m_OriginalPhysicalFormat;
101 SetPhysicalFormat(&setFormat);
104 m_OriginalVirtualFormat.mFormatID = 0;
105 m_OriginalPhysicalFormat.mFormatID = 0;
106 CLog::Log(LOGDEBUG, "CCoreAudioStream::Close: Closed stream 0x%04x.", (uint)m_StreamId);
110 UInt32 CCoreAudioStream::GetDirection()
116 UInt32 size = sizeof(UInt32);
118 AudioObjectPropertyAddress propertyAddress;
119 propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
120 propertyAddress.mElement = kAudioObjectPropertyElementMaster;
121 propertyAddress.mSelector = kAudioStreamPropertyDirection;
123 OSStatus ret = AudioObjectGetPropertyData(m_StreamId, &propertyAddress, 0, NULL, &size, &val);
130 // WARNING - don't rely on this method - the return value of
131 // GetTerminalType is driver specific - the checked return
132 // values are only recommendations from apple
133 bool CCoreAudioStream::IsDigitalOuptut(AudioStreamID id)
135 UInt32 type = GetTerminalType(id);
136 // yes apple is mixing types here...
137 return (type == kAudioStreamTerminalTypeDigitalAudioInterface ||
138 type == kIOAudioDeviceTransportTypeDisplayPort ||
139 type == kIOAudioDeviceTransportTypeHdmi ||
140 type == kIOAudioDeviceTransportTypeFireWire ||
141 type == kIOAudioDeviceTransportTypeThunderbolt ||
142 type == kIOAudioDeviceTransportTypeUSB);
145 UInt32 CCoreAudioStream::GetTerminalType(AudioStreamID id)
151 UInt32 size = sizeof(UInt32);
153 AudioObjectPropertyAddress propertyAddress;
154 propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
155 propertyAddress.mElement = kAudioObjectPropertyElementMaster;
156 propertyAddress.mSelector = kAudioStreamPropertyTerminalType;
158 OSStatus ret = AudioObjectGetPropertyData(id, &propertyAddress, 0, NULL, &size, &val);
164 UInt32 CCoreAudioStream::GetNumLatencyFrames()
169 UInt32 i_param_size = sizeof(uint32_t);
170 UInt32 i_param, num_latency_frames = 0;
172 AudioObjectPropertyAddress propertyAddress;
173 propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
174 propertyAddress.mElement = kAudioObjectPropertyElementMaster;
175 propertyAddress.mSelector = kAudioStreamPropertyLatency;
177 // number of frames of latency in the AudioStream
178 OSStatus ret = AudioObjectGetPropertyData(m_StreamId, &propertyAddress, 0, NULL, &i_param_size, &i_param);
181 num_latency_frames += i_param;
184 return num_latency_frames;
187 bool CCoreAudioStream::GetVirtualFormat(AudioStreamBasicDescription* pDesc)
189 if (!pDesc || !m_StreamId)
192 UInt32 size = sizeof(AudioStreamBasicDescription);
194 AudioObjectPropertyAddress propertyAddress;
195 propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
196 propertyAddress.mElement = kAudioObjectPropertyElementMaster;
197 propertyAddress.mSelector = kAudioStreamPropertyVirtualFormat;
198 OSStatus ret = AudioObjectGetPropertyDataSize(m_StreamId, &propertyAddress, 0, NULL, &size);
202 ret = AudioObjectGetPropertyData(m_StreamId, &propertyAddress, 0, NULL, &size, pDesc);
208 bool CCoreAudioStream::SetVirtualFormat(AudioStreamBasicDescription* pDesc)
210 if (!pDesc || !m_StreamId)
213 std::string formatString;
215 if (!m_OriginalVirtualFormat.mFormatID)
217 // Store the original format (as we found it) so that it can be restored later
218 if (!GetVirtualFormat(&m_OriginalVirtualFormat))
220 CLog::Log(LOGERROR, "CCoreAudioStream::SetVirtualFormat: "
221 "Unable to retrieve current virtual format for stream 0x%04x.", (uint)m_StreamId);
225 m_virtual_format_event.Reset();
227 AudioObjectPropertyAddress propertyAddress;
228 propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
229 propertyAddress.mElement = kAudioObjectPropertyElementMaster;
230 propertyAddress.mSelector = kAudioStreamPropertyVirtualFormat;
232 UInt32 propertySize = sizeof(AudioStreamBasicDescription);
233 OSStatus ret = AudioObjectSetPropertyData(m_StreamId, &propertyAddress, 0, NULL, propertySize, pDesc);
236 CLog::Log(LOGERROR, "CCoreAudioStream::SetVirtualFormat: "
237 "Unable to set virtual format for stream 0x%04x. Error = %s",
238 (uint)m_StreamId, GetError(ret).c_str());
242 // The AudioStreamSetProperty is not only asynchronious,
243 // it is also not Atomic, in its behaviour.
244 // Therefore we check 5 times before we really give up.
245 // FIXME: failing isn't actually implemented yet.
246 for (int i = 0; i < 10; ++i)
248 AudioStreamBasicDescription checkVirtualFormat;
249 if (!GetVirtualFormat(&checkVirtualFormat))
251 CLog::Log(LOGERROR, "CCoreAudioStream::SetVirtualFormat: "
252 "Unable to retrieve current physical format for stream 0x%04x.", (uint)m_StreamId);
255 if (checkVirtualFormat.mSampleRate == pDesc->mSampleRate &&
256 checkVirtualFormat.mFormatID == pDesc->mFormatID &&
257 checkVirtualFormat.mFramesPerPacket == pDesc->mFramesPerPacket)
259 // The right format is now active.
260 CLog::Log(LOGDEBUG, "CCoreAudioStream::SetVirtualFormat: "
261 "Virtual format for stream 0x%04x. now active (%s)",
262 (uint)m_StreamId, StreamDescriptionToString(checkVirtualFormat, formatString));
265 m_virtual_format_event.WaitMSec(100);
270 bool CCoreAudioStream::GetPhysicalFormat(AudioStreamBasicDescription* pDesc)
272 if (!pDesc || !m_StreamId)
275 UInt32 size = sizeof(AudioStreamBasicDescription);
277 AudioObjectPropertyAddress propertyAddress;
278 propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
279 propertyAddress.mElement = kAudioObjectPropertyElementMaster;
280 propertyAddress.mSelector = kAudioStreamPropertyPhysicalFormat;
282 OSStatus ret = AudioObjectGetPropertyData(m_StreamId, &propertyAddress, 0, NULL, &size, pDesc);
288 bool CCoreAudioStream::SetPhysicalFormat(AudioStreamBasicDescription* pDesc)
290 if (!pDesc || !m_StreamId)
293 std::string formatString;
295 if (!m_OriginalPhysicalFormat.mFormatID)
297 // Store the original format (as we found it) so that it can be restored later
298 if (!GetPhysicalFormat(&m_OriginalPhysicalFormat))
300 CLog::Log(LOGERROR, "CCoreAudioStream::SetPhysicalFormat: "
301 "Unable to retrieve current physical format for stream 0x%04x.", (uint)m_StreamId);
305 m_physical_format_event.Reset();
307 AudioObjectPropertyAddress propertyAddress;
308 propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
309 propertyAddress.mElement = kAudioObjectPropertyElementMaster;
310 propertyAddress.mSelector = kAudioStreamPropertyPhysicalFormat;
312 UInt32 propertySize = sizeof(AudioStreamBasicDescription);
313 OSStatus ret = AudioObjectSetPropertyData(m_StreamId, &propertyAddress, 0, NULL, propertySize, pDesc);
316 CLog::Log(LOGERROR, "CCoreAudioStream::SetPhysicalFormat: "
317 "Unable to set physical format for stream 0x%04x. Error = %s",
318 (uint)m_StreamId, GetError(ret).c_str());
322 // The AudioStreamSetProperty is not only asynchronious,
323 // it is also not Atomic, in its behaviour.
324 // Therefore we check 5 times before we really give up.
325 // FIXME: failing isn't actually implemented yet.
326 for(int i = 0; i < 10; ++i)
328 AudioStreamBasicDescription checkPhysicalFormat;
329 if (!GetPhysicalFormat(&checkPhysicalFormat))
331 CLog::Log(LOGERROR, "CCoreAudioStream::SetPhysicalFormat: "
332 "Unable to retrieve current physical format for stream 0x%04x.", (uint)m_StreamId);
335 if (checkPhysicalFormat.mSampleRate == pDesc->mSampleRate &&
336 checkPhysicalFormat.mFormatID == pDesc->mFormatID &&
337 checkPhysicalFormat.mFramesPerPacket == pDesc->mFramesPerPacket &&
338 checkPhysicalFormat.mChannelsPerFrame == pDesc->mChannelsPerFrame)
340 // The right format is now active.
341 CLog::Log(LOGDEBUG, "CCoreAudioStream::SetPhysicalFormat: "
342 "Physical format for stream 0x%04x. now active (%s)",
343 (uint)m_StreamId, StreamDescriptionToString(checkPhysicalFormat, formatString));
346 m_physical_format_event.WaitMSec(100);
352 bool CCoreAudioStream::GetAvailableVirtualFormats(StreamFormatList* pList)
354 return GetAvailableVirtualFormats(m_StreamId, pList);
357 bool CCoreAudioStream::GetAvailableVirtualFormats(AudioStreamID id, StreamFormatList* pList)
362 AudioObjectPropertyAddress propertyAddress;
363 propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
364 propertyAddress.mElement = kAudioObjectPropertyElementMaster;
365 propertyAddress.mSelector = kAudioStreamPropertyAvailableVirtualFormats;
367 UInt32 propertySize = 0;
368 OSStatus ret = AudioObjectGetPropertyDataSize(id, &propertyAddress, 0, NULL, &propertySize);
372 UInt32 formatCount = propertySize / sizeof(AudioStreamRangedDescription);
373 AudioStreamRangedDescription *pFormatList = new AudioStreamRangedDescription[formatCount];
374 ret = AudioObjectGetPropertyData(id, &propertyAddress, 0, NULL, &propertySize, pFormatList);
377 for (UInt32 format = 0; format < formatCount; format++)
378 pList->push_back(pFormatList[format]);
380 delete[] pFormatList;
381 return (ret == noErr);
384 bool CCoreAudioStream::GetAvailablePhysicalFormats(StreamFormatList* pList)
386 return GetAvailablePhysicalFormats(m_StreamId, pList);
389 bool CCoreAudioStream::GetAvailablePhysicalFormats(AudioStreamID id, StreamFormatList* pList)
394 AudioObjectPropertyAddress propertyAddress;
395 propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
396 propertyAddress.mElement = kAudioObjectPropertyElementMaster;
397 propertyAddress.mSelector = kAudioStreamPropertyAvailablePhysicalFormats;
399 UInt32 propertySize = 0;
400 OSStatus ret = AudioObjectGetPropertyDataSize(id, &propertyAddress, 0, NULL, &propertySize);
404 UInt32 formatCount = propertySize / sizeof(AudioStreamRangedDescription);
405 AudioStreamRangedDescription *pFormatList = new AudioStreamRangedDescription[formatCount];
406 ret = AudioObjectGetPropertyData(id, &propertyAddress, 0, NULL, &propertySize, pFormatList);
409 for (UInt32 format = 0; format < formatCount; format++)
410 pList->push_back(pFormatList[format]);
412 delete[] pFormatList;
413 return (ret == noErr);
416 OSStatus CCoreAudioStream::HardwareStreamListener(AudioObjectID inObjectID,
417 UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData)
419 CCoreAudioStream *ca_stream = (CCoreAudioStream*)inClientData;
421 for (UInt32 i = 0; i < inNumberAddresses; i++)
423 if (inAddresses[i].mSelector == kAudioStreamPropertyPhysicalFormat)
425 AudioStreamBasicDescription actualFormat;
426 UInt32 propertySize = sizeof(AudioStreamBasicDescription);
427 // hardware physical format has changed.
428 if (AudioObjectGetPropertyData(ca_stream->m_StreamId, &inAddresses[i], 0, NULL, &propertySize, &actualFormat) == noErr)
430 CStdString formatString;
431 CLog::Log(LOGINFO, "CCoreAudioStream::HardwareStreamListener: "
432 "Hardware physical format changed to %s", StreamDescriptionToString(actualFormat, formatString));
433 ca_stream->m_physical_format_event.Set();
436 else if (inAddresses[i].mSelector == kAudioStreamPropertyVirtualFormat)
438 // hardware virtual format has changed.
439 AudioStreamBasicDescription actualFormat;
440 UInt32 propertySize = sizeof(AudioStreamBasicDescription);
441 if (AudioObjectGetPropertyData(ca_stream->m_StreamId, &inAddresses[i], 0, NULL, &propertySize, &actualFormat) == noErr)
443 CStdString formatString;
444 CLog::Log(LOGINFO, "CCoreAudioStream::HardwareStreamListener: "
445 "Hardware virtual format changed to %s", StreamDescriptionToString(actualFormat, formatString));
446 ca_stream->m_virtual_format_event.Set();