/*
 *      Copyright (C) 2010-2012 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 <string.h>
#include <sstream>
#include <iterator>

#include "system.h"
#include "utils/log.h"
#include "utils/TimeUtils.h"
#include "utils/MathUtils.h"
#include "utils/EndianSwap.h"
#include "threads/SingleLock.h"
#include "settings/GUISettings.h"
#include "settings/Settings.h"
#include "settings/AdvancedSettings.h"

#include "SoftAE.h"
#include "SoftAESound.h"
#include "SoftAEStream.h"
#include "AESinkFactory.h"
#include "Interfaces/AESink.h"
#include "Utils/AEUtil.h"
#include "Encoders/AEEncoderFFmpeg.h"

using namespace std;

/* Define idle wait time based on platform in milliseconds */
/* Higher wait times reduce thread CPU overhead when in    */
/* idle or Suspend() modes                                 */
#if defined (TARGET_WINDOWS) || defined (TARGET_LINUX) || \
  defined (TARGET_DARWIN_OSX) || defined (TARGET_FREEBSD)
#define SOFTAE_IDLE_WAIT_MSEC 50  // shorter sleep for HTPC's
#elif defined (TARGET_RASPBERRY_PI) || defined (TARGET_ANDROID)
#define SOFTAE_IDLE_WAIT_MSEC 100 // longer for R_PI and Android
#else
#define SOFTAE_IDLE_WAIT_MSEC 100 // catchall for undefined platforms
#endif

CSoftAE::CSoftAE():
  m_thread             (NULL        ),
  m_audiophile         (true        ),
  m_running            (false       ),
  m_reOpen             (false       ),
  m_sinkIsSuspended    (false       ),
  m_isSuspended        (false       ),
  m_softSuspend        (false       ),
  m_softSuspendTimer   (0           ),
  m_sink               (NULL        ),
  m_transcode          (false       ),
  m_rawPassthrough     (false       ),
  m_soundMode          (AE_SOUND_OFF),
  m_streamsPlaying     (false       ),
  m_encoder            (NULL        ),
  m_converted          (NULL        ),
  m_convertedSize      (0           ),
  m_masterStream       (NULL        ),
  m_outputStageFn      (NULL        ),
  m_streamStageFn      (NULL        )
{
  unsigned int c_retry = 5;
  CAESinkFactory::EnumerateEx(m_sinkInfoList);
  while(m_sinkInfoList.size() == 0 && c_retry > 0)
  {
    CLog::Log(LOGNOTICE, "No Devices found - retry: %d", c_retry);
    Sleep(2000);
    c_retry--;
    // retry the enumeration
    CAESinkFactory::EnumerateEx(m_sinkInfoList, true);
  }
  CLog::Log(LOGNOTICE, "Found %lu Lists of Devices", m_sinkInfoList.size());
  PrintSinks();
}

CSoftAE::~CSoftAE()
{
  Deinitialize();

  /* free the streams */
  CSingleLock streamLock(m_streamLock);
  while (!m_streams.empty())
  {
    CSoftAEStream *s = m_streams.front();
    delete s;
  }

  /* free the sounds */
  CSingleLock soundLock(m_soundLock);
  while (!m_sounds.empty())
  {
    CSoftAESound *s = m_sounds.front();
    m_sounds.pop_front();
    delete s;
  }
}

IAESink *CSoftAE::GetSink(AEAudioFormat &newFormat, bool passthrough, std::string &device)
{
  device = passthrough ? m_passthroughDevice : m_device;

  /* if we are raw, force the sample rate */
  if (AE_IS_RAW(newFormat.m_dataFormat))
  {
    switch (newFormat.m_dataFormat)
    {
      case AE_FMT_AC3:
      case AE_FMT_DTS:
        break;

      case AE_FMT_EAC3:
        newFormat.m_sampleRate = 192000;
        break;

      case AE_FMT_TRUEHD:
      case AE_FMT_DTSHD:
        newFormat.m_sampleRate = 192000;
        break;

      default:
        break;
    }
  }

  IAESink *sink = CAESinkFactory::Create(device, newFormat, passthrough);
  return sink;
}

/* this method MUST be called while holding m_streamLock */
inline CSoftAEStream *CSoftAE::GetMasterStream()
{
  /* remove any destroyed streams first */
  for (StreamList::iterator itt = m_streams.begin(); itt != m_streams.end();)
  {
    CSoftAEStream *stream = *itt;
    if (stream->IsDestroyed())
    {
      RemoveStream(m_playingStreams, stream);
      RemoveStream(m_streams       , stream);
      delete stream;
      continue;
    }
    ++itt;
  }

  if (!m_newStreams.empty())
    return m_newStreams.back();

  if (!m_streams.empty())
    return m_streams.back();

  return NULL;
}

/* save method to call outside of the main thread, use this one */
void CSoftAE::OpenSink()
{
  m_reOpenEvent.Reset();
  m_reOpen = true;
  m_reOpenEvent.Wait();
  m_wake.Set();
}

