Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

582 lines (479 sloc) 15.441 kb
/*
* Copyright (C) 2011-2013 Team XBMC
* http://xbmc.org
*
* This Program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This Program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with XBMC; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*
*/
#include "CoreAudioGraph.h"
#include "CoreAudioAEHAL.h"
#include "CoreAudioMixMap.h"
#include "CoreAudioUnit.h"
#include "utils/log.h"
#include "utils/SystemInfo.h"
CCoreAudioGraph::CCoreAudioGraph() :
m_audioGraph (NULL ),
m_inputUnit (NULL ),
m_audioUnit (NULL ),
m_mixerUnit (NULL ),
m_initialized (false),
m_deviceId (NULL ),
m_allowMixing (false),
m_mixMap (NULL )
{
for (int i = 0; i < MAX_CONNECTION_LIMIT; i++)
{
m_reservedBusNumber[i] = false;
}
}
CCoreAudioGraph::~CCoreAudioGraph()
{
Close();
delete m_mixMap;
}
bool CCoreAudioGraph::Open(ICoreAudioSource *pSource, AEAudioFormat &format,
AudioDeviceID deviceId, bool allowMixing, AudioChannelLayoutTag layoutTag, float initVolume, bool encoded)
{
AudioStreamBasicDescription fmt = {0};
AudioStreamBasicDescription inputFormat = {0};
AudioStreamBasicDescription outputFormat = {0};
m_deviceId = deviceId;
m_allowMixing = allowMixing;
OSStatus ret = NewAUGraph(&m_audioGraph);
if (ret)
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
"Error create audio grpah. Error = %s", GetError(ret).c_str());
return false;
}
ret = AUGraphOpen(m_audioGraph);
if (ret)
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
"Error open audio grpah. Error = %s", GetError(ret).c_str());
return false;
}
// get output unit
if (m_audioUnit)
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
"Error audio unit already open. double call ?");
return false;
}
m_audioUnit = new CAUOutputDevice();
if (!m_audioUnit->Open(m_audioGraph,
kAudioUnitType_Output, kAudioUnitSubType_HALOutput, kAudioUnitManufacturer_Apple))
return false;
m_audioUnit->SetBus(GetFreeBus());
m_audioUnit->GetFormatDesc(format, &inputFormat, &fmt, encoded);
if (!m_audioUnit->EnableInputOuput())
return false;
if (!m_audioUnit->SetCurrentDevice(deviceId))
return false;
SetCurrentVolume(initVolume);
if (allowMixing)
{
delete m_mixMap;
m_mixMap = CCoreAudioMixMap::CreateMixMap(m_audioUnit, format, layoutTag);
if (m_mixMap && m_mixMap->IsValid())
{
// maximum input channel ber input bus
//fmt.mChannelsPerFrame = MAXIMUM_MIXER_CHANNELS;
// get output unit
if (m_inputUnit)
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Open: Error mixer unit already open. double call ?");
return false;
}
m_inputUnit = new CAUOutputDevice();
if (!m_inputUnit->Open(m_audioGraph,
kAudioUnitType_FormatConverter, kAudioUnitSubType_AUConverter, kAudioUnitManufacturer_Apple))
return false;
if (!m_inputUnit->SetFormat(&inputFormat, kAudioUnitScope_Input, kOutputBus))
return false;
if (!m_inputUnit->SetFormat(&fmt, kAudioUnitScope_Output, kOutputBus))
return false;
// get mixer unit
if (m_mixerUnit)
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Open: Error mixer unit already open. double call ?");
return false;
}
m_mixerUnit = new CAUMatrixMixer();
if (!m_mixerUnit->Open(m_audioGraph,
kAudioUnitType_Mixer, kAudioUnitSubType_MatrixMixer, kAudioUnitManufacturer_Apple))
return false;
// set number of input buses
if (!m_mixerUnit->SetInputBusCount(MAX_CONNECTION_LIMIT))
return false;
// set number of output buses
if (!m_mixerUnit->SetOutputBusCount(1))
return false;
if (!m_mixerUnit->SetInputBusFormat(MAX_CONNECTION_LIMIT, &fmt))
return false;
// Update format structure to reflect the desired format from the mixer
// The output format of the mixer is identical to the input format, except for the channel count
AudioStreamBasicDescription mixOutput(fmt);
mixOutput.mChannelsPerFrame = m_mixMap->GetOutputChannels();
if (!m_mixerUnit->SetFormat(&mixOutput, kAudioUnitScope_Output, kOutputBus))
return false;
ret = AUGraphConnectNodeInput(m_audioGraph, m_mixerUnit->GetNode(), 0, m_audioUnit->GetNode(), 0);
if (ret)
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
"Error connecting m_m_mixerNode. Error = %s", GetError(ret).c_str());
return false;
}
m_mixerUnit->SetBus(0);
// configure output unit
int busNumber = GetFreeBus();
ret = AUGraphConnectNodeInput(m_audioGraph, m_inputUnit->GetNode(), 0, m_mixerUnit->GetNode(), busNumber);
if (ret)
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
"Error connecting m_converterNode. Error = %s", GetError(ret).c_str());
return false;
}
m_inputUnit->SetBus(busNumber);
ret = AUGraphUpdate(m_audioGraph, NULL);
if (ret)
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
"Error update graph. Error = %s", GetError(ret).c_str());
return false;
}
ret = AUGraphInitialize(m_audioGraph);
if (ret)
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
"Error initialize graph. Error = %s", GetError(ret).c_str());
return false;
}
UInt32 inputNumber = m_inputUnit->GetBus();
int channelOffset = GetMixerChannelOffset(inputNumber);
if (!CCoreAudioMixMap::SetMixingMatrix(m_mixerUnit, m_mixMap, &fmt, &mixOutput, channelOffset))
return false;
// Regenerate audio format and copy format for the Output AU
outputFormat = mixOutput;
}
else
{
outputFormat = inputFormat;
}
}
else
{
outputFormat = inputFormat;
}
if (!m_audioUnit->SetFormat(&outputFormat, kAudioUnitScope_Input, kOutputBus))
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
"Error setting input format on audio device. Channel count %d, set it to %d",
(int)outputFormat.mChannelsPerFrame, format.m_channelLayout.Count());
outputFormat.mChannelsPerFrame = format.m_channelLayout.Count();
if (!m_audioUnit->SetFormat(&outputFormat, kAudioUnitScope_Input, kOutputBus))
return false;
}
std::string formatString;
// asume we are in dd-wave mode
if (!m_inputUnit)
{
if (!m_audioUnit->SetFormat(&inputFormat, kAudioUnitScope_Output, kInputBus))
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
"Error setting Device Output Stream Format %s",
StreamDescriptionToString(inputFormat, formatString));
}
}
ret = AUGraphUpdate(m_audioGraph, NULL);
if (ret)
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
"Error update graph. Error = %s", GetError(ret).c_str());
return false;
}
AudioStreamBasicDescription inputDesc_end, outputDesc_end;
m_audioUnit->GetFormat(&inputDesc_end, kAudioUnitScope_Input, kOutputBus);
m_audioUnit->GetFormat(&outputDesc_end, kAudioUnitScope_Output, kInputBus);
CLog::Log(LOGDEBUG, "CCoreAudioGraph::Open: audioUnit, Input Stream Format %s",
StreamDescriptionToString(inputDesc_end, formatString));
CLog::Log(LOGDEBUG, "CCoreAudioGraph::Open: audioUnit, Output Stream Format %s",
StreamDescriptionToString(outputDesc_end, formatString));
if (m_mixerUnit)
{
m_mixerUnit->GetFormat(&inputDesc_end, kAudioUnitScope_Input, kOutputBus);
m_mixerUnit->GetFormat(&outputDesc_end, kAudioUnitScope_Output, kOutputBus);
CLog::Log(LOGDEBUG, "CCoreAudioGraph::Open: mixerUnit, Input Stream Format %s",
StreamDescriptionToString(inputDesc_end, formatString));
CLog::Log(LOGDEBUG, "CCoreAudioGraph::Open: mixerUnit, Output Stream Format %s",
StreamDescriptionToString(outputDesc_end, formatString));
}
if (m_inputUnit)
{
m_inputUnit->GetFormat(&inputDesc_end, kAudioUnitScope_Input, kOutputBus);
m_inputUnit->GetFormat(&outputDesc_end, kAudioUnitScope_Output, kOutputBus);
CLog::Log(LOGDEBUG, "CCoreAudioGraph::Open: inputUnit, Input Stream Format %s",
StreamDescriptionToString(inputDesc_end, formatString));
CLog::Log(LOGDEBUG, "CCoreAudioGraph::Open: inputUnit, Output Stream Format %s",
StreamDescriptionToString(outputDesc_end, formatString));
}
ret = AUGraphInitialize(m_audioGraph);
if (ret)
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Open: "
"Error initialize graph. Error = %s", GetError(ret).c_str());
return false;
}
UInt32 bufferFrames = m_audioUnit->GetBufferFrameSize();
if (!m_audioUnit->SetMaxFramesPerSlice(bufferFrames))
return false;
SetInputSource(pSource);
return Start();
}
bool CCoreAudioGraph::Close()
{
if (!m_audioGraph)
return false;
Stop();
SetInputSource(NULL);
while (!m_auUnitList.empty())
{
CAUOutputDevice *d = m_auUnitList.front();
m_auUnitList.pop_front();
ReleaseBus(d->GetBus());
d->SetInputSource(NULL);
d->Close();
delete d;
}
if (m_inputUnit)
{
ReleaseBus(m_inputUnit->GetBus());
m_inputUnit->Close();
delete m_inputUnit;
m_inputUnit = NULL;
}
if (m_mixerUnit)
{
m_mixerUnit->Close();
delete m_mixerUnit;
m_mixerUnit = NULL;
}
if (m_audioUnit)
{
m_audioUnit->Close();
delete m_audioUnit;
m_audioUnit = NULL;
}
OSStatus ret = AUGraphUninitialize(m_audioGraph);
if (ret)
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Close: "
"Error unitialize. Error = %s", GetError(ret).c_str());
}
ret = AUGraphClose(m_audioGraph);
if (ret)
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Close: "
"Error close. Error = %s", GetError(ret).c_str());
}
ret = DisposeAUGraph(m_audioGraph);
if (ret)
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Close: "
"Error dispose. Error = %s", GetError(ret).c_str());
}
return true;
}
bool CCoreAudioGraph::Start()
{
if (!m_audioGraph)
return false;
Boolean isRunning = false;
OSStatus ret = AUGraphIsRunning(m_audioGraph, &isRunning);
if (ret)
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Start: "
"Audio graph not running. Error = %s", GetError(ret).c_str());
return false;
}
if (!isRunning)
{
if (m_audioUnit)
m_audioUnit->Start();
if (m_mixerUnit)
m_mixerUnit->Start();
if (m_inputUnit)
m_inputUnit->Start();
ret = AUGraphStart(m_audioGraph);
if (ret)
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Start: "
"Error starting audio graph. Error = %s", GetError(ret).c_str());
}
}
return true;
}
bool CCoreAudioGraph::Stop()
{
if (!m_audioGraph)
return false;
Boolean isRunning = false;
OSStatus ret = AUGraphIsRunning(m_audioGraph, &isRunning);
if (ret)
{
if (m_inputUnit)
m_inputUnit->Stop();
if (m_mixerUnit)
m_mixerUnit->Stop();
if (m_audioUnit)
m_audioUnit->Stop();
CLog::Log(LOGERROR, "CCoreAudioGraph::Stop: "
"Audio graph not running. Error = %s", GetError(ret).c_str());
return false;
}
if (isRunning)
{
ret = AUGraphStop(m_audioGraph);
if (ret)
{
CLog::Log(LOGERROR, "CCoreAudioGraph::Stop: "
"Error stopping audio graph. Error = %s", GetError(ret).c_str());
}
}
return true;
}
AudioChannelLayoutTag CCoreAudioGraph::GetChannelLayoutTag(int layout)
{
return g_LayoutMap[layout];
}
bool CCoreAudioGraph::SetInputSource(ICoreAudioSource* pSource)
{
if (m_inputUnit)
return m_inputUnit->SetInputSource(pSource);
else if (m_audioUnit)
return m_audioUnit->SetInputSource(pSource);
return false;
}
bool CCoreAudioGraph::SetCurrentVolume(Float32 vol)
{
if (!m_audioUnit)
return false;
return m_audioUnit->SetCurrentVolume(vol);
}
CAUOutputDevice *CCoreAudioGraph::DestroyUnit(CAUOutputDevice *outputUnit)
{
if (!outputUnit)
return NULL;
Stop();
for (AUUnitList::iterator itt = m_auUnitList.begin(); itt != m_auUnitList.end(); ++itt)
{
if (*itt == outputUnit)
{
m_auUnitList.erase(itt);
break;
}
}
ReleaseBus(outputUnit->GetBus());
outputUnit->SetInputSource(NULL);
outputUnit->Close();
delete outputUnit;
AUGraphUpdate(m_audioGraph, NULL);
Start();
return NULL;
}
CAUOutputDevice *CCoreAudioGraph::CreateUnit(AEAudioFormat &format)
{
if (!m_audioUnit || !m_mixerUnit)
return NULL;
AudioStreamBasicDescription fmt = {0};
AudioStreamBasicDescription inputFormat = {0};
AudioStreamBasicDescription outputFormat = {0};
int busNumber = GetFreeBus();
if (busNumber == INVALID_BUS)
return NULL;
OSStatus ret;
// create output unit
CAUOutputDevice *outputUnit = new CAUOutputDevice();
if (!outputUnit->Open(m_audioGraph,
kAudioUnitType_FormatConverter, kAudioUnitSubType_AUConverter, kAudioUnitManufacturer_Apple))
goto error;
m_audioUnit->GetFormatDesc(format, &inputFormat, &fmt);
// get the format frm the mixer
if (!m_mixerUnit->GetFormat(&outputFormat, kAudioUnitScope_Input, kOutputBus))
goto error;
if (!outputUnit->SetFormat(&inputFormat, kAudioUnitScope_Input, kOutputBus))
goto error;
if (!outputUnit->SetFormat(&outputFormat, kAudioUnitScope_Output, kOutputBus))
goto error;
ret = AUGraphConnectNodeInput(m_audioGraph, outputUnit->GetNode(), 0, m_mixerUnit->GetNode(), busNumber);
if (ret)
{
CLog::Log(LOGERROR, "CCoreAudioGraph::CreateUnit: "
"Error connecting outputUnit. Error = %s", GetError(ret).c_str());
goto error;
}
// TODO: setup mixmap, get free bus number for connection
outputUnit->SetBus(busNumber);
if (m_mixMap || m_mixMap->IsValid())
{
UInt32 inputNumber = outputUnit->GetBus();
int channelOffset = GetMixerChannelOffset(inputNumber);
CCoreAudioMixMap::SetMixingMatrix(m_mixerUnit, m_mixMap, &inputFormat, &fmt, channelOffset);
}
AUGraphUpdate(m_audioGraph, NULL);
m_auUnitList.push_back(outputUnit);
return outputUnit;
error:
delete outputUnit;
return NULL;
}
int CCoreAudioGraph::GetFreeBus()
{
for (int i = 0; i < MAX_CONNECTION_LIMIT; i++)
{
if (!m_reservedBusNumber[i])
{
m_reservedBusNumber[i] = true;
return i;
}
}
return INVALID_BUS;
}
void CCoreAudioGraph::ReleaseBus(int busNumber)
{
if (busNumber > MAX_CONNECTION_LIMIT || busNumber < 0)
return;
m_reservedBusNumber[busNumber] = false;
}
bool CCoreAudioGraph::IsBusFree(int busNumber)
{
if (busNumber > MAX_CONNECTION_LIMIT || busNumber < 0)
return false;
return m_reservedBusNumber[busNumber];
}
int CCoreAudioGraph::GetMixerChannelOffset(int busNumber)
{
if (!m_mixerUnit)
return 0;
int offset = 0;
AudioStreamBasicDescription fmt = {0};
for (int i = 0; i < busNumber; i++)
{
memset(&fmt, 0x0, sizeof(fmt));
m_mixerUnit->GetFormat(&fmt, kAudioUnitScope_Input, busNumber);
offset += fmt.mChannelsPerFrame;
}
return offset;
}
Jump to Line
Something went wrong with that request. Please try again.