Merge pull request #4955 from Memphiz/osxfixoptical2
[vuplus_xbmc] / xbmc / cores / AudioEngine / Sinks / AESinkDARWINOSX.cpp
index f19acba..d390368 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"
 
 #include <sstream>
 
-#define CA_MAX_CHANNELS 8
+#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_UNKNOWN2 ,
+  AE_CH_UNKNOWN3 ,
+  AE_CH_UNKNOWN4 ,
+  AE_CH_UNKNOWN5 ,
+  AE_CH_UNKNOWN6 ,
+  AE_CH_UNKNOWN7 ,
+  AE_CH_UNKNOWN8 ,
   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)
@@ -83,8 +282,15 @@ static void EnumerateDevices(CADeviceList &list)
     device.m_displayName = device.m_deviceName;
     device.m_displayNameExtra = "";
 
-    if (device.m_deviceName.find("HDMI") != std::string::npos)
-      device.m_deviceType = AE_DEVTYPE_HDMI;
+    // flag indicating that passthroughformats where added throughout the stream enumeration
+    bool hasPassthroughFormats = false;
+    // the maximum number of channels found in the streams
+    UInt32 numMaxChannels = 0;
+    // the terminal type as reported by ca
+    UInt32 caTerminalType = 0;
+      
+    bool isDigital = caDevice.IsDigital(caTerminalType);
+
 
     CLog::Log(LOGDEBUG, "EnumerateDevices:Device(%s)" , device.m_deviceName.c_str());
     AudioStreamIdList streams;
@@ -92,15 +298,16 @@ static void EnumerateDevices(CADeviceList &list)
     {
       for (AudioStreamIdList::iterator j = streams.begin(); j != streams.end(); ++j)
       {
-        StreamFormatList streams;
-        if (CCoreAudioStream::GetAvailablePhysicalFormats(*j, &streams))
+        StreamFormatList streamFormats;
+        if (CCoreAudioStream::GetAvailablePhysicalFormats(*j, &streamFormats))
         {
-          for (StreamFormatList::iterator i = streams.begin(); i != streams.end(); ++i)
+          for (StreamFormatList::iterator i = streamFormats.begin(); i != streamFormats.end(); ++i)
           {
             AudioStreamBasicDescription desc = i->mFormat;
             std::string formatString;
             CLog::Log(LOGDEBUG, "EnumerateDevices:Format(%s)" ,
                                 StreamDescriptionToString(desc, formatString));
+
             // add stream format info
             switch (desc.mFormatID)
             {
@@ -110,9 +317,8 @@ static void EnumerateDevices(CADeviceList &list)
                   device.m_dataFormats.push_back(AE_FMT_AC3);
                 if (!HasDataFormat(device.m_dataFormats, AE_FMT_DTS))
                   device.m_dataFormats.push_back(AE_FMT_DTS);
-                // if we are not hdmi, this is an S/PDIF device
-                if (device.m_deviceType != AE_DEVTYPE_HDMI)
-                  device.m_deviceType = AE_DEVTYPE_IEC958;
+                hasPassthroughFormats = true;
+                isDigital = true;// sanity - those are always digital devices!
                 break;
               default:
                 AEDataFormat format = AE_FMT_INVALID;
@@ -123,8 +329,19 @@ static void EnumerateDevices(CADeviceList &list)
                       format = AE_FMT_S16BE;
                     else
                     {
+                      // if it is no digital stream per definition
+                      // check if the device name suggests that it is digital
+                      // (some hackintonshs are not so smart in announcing correct
+                      // ca devices ...
+                      if (!isDigital)
+                      {
+                        std::string devNameLower = device.m_deviceName;
+                        StringUtils::ToLower(devNameLower);                       
+                        isDigital = devNameLower.find("digital") != std::string::npos;
+                      }
+
                       /* Passthrough is possible with a 2ch digital output */
-                      if (desc.mChannelsPerFrame == 2 && CCoreAudioStream::IsDigitalOuptut(*j))
+                      if (desc.mChannelsPerFrame == 2 && isDigital)
                       {
                         if (desc.mSampleRate == 48000)
                         {
@@ -132,11 +349,13 @@ static void EnumerateDevices(CADeviceList &list)
                             device.m_dataFormats.push_back(AE_FMT_AC3);
                           if (!HasDataFormat(device.m_dataFormats, AE_FMT_DTS))
                             device.m_dataFormats.push_back(AE_FMT_DTS);
+                          hasPassthroughFormats = true;
                         }
                         else if (desc.mSampleRate == 192000)
                         {
                           if (!HasDataFormat(device.m_dataFormats, AE_FMT_EAC3))
                             device.m_dataFormats.push_back(AE_FMT_EAC3);
+                          hasPassthroughFormats = true;
                         }
                       }
                       format = AE_FMT_S16LE;
@@ -160,21 +379,28 @@ static void EnumerateDevices(CADeviceList &list)
                     }
                     break;
                 }
+                
+                if (numMaxChannels < desc.mChannelsPerFrame)
+                  numMaxChannels = desc.mChannelsPerFrame;
+                
                 if (format != AE_FMT_INVALID && !HasDataFormat(device.m_dataFormats, format))
                   device.m_dataFormats.push_back(format);
                 break;
             }
 
-            // add channel info
-            CAEChannelInfo channel_info;
-            for (UInt32 chan = 0; chan < CA_MAX_CHANNELS && chan < desc.mChannelsPerFrame; ++chan)
+            // add sample rate info
+            // for devices which return kAudioStreamAnyRatee
+            // we add 44.1khz and 48khz - user can use
+            // the "fixed" audio config to force one of them
+            if (desc.mSampleRate == kAudioStreamAnyRate)
             {
-              if (!device.m_channels.HasChannel(CAChannelMap[chan]))
-                device.m_channels += CAChannelMap[chan];
-              channel_info += CAChannelMap[chan];
+              CLog::Log(LOGINFO, "%s reported samplerate is kAudioStreamAnyRate adding 44.1khz and 48khz", __FUNCTION__);
+              desc.mSampleRate = 44100;
+              if (!HasSampleRate(device.m_sampleRates, desc.mSampleRate))
+                device.m_sampleRates.push_back(desc.mSampleRate);
+              desc.mSampleRate = 48000;
             }
 
-            // add sample rate info
             if (!HasSampleRate(device.m_sampleRates, desc.mSampleRate))
               device.m_sampleRates.push_back(desc.mSampleRate);
           }
@@ -182,6 +408,52 @@ static void EnumerateDevices(CADeviceList &list)
       }
     }
 