void CSoftAE::InternalCloseSink()
{
  /* close the old sink if it was open */
  if (m_sink)
  {
    CExclusiveLock sinkLock(m_sinkLock);
    m_sink->Drain();
    m_sink->Deinitialize();
    delete m_sink;
    m_sink = NULL;
  }
}
/* this must NEVER be called from outside the main thread or Initialization */
void CSoftAE::InternalOpenSink()
{
  /* save off our raw/passthrough mode for checking */
  bool wasTranscode           = m_transcode;
  bool wasRawPassthrough      = m_rawPassthrough;
  bool reInit                 = false;

  LoadSettings();

  /* initialize for analog output */
  m_rawPassthrough = false;
  m_streamStageFn = &CSoftAE::RunStreamStage;
  m_outputStageFn = &CSoftAE::RunOutputStage;

  /* initialize the new format for basic 2.0 output */
  AEAudioFormat newFormat;
  newFormat.m_dataFormat    = AE_FMT_FLOAT;
  newFormat.m_sampleRate    = 44100;
  newFormat.m_encodedRate   = 0;
  newFormat.m_channelLayout = m_stereoUpmix ? m_stdChLayout : AE_CH_LAYOUT_2_0;
  newFormat.m_frames        = 0;
  newFormat.m_frameSamples  = 0;
  newFormat.m_frameSize     = 0;

  CSingleLock streamLock(m_streamLock);

  m_masterStream = GetMasterStream();
  if (m_masterStream)
  {
    /* choose the sample rate & channel layout based on the master stream */
    newFormat.m_sampleRate = m_masterStream->GetSampleRate();
    if (!m_stereoUpmix)
      newFormat.m_channelLayout = m_masterStream->m_initChannelLayout;

    if (m_masterStream->IsRaw())
    {
      newFormat.m_sampleRate    = m_masterStream->GetSampleRate();
      newFormat.m_encodedRate   = m_masterStream->GetEncodedSampleRate();
      newFormat.m_dataFormat    = m_masterStream->GetDataFormat();
      newFormat.m_channelLayout = m_masterStream->m_initChannelLayout;
      m_rawPassthrough = true;
      m_streamStageFn  = &CSoftAE::RunRawStreamStage;
      m_outputStageFn  = &CSoftAE::RunRawOutputStage;
    }
    else
    {
      if (!m_transcode)
        newFormat.m_channelLayout.ResolveChannels(m_stdChLayout);
      else
      {
        if (m_masterStream->m_initChannelLayout == AE_CH_LAYOUT_2_0)
          m_transcode = false;
        m_encoderInitSampleRateMul = 1.0 / newFormat.m_sampleRate;
      }
    }

    /* if the stream is paused we cant use it for anything else */
    if (m_masterStream->m_paused)
      m_masterStream = NULL;
  }
  else
    m_transcode = false;

  if (!m_rawPassthrough && m_transcode)
    newFormat.m_dataFormat = AE_FMT_AC3;

  streamLock.Leave();

  std::string device, driver;
  if (m_transcode || m_rawPassthrough)
    device = m_passthroughDevice;
  else
    device = m_device;

  CAESinkFactory::ParseDevice(device, driver);
  if (driver.empty() && m_sink)
    driver = m_sink->GetName();

  if (m_rawPassthrough)
    CLog::Log(LOGINFO, "CSoftAE::InternalOpenSink - RAW passthrough enabled");
  else if (m_transcode)
    CLog::Log(LOGINFO, "CSoftAE::InternalOpenSink - Transcode passthrough enabled");

  /*
    try to use 48000hz if we are going to transcode, this prevents the sink
    from being re-opened repeatedly when switching sources, which locks up
    some receivers & crappy integrated sound drivers. Check for as.xml override
  */
  if (m_transcode && !m_rawPassthrough)
  {
    enum AEChannel ac3Layout[3] = {AE_CH_RAW, AE_CH_RAW, AE_CH_NULL};
    newFormat.m_channelLayout = ac3Layout;
    m_outputStageFn = &CSoftAE::RunTranscodeStage;
    if (!g_advancedSettings.m_allowTranscode44100)
      newFormat.m_sampleRate    = 48000;
  }

  /*
    if there is an audio resample rate set, use it, this MAY NOT be honoured as
    the audio sink may not support the requested format, and may change it.
  */
  if (g_advancedSettings.m_audioResample)
  {
    newFormat.m_sampleRate = g_advancedSettings.m_audioResample;
    CLog::Log(LOGINFO, "CSoftAE::InternalOpenSink - Forcing samplerate to %d", newFormat.m_sampleRate);
  }

  /* only re-open the sink if its not compatible with what we need */
  std::string sinkName;
  if (m_sink)
  {
    sinkName = m_sink->GetName();
    std::transform(sinkName.begin(), sinkName.end(), sinkName.begin(), ::toupper);
  }

  if (!m_sink || sinkName != driver || !m_sink->IsCompatible(newFormat, device))
  {
    CLog::Log(LOGINFO, "CSoftAE::InternalOpenSink - sink incompatible, re-starting");

    /* take the sink lock */
    CExclusiveLock sinkLock(m_sinkLock);

    reInit = true;
    //close the sink cause it gets reinited
    InternalCloseSink();

    /* get the display name of the device */
    GetDeviceFriendlyName(device);

    /* if we already have a driver, prepend it to the device string */
    if (!driver.empty())
      device = driver + ":" + device;

    /* create the new sink */
    m_sink = GetSink(newFormat, m_transcode || m_rawPassthrough, device);

    /* perform basic sanity checks on the format returned by the sink */
    ASSERT(newFormat.m_channelLayout.Count() > 0);
    ASSERT(newFormat.m_dataFormat           <= AE_FMT_FLOAT);
    ASSERT(newFormat.m_frames                > 0);
    ASSERT(newFormat.m_frameSamples          > 0);
    ASSERT(newFormat.m_frameSize            == (CAEUtil::DataFormatToBits(newFormat.m_dataFormat) >> 3) * newFormat.m_channelLayout.Count());
    ASSERT(newFormat.m_sampleRate            > 0);

    CLog::Log(LOGDEBUG, "CSoftAE::InternalOpenSink - %s Initialized:", m_sink->GetName());
    CLog::Log(LOGDEBUG, "  Output Device : %s", m_deviceFriendlyName.c_str());
    CLog::Log(LOGDEBUG, "  Sample Rate   : %d", newFormat.m_sampleRate);
    CLog::Log(LOGDEBUG, "  Sample Format : %s", CAEUtil::DataFormatToStr(newFormat.m_dataFormat));
    CLog::Log(LOGDEBUG, "  Channel Count : %d", newFormat.m_channelLayout.Count());
    CLog::Log(LOGDEBUG, "  Channel Layout: %s", ((std::string)newFormat.m_channelLayout).c_str());
    CLog::Log(LOGDEBUG, "  Frames        : %d", newFormat.m_frames);
    CLog::Log(LOGDEBUG, "  Frame Samples : %d", newFormat.m_frameSamples);
    CLog::Log(LOGDEBUG, "  Frame Size    : %d", newFormat.m_frameSize);

    m_sinkFormat              = newFormat;
    m_sinkFormatSampleRateMul = 1.0 / (double)newFormat.m_sampleRate;
    m_sinkBlockSize           = newFormat.m_frames * newFormat.m_frameSize;
    // check if sink controls volume, if so, init the volume.
    m_sinkHandlesVolume       = m_sink->HasVolume();
    if (m_sinkHandlesVolume)
      m_sink->SetVolume(m_volume);

    /* invalidate the buffer */
    m_buffer.Empty();
  }
  else
    CLog::Log(LOGINFO, "CSoftAE::InternalOpenSink - keeping old sink with : %s, %s, %dhz",
                          CAEUtil::DataFormatToStr(newFormat.m_dataFormat),
                          ((std::string)newFormat.m_channelLayout).c_str(),
                          newFormat.m_sampleRate);

  reInit = (reInit || m_chLayout != m_sinkFormat.m_channelLayout);
  m_chLayout = m_sinkFormat.m_channelLayout;

  size_t neededBufferSize = 0;
  if (m_rawPassthrough)
  {
    if (!wasRawPassthrough)
      m_buffer.Empty();

    m_convertFn      = NULL;
    m_bytesPerSample = CAEUtil::DataFormatToBits(m_sinkFormat.m_dataFormat) >> 3;
    m_frameSize      = m_sinkFormat.m_frameSize;
    m_frameSizeMul   = 1.0 / (double)m_frameSize;
    neededBufferSize = m_sinkFormat.m_frames * m_sinkFormat.m_frameSize;
  }
  else
  {
    /* if we are transcoding */
    if (m_transcode)
    {
      if (!wasTranscode || wasRawPassthrough)
      {
        /* invalidate the buffer */
        m_buffer.Empty();
        if (m_encoder)
          m_encoder->Reset();
      }

      /* configure the encoder */
      AEAudioFormat encoderFormat;
      encoderFormat.m_dataFormat    = AE_FMT_FLOAT;
      encoderFormat.m_sampleRate    = m_sinkFormat.m_sampleRate;
      encoderFormat.m_encodedRate   = 0;
      encoderFormat.m_channelLayout = m_chLayout;
      encoderFormat.m_frames        = 0;
      encoderFormat.m_frameSamples  = 0;
      encoderFormat.m_frameSize     = 0;
      
      if (!m_encoder || !m_encoder->IsCompatible(encoderFormat))
      {
        m_buffer.Empty();
        SetupEncoder(encoderFormat);
        m_encoderFormat       = encoderFormat;
        if (encoderFormat.m_frameSize > 0)
          m_encoderFrameSizeMul = 1.0 / (double)m_sinkFormat.m_frameSize;
        else
          m_encoderFrameSizeMul = 1.0;
      }

      /* remap directly to the format we need for encode */
      reInit = (reInit || m_chLayout != m_encoderFormat.m_channelLayout);
      m_chLayout       = m_encoderFormat.m_channelLayout;
      m_convertFn      = CAEConvert::FrFloat(m_encoderFormat.m_dataFormat);
      neededBufferSize = m_encoderFormat.m_frames * sizeof(float) * m_chLayout.Count();
      CLog::Log(LOGDEBUG, "CSoftAE::InternalOpenSink - Encoding using layout: %s", ((std::string)m_chLayout).c_str());
    }
    else
    {
      m_convertFn      = CAEConvert::FrFloat(m_sinkFormat.m_dataFormat);
      neededBufferSize = m_sinkFormat.m_frames * sizeof(float) * m_chLayout.Count();
      CLog::Log(LOGDEBUG, "CSoftAE::InternalOpenSink - Using speaker layout: %s", CAEUtil::GetStdChLayoutName(m_stdChLayout));
    }

    m_bytesPerSample = CAEUtil::DataFormatToBits(AE_FMT_FLOAT) >> 3;
    m_frameSize      = m_bytesPerSample * m_chLayout.Count();
    m_frameSizeMul   = 1.0 / (double)m_frameSize;
  }

  CLog::Log(LOGDEBUG, "CSoftAE::InternalOpenSink - Internal Buffer Size: %d", (int)neededBufferSize);
  if (m_buffer.Size() < neededBufferSize)
    m_buffer.Alloc(neededBufferSize);

  if (reInit)
  {
    if (!m_rawPassthrough)
    {
      /* re-init incompatible sounds */
      CSingleLock soundLock(m_soundLock);
      for (SoundList::iterator itt = m_sounds.begin(); itt != m_sounds.end(); ++itt)
      {
        CSoftAESound *sound = *itt;
        if (!sound->IsCompatible())
        {
          StopSound(sound);
          sound->Initialize();
        }
      }
    }

    /* re-init streams */
    streamLock.Enter();
    for (StreamList::iterator itt = m_streams.begin(); itt != m_streams.end(); ++itt)
      (*itt)->Initialize();
    streamLock.Leave();
  }

  /* any new streams need to be initialized */
  for (StreamList::iterator itt = m_newStreams.begin(); itt != m_newStreams.end(); ++itt)
  {
    (*itt)->Initialize();
    m_streams.push_back(*itt);
    if (!(*itt)->m_paused)
      m_playingStreams.push_back(*itt);
  }
  m_newStreams.clear();
  m_streamsPlaying = !m_playingStreams.empty();

  m_softSuspend = false;

  /* notify any event listeners that we are done */
  m_reOpen = false;
  m_reOpenEvent.Set();
  m_wake.Set();
}

