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 "CoreAudioGraph.h"
23 #include "CoreAudioAEHAL.h"
24 #include "CoreAudioMixMap.h"
25 #include "CoreAudioUnit.h"
27 #include "utils/log.h"
28 #include "utils/SystemInfo.h"
30 CCoreAudioGraph::CCoreAudioGraph() :
35 m_initialized (false),
37 m_allowMixing (false),
40 for (int i = 0; i < MAX_CONNECTION_LIMIT; i++)
42 m_reservedBusNumber[i] = false;
46 CCoreAudioGraph::~CCoreAudioGraph()
53 bool CCoreAudioGraph::Open(ICoreAudioSource *pSource, AEAudioFormat &format,
54 AudioDeviceID deviceId, bool allowMixing, AudioChannelLayoutTag layoutTag, float initVolume, bool encoded)
56 AudioStreamBasicDescription fmt = {0};
57 AudioStreamBasicDescription inputFormat = {0};
58 AudioStreamBasicDescription outputFormat = {0};
60 m_deviceId = deviceId;
61 m_allowMixing = allowMixing;
63 OSStatus ret = NewAUGraph(&m_audioGraph);
66 CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
67 "Error create audio grpah. Error = %s", GetError(ret).c_str());
70 ret = AUGraphOpen(m_audioGraph);
73 CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
74 "Error open audio grpah. Error = %s", GetError(ret).c_str());
81 CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
82 "Error audio unit already open. double call ?");
86 m_audioUnit = new CAUOutputDevice();
87 if (!m_audioUnit->Open(m_audioGraph,
88 kAudioUnitType_Output, kAudioUnitSubType_HALOutput, kAudioUnitManufacturer_Apple))
90 m_audioUnit->SetBus(GetFreeBus());
92 m_audioUnit->GetFormatDesc(format, &inputFormat, &fmt, encoded);
94 if (!m_audioUnit->EnableInputOuput())
97 if (!m_audioUnit->SetCurrentDevice(deviceId))
100 SetCurrentVolume(initVolume);
105 m_mixMap = CCoreAudioMixMap::CreateMixMap(m_audioUnit, format, layoutTag);
107 if (m_mixMap && m_mixMap->IsValid())
109 // maximum input channel ber input bus
110 //fmt.mChannelsPerFrame = MAXIMUM_MIXER_CHANNELS;
115 CLog::Log(LOGERROR, "CCoreAudioGraph::Open: Error mixer unit already open. double call ?");
119 m_inputUnit = new CAUOutputDevice();
121 if (!m_inputUnit->Open(m_audioGraph,
122 kAudioUnitType_FormatConverter, kAudioUnitSubType_AUConverter, kAudioUnitManufacturer_Apple))
125 if (!m_inputUnit->SetFormat(&inputFormat, kAudioUnitScope_Input, kOutputBus))
128 if (!m_inputUnit->SetFormat(&fmt, kAudioUnitScope_Output, kOutputBus))
134 CLog::Log(LOGERROR, "CCoreAudioGraph::Open: Error mixer unit already open. double call ?");
138 m_mixerUnit = new CAUMatrixMixer();
140 if (!m_mixerUnit->Open(m_audioGraph,
141 kAudioUnitType_Mixer, kAudioUnitSubType_MatrixMixer, kAudioUnitManufacturer_Apple))
144 // set number of input buses
145 if (!m_mixerUnit->SetInputBusCount(MAX_CONNECTION_LIMIT))
148 // set number of output buses
149 if (!m_mixerUnit->SetOutputBusCount(1))
152 if (!m_mixerUnit->SetInputBusFormat(MAX_CONNECTION_LIMIT, &fmt))
155 // Update format structure to reflect the desired format from the mixer
156 // The output format of the mixer is identical to the input format, except for the channel count
157 AudioStreamBasicDescription mixOutput(fmt);
158 mixOutput.mChannelsPerFrame = m_mixMap->GetOutputChannels();
160 if (!m_mixerUnit->SetFormat(&mixOutput, kAudioUnitScope_Output, kOutputBus))
163 ret = AUGraphConnectNodeInput(m_audioGraph, m_mixerUnit->GetNode(), 0, m_audioUnit->GetNode(), 0);
166 CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
167 "Error connecting m_m_mixerNode. Error = %s", GetError(ret).c_str());
171 m_mixerUnit->SetBus(0);
173 // configure output unit
174 int busNumber = GetFreeBus();
176 ret = AUGraphConnectNodeInput(m_audioGraph, m_inputUnit->GetNode(), 0, m_mixerUnit->GetNode(), busNumber);
179 CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
180 "Error connecting m_converterNode. Error = %s", GetError(ret).c_str());
184 m_inputUnit->SetBus(busNumber);
186 ret = AUGraphUpdate(m_audioGraph, NULL);
189 CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
190 "Error update graph. Error = %s", GetError(ret).c_str());
193 ret = AUGraphInitialize(m_audioGraph);
196 CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
197 "Error initialize graph. Error = %s", GetError(ret).c_str());
201 UInt32 inputNumber = m_inputUnit->GetBus();
202 int channelOffset = GetMixerChannelOffset(inputNumber);
203 if (!CCoreAudioMixMap::SetMixingMatrix(m_mixerUnit, m_mixMap, &fmt, &mixOutput, channelOffset))
206 // Regenerate audio format and copy format for the Output AU
207 outputFormat = mixOutput;
211 outputFormat = inputFormat;
217 outputFormat = inputFormat;
220 if (!m_audioUnit->SetFormat(&outputFormat, kAudioUnitScope_Input, kOutputBus))
222 CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
223 "Error setting input format on audio device. Channel count %d, set it to %d",
224 (int)outputFormat.mChannelsPerFrame, format.m_channelLayout.Count());
225 outputFormat.mChannelsPerFrame = format.m_channelLayout.Count();
226 if (!m_audioUnit->SetFormat(&outputFormat, kAudioUnitScope_Input, kOutputBus))
230 std::string formatString;
231 // asume we are in dd-wave mode
234 if (!m_audioUnit->SetFormat(&inputFormat, kAudioUnitScope_Output, kInputBus))
236 CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
237 "Error setting Device Output Stream Format %s",
238 StreamDescriptionToString(inputFormat, formatString));
242 ret = AUGraphUpdate(m_audioGraph, NULL);
245 CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
246 "Error update graph. Error = %s", GetError(ret).c_str());
250 AudioStreamBasicDescription inputDesc_end, outputDesc_end;
251 m_audioUnit->GetFormat(&inputDesc_end, kAudioUnitScope_Input, kOutputBus);
252 m_audioUnit->GetFormat(&outputDesc_end, kAudioUnitScope_Output, kInputBus);
253 CLog::Log(LOGDEBUG, "CCoreAudioGraph::Open: audioUnit, Input Stream Format %s",
254 StreamDescriptionToString(inputDesc_end, formatString));
255 CLog::Log(LOGDEBUG, "CCoreAudioGraph::Open: audioUnit, Output Stream Format %s",
256 StreamDescriptionToString(outputDesc_end, formatString));
260 m_mixerUnit->GetFormat(&inputDesc_end, kAudioUnitScope_Input, kOutputBus);
261 m_mixerUnit->GetFormat(&outputDesc_end, kAudioUnitScope_Output, kOutputBus);
262 CLog::Log(LOGDEBUG, "CCoreAudioGraph::Open: mixerUnit, Input Stream Format %s",
263 StreamDescriptionToString(inputDesc_end, formatString));
264 CLog::Log(LOGDEBUG, "CCoreAudioGraph::Open: mixerUnit, Output Stream Format %s",
265 StreamDescriptionToString(outputDesc_end, formatString));
270 m_inputUnit->GetFormat(&inputDesc_end, kAudioUnitScope_Input, kOutputBus);
271 m_inputUnit->GetFormat(&outputDesc_end, kAudioUnitScope_Output, kOutputBus);
272 CLog::Log(LOGDEBUG, "CCoreAudioGraph::Open: inputUnit, Input Stream Format %s",
273 StreamDescriptionToString(inputDesc_end, formatString));
274 CLog::Log(LOGDEBUG, "CCoreAudioGraph::Open: inputUnit, Output Stream Format %s",
275 StreamDescriptionToString(outputDesc_end, formatString));
278 ret = AUGraphInitialize(m_audioGraph);
281 CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
282 "Error initialize graph. Error = %s", GetError(ret).c_str());
286 UInt32 bufferFrames = m_audioUnit->GetBufferFrameSize();
287 if (!m_audioUnit->SetMaxFramesPerSlice(bufferFrames))
290 SetInputSource(pSource);
295 bool CCoreAudioGraph::Close()
302 SetInputSource(NULL);
304 while (!m_auUnitList.empty())
306 CAUOutputDevice *d = m_auUnitList.front();
307 m_auUnitList.pop_front();
308 ReleaseBus(d->GetBus());
309 d->SetInputSource(NULL);
316 ReleaseBus(m_inputUnit->GetBus());
317 m_inputUnit->Close();
324 m_mixerUnit->Close();
331 m_audioUnit->Close();
336 OSStatus ret = AUGraphUninitialize(m_audioGraph);
339 CLog::Log(LOGERROR, "CCoreAudioGraph::Close: "
340 "Error unitialize. Error = %s", GetError(ret).c_str());
343 ret = AUGraphClose(m_audioGraph);
346 CLog::Log(LOGERROR, "CCoreAudioGraph::Close: "
347 "Error close. Error = %s", GetError(ret).c_str());
350 ret = DisposeAUGraph(m_audioGraph);
353 CLog::Log(LOGERROR, "CCoreAudioGraph::Close: "
354 "Error dispose. Error = %s", GetError(ret).c_str());
360 bool CCoreAudioGraph::Start()
365 Boolean isRunning = false;
366 OSStatus ret = AUGraphIsRunning(m_audioGraph, &isRunning);
369 CLog::Log(LOGERROR, "CCoreAudioGraph::Start: "
370 "Audio graph not running. Error = %s", GetError(ret).c_str());
376 m_audioUnit->Start();
378 m_mixerUnit->Start();
380 m_inputUnit->Start();
382 ret = AUGraphStart(m_audioGraph);
385 CLog::Log(LOGERROR, "CCoreAudioGraph::Start: "
386 "Error starting audio graph. Error = %s", GetError(ret).c_str());
393 bool CCoreAudioGraph::Stop()
398 Boolean isRunning = false;
399 OSStatus ret = AUGraphIsRunning(m_audioGraph, &isRunning);
409 CLog::Log(LOGERROR, "CCoreAudioGraph::Stop: "
410 "Audio graph not running. Error = %s", GetError(ret).c_str());
415 ret = AUGraphStop(m_audioGraph);
418 CLog::Log(LOGERROR, "CCoreAudioGraph::Stop: "
419 "Error stopping audio graph. Error = %s", GetError(ret).c_str());
426 AudioChannelLayoutTag CCoreAudioGraph::GetChannelLayoutTag(int layout)
428 return g_LayoutMap[layout];
431 bool CCoreAudioGraph::SetInputSource(ICoreAudioSource* pSource)
434 return m_inputUnit->SetInputSource(pSource);
435 else if (m_audioUnit)
436 return m_audioUnit->SetInputSource(pSource);
441 bool CCoreAudioGraph::SetCurrentVolume(Float32 vol)
446 return m_audioUnit->SetCurrentVolume(vol);
449 CAUOutputDevice *CCoreAudioGraph::DestroyUnit(CAUOutputDevice *outputUnit)
456 for (AUUnitList::iterator itt = m_auUnitList.begin(); itt != m_auUnitList.end(); ++itt)
458 if (*itt == outputUnit)
460 m_auUnitList.erase(itt);
465 ReleaseBus(outputUnit->GetBus());
466 outputUnit->SetInputSource(NULL);
470 AUGraphUpdate(m_audioGraph, NULL);
477 CAUOutputDevice *CCoreAudioGraph::CreateUnit(AEAudioFormat &format)
479 if (!m_audioUnit || !m_mixerUnit)
482 AudioStreamBasicDescription fmt = {0};
483 AudioStreamBasicDescription inputFormat = {0};
484 AudioStreamBasicDescription outputFormat = {0};
486 int busNumber = GetFreeBus();
487 if (busNumber == INVALID_BUS)
491 // create output unit
492 CAUOutputDevice *outputUnit = new CAUOutputDevice();
493 if (!outputUnit->Open(m_audioGraph,
494 kAudioUnitType_FormatConverter, kAudioUnitSubType_AUConverter, kAudioUnitManufacturer_Apple))
497 m_audioUnit->GetFormatDesc(format, &inputFormat, &fmt);
499 // get the format frm the mixer
500 if (!m_mixerUnit->GetFormat(&outputFormat, kAudioUnitScope_Input, kOutputBus))
503 if (!outputUnit->SetFormat(&inputFormat, kAudioUnitScope_Input, kOutputBus))
506 if (!outputUnit->SetFormat(&outputFormat, kAudioUnitScope_Output, kOutputBus))
509 ret = AUGraphConnectNodeInput(m_audioGraph, outputUnit->GetNode(), 0, m_mixerUnit->GetNode(), busNumber);
512 CLog::Log(LOGERROR, "CCoreAudioGraph::CreateUnit: "
513 "Error connecting outputUnit. Error = %s", GetError(ret).c_str());
517 // TODO: setup mixmap, get free bus number for connection
519 outputUnit->SetBus(busNumber);
521 if (m_mixMap || m_mixMap->IsValid())
523 UInt32 inputNumber = outputUnit->GetBus();
524 int channelOffset = GetMixerChannelOffset(inputNumber);
525 CCoreAudioMixMap::SetMixingMatrix(m_mixerUnit, m_mixMap, &inputFormat, &fmt, channelOffset);
528 AUGraphUpdate(m_audioGraph, NULL);
529 m_auUnitList.push_back(outputUnit);
538 int CCoreAudioGraph::GetFreeBus()
540 for (int i = 0; i < MAX_CONNECTION_LIMIT; i++)
542 if (!m_reservedBusNumber[i])
544 m_reservedBusNumber[i] = true;
551 void CCoreAudioGraph::ReleaseBus(int busNumber)
553 if (busNumber > MAX_CONNECTION_LIMIT || busNumber < 0)
556 m_reservedBusNumber[busNumber] = false;
559 bool CCoreAudioGraph::IsBusFree(int busNumber)
561 if (busNumber > MAX_CONNECTION_LIMIT || busNumber < 0)
563 return m_reservedBusNumber[busNumber];
566 int CCoreAudioGraph::GetMixerChannelOffset(int busNumber)
572 AudioStreamBasicDescription fmt = {0};
574 for (int i = 0; i < busNumber; i++)
576 memset(&fmt, 0x0, sizeof(fmt));
577 m_mixerUnit->GetFormat(&fmt, kAudioUnitScope_Input, busNumber);
578 offset += fmt.mChannelsPerFrame;