+    
+    // flag indicating that the device name "sounds" like HDMI
+    bool hasHdmiName = device.m_deviceName.find("HDMI") != std::string::npos;
+    // flag indicating that the device name "sounds" like DisplayPort
+    bool hasDisplayPortName = device.m_deviceName.find("DisplayPort") != std::string::npos;
+    
+    // decide the type of the device based on the discovered information
+    // in the streams
+    // device defaults to PCM (see start of the while loop)
+    // it can be HDMI, DisplayPort or Optical
+    // for all of those types it needs to support
+    // passthroughformats and needs to be a digital port
+    if (hasPassthroughFormats && isDigital)
+    {
+      // if the max number of channels was more then 2
+      // this can be HDMI or DisplayPort or Thunderbolt
+      if (numMaxChannels > 2)
+      {
+        // either the devicename suggests its HDMI
+        // or CA reported the terminalType as HDMI
+        if (hasHdmiName || caTerminalType == kIOAudioDeviceTransportTypeHdmi)
+          device.m_deviceType = AE_DEVTYPE_HDMI;
+
+        // either the devicename suggests its DisplayPort
+        // or CA reported the terminalType as DisplayPort or Thunderbolt
+        if (hasDisplayPortName || caTerminalType == kIOAudioDeviceTransportTypeDisplayPort || caTerminalType == kIOAudioDeviceTransportTypeThunderbolt)
+          device.m_deviceType = AE_DEVTYPE_DP;
+      }
+      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
+    // are sure that the upper detection does its job in all[tm] use cases
+    if (hasHdmiName)
+      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
     //with name "default" - if this is selected
@@ -228,16 +500,45 @@ OSStatus deviceChangedCB(AudioObjectID                       inObjectID,
                          const AudioObjectPropertyAddress    inAddresses[],
                          void*                               inClientData)
 {
-  CLog::Log(LOGDEBUG, "CoreAudio: audiodevicelist changed - reenumerating");
-  CAEFactory::DeviceChange();
-  CLog::Log(LOGDEBUG, "CoreAudio: audiodevicelist changed - done");
+  bool deviceChanged = false;
+  static AudioDeviceID oldDefaultDevice = 0;
+  AudioDeviceID currentDefaultOutputDevice = 0;
+
+  for (unsigned int i = 0; i < inNumberAddresses; i++)
+  {
+    switch (inAddresses[i].mSelector)
+    {
+      case kAudioHardwarePropertyDefaultOutputDevice:
+        currentDefaultOutputDevice = CCoreAudioHardware::GetDefaultOutputDevice();
+        // This listener is called on every change of the hardware
+        // device. So check if the default device has really changed.
+        if (oldDefaultDevice != currentDefaultOutputDevice)
+        {
+          deviceChanged = true;
+          oldDefaultDevice = currentDefaultOutputDevice;
+        }
+        break;
+      default:
+        deviceChanged = true;
+        break;
+    }
+    if (deviceChanged)
+      break;
+  }
+
+  if  (deviceChanged)
+  {
+    CLog::Log(LOGDEBUG, "CoreAudio: audiodevicelist changed - reenumerating");
+    CAEFactory::DeviceChange();
+    CLog::Log(LOGDEBUG, "CoreAudio: audiodevicelist changed - done");
+  }
   return noErr;
 }
 
 void RegisterDeviceChangedCB(bool bRegister, void *ref)
 {
   OSStatus ret = noErr;
-  const AudioObjectPropertyAddress inAdr =
+  AudioObjectPropertyAddress inAdr =
   {
     kAudioHardwarePropertyDevices,
     kAudioObjectPropertyScopeGlobal,
@@ -245,9 +546,17 @@ void RegisterDeviceChangedCB(bool bRegister, void *ref)
   };
 
   if (bRegister)
+  {
     ret = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &inAdr, deviceChangedCB, ref);
+    inAdr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
+    ret = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &inAdr, deviceChangedCB, ref);
+  }
   else