void CSoftAE::ResetEncoder()
{
  if (m_encoder)
    m_encoder->Reset();
  m_encodedBuffer.Empty();
}

bool CSoftAE::SetupEncoder(AEAudioFormat &format)
{
  ResetEncoder();
  delete m_encoder;
  m_encoder = NULL;

  if (!m_transcode)
    return false;

  m_encoder = new CAEEncoderFFmpeg();
  if (m_encoder->Initialize(format))
    return true;

  delete m_encoder;
  m_encoder = NULL;
  return false;
}

void CSoftAE::Shutdown()
{
  Deinitialize();
}

bool CSoftAE::Initialize()
{
  CSingleLock lock(m_threadLock);
  InternalOpenSink();
  m_running = true;
  m_thread  = new CThread(this, "CSoftAE");
  m_thread->Create();
  m_thread->SetPriority(THREAD_PRIORITY_ABOVE_NORMAL);
  return true;
}

void CSoftAE::OnSettingsChange(const std::string& setting)
{
  if (setting == "audiooutput.passthroughdevice" ||
      setting == "audiooutput.audiodevice"       ||
      setting == "audiooutput.mode"              ||
      setting == "audiooutput.ac3passthrough"    ||
      setting == "audiooutput.dtspassthrough"    ||
      setting == "audiooutput.passthroughaac"    ||
      setting == "audiooutput.truehdpassthrough" ||
      setting == "audiooutput.dtshdpassthrough"  ||
      setting == "audiooutput.channels"     ||
      setting == "audiooutput.useexclusivemode"  ||
      setting == "audiooutput.multichannellpcm"  ||
      setting == "audiooutput.stereoupmix")
  {
    OpenSink();
  }

  if (setting == "audiooutput.normalizelevels" || setting == "audiooutput.stereoupmix")
  {
    /* re-init stream reamppers */
    CSingleLock streamLock(m_streamLock);
    for (StreamList::iterator itt = m_streams.begin(); itt != m_streams.end(); ++itt)
      (*itt)->InitializeRemap();
  }
}

