#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)
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;
{
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)
{
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;
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)
{
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;
}
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);
}
}
}
+
+ // 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
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,
};
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");
////////////////////////////////////////////////////////////////////////////////////////////
CAESinkDARWINOSX::CAESinkDARWINOSX()
-: m_latentFrames(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
}
RegisterDeviceChangedCB(true, this);
m_started = false;
+ m_planar = false;
}
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;
{ // 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())
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());
bool passthrough = false;
UInt32 outputIndex = 0;
+ UInt32 numOutputChannels = 0;
float outputScore = 0;
AudioStreamBasicDescription outputFormat = {0};
AudioStreamID outputStream = 0;
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);
if (score > outputScore)
{
- passthrough = score > 1000;
+ passthrough = score > 10000;
outputScore = score;
outputFormat = desc;
outputStream = *i;
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__);
/* 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_outputBitstream = passthrough && outputFormat.mFormatID == kAudioFormatLinearPCM;
std::string formatString;
- CLog::Log(LOGDEBUG, "%s: Selected stream[%u] - id: 0x%04X, Physical Format: %s %s", __FUNCTION__, outputIndex, 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);
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();
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));
delete[] m_outputBuffer;
m_outputBuffer = NULL;
+ m_planar = false;
+ delete[] m_planarBuffer;
+ m_planarBuffer = NULL;
+
m_started = false;
}
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);
void CAESinkDARWINOSX::Drain()
{
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;
}
}
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;
- for (unsigned int i = 0; i < outOutputData->mNumberBuffers; i++)
+ 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[i].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[i].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[i].mDataByteSize;
- unsigned int bytes = std::min(sink->m_buffer->GetReadSize(), wanted);
- sink->m_buffer->Read((unsigned char*)outOutputData->mBuffers[i].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;
}