+  {
+    ret = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &inAdr, deviceChangedCB, ref);
+    inAdr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
     ret = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &inAdr, deviceChangedCB, ref);
+  }
 
   if (ret != noErr)
     CLog::Log(LOGERROR, "CCoreAudioAE::Deinitialize - error %s a listener callback for device changes!", bRegister?"attaching":"removing");
@@ -256,7 +565,7 @@ void RegisterDeviceChangedCB(bool bRegister, void *ref)
 
 ////////////////////////////////////////////////////////////////////////////////////////////
 CAESinkDARWINOSX::CAESinkDARWINOSX()
-: m_latentFrames(0), m_outputBufferIndex(0), m_outputBitstream(false), m_outputBuffer(NULL), m_buffer(NULL)
+: m_latentFrames(0), m_outputBitstream(false), m_outputBuffer(NULL), m_planar(false), m_planarBuffer(NULL), m_buffer(NULL)
 {
   // By default, kAudioHardwarePropertyRunLoop points at the process's main thread on SnowLeopard,
   // If your process lacks such a run loop, you can set kAudioHardwarePropertyRunLoop to NULL which
@@ -276,6 +585,7 @@ CAESinkDARWINOSX::CAESinkDARWINOSX()
   }
   RegisterDeviceChangedCB(true, this);
   m_started = false;
+  m_planar = false;
 }
 
 CAESinkDARWINOSX::~CAESinkDARWINOSX()
@@ -283,6 +593,19 @@ CAESinkDARWINOSX::~CAESinkDARWINOSX()
   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;