void CSoftAE::LoadSettings()
{
  m_audiophile = g_advancedSettings.m_audioAudiophile;
  if (m_audiophile)
    CLog::Log(LOGINFO, "CSoftAE::LoadSettings - Audiophile switch enabled");

  m_stereoUpmix = g_guiSettings.GetBool("audiooutput.stereoupmix");
  if (m_stereoUpmix)
    CLog::Log(LOGINFO, "CSoftAE::LoadSettings - Stereo upmix is enabled");

  /* load the configuration */
  m_stdChLayout = AE_CH_LAYOUT_2_0;
  switch (g_guiSettings.GetInt("audiooutput.channels"))
  {
    default:
    case  0: m_stdChLayout = AE_CH_LAYOUT_2_0; break; /* dont alow 1_0 output */
    case  1: m_stdChLayout = AE_CH_LAYOUT_2_0; break;
    case  2: m_stdChLayout = AE_CH_LAYOUT_2_1; break;
    case  3: m_stdChLayout = AE_CH_LAYOUT_3_0; break;
    case  4: m_stdChLayout = AE_CH_LAYOUT_3_1; break;
    case  5: m_stdChLayout = AE_CH_LAYOUT_4_0; break;
    case  6: m_stdChLayout = AE_CH_LAYOUT_4_1; break;
    case  7: m_stdChLayout = AE_CH_LAYOUT_5_0; break;
    case  8: m_stdChLayout = AE_CH_LAYOUT_5_1; break;
    case  9: m_stdChLayout = AE_CH_LAYOUT_7_0; break;
    case 10: m_stdChLayout = AE_CH_LAYOUT_7_1; break;
  }

  // force optical/coax to 2.0 output channels
  if (!m_rawPassthrough && g_guiSettings.GetInt("audiooutput.mode") == AUDIO_IEC958)
    m_stdChLayout = AE_CH_LAYOUT_2_0;

  /* get the output devices and ensure they exist */
  m_device            = g_guiSettings.GetString("audiooutput.audiodevice");
  m_passthroughDevice = g_guiSettings.GetString("audiooutput.passthroughdevice");
  VerifySoundDevice(m_device           , false);
  VerifySoundDevice(m_passthroughDevice, true );

  m_transcode = (
    g_guiSettings.GetBool("audiooutput.ac3passthrough") /*||
    g_guiSettings.GetBool("audiooutput.dtspassthrough") */
  ) && (
      (g_guiSettings.GetInt("audiooutput.mode") == AUDIO_IEC958) ||
      (g_guiSettings.GetInt("audiooutput.mode") == AUDIO_HDMI && !g_guiSettings.GetBool("audiooutput.multichannellpcm"))
  );
}

void CSoftAE::VerifySoundDevice(std::string& device, bool passthrough)
{
  /* check that the specified device exists */
  std::string firstDevice;
  for (AESinkInfoList::iterator itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt)
  {
    AESinkInfo sinkInfo = *itt;
    for (AEDeviceInfoList::iterator itt2 = sinkInfo.m_deviceInfoList.begin(); itt2 != sinkInfo.m_deviceInfoList.end(); ++itt2)
    {
      CAEDeviceInfo& devInfo = *itt2;
      if (passthrough && devInfo.m_deviceType == AE_DEVTYPE_PCM)
        continue;
      std::string deviceName = sinkInfo.m_sinkName + ":" + devInfo.m_deviceName;

      /* remember the first device so we can default to it if required */
      if (firstDevice.empty())
        firstDevice = deviceName;

      if (device == deviceName)
        return;
    }
  }

  /* if the device wasnt found, set it to the first viable output */
  device = firstDevice;
}

inline void CSoftAE::GetDeviceFriendlyName(std::string &device)
{
  m_deviceFriendlyName = "Device not found";
  /* Match the device and find its friendly name */
  for (AESinkInfoList::iterator itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt)
  {
    AESinkInfo sinkInfo = *itt;
    for (AEDeviceInfoList::iterator itt2 = sinkInfo.m_deviceInfoList.begin(); itt2 != sinkInfo.m_deviceInfoList.end(); ++itt2)
    {
      CAEDeviceInfo& devInfo = *itt2;
      if (devInfo.m_deviceName == device)
      {
        m_deviceFriendlyName = devInfo.m_displayName;
        break;
      }
    }
  }
  return;
}

void CSoftAE::Deinitialize()
{
  CSingleLock lock(m_threadLock);
  if (m_thread)
  {
    Stop();
    m_thread->StopThread(true);
    delete m_thread;
    m_thread = NULL;
  }

  if (m_sink)
  {
    /* shutdown the sink */
    m_sink->Deinitialize();
    delete m_sink;
    m_sink = NULL;
  }

  delete m_encoder;
  m_encoder = NULL;
  ResetEncoder();
  m_buffer.DeAlloc();

  _aligned_free(m_converted);
  m_converted = NULL;
  m_convertedSize = 0;

  m_sinkInfoList.clear();  
}

void CSoftAE::EnumerateOutputDevices(AEDeviceList &devices, bool passthrough)
{
  for (AESinkInfoList::iterator itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt)
  {
    AESinkInfo sinkInfo = *itt;
    for (AEDeviceInfoList::iterator itt2 = sinkInfo.m_deviceInfoList.begin(); itt2 != sinkInfo.m_deviceInfoList.end(); ++itt2)
    {
      CAEDeviceInfo devInfo = *itt2;
      if (passthrough && devInfo.m_deviceType == AE_DEVTYPE_PCM)
        continue;

        /* PLEX */
      /* Direct Sound can NOT be used for HD formats */
      if ((g_guiSettings.GetBool("audiooutput.truehdpassthrough") || g_guiSettings.GetBool("audiooutput.dtshdpassthrough")) &&
        sinkInfo.m_sinkName == "DirectSound" && passthrough)
        continue;
      /* END PLEX */


      std::string device = sinkInfo.m_sinkName + ":" + devInfo.m_deviceName;

      std::stringstream ss;

      /* add the sink name if we have more then one sink type */
      if (m_sinkInfoList.size() > 1)
        ss << sinkInfo.m_sinkName << ": ";

      ss << devInfo.m_displayName;
      if (!devInfo.m_displayNameExtra.empty())
        ss << ", " << devInfo.m_displayNameExtra;

      devices.push_back(AEDevice(ss.str(), device));
    }
  }
}

std::string CSoftAE::GetDefaultDevice(bool passthrough)
{
  for (AESinkInfoList::iterator itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt)
  {
    AESinkInfo sinkInfo = *itt;
    for (AEDeviceInfoList::iterator itt2 = sinkInfo.m_deviceInfoList.begin(); itt2 != sinkInfo.m_deviceInfoList.end(); ++itt2)
    {
      CAEDeviceInfo devInfo = *itt2;
      if (passthrough && devInfo.m_deviceType == AE_DEVTYPE_PCM)
        continue;

      std::string device = sinkInfo.m_sinkName + ":" + devInfo.m_deviceName;
      return device;
    }
  }
  return "default";
}

bool CSoftAE::SupportsRaw()
{
  /* CSoftAE supports raw formats */
  return true;
}

void CSoftAE::PauseStream(CSoftAEStream *stream)
{
  CSingleLock streamLock(m_streamLock);
  RemoveStream(m_playingStreams, stream);
  stream->m_paused = true;

  m_reOpen = true;
  m_wake.Set();
}

void CSoftAE::ResumeStream(CSoftAEStream *stream)
{
  CSingleLock streamLock(m_streamLock);
  m_playingStreams.push_back(stream);
  stream->m_paused = false;
  streamLock.Leave();

  m_streamsPlaying = true;
  m_reOpen = true;
  m_wake.Set();
}

void CSoftAE::Stop()
{
  m_running = false;
  m_isSuspended = false;
  m_wake.Set();

  /* wait for the thread to stop */
  CSingleLock lock(m_runningLock);
}

