#include "cores/AudioEngine/Utils/AERingBuffer.h"
#include "cores/AudioEngine/Sinks/osx/CoreAudioHelpers.h"
#include "cores/AudioEngine/Sinks/osx/CoreAudioHardware.h"
+#include "cores/AudioEngine/Sinks/osx/CoreAudioChannelLayout.h"
#include "osx/DarwinUtils.h"
#include "utils/log.h"
#include "utils/StringUtils.h"
#include <sstream>
#define CA_MAX_CHANNELS 16
+// default channel map - in case it can't be fetched from the device
static enum AEChannel CAChannelMap[CA_MAX_CHANNELS + 1] = {
AE_CH_FL , AE_CH_FR , AE_CH_BL , AE_CH_BR , AE_CH_FC , AE_CH_LFE , AE_CH_SL , AE_CH_SR ,
AE_CH_UNKNOWN1 ,
AE_CH_NULL
};
+// map coraudio channel labels to activeae channel labels
+static enum AEChannel CAChannelToAEChannel(AudioChannelLabel CAChannelLabel)
+{
+ enum AEChannel ret = AE_CH_NULL;
+ static unsigned int unknownChannel = AE_CH_UNKNOWN1;
+ switch(CAChannelLabel)
+ {
+ case kAudioChannelLabel_Left:
+ ret = AE_CH_FL;
+ break;
+ case kAudioChannelLabel_Right:
+ ret = AE_CH_FR;
+ break;
+ case kAudioChannelLabel_Center:
+ ret = AE_CH_FC;
+ break;
+ case kAudioChannelLabel_LFEScreen:
+ ret = AE_CH_LFE;
+ break;
+ case kAudioChannelLabel_LeftSurroundDirect:
+ ret = AE_CH_SL;
+ break;
+ case kAudioChannelLabel_RightSurroundDirect:
+ ret = AE_CH_SR;
+ break;
+ case kAudioChannelLabel_LeftCenter:
+ ret = AE_CH_FLOC;
+ break;
+ case kAudioChannelLabel_RightCenter:
+ ret = AE_CH_FROC;
+ break;
+ case kAudioChannelLabel_CenterSurround:
+ ret = AE_CH_TC;
+ break;
+ case kAudioChannelLabel_LeftSurround:
+ ret = AE_CH_SL;
+ break;
+ case kAudioChannelLabel_RightSurround:
+ ret = AE_CH_SR;
+ break;
+ case kAudioChannelLabel_VerticalHeightLeft:
+ ret = AE_CH_TFL;
+ break;
+ case kAudioChannelLabel_VerticalHeightRight:
+ ret = AE_CH_TFR;
+ break;
+ case kAudioChannelLabel_VerticalHeightCenter:
+ ret = AE_CH_TFC;
+ break;
+ case kAudioChannelLabel_TopCenterSurround:
+ ret = AE_CH_TC;
+ break;
+ case kAudioChannelLabel_TopBackLeft:
+ ret = AE_CH_TBL;
+ break;
+ case kAudioChannelLabel_TopBackRight:
+ ret = AE_CH_TBR;
+ break;
+ case kAudioChannelLabel_TopBackCenter:
+ ret = AE_CH_TBC;
+ break;
+ case kAudioChannelLabel_RearSurroundLeft:
+ ret = AE_CH_BL;
+ break;
+ case kAudioChannelLabel_RearSurroundRight:
+ ret = AE_CH_BR;
+ break;
+ case kAudioChannelLabel_LeftWide:
+ ret = AE_CH_BLOC;
+ break;
+ case kAudioChannelLabel_RightWide:
+ ret = AE_CH_BROC;
+ break;
+ case kAudioChannelLabel_LFE2:
+ ret = AE_CH_LFE;
+ break;
+ case kAudioChannelLabel_LeftTotal:
+ ret = AE_CH_FL;
+ break;
+ case kAudioChannelLabel_RightTotal:
+ ret = AE_CH_FR;
+ break;
+ case kAudioChannelLabel_HearingImpaired:
+ ret = AE_CH_FC;
+ break;
+ case kAudioChannelLabel_Narration:
+ ret = AE_CH_FC;
+ break;
+ case kAudioChannelLabel_Mono:
+ ret = AE_CH_FC;
+ break;
+ case kAudioChannelLabel_DialogCentricMix:
+ ret = AE_CH_FC;
+ break;
+ case kAudioChannelLabel_CenterSurroundDirect:
+ ret = AE_CH_TC;
+ break;
+ case kAudioChannelLabel_Haptic:
+ ret = AE_CH_FC;
+ break;
+ default:
+ ret = (enum AEChannel)unknownChannel++;
+ }
+ if (unknownChannel > AE_CH_UNKNOWN8)
+ unknownChannel = AE_CH_UNKNOWN1;
+
+ return ret;
+}
+
+//Note: in multichannel mode CA will either pull 2 channels of data (stereo) or 6/8 channels of data
+//(every speaker setup with more then 2 speakers). The difference between the number of real speakers
+//and 6/8 channels needs to be padded with unknown channels so that the sample size fits 6/8 channels
+//
+//device [in] - the device whose channel layout should be used
+//channelMap [in/out] - if filled it will it indicates that we are called from initialize and we log the requested map, out returns the channelMap for device
+//channelsPerFrame [in] - the number of channels this device is configured to (e.x. 2 or 6/8)
+static void GetAEChannelMap(CCoreAudioDevice &device, CAEChannelInfo &channelMap, unsigned int channelsPerFrame)
+{
+ CCoreAudioChannelLayout calayout;
+ bool logMapping = channelMap.Count() > 0; // only log if the engine requests a layout during init
+ bool mapAvailable = false;
+ unsigned int numberChannelsInDeviceLayout = CA_MAX_CHANNELS; // default 8 channels from CAChannelMap
+ AudioChannelLayout *layout = NULL;
+
+ // try to fetch either the multichannel or the stereo channel layout from the device
+ if (channelsPerFrame == 2 || channelMap.Count() == 2)
+ mapAvailable = device.GetPreferredChannelLayoutForStereo(calayout);
+ else
+ mapAvailable = device.GetPreferredChannelLayout(calayout);
+
+ // if a map was fetched - check if it is usable
+ if (mapAvailable)
+ {
+ layout = calayout;
+ if (layout == NULL || layout->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions)
+ mapAvailable = false;// wrong map format
+ else
+ numberChannelsInDeviceLayout = layout->mNumberChannelDescriptions;
+ }
+
+ // start the mapping action
+ // the number of channels to be added to the outgoing channelmap
+ // this is CA_MAX_CHANNELS at max and might be lower for some output devices (channelsPerFrame)
+ unsigned int numChannelsToMap = std::min((unsigned int)CA_MAX_CHANNELS, (unsigned int)channelsPerFrame);
+
+ // if there was a map fetched we force the number of
+ // channels to map to channelsPerFrame (this allows mapping
+ // of more then CA_MAX_CHANNELS if needed)
+ if (mapAvailable)
+ numChannelsToMap = channelsPerFrame;
+
+ std::string layoutStr;
+
+ if (logMapping)
+ {
+ CLog::Log(LOGDEBUG, "%s Engine requests layout %s", __FUNCTION__, ((std::string)channelMap).c_str());
+
+ if (mapAvailable)
+ CLog::Log(LOGDEBUG, "%s trying to map to %s layout: %s", __FUNCTION__, channelsPerFrame == 2 ? "stereo" : "multichannel", calayout.ChannelLayoutToString(*layout, layoutStr));
+ else
+ CLog::Log(LOGDEBUG, "%s no map available - using static multichannel map layout", __FUNCTION__);
+ }
+
+ channelMap.Reset();// start with an empty map
+
+ for (unsigned int channel = 0; channel < numChannelsToMap; channel++)
+ {
+ // we only try to map channels which are defined in the device layout
+ enum AEChannel currentChannel;
+ if (channel < numberChannelsInDeviceLayout)
+ {
+ // get the channel from the fetched map
+ if (mapAvailable)
+ currentChannel = CAChannelToAEChannel(layout->mChannelDescriptions[channel].mChannelLabel);
+ else// get the channel from the default map
+ currentChannel = CAChannelMap[channel];
+
+ }
+ else// fill with unknown channels
+ currentChannel = CAChannelToAEChannel(kAudioChannelLabel_Unknown);
+
+ if(!channelMap.HasChannel(currentChannel))// only add if not already added
+ channelMap += currentChannel;
+ }
+
+ if (logMapping)
+ CLog::Log(LOGDEBUG, "%s mapped channels to layout %s", __FUNCTION__, ((std::string)channelMap).c_str());
+}
+
static bool HasSampleRate(const AESampleRateList &list, const unsigned int samplerate)
{
for (size_t i = 0; i < list.size(); ++i)
break;
}
- // add channel info
- CAEChannelInfo channel_info;
- for (UInt32 chan = 0; chan < CA_MAX_CHANNELS && chan < desc.mChannelsPerFrame; ++chan)
- {
- if (!device.m_channels.HasChannel(CAChannelMap[chan]))
- device.m_channels += CAChannelMap[chan];
- channel_info += CAChannelMap[chan];
- }
-
// add sample rate info
// for devices which return kAudioStreamAnyRatee
// we add 44.1khz and 48khz - user can use
}
else// treat all other digital passthrough devices as optical
device.m_deviceType = AE_DEVTYPE_IEC958;
+
+ //treat all other digital devices as HDMI to let options open to the user
+ if (device.m_deviceType == AE_DEVTYPE_PCM)
+ device.m_deviceType = AE_DEVTYPE_HDMI;
}
// devicename based overwrites from former code - maybe FIXME at some point when we
device.m_deviceType = AE_DEVTYPE_HDMI;
if (hasDisplayPortName)
device.m_deviceType = AE_DEVTYPE_DP;
-
+
+ //get channel map to match the devices channel layout as set in audio-midi-setup
+ GetAEChannelMap(caDevice, device.m_channels, caDevice.GetTotalOutputChannels());
list.push_back(std::make_pair(deviceID, device));
//in the first place of the list add the default device
RegisterDeviceChangedCB(false, this);
}
+float scoreSampleRate(Float64 destinationRate, unsigned int sourceRate)
+{
+ float score = 0;
+ double intPortion;
+ double fracPortion = modf(destinationRate / sourceRate, &intPortion);
+
+ score += (1 - fracPortion) * 1000; // prefer sample rates that are multiples of the source sample rate
+ score += (intPortion == 1.0) ? 500 : 0; // prefer exact matches over other multiples
+ score += (intPortion > 1 && intPortion < 100) ? (100 - intPortion) / 100 * 100 : 0; // prefer smaller multiples otherwise
+
+ return score;
+}
+
float ScoreStream(const AudioStreamBasicDescription &desc, const AEAudioFormat &format)
{
float score = 0;
{ // non-passthrough, whatever works is fine
if (desc.mFormatID == kAudioFormatLinearPCM)
{
- if (desc.mSampleRate == format.m_sampleRate)
- score += 10;
- else if (desc.mSampleRate > format.m_sampleRate)
- score += 1;
+ score += scoreSampleRate(desc.mSampleRate, format.m_sampleRate);
+
if (desc.mChannelsPerFrame == format.m_channelLayout.Count())
score += 5;
else if (desc.mChannelsPerFrame > format.m_channelLayout.Count())
bool passthrough = false;
UInt32 outputIndex = 0;
+ UInt32 numOutputChannels = 0;
float outputScore = 0;
AudioStreamBasicDescription outputFormat = {0};
AudioStreamID outputStream = 0;
if (score > outputScore)
{
- passthrough = score > 1000;
+ passthrough = score > 10000;
outputScore = score;
outputFormat = desc;
outputStream = *i;
}
m_planar = false;
+ numOutputChannels = outputFormat.mChannelsPerFrame;
if (streams.size() > 1 && outputFormat.mChannelsPerFrame == 1)
{
- CLog::Log(LOGDEBUG, "%s Found planar audio with %u channels?", __FUNCTION__, streams.size());
- outputFormat.mChannelsPerFrame = std::min((size_t)format.m_channelLayout.Count(), streams.size());
+ numOutputChannels = std::min((size_t)format.m_channelLayout.Count(), streams.size());
m_planar = true;
+ CLog::Log(LOGDEBUG, "%s Found planar audio with %u channels using %u of them.", __FUNCTION__, (unsigned int)streams.size(), (unsigned int)numOutputChannels);
}
if (!outputFormat.mFormatID)
/* Update our AE format */
format.m_sampleRate = outputFormat.mSampleRate;
- if (outputFormat.mChannelsPerFrame != format.m_channelLayout.Count())
- { /* update the channel count. We assume that they're layed out as given in CAChannelMap.
- if they're not, this is plain wrong */
- format.m_channelLayout.Reset();
- for (unsigned int i = 0; i < outputFormat.mChannelsPerFrame && i < CA_MAX_CHANNELS; i++)
- format.m_channelLayout += CAChannelMap[i];
- }
-
+
m_outputBitstream = passthrough && outputFormat.mFormatID == kAudioFormatLinearPCM;
std::string formatString;
CLog::Log(LOGDEBUG, "%s: New Virtual Format: %s", __FUNCTION__, StreamDescriptionToString(virtualFormat, formatString));
CLog::Log(LOGDEBUG, "%s: New Physical Format: %s", __FUNCTION__, StreamDescriptionToString(outputFormat, formatString));
+ // update the channel map based on the new stream format
+ GetAEChannelMap(m_device, format.m_channelLayout, numOutputChannels);
+
+
m_latentFrames = m_device.GetNumLatencyFrames();
m_latentFrames += m_outputStream.GetNumLatencyFrames();