Merge pull request #4719 from Memphiz/osxsink_channelmap
authorjmarshallnz <jcmarsha@gmail.com>
Sat, 7 Jun 2014 05:18:49 +0000 (17:18 +1200)
committerTrent Nelson <trent.nelson@pivosgroup.com>
Sat, 7 Jun 2014 06:00:34 +0000 (14:00 +0800)
[AE/osxsink] - fix multichannel speaker layout / channel mapping

xbmc/cores/AudioEngine/Sinks/AESinkDARWINOSX.cpp
xbmc/cores/AudioEngine/Sinks/osx/CoreAudioChannelLayout.cpp
xbmc/cores/AudioEngine/Sinks/osx/CoreAudioChannelLayout.h
xbmc/cores/AudioEngine/Sinks/osx/CoreAudioDevice.cpp
xbmc/cores/AudioEngine/Sinks/osx/CoreAudioDevice.h

index b150fe0..a6006c6 100644 (file)
@@ -24,6 +24,7 @@
 #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"
@@ -33,6 +34,7 @@
 #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 ,
@@ -46,6 +48,195 @@ static enum AEChannel CAChannelMap[CA_MAX_CHANNELS + 1] = {
   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)
@@ -197,15 +388,6 @@ static void EnumerateDevices(CADeviceList &list)
                 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
@@ -264,7 +446,9 @@ static void EnumerateDevices(CADeviceList &list)
       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
@@ -563,14 +747,7 @@ bool CAESinkDARWINOSX::Initialize(AEAudioFormat &format, std::string &device)
 
   /* 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;
@@ -592,6 +769,10 @@ bool CAESinkDARWINOSX::Initialize(AEAudioFormat &format, std::string &device)
   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, outputFormat.mChannelsPerFrame);
+
+
   m_latentFrames = m_device.GetNumLatencyFrames();
   m_latentFrames += m_outputStream.GetNumLatencyFrames();
 
index 05645c2..6ca5df4 100644 (file)
@@ -105,6 +105,34 @@ bool CCoreAudioChannelLayout::CopyLayout(AudioChannelLayout& layout)
   return (ret == noErr);
 }
 
+bool CCoreAudioChannelLayout::CopyLayoutForStereo(UInt32 layout[2])
+{
+  enum {
+    kVariableLengthArray_deprecated = 1
+  };
+  
+  free(m_pLayout);
+  m_pLayout = NULL;
+  
+  UInt32 channels = 2;
+  UInt32 size = sizeof(AudioChannelLayout) + (channels - kVariableLengthArray_deprecated) * sizeof(AudioChannelDescription);
+  
+  m_pLayout = (AudioChannelLayout*)malloc(size);
+  m_pLayout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
+  m_pLayout->mNumberChannelDescriptions = 2;//stereo
+
+  AudioChannelDescription desc;
+  desc.mChannelFlags = kAudioChannelFlags_AllOff;
+  memset(desc.mCoordinates, 0, sizeof(desc.mCoordinates));
+
+  desc.mChannelLabel = layout[0];// label for channel 1
+  m_pLayout->mChannelDescriptions[0] = desc;
+
+  desc.mChannelLabel = layout[1];// label for channel 2
+  m_pLayout->mChannelDescriptions[1] = desc;
+  return true;
+}
+
 UInt32 CCoreAudioChannelLayout::GetChannelCountForLayout(AudioChannelLayout& layout)
 {
   UInt32 channels = 0;
index cf95c83..64721c9 100644 (file)
@@ -79,6 +79,7 @@ public:
   operator AudioChannelLayout*() {return m_pLayout;}
 
   bool                CopyLayout(AudioChannelLayout &layout);
+  bool                CopyLayoutForStereo(UInt32 layout[2]);
   static UInt32       GetChannelCountForLayout(AudioChannelLayout &layout);
   static const char*  ChannelLabelToString(UInt32 label);
   static const char*  ChannelLayoutToString(AudioChannelLayout &layout, std::string &str);
index 20b8728..2fe3bb9 100644 (file)
@@ -547,6 +547,31 @@ bool CCoreAudioDevice::GetPreferredChannelLayout(CCoreAudioChannelLayout& layout
   return (ret == noErr);
 }
 
+bool CCoreAudioDevice::GetPreferredChannelLayoutForStereo(CCoreAudioChannelLayout &layout)
+{
+  if (!m_DeviceId)
+    return false;
+  
+  AudioObjectPropertyAddress  propertyAddress;
+  propertyAddress.mScope    = kAudioDevicePropertyScopeOutput;
+  propertyAddress.mElement  = 0;
+  propertyAddress.mSelector = kAudioDevicePropertyPreferredChannelsForStereo;
+
+  UInt32 channels[2];// this will receive the channel labels
+  UInt32 propertySize = sizeof(channels);
+
+  OSStatus ret = AudioObjectGetPropertyData(m_DeviceId, &propertyAddress, 0, NULL, &propertySize, &channels);
+  if (ret != noErr)
+    CLog::Log(LOGERROR, "CCoreAudioDevice::GetPreferredChannelLayoutForStereo: "
+              "Unable to retrieve preferred channel layout. Error = %s", GetError(ret).c_str());
+  else
+  {
+    // Copy/generate a layout into the result into the caller's instance
+    layout.CopyLayoutForStereo(channels);
+  }
+  return (ret == noErr);
+}
+
 bool CCoreAudioDevice::GetDataSources(CoreAudioDataSourceList* pList)
 {
   if (!pList || !m_DeviceId)
index e95f8c1..afe90a1 100644 (file)
@@ -61,6 +61,7 @@ public:
   bool          GetMixingSupport();
   bool          SetCurrentVolume(Float32 vol);
   bool          GetPreferredChannelLayout(CCoreAudioChannelLayout &layout);
+  bool          GetPreferredChannelLayoutForStereo(CCoreAudioChannelLayout &layout);
   bool          GetDataSources(CoreAudioDataSourceList *pList);
   Float64       GetNominalSampleRate();
   bool          SetNominalSampleRate(Float64 sampleRate);