void CSoftAE::SetSoundMode(const int mode)
{
  m_soundMode = mode;

  /* stop all currently playing sounds if they are being turned off */
  if (mode == AE_SOUND_OFF || (mode == AE_SOUND_IDLE && m_streamsPlaying))
    StopAllSounds();
}

IAEStream *CSoftAE::MakeStream(enum AEDataFormat dataFormat, unsigned int sampleRate, unsigned int encodedSampleRate, CAEChannelInfo channelLayout, unsigned int options/* = 0 */)
{
  CAEChannelInfo channelInfo(channelLayout);
  CLog::Log(LOGINFO, "CSoftAE::MakeStream - %s, %u, %s",
            CAEUtil::DataFormatToStr(dataFormat),
            sampleRate, ((std::string)channelInfo).c_str());

  /* ensure we have the encoded sample rate if the format is RAW */
  if (AE_IS_RAW(dataFormat))
    ASSERT(encodedSampleRate);

  CSingleLock streamLock(m_streamLock);
  CSoftAEStream *stream = new CSoftAEStream(dataFormat, sampleRate, encodedSampleRate, channelLayout, options);
  m_newStreams.push_back(stream);
  streamLock.Leave();
  // this is really needed here
  OpenSink();
  return stream;
}

IAESound *CSoftAE::MakeSound(const std::string& file)
{
  CSingleLock soundLock(m_soundLock);

  CSoftAESound *sound = new CSoftAESound(file);
  if (!sound->Initialize())
  {
    delete sound;
    return NULL;
  }

  m_sounds.push_back(sound);
  return sound;
}

void CSoftAE::PlaySound(IAESound *sound)
{
  if (m_soundMode == AE_SOUND_OFF || (m_soundMode == AE_SOUND_IDLE && m_streamsPlaying))
    return;

  float *samples = ((CSoftAESound*)sound)->GetSamples();
  if (!samples)
    return;

  /* add the sound to the play list */
  CSingleLock soundSampleLock(m_soundSampleLock);
  SoundState ss = {
    ((CSoftAESound*)sound),
    samples,
    ((CSoftAESound*)sound)->GetSampleCount()
  };
  m_playing_sounds.push_back(ss);

  /* wake to play the sound */
  m_softSuspend = false;
  m_wake.Set();
}

void CSoftAE::FreeSound(IAESound *sound)
{
  if (!sound)
    return;

  sound->Stop();
  CSingleLock soundLock(m_soundLock);
  for (SoundList::iterator itt = m_sounds.begin(); itt != m_sounds.end(); ++itt)
    if (*itt == sound)
    {
      m_sounds.erase(itt);
      break;
    }

  delete (CSoftAESound*)sound;
}

void CSoftAE::GarbageCollect()
{
}

unsigned int CSoftAE::GetSampleRate()
{
  if (m_transcode && m_encoder && !m_rawPassthrough)
    return m_encoderFormat.m_sampleRate;

  return m_sinkFormat.m_sampleRate;
}

void CSoftAE::StopSound(IAESound *sound)
{
  CSingleLock lock(m_soundSampleLock);
  for (SoundStateList::iterator itt = m_playing_sounds.begin(); itt != m_playing_sounds.end(); )
  {
    if ((*itt).owner == sound)
    {
      (*itt).owner->ReleaseSamples();
      itt = m_playing_sounds.erase(itt);
    }
    else
      ++itt;
  }
}

IAEStream *CSoftAE::FreeStream(IAEStream *stream)
{
  CSingleLock lock(m_streamLock);
  RemoveStream(m_playingStreams, (CSoftAEStream*)stream);
  RemoveStream(m_streams       , (CSoftAEStream*)stream);
  // Reopen is old behaviour. Not opening when masterstream stops means clipping on S/PDIF.
  if(!m_isSuspended && (m_masterStream == stream))
  {
    m_reOpen = true;
    m_masterStream = NULL;
  }

  delete (CSoftAEStream*)stream;
  return NULL;
}

double CSoftAE::GetDelay()
{
  double delayBuffer = 0.0, delaySink = 0.0, delayTranscoder = 0.0;

  CSharedLock sinkLock(m_sinkLock);
  if (m_sink)
    delaySink = m_sink->GetDelay();
 
  if (m_transcode && m_encoder && !m_rawPassthrough)
  {
    delayBuffer     = (double)m_buffer.Used() * m_frameSizeMul * m_encoderInitSampleRateMul;
    delayTranscoder = m_encoder->GetDelay((double)m_encodedBuffer.Used() * m_encoderFrameSizeMul);
  }
  else
    delayBuffer = (double)m_buffer.Used() * m_frameSizeMul *m_sinkFormatSampleRateMul;

  return delayBuffer + delaySink + delayTranscoder;
}

double CSoftAE::GetCacheTime()
{
  double timeBuffer = 0.0, timeSink = 0.0, timeTranscoder = 0.0;

  CSharedLock sinkLock(m_sinkLock);
  if (m_sink)
    timeSink = m_sink->GetCacheTime();

  if (m_transcode && m_encoder && !m_rawPassthrough)
  {
    timeBuffer     = (double)m_buffer.Used() * m_frameSizeMul * m_encoderInitSampleRateMul;
    timeTranscoder = m_encoder->GetDelay((double)m_encodedBuffer.Used() * m_encoderFrameSizeMul);
  }
  else
    timeBuffer = (double)m_buffer.Used() * m_frameSizeMul *m_sinkFormatSampleRateMul;

  return timeBuffer + timeSink + timeTranscoder;
}

double CSoftAE::GetCacheTotal()
{
  double timeBuffer = 0.0, timeSink = 0.0, timeTranscoder = 0.0;

  CSharedLock sinkLock(m_sinkLock);
  if (m_sink)
    timeSink = m_sink->GetCacheTotal();

  if (m_transcode && m_encoder && !m_rawPassthrough)
  {
    timeBuffer     = (double)m_buffer.Size() * m_frameSizeMul * m_encoderInitSampleRateMul;
    timeTranscoder = m_encoder->GetDelay((double)m_encodedBuffer.Size() * m_encoderFrameSizeMul);
  }
  else
    timeBuffer = (double)m_buffer.Size() * m_frameSizeMul *m_sinkFormatSampleRateMul;

  return timeBuffer + timeSink + timeTranscoder;
}

bool CSoftAE::IsSuspended()
{
  return m_isSuspended;
}

float CSoftAE::GetVolume()
{
  return m_volume;
}

void CSoftAE::SetVolume(float volume)
{
  m_volume = volume;
  if (!m_sinkHandlesVolume)
    return;

  CSharedLock sinkLock(m_sinkLock);
  if (m_sink)
    m_sink->SetVolume(m_volume);
}

void CSoftAE::StopAllSounds()
{
  CSingleLock lock(m_soundSampleLock);
  while (!m_playing_sounds.empty())
  {
    SoundState *ss = &(*m_playing_sounds.begin());
    ss->owner->ReleaseSamples();
    m_playing_sounds.pop_front();
  }
}