@@ -318,10 +641,8 @@ float ScoreStream(const AudioStreamBasicDescription &desc, const AEAudioFormat &
   { // 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())
@@ -350,18 +671,21 @@ bool CAESinkDARWINOSX::Initialize(AEAudioFormat &format, std::string &device)
   if (StringUtils::EqualsNoCase(device, "default"))
   {
     CCoreAudioHardware::GetOutputDeviceName(device);
+    deviceID = CCoreAudioHardware::GetDefaultOutputDevice();
     CLog::Log(LOGNOTICE, "%s: Opening default device %s", __PRETTY_FUNCTION__, device.c_str());
   }
-      
-  for (size_t i = 0; i < devices.size(); i++)
+  else
   {
-    if (device.find(devices[i].second.m_deviceName) != std::string::npos)
+    for (size_t i = 0; i < devices.size(); i++)
     {
-      m_info = devices[i].second;
-      deviceID = devices[i].first;
-      break;
+      if (device.find(devices[i].second.m_deviceName) != std::string::npos)
+      {
+        deviceID = devices[i].first;
+        break;
+      }
     }
   }
+
   if (!deviceID)
   {
     CLog::Log(LOGERROR, "%s: Unable to find device %s", __FUNCTION__, device.c_str());
@@ -378,6 +702,7 @@ bool CAESinkDARWINOSX::Initialize(AEAudioFormat &format, std::string &device)
 
   bool                        passthrough  = false;
   UInt32                      outputIndex  = 0;
+  UInt32                      numOutputChannels = 0;
   float                       outputScore  = 0;
   AudioStreamBasicDescription outputFormat = {0};
   AudioStreamID               outputStream = 0;
@@ -396,7 +721,14 @@ bool CAESinkDARWINOSX::Initialize(AEAudioFormat &format, std::string &device)
     CCoreAudioStream::GetAvailablePhysicalFormats(*i, &formats);
     for (StreamFormatList::const_iterator j = formats.begin(); j != formats.end(); ++j)
     {
-      const AudioStreamBasicDescription &desc = j->mFormat;
+      AudioStreamBasicDescription desc = j->mFormat;
+
+      // for devices with kAudioStreamAnyRate
+      // assume that the user uses a fixed config
+      // and knows what he is doing - so we use
+      // the requested samplerate here
+      if (desc.mSampleRate == kAudioStreamAnyRate)
+        desc.mSampleRate = format.m_sampleRate;
 
       float score = ScoreStream(desc, format);
 
@@ -405,7 +737,7 @@ bool CAESinkDARWINOSX::Initialize(AEAudioFormat &format, std::string &device)
 
       if (score > outputScore)
       {
-        passthrough  = score > 1000;
+        passthrough  = score > 10000;
         outputScore  = score;
         outputFormat = desc;
         outputStream = *i;
@@ -415,6 +747,15 @@ bool CAESinkDARWINOSX::Initialize(AEAudioFormat &format, std::string &device)
     index++;
   }
 
+  m_planar = false;
+  numOutputChannels = outputFormat.mChannelsPerFrame;
+  if (streams.size() > 1 && outputFormat.mChannelsPerFrame == 1)
+  {
+    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)
   {
     CLog::Log(LOGERROR, "%s, Unable to find suitable stream", __FUNCTION__);
@@ -423,19 +764,11 @@ 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++)
-      format.m_channelLayout += CAChannelMap[i];
-  }
-
-  m_outputBufferIndex = outputIndex;
+  
   m_outputBitstream   = passthrough && outputFormat.mFormatID == kAudioFormatLinearPCM;
 
   std::string formatString;
-  CLog::Log(LOGDEBUG, "%s: Selected stream[%u] - id: 0x%04X, Physical Format: %s %s", __FUNCTION__, m_outputBufferIndex, outputStream, StreamDescriptionToString(outputFormat, formatString), m_outputBitstream ? "bitstreamed passthrough" : "");
+  CLog::Log(LOGDEBUG, "%s: Selected stream[%u] - id: 0x%04X, Physical Format: %s %s", __FUNCTION__, (unsigned int)outputIndex, (unsigned int)outputStream, StreamDescriptionToString(outputFormat, formatString), m_outputBitstream ? "bitstreamed passthrough" : "");
 
   SetHogMode(passthrough);
 
@@ -453,6 +786,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, numOutputChannels);
+
+
   m_latentFrames = m_device.GetNumLatencyFrames();
   m_latentFrames += m_outputStream.GetNumLatencyFrames();
 
@@ -468,6 +805,9 @@ bool CAESinkDARWINOSX::Initialize(AEAudioFormat &format, std::string &device)
     m_device.SetNominalSampleRate(format.m_sampleRate);
   }
 
+  if (m_planar)
+    m_planarBuffer = new float[format.m_frameSamples];
+
   unsigned int num_buffers = 4;
   m_buffer = new AERingBuffer(num_buffers * format.m_frames * format.m_frameSize);
   CLog::Log(LOGDEBUG, "%s: using buffer size: %u (%f ms)", __FUNCTION__, m_buffer->GetMaxSize(), (float)m_buffer->GetMaxSize() / (format.m_sampleRate * format.m_frameSize));
@@ -521,12 +861,15 @@ void CAESinkDARWINOSX::Deinitialize()
     delete m_buffer;
     m_buffer = NULL;
   }