bool CSoftAE::Suspend()
{
  CLog::Log(LOGDEBUG, "CSoftAE::Suspend - Suspending AE processing");
  m_isSuspended = true;
  CSingleLock streamLock(m_streamLock);
  
  for (StreamList::iterator itt = m_playingStreams.begin(); itt != m_playingStreams.end(); ++itt)
  {
    CSoftAEStream *stream = *itt;
    stream->Flush();
  }
  streamLock.Leave();
  #if defined(TARGET_LINUX)
  /*workaround sinks not playing sound after resume */
    StopAllSounds();
    bool ret = true;
    if(m_sink)
    {
      /* Deinitialize and delete current m_sink */
      // we don't want that Run reopens our device, so we wait.
      m_saveSuspend.Reset();
      // wait until we are looping in ProcessSuspend()
      // this is more save to not come up unclean
      // we cannot wait forever
      ret = m_saveSuspend.WaitMSec(500);
      if(ret)
      {
        CLog::Log(LOGDEBUG, "CSoftAE::Suspend - After Event");
        CExclusiveLock sinkLock(m_sinkLock);
        // remove all the sinks
        for (AESinkInfoList::iterator itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt)
        {
          itt->m_deviceInfoList.pop_back();
        }
        InternalCloseSink();
      }
      else
      {
        CLog::Log(LOGDEBUG, "CSoftAE::Suspend - Unload failed will continue");
        m_saveSuspend.Reset();
      }
    }
    // The device list is now empty and must be reenumerated afterwards.
    if(ret)
      m_sinkInfoList.clear();

    // signal anybody, that we are gone now (beware of deadlocks)
    // we don't unset the fields here, to care for reinit after resume
    if(m_reOpen)
      m_reOpenEvent.Set();
  #endif

  return true;
}

bool CSoftAE::Resume()
{
#if defined(TARGET_LINUX)
  // We must make sure, that we don't return empty.
  if(m_sinkInfoList.empty())
  {
    CLog::Log(LOGDEBUG, "CSoftAE::Resume - Re Enumerating Sinks");
    CExclusiveLock sinkLock(m_sinkLock);
    // Forced enumeration - we are sure that we start completely fresh.
    CAESinkFactory::EnumerateEx(m_sinkInfoList, true);
    sinkLock.Leave(); // we leave here explicitly to not lock while printing new sinks
    PrintSinks();
  }
#endif
  CLog::Log(LOGDEBUG, "CSoftAE::Resume - Resuming AE processing");
  m_isSuspended = false;
  // we flag reopen
  m_reOpen = true;

  return true;
}

void CSoftAE::Run()
{
  /* we release this when we exit the thread unblocking anyone waiting on "Stop" */
  CSingleLock runningLock(m_runningLock);
  CLog::Log(LOGINFO, "CSoftAE::Run - Thread Started");

  bool hasAudio = false;
  while (m_running)
  {
    bool restart = false;

    /* with the new non blocking implementation - we just reOpen here, when it tells reOpen */
    if ((this->*m_outputStageFn)(hasAudio) > 0)
      hasAudio = false; /* taken some audio - reset our silence flag */

    /* if we have enough room in the buffer */
    if (m_buffer.Free() >= m_frameSize)
    {
      /* take some data for our use from the buffer */
      uint8_t *out = (uint8_t*)m_buffer.Take(m_frameSize);
      memset(out, 0, m_frameSize);

      /* run the stream stage */
      CSoftAEStream *oldMaster = m_masterStream;
      if ((this->*m_streamStageFn)(m_chLayout.Count(), out, restart) > 0)
        hasAudio = true; /* have some audio */

      /* if in audiophile mode and the master stream has changed, flag for restart */
      if (m_audiophile && oldMaster != m_masterStream)
        restart = true;
    }

    /* Handle idle or forced suspend */
    ProcessSuspend();

    /* if we are told to restart */
    if (m_reOpen || restart || !m_sink)
    {
      if(m_sinkIsSuspended && m_sink)
      {
        // hint for fritsch: remember lazy evaluation
        m_reOpen = !m_sink->SoftResume() || m_reOpen;
        m_sinkIsSuspended = false;
        CLog::Log(LOGDEBUG, "CSoftAE::Run - Sink was forgotten");   
      }
      CLog::Log(LOGDEBUG, "CSoftAE::Run - Sink restart flagged");
      InternalOpenSink();
    }

#if defined(TARGET_ANDROID)
    else if (m_playingStreams.empty() 
      &&     m_playing_sounds.empty()
      && !g_advancedSettings.m_streamSilence)
    {
      // if we have nothing to do, take a dirt nap.
      // we do not have to take a lock just to check empty.
      // this keeps AE from sucking CPU if nothing is going on.
      m_wake.WaitMSec(SOFTAE_IDLE_WAIT_MSEC);
    }
#endif
  }
}

void CSoftAE::AllocateConvIfNeeded(size_t convertedSize, bool prezero)
{
  if (m_convertedSize < convertedSize)
  {
    _aligned_free(m_converted);
    m_converted = (uint8_t *)_aligned_malloc(convertedSize, 16);
    m_convertedSize = convertedSize;
  }
  if (prezero)
    memset(m_converted, 0x00, convertedSize);
}

unsigned int CSoftAE::MixSounds(float *buffer, unsigned int samples)
{
  // no point doing anything if we have no sounds,
  // we do not have to take a lock just to check empty
  if (m_playing_sounds.empty())
    return 0;

  SoundStateList::iterator itt;
  unsigned int mixed = 0;
  CSingleLock lock(m_soundSampleLock);
  for (itt = m_playing_sounds.begin(); itt != m_playing_sounds.end(); )
  {
    SoundState *ss = &(*itt);
    float *out = buffer;

    /* no more frames, so remove it from the list */
    if (ss->sampleCount == 0)
    {
      ss->owner->ReleaseSamples();
      itt = m_playing_sounds.erase(itt);
      continue;
    }

    float volume = ss->owner->GetVolume();
    unsigned int mixSamples = std::min(ss->sampleCount, samples);
    #ifdef __SSE__
      CAEUtil::SSEMulAddArray(out, ss->samples, volume, mixSamples);
    #else
      float *sample_buffer = ss->samples;
      for (unsigned int i = 0; i < mixSamples; ++i)
        *out++ += *sample_buffer++ * volume;
    #endif

    ss->sampleCount -= mixSamples;
    ss->samples     += mixSamples;

    ++itt;
    ++mixed;
  }
  return mixed;
}

bool CSoftAE::FinalizeSamples(float *buffer, unsigned int samples, bool hasAudio)
{
  if (m_soundMode != AE_SOUND_OFF)
    hasAudio |= (MixSounds(buffer, samples) > 0);

  /* no need to process if we don't have audio (buffer is memset to 0) */
  if (!hasAudio)
    return false;

  if (m_muted)
  {
    memset(buffer, 0, samples * sizeof(float));
    return false;
  }

  /* deamplify */
  if (!m_sinkHandlesVolume && m_volume < 1.0)
  {
    #ifdef __SSE__
      CAEUtil::SSEMulArray(buffer, m_volume, samples);
    #else
      float *fbuffer = buffer;
      for (unsigned int i = 0; i < samples; i++)
        *fbuffer++ *= m_volume;
    #endif
  }

  /* check if we need to clamp */
  bool clamp = false;
  float *fbuffer = buffer;
  for (unsigned int i = 0; i < samples; i++, fbuffer++)
  {
    if (*fbuffer < -1.0f || *fbuffer > 1.0f)
    {
      clamp = true;
      break;
    }
  }

  /* if there were no samples outside of the range, dont clamp the buffer */
  if (!clamp)
    return true;

  CLog::Log(LOGDEBUG, "CSoftAE::FinalizeSamples - Clamping buffer of %d samples", samples);
  CAEUtil::ClampArray(buffer, samples);
  return true;
}

int CSoftAE::RunOutputStage(bool hasAudio)
{
  const unsigned int needSamples = m_sinkFormat.m_frames * m_sinkFormat.m_channelLayout.Count();
  const size_t needBytes = needSamples * sizeof(float);
  if (m_buffer.Used() < needBytes)
    return 0;

  void *data = m_buffer.Raw(needBytes);
  hasAudio = FinalizeSamples((float*)data, needSamples, hasAudio);

  int wroteFrames = 0;
  if (m_convertFn)
  {
    const unsigned int convertedBytes = m_sinkFormat.m_frames * m_sinkFormat.m_frameSize;
    AllocateConvIfNeeded(convertedBytes, !hasAudio);
    if (hasAudio)
      m_convertFn((float*)data, needSamples, m_converted);
    data = m_converted;
  }

  /* Output frames to sink */
  if (m_sink)
    wroteFrames = m_sink->AddPackets((uint8_t*)data, m_sinkFormat.m_frames, hasAudio);

  /* Return value of INT_MAX signals error in sink - restart */
  if (wroteFrames == INT_MAX)
  {
    CLog::Log(LOGERROR, "CSoftAE::RunOutputStage - sink error - reinit flagged");
    wroteFrames = 0;
    m_reOpen = true;
  }

  if (wroteFrames)
    m_buffer.Shift(NULL, wroteFrames * m_sinkFormat.m_channelLayout.Count() * sizeof(float));

  return wroteFrames;
}

int CSoftAE::RunRawOutputStage(bool hasAudio)
{
  if(m_buffer.Used() < m_sinkBlockSize)
    return 0;

  void *data = m_buffer.Raw(m_sinkBlockSize);

  if (CAEUtil::S16NeedsByteSwap(AE_FMT_S16NE, m_sinkFormat.m_dataFormat))
  {
    /*
     * It would really be preferable to handle this at packing stage, so that
     * it could byteswap the data efficiently without wasting CPU time on
     * swapping the huge IEC 61937 zero padding between frames (or not
     * byteswap at all, if there are two byteswaps).
     *
     * Unfortunately packing is done on a higher level and we can't easily
     * tell it the needed format from here, so do it here for now (better than
     * nothing)...
     */
    AllocateConvIfNeeded(m_sinkBlockSize, !hasAudio);
    if (hasAudio)
      Endian_Swap16_buf((uint16_t *)m_converted, (uint16_t *)data, m_sinkBlockSize / 2);
    data = m_converted;
  }

  int wroteFrames = 0;
  if (m_sink)
    wroteFrames = m_sink->AddPackets((uint8_t *)data, m_sinkFormat.m_frames, hasAudio);

  /* Return value of INT_MAX signals error in sink - restart */
  if (wroteFrames == INT_MAX)
  {
    CLog::Log(LOGERROR, "CSoftAE::RunRawOutputStage - sink error - reinit flagged");
    wroteFrames = 0;
    m_reOpen = true;
  }

  m_buffer.Shift(NULL, wroteFrames * m_sinkFormat.m_frameSize);
  return wroteFrames;
}

int CSoftAE::RunTranscodeStage(bool hasAudio)
{
  /* if we dont have enough samples to encode yet, return */
  unsigned int block     = m_encoderFormat.m_frames * m_encoderFormat.m_frameSize;
  unsigned int sinkBlock = m_sinkFormat.m_frames    * m_sinkFormat.m_frameSize;

  int encodedFrames = 0;
  if (m_buffer.Used() >= block && m_encodedBuffer.Used() < sinkBlock * 2)
  {
    hasAudio = FinalizeSamples((float*)m_buffer.Raw(block), m_encoderFormat.m_frameSamples, hasAudio);

    void *buffer;
    if (m_convertFn)
    {
      unsigned int newsize = m_encoderFormat.m_frames * m_encoderFormat.m_frameSize;
      AllocateConvIfNeeded(newsize, !hasAudio);
      if (hasAudio)
        m_convertFn((float*)m_buffer.Raw(block),
          m_encoderFormat.m_frames * m_encoderFormat.m_channelLayout.Count(), m_converted);
      buffer = m_converted;
    }
    else
      buffer = m_buffer.Raw(block);

    encodedFrames = m_encoder->Encode((float*)buffer, m_encoderFormat.m_frames);
    m_buffer.Shift(NULL, encodedFrames * m_encoderFormat.m_frameSize);

    uint8_t *packet;
    unsigned int size = m_encoder->GetData(&packet);

    /* if there is not enough space for another encoded packet enlarge the buffer */
    if (m_encodedBuffer.Free() < size)
      m_encodedBuffer.ReAlloc(m_encodedBuffer.Used() + size);

    m_encodedBuffer.Push(packet, size);
  }

  /* if we have enough data to write */
  if (m_encodedBuffer.Used() >= sinkBlock)
  {
    int wroteFrames = m_sink->AddPackets((uint8_t*)m_encodedBuffer.Raw(sinkBlock), m_sinkFormat.m_frames, hasAudio);
    
    /* Return value of INT_MAX signals error in sink - restart */
    if (wroteFrames == INT_MAX)
    {
      CLog::Log(LOGERROR, "CSoftAE::RunTranscodeStage - sink error - reinit flagged");
      wroteFrames = 0;
      m_reOpen = true;
    }

    m_encodedBuffer.Shift(NULL, wroteFrames * m_sinkFormat.m_frameSize);
  }
  return encodedFrames;
}