-  m_outputBufferIndex = 0;
   m_outputBitstream = false;
 
   delete[] m_outputBuffer;
   m_outputBuffer = NULL;
 
+  m_planar = false;
+  delete[] m_planarBuffer;
+  m_planarBuffer = NULL;
+
   m_started = false;
 }
 
@@ -563,10 +906,19 @@ unsigned int CAESinkDARWINOSX::AddPackets(uint8_t *data, unsigned int frames, bo
   if (m_buffer->GetWriteSize() < frames * m_format.m_frameSize)
   { // no space to write - wait for a bit
     CSingleLock lock(mutex);
+    unsigned int timeout = 900 * frames / m_format.m_sampleRate;
     if (!m_started)
-      condVar.wait(lock);
-    else
-      condVar.wait(lock, 900 * frames / m_format.m_sampleRate);
+      timeout = 4500;
+
+    // we are using a timer here for beeing sure for timeouts
+    // condvar can be woken spuriously as signaled
+    XbmcThreads::EndTime timer(timeout);
+    condVar.wait(mutex, timeout);
+    if (!m_started && timer.IsTimePast())
+    {
+      CLog::Log(LOGERROR, "%s engine didn't start in %d ms!", __FUNCTION__, timeout);
+      return INT_MAX;    
+    }
   }
 
   unsigned int write_frames = std::min(frames, m_buffer->GetWriteSize() / m_format.m_frameSize);
@@ -578,13 +930,22 @@ unsigned int CAESinkDARWINOSX::AddPackets(uint8_t *data, unsigned int frames, bo
 
 void CAESinkDARWINOSX::Drain()
 {
-  CCriticalSection mutex;
   int bytes = m_buffer->GetReadSize();
-  while (bytes)
+  int totalBytes = bytes;
+  int maxNumTimeouts = 3;
+  unsigned int timeout = 900 * bytes / (m_format.m_sampleRate * m_format.m_frameSize);
+  while (bytes && maxNumTimeouts > 0)
   {
     CSingleLock lock(mutex);
-    condVar.wait(mutex, 900 * bytes / (m_format.m_sampleRate * m_format.m_frameSize));
+    XbmcThreads::EndTime timer(timeout);
+    condVar.wait(mutex, timeout);
+
     bytes = m_buffer->GetReadSize();
+    // if we timeout and don't
+    // consum bytes - decrease maxNumTimeouts
+    if (timer.IsTimePast() && bytes == totalBytes)
+      maxNumTimeouts--;
+    totalBytes = bytes;
   }
 }
 
@@ -596,41 +957,83 @@ void CAESinkDARWINOSX::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
     list.push_back(i->second);
 }
 
+inline void LogLevel(unsigned int got, unsigned int wanted)
+{
+  static unsigned int lastReported = INT_MAX;
+  if (got != wanted)
+  {
+    if (got != lastReported)
+    {
+      CLog::Log(LOGWARNING, "DARWINOSX: %sflow (%u vs %u bytes)", got > wanted ? "over" : "under", got, wanted);
+      lastReported = got;
+    }    
+  }
+  else
+    lastReported = INT_MAX; // indicate we were good at least once
+}
+
 OSStatus CAESinkDARWINOSX::renderCallback(AudioDeviceID inDevice, const AudioTimeStamp* inNow, const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime, void* inClientData)
 {
   CAESinkDARWINOSX *sink = (CAESinkDARWINOSX*)inClientData;
 
   sink->m_started = true;
-  if (sink->m_outputBufferIndex < outOutputData->mNumberBuffers)
+  if (sink->m_planar)
   {
-    if (sink->m_outputBitstream)
+    unsigned int channels = std::min((unsigned int)outOutputData->mNumberBuffers, sink->m_format.m_channelLayout.Count());
+    unsigned int wanted = outOutputData->mBuffers[0].mDataByteSize;
+    unsigned int bytes = std::min(sink->m_buffer->GetReadSize() / channels, wanted);
+    sink->m_buffer->Read((unsigned char *)sink->m_planarBuffer, bytes * channels);
+    // transform from interleaved to planar
+    const float *src = sink->m_planarBuffer;
+    for (unsigned int i = 0; i < bytes / sizeof(float); i++)
     {
-      /* HACK for bitstreaming AC3/DTS via PCM.
-       We reverse the float->S16LE conversion done in the stream or device */
-      static const float mul = 1.0f / (INT16_MAX + 1);
-
-      unsigned int wanted = std::min(outOutputData->mBuffers[sink->m_outputBufferIndex].mDataByteSize / sizeof(float), (size_t)sink->m_format.m_frameSamples)  * sizeof(int16_t);
-      if (wanted <= sink->m_buffer->GetReadSize())
+      for (unsigned int j = 0; j < channels; j++)
       {
-        sink->m_buffer->Read((unsigned char *)sink->m_outputBuffer, wanted);
-        int16_t *src = sink->m_outputBuffer;
-        float  *dest = (float*)outOutputData->mBuffers[sink->m_outputBufferIndex].mData;
-        for (unsigned int i = 0; i < wanted / 2; i++)
-          *dest++ = *src++ * mul;
+        float *dst = (float *)outOutputData->mBuffers[j].mData;
+        dst[i] = *src++;
       }
     }
-    else
-    {
-      /* buffers appear to come from CA already zero'd, so just copy what is wanted */
-      unsigned int wanted = outOutputData->mBuffers[sink->m_outputBufferIndex].mDataByteSize;
-      unsigned int bytes = std::min(sink->m_buffer->GetReadSize(), wanted);
-      sink->m_buffer->Read((unsigned char*)outOutputData->mBuffers[sink->m_outputBufferIndex].mData, bytes);
-      if (bytes != wanted)
-        CLog::Log(LOGERROR, "%s: %sFLOW (%i vs %i) bytes", __FUNCTION__, bytes > wanted ? "OVER" : "UNDER", bytes, wanted);
-    }
-
+    LogLevel(bytes, wanted);
     // tell the sink we're good for more data
     condVar.notifyAll();
   }
+  else
+  {
+    for (unsigned int i = 0; i < outOutputData->mNumberBuffers; i++)
+    {
+      // NULL indicates a disabled stream
+      // skip it...
+      if (outOutputData->mBuffers[i].mData == NULL)
+        continue;
+
+      if (sink->m_outputBitstream)
+      {
+        /* HACK for bitstreaming AC3/DTS via PCM.
+         We reverse the float->S16LE conversion done in the stream or device */
+        static const float mul = 1.0f / (INT16_MAX + 1);
+
+        unsigned int wanted = std::min(outOutputData->mBuffers[i].mDataByteSize / sizeof(float), (size_t)sink->m_format.m_frameSamples)  * sizeof(int16_t);
+        if (wanted <= sink->m_buffer->GetReadSize())
+        {
+          sink->m_buffer->Read((unsigned char *)sink->m_outputBuffer, wanted);
+          int16_t *src = sink->m_outputBuffer;
+          float  *dest = (float*)outOutputData->mBuffers[i].mData;
+          for (unsigned int i = 0; i < wanted / 2; i++)
+            *dest++ = *src++ * mul;
+        }
+      }
+      else
+      {
+        /* buffers appear to come from CA already zero'd, so just copy what is wanted */
+        unsigned int wanted = outOutputData->mBuffers[i].mDataByteSize;
+        unsigned int bytes = std::min(sink->m_buffer->GetReadSize(), wanted);
+        sink->m_buffer->Read((unsigned char*)outOutputData->mBuffers[i].mData, bytes);
+        LogLevel(bytes, wanted);
+      }
+
+      // tell the sink we're good for more data
+      condVar.notifyAll();
+    }
+  }
   return noErr;
 }