void CSoftAE::PrintSinks()
{
  for (AESinkInfoList::iterator itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt)
  {
    CLog::Log(LOGNOTICE, "Enumerated %s devices:", itt->m_sinkName.c_str());
    int count = 0;
    for (AEDeviceInfoList::iterator itt2 = itt->m_deviceInfoList.begin(); itt2 != itt->m_deviceInfoList.end(); ++itt2)
    {
      CLog::Log(LOGNOTICE, "    Device %d", ++count);
      CAEDeviceInfo& info = *itt2;
      std::stringstream ss((std::string)info);
      std::string line;
      while(std::getline(ss, line, '\n'))
        CLog::Log(LOGNOTICE, "        %s", line.c_str());
    }
  }
}

unsigned int CSoftAE::RunRawStreamStage(unsigned int channelCount, void *out, bool &restart)
{
  StreamList resumeStreams;
  static StreamList::iterator itt;
  CSingleLock streamLock(m_streamLock);
  /* handle playing streams */
  for (itt = m_playingStreams.begin(); itt != m_playingStreams.end(); ++itt)
  {
    CSoftAEStream *sitt = *itt;
    if (sitt == m_masterStream)
      continue;

    /* consume data from streams even though we cant use it */
    uint8_t *frame = sitt->GetFrame();

    /* flag the stream's slave to be resumed if it has drained */
    if (!frame && sitt->IsDrained() && sitt->m_slave && sitt->m_slave->IsPaused())
      resumeStreams.push_back(sitt);
  }

  /* nothing to do if we dont have a master stream */
  if (!m_masterStream)
    return 0;

  /* get the frame and append it to the output */
  uint8_t *frame = m_masterStream->GetFrame();
  unsigned int mixed;
  if (frame)
  {
    mixed = 1;
    memcpy(out, frame, m_sinkFormat.m_frameSize);
  }
  else
  {
    mixed = 0;
    if (m_masterStream->IsDrained() && m_masterStream->m_slave && m_masterStream->m_slave->IsPaused())
      resumeStreams.push_back(m_masterStream);
  }

  ResumeSlaveStreams(resumeStreams);
  return mixed;
}

unsigned int CSoftAE::RunStreamStage(unsigned int channelCount, void *out, bool &restart)
{
  // no point doing anything if we have no streams,
  // we do not have to take a lock just to check empty
  if (m_playingStreams.empty())
    return 0;

  float *dst = (float*)out;
  unsigned int mixed = 0;

  /* identify the master stream */
  CSingleLock streamLock(m_streamLock);

  /* mix in any running streams */
  StreamList resumeStreams;
  for (StreamList::iterator itt = m_playingStreams.begin(); itt != m_playingStreams.end(); ++itt)
  {
    CSoftAEStream *stream = *itt;

    float *frame = (float*)stream->GetFrame();
    if (!frame && stream->IsDrained() && stream->m_slave && stream->m_slave->IsPaused())
      resumeStreams.push_back(stream);

    if (!frame)
      continue;

    float volume = stream->GetVolume() * stream->GetReplayGain() * stream->RunLimiter(frame, channelCount);
    #ifdef __SSE__
    if (channelCount > 1)
      CAEUtil::SSEMulAddArray(dst, frame, volume, channelCount);
    else
    #endif
    {
      for (unsigned int i = 0; i < channelCount; ++i)
        *dst++ += *frame++ * volume;
    }

    ++mixed;
  }

  ResumeSlaveStreams(resumeStreams);
  return mixed;
}

inline void CSoftAE::ResumeSlaveStreams(const StreamList &streams)
{
  if (streams.empty())
    return;

  /* resume any streams that need to be */
  for (StreamList::const_iterator itt = streams.begin(); itt != streams.end(); ++itt)
  {
    CSoftAEStream *stream = *itt;
    m_playingStreams.push_back(stream->m_slave);
    stream->m_slave->m_paused = false;
    stream->m_slave = NULL;
  }
}

inline void CSoftAE::RemoveStream(StreamList &streams, CSoftAEStream *stream)
{
  StreamList::iterator f = std::find(streams.begin(), streams.end(), stream);
  if (f != streams.end())
    streams.erase(f);

  if (streams == m_playingStreams)
    m_streamsPlaying = !m_playingStreams.empty();
}

inline void CSoftAE::ProcessSuspend()
{
  unsigned int curSystemClock = 0;
#if defined(TARGET_WINDOWS) || defined(TARGET_LINUX)
  if (!m_softSuspend && m_playingStreams.empty() && m_playing_sounds.empty() &&
      !g_advancedSettings.m_streamSilence)
  {
    m_softSuspend = true;
    m_softSuspendTimer = XbmcThreads::SystemClockMillis() + 10000; //10.0 second delay for softSuspend
    Sleep(10);
  }

  if (m_softSuspend)
    curSystemClock = XbmcThreads::SystemClockMillis();
#endif
  /* idle while in Suspend() state until Resume() called */
  /* idle if nothing to play and user hasn't enabled     */
  /* continuous streaming (silent stream) in as.xml      */
  /* In case of Suspend stay in there until Resume is called from outer thread */
  while (m_isSuspended || ((m_softSuspend && (curSystemClock > m_softSuspendTimer)) &&
          m_running     && !m_reOpen))
  {
    if (!m_isSuspended && m_sink && !m_sinkIsSuspended)
    {
      /* put the sink in Suspend mode */
      CExclusiveLock sinkLock(m_sinkLock);
      if (m_sink && !m_sink->SoftSuspend())
      {
        m_sinkIsSuspended = false; //sink cannot be suspended
        m_softSuspend   = false; //break suspend loop
        break;
      }
      else
      {
        CLog::Log(LOGDEBUG, "Suspended the Sink");
        m_sinkIsSuspended = true; //sink has suspended processing
      }
      sinkLock.Leave();
    }
    // Signal that the Suspend can go on now.
    // Idea: Outer thread calls Suspend() - but
    // because of AddPackets does not care about locks, we must make
    // sure, that our school bus (AE::Run) is currently driving through
    // some gas station, before we move away the sink.
    if(m_isSuspended)
      m_saveSuspend.Set();

    /* idle for platform-defined time */
    m_wake.WaitMSec(SOFTAE_IDLE_WAIT_MSEC);

    /* check if we need to resume for stream or sound or somebody wants to open us
     * the suspend checks are only there to:
     * a) not run out of softSuspend directly when we are sleeping
     * b) nail(!) the thread during real Suspend into this method
     * Note: It is not enough to check the streams buffer, cause it might not be filled yet
     * We have to check after ProcessSuspending() if the sink is still in softsleep and resume it
     */
    if (!m_isSuspended && (!m_playingStreams.empty() || !m_playing_sounds.empty()))
    {
      // the sink might still be not initialized after Resume of real suspend
      m_reOpen = m_sink && (!m_sink->SoftResume() || m_reOpen); // sink returns false if it requires reinit (worthless with current implementation)
      m_sinkIsSuspended = false; //sink processing data
      m_softSuspend   = false; //break suspend loop (under some conditions)
      CLog::Log(LOGDEBUG, "Resumed the Sink");
      break;
    }
  }
}

