1 /*
2  *  Copyright (C) 2014-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "AEDeviceEnumerationOSX.h"
10 
11 #include "cores/AudioEngine/Sinks/darwin/CoreAudioHelpers.h"
12 #include "cores/AudioEngine/Sinks/osx/CoreAudioChannelLayout.h"
13 #include "cores/AudioEngine/Utils/AEUtil.h"
14 #include "utils/StringUtils.h"
15 #include "utils/log.h"
16 
17 #include <sstream>
18 
19 #define CA_MAX_CHANNELS 72
20 // default channel map - in case it can't be fetched from the device
21 static enum AEChannel CAChannelMap[CA_MAX_CHANNELS + 1] = {
22   AE_CH_FL , AE_CH_FR , AE_CH_BL , AE_CH_BR , AE_CH_FC , AE_CH_LFE , AE_CH_SL , AE_CH_SR ,
23   AE_CH_UNKNOWN1  , AE_CH_UNKNOWN2  , AE_CH_UNKNOWN3  , AE_CH_UNKNOWN4  ,
24   AE_CH_UNKNOWN5  , AE_CH_UNKNOWN6  , AE_CH_UNKNOWN7  , AE_CH_UNKNOWN8  ,
25   AE_CH_UNKNOWN9  , AE_CH_UNKNOWN10 , AE_CH_UNKNOWN11 , AE_CH_UNKNOWN12 ,
26   AE_CH_UNKNOWN13 , AE_CH_UNKNOWN14 , AE_CH_UNKNOWN15 , AE_CH_UNKNOWN16 ,
27   AE_CH_UNKNOWN17 , AE_CH_UNKNOWN18 , AE_CH_UNKNOWN19 , AE_CH_UNKNOWN20 ,
28   AE_CH_UNKNOWN21 , AE_CH_UNKNOWN22 , AE_CH_UNKNOWN23 , AE_CH_UNKNOWN24 ,
29   AE_CH_UNKNOWN25 , AE_CH_UNKNOWN26 , AE_CH_UNKNOWN27 , AE_CH_UNKNOWN28 ,
30   AE_CH_UNKNOWN29 , AE_CH_UNKNOWN30 , AE_CH_UNKNOWN31 , AE_CH_UNKNOWN32 ,
31   AE_CH_UNKNOWN33 , AE_CH_UNKNOWN34 , AE_CH_UNKNOWN35 , AE_CH_UNKNOWN36 ,
32   AE_CH_UNKNOWN37 , AE_CH_UNKNOWN38 , AE_CH_UNKNOWN39 , AE_CH_UNKNOWN40 ,
33   AE_CH_UNKNOWN41 , AE_CH_UNKNOWN42 , AE_CH_UNKNOWN43 , AE_CH_UNKNOWN44 ,
34   AE_CH_UNKNOWN45 , AE_CH_UNKNOWN46 , AE_CH_UNKNOWN47 , AE_CH_UNKNOWN48 ,
35   AE_CH_UNKNOWN49 , AE_CH_UNKNOWN50 , AE_CH_UNKNOWN51 , AE_CH_UNKNOWN52 ,
36   AE_CH_UNKNOWN53 , AE_CH_UNKNOWN54 , AE_CH_UNKNOWN55 , AE_CH_UNKNOWN56 ,
37   AE_CH_UNKNOWN57 , AE_CH_UNKNOWN58 , AE_CH_UNKNOWN59 , AE_CH_UNKNOWN60 ,
38   AE_CH_UNKNOWN61 , AE_CH_UNKNOWN62 , AE_CH_UNKNOWN63 , AE_CH_UNKNOWN64 ,
39 
40   AE_CH_NULL
41 };
42 
AEDeviceEnumerationOSX(AudioDeviceID deviceID)43 AEDeviceEnumerationOSX::AEDeviceEnumerationOSX(AudioDeviceID deviceID)
44 : m_deviceID(deviceID)
45 , m_isPlanar(false)
46 , m_caDevice(deviceID)
47 {
48   Enumerate();
49 }
50 
Enumerate()51 bool AEDeviceEnumerationOSX::Enumerate()
52 {
53   AudioStreamIdList streamList;
54   bool isDigital = isDigitalDevice();
55   bool ret = false;
56   UInt32 transportType = m_caDevice.GetTransportType();
57   m_caStreamInfos.clear();
58   m_isPlanar = true;
59   m_deviceName = m_caDevice.GetName();
60 
61   if (m_caDevice.GetStreams(&streamList))
62   {
63     for (UInt32 streamIdx = 0; streamIdx < streamList.size(); streamIdx++)
64     {
65       caStreamInfo info;
66       info.streamID = streamList[streamIdx];
67       info.numChannels = m_caDevice.GetNumChannelsOfStream(streamIdx);
68       // one stream with num channels other then 1 is enough to make this device non-planar
69       if (info.numChannels != 1)
70         m_isPlanar = false;
71 
72       CCoreAudioStream::GetAvailablePhysicalFormats(streamList[streamIdx], &info.formatList);
73 
74       CCoreAudioStream::GetAvailableVirtualFormats(streamList[streamIdx], &info.formatListVirt);
75 
76       hasPassthroughOrDigitalFormats(info.formatList, info.hasPassthroughFormats, info.isDigital);
77 
78       info.isDigital |= isDigital;
79       info.deviceType = getDeviceType(info.hasPassthroughFormats, info.isDigital, info.numChannels, transportType);
80       m_caStreamInfos.push_back(info);
81     }
82     ret = true;
83   }
84 
85   return ret;
86 }
87 
88 // device/stream is digital if the transportType is digital
89 // or the devicename suggests that it is digital
isDigitalDevice() const90 bool AEDeviceEnumerationOSX::isDigitalDevice() const
91 {
92   bool isDigital = m_caDevice.IsDigital();
93 
94   // if it is no digital stream per definition
95   // check if the device name suggests that it is digital
96   // (some hackintonshs are not so smart in announcing correct
97   // ca devices ...
98   if (!isDigital)
99   {
100     std::string devNameLower = m_caDevice.GetName();
101     StringUtils::ToLower(devNameLower);
102     isDigital = devNameLower.find("digital") != std::string::npos;
103   }
104   return isDigital;
105 }
106 
hasPassthroughOrDigitalFormats(const StreamFormatList & formatList,bool & hasPassthroughFormats,bool & hasDigitalFormat) const107 void AEDeviceEnumerationOSX::hasPassthroughOrDigitalFormats(const StreamFormatList &formatList, bool &hasPassthroughFormats, bool &hasDigitalFormat) const
108 {
109   hasDigitalFormat = false;
110   hasPassthroughFormats = false;
111 
112   for(UInt32 formatIdx = 0; formatIdx < formatList.size(); formatIdx++)
113   {
114     const AudioStreamBasicDescription &desc = formatList[formatIdx].mFormat;
115     if (desc.mFormatID == kAudioFormatAC3 || desc.mFormatID == kAudioFormat60958AC3)
116     {
117       hasDigitalFormat = true;
118       hasPassthroughFormats = true;
119       break;
120     }
121     else
122     {
123       // PassthroughFormat 2ch 16bit LE 48000Hz / 192000Hz */
124       if (desc.mBitsPerChannel == 16 &&
125           !(desc.mFormatFlags & kAudioFormatFlagIsBigEndian) &&
126           desc.mChannelsPerFrame == 2 &&
127           (desc.mSampleRate == 48000 || desc.mSampleRate == 192000))
128       {
129         hasPassthroughFormats = true;
130       }
131     }
132   }
133 }
134 
getDeviceType(bool hasPassthroughFormats,bool isDigital,UInt32 numChannels,UInt32 transportType) const135 enum AEDeviceType AEDeviceEnumerationOSX::getDeviceType(bool hasPassthroughFormats,
136                                                         bool isDigital,
137                                                         UInt32 numChannels,
138                                                         UInt32 transportType) const
139 {
140   // flag indicating that the device name "sounds" like HDMI
141   bool hasHdmiName = m_deviceName.find("HDMI") != std::string::npos;
142   // flag indicating that the device name "sounds" like DisplayPort
143   bool hasDisplayPortName = m_deviceName.find("DisplayPort") != std::string::npos;
144   enum AEDeviceType deviceType = AE_DEVTYPE_PCM;//default
145 
146   // decide the type of the device based on the discovered information
147   // in the streams
148   // device defaults to PCM (see start of the while loop)
149   // it can be HDMI, DisplayPort or Optical
150   // for all of those types it needs to support
151   // passthroughformats and needs to be a digital port
152   if (hasPassthroughFormats && isDigital)
153   {
154     // if the max number of channels was more then 2
155     // this can be HDMI or DisplayPort or Thunderbolt
156     if (numChannels > 2)
157     {
158       // either the devicename suggests its HDMI
159       // or CA reported the transportType as HDMI
160       if (hasHdmiName || transportType == kIOAudioDeviceTransportTypeHdmi)
161         deviceType = AE_DEVTYPE_HDMI;
162 
163       // either the devicename suggests its DisplayPort
164       // or CA reported the transportType as DisplayPort or Thunderbolt
165       if (hasDisplayPortName ||
166           transportType == kIOAudioDeviceTransportTypeDisplayPort ||
167           transportType == kIOAudioDeviceTransportTypeThunderbolt)
168         deviceType = AE_DEVTYPE_DP;
169     }
170     else// treat all other digital passthrough devices as optical
171       deviceType = AE_DEVTYPE_IEC958;
172 
173     //treat all other digital devices as HDMI to let options open to the user
174     if (deviceType == AE_DEVTYPE_PCM)
175       deviceType = AE_DEVTYPE_HDMI;
176   }
177 
178   // devicename based overwrites from former code - maybe FIXME at some point when we
179   // are sure that the upper detection does its job in all[tm] use cases
180   if (hasHdmiName)
181     deviceType = AE_DEVTYPE_HDMI;
182   if (hasDisplayPortName)
183     deviceType = AE_DEVTYPE_DP;
184 
185   return deviceType;
186 }
187 
GetDeviceInfoList() const188 CADeviceList AEDeviceEnumerationOSX::GetDeviceInfoList() const
189 {
190   CADeviceList list;
191   UInt32 numDevices = m_caStreamInfos.size();
192   if (m_isPlanar)
193     numDevices = 1;
194 
195   for (UInt32 streamIdx = 0; streamIdx < numDevices; streamIdx++)
196   {
197     CAEDeviceInfo deviceInfo;
198     struct CADeviceInstance devInstance;
199     devInstance.audioDeviceId = m_deviceID;
200     devInstance.streamIndex = streamIdx;
201     devInstance.sourceId = INT_MAX;//don't set audio source by default
202 
203     deviceInfo.m_deviceName = getDeviceNameForStream(streamIdx);
204     deviceInfo.m_displayName = m_deviceName;
205     deviceInfo.m_displayNameExtra = getExtraDisplayNameForStream(streamIdx);
206     deviceInfo.m_channels = getChannelInfoForStream(streamIdx);
207     deviceInfo.m_sampleRates = getSampleRateListForStream(streamIdx);
208     deviceInfo.m_dataFormats = getFormatListForStream(streamIdx);
209     deviceInfo.m_streamTypes = getTypeListForStream(streamIdx);
210     deviceInfo.m_deviceType = m_caStreamInfos[streamIdx].deviceType;
211     deviceInfo.m_wantsIECPassthrough = true;
212 
213     CoreAudioDataSourceList sourceList;
214     // if this enumerator contains multiple devices with more then 1 source we add :source suffixes to the
215     // device names and overwrite the extraname with the source name
216     if (numDevices == 1 && m_caDevice.GetDataSources(&sourceList) && sourceList.size() > 1)
217     {
218       for (unsigned sourceIdx = 0; sourceIdx < sourceList.size(); sourceIdx++)
219       {
220         std::stringstream sourceIdxStr;
221         sourceIdxStr << sourceIdx;
222         deviceInfo.m_deviceName = getDeviceNameForStream(streamIdx) + ":source" + sourceIdxStr.str();
223         deviceInfo.m_displayNameExtra = m_caDevice.GetDataSourceName(sourceList[sourceIdx]);
224         devInstance.sourceId = sourceList[sourceIdx];
225         list.push_back(std::make_pair(devInstance, deviceInfo));
226       }
227     }
228     else
229       list.push_back(std::make_pair(devInstance, deviceInfo));
230   }
231   return list;
232 }
233 
GetNumPlanes() const234 unsigned int AEDeviceEnumerationOSX::GetNumPlanes() const
235 {
236   if (m_isPlanar)
237     return m_caStreamInfos.size();
238   else
239     return 1;//interleaved - one plane
240 }
241 
hasSampleRate(const AESampleRateList & list,const unsigned int samplerate) const242 bool AEDeviceEnumerationOSX::hasSampleRate(const AESampleRateList &list, const unsigned int samplerate) const
243 {
244   for (size_t i = 0; i < list.size(); ++i)
245   {
246     if (list[i] == samplerate)
247       return true;
248   }
249   return false;
250 }
251 
hasDataFormat(const AEDataFormatList & list,const enum AEDataFormat format) const252 bool AEDeviceEnumerationOSX::hasDataFormat(const AEDataFormatList &list, const enum AEDataFormat format) const
253 {
254   for (size_t i = 0; i < list.size(); ++i)
255   {
256     if (list[i] == format)
257       return true;
258   }
259   return false;
260 }
261 
hasDataType(const AEDataTypeList & list,CAEStreamInfo::DataType type) const262 bool AEDeviceEnumerationOSX::hasDataType(const AEDataTypeList &list, CAEStreamInfo::DataType type) const
263 {
264   for (size_t i = 0; i < list.size(); ++i)
265   {
266     if (list[i] == type)
267       return true;
268   }
269   return false;
270 }
271 
getFormatListForStream(UInt32 streamIdx) const272 AEDataFormatList AEDeviceEnumerationOSX::getFormatListForStream(UInt32 streamIdx) const
273 {
274   AEDataFormatList returnDataFormatList;
275   if (streamIdx >= m_caStreamInfos.size())
276     return returnDataFormatList;
277 
278   // check the streams
279   const StreamFormatList &formatList = m_caStreamInfos[streamIdx].formatList;
280   for(UInt32 formatIdx = 0; formatIdx < formatList.size(); formatIdx++)
281   {
282     AudioStreamBasicDescription formatDesc = formatList[formatIdx].mFormat;
283     AEDataFormatList aeFormatList = caFormatToAE(formatDesc, m_caStreamInfos[streamIdx].isDigital);
284     for (UInt32 formatListIdx = 0; formatListIdx < aeFormatList.size(); formatListIdx++)
285     {
286       if (!hasDataFormat(returnDataFormatList, aeFormatList[formatListIdx]))
287         returnDataFormatList.push_back(aeFormatList[formatListIdx]);
288     }
289   }
290   return returnDataFormatList;
291 }
292 
getTypeListForStream(UInt32 streamIdx) const293 AEDataTypeList AEDeviceEnumerationOSX::getTypeListForStream(UInt32 streamIdx) const
294 {
295   AEDataTypeList returnDataTypeList;
296   if (streamIdx >= m_caStreamInfos.size())
297     return returnDataTypeList;
298 
299   // check the streams
300   const StreamFormatList &formatList = m_caStreamInfos[streamIdx].formatList;
301   for(UInt32 formatIdx = 0; formatIdx < formatList.size(); formatIdx++)
302   {
303     AudioStreamBasicDescription formatDesc = formatList[formatIdx].mFormat;
304     AEDataTypeList aeTypeList = caFormatToAEType(formatDesc, m_caStreamInfos[streamIdx].isDigital);
305     for (UInt32 typeListIdx = 0; typeListIdx < aeTypeList.size(); typeListIdx++)
306     {
307       if (!hasDataType(returnDataTypeList, aeTypeList[typeListIdx]))
308         returnDataTypeList.push_back(aeTypeList[typeListIdx]);
309     }
310   }
311   return returnDataTypeList;
312 }
313 
getChannelInfoForStream(UInt32 streamIdx) const314 CAEChannelInfo AEDeviceEnumerationOSX::getChannelInfoForStream(UInt32 streamIdx) const
315 {
316   CAEChannelInfo channelInfo;
317   if (streamIdx >= m_caStreamInfos.size())
318     return channelInfo;
319 
320   if (m_isPlanar)
321   {
322     //get channel map to match the devices channel layout as set in audio-midi-setup
323     GetAEChannelMap(channelInfo, GetNumPlanes());
324   }
325   else
326   {
327     //get channel map to match the devices channel layout as set in audio-midi-setup
328     GetAEChannelMap(channelInfo, m_caDevice.GetNumChannelsOfStream(streamIdx));
329   }
330   return channelInfo;
331 }
332 
caFormatToAE(const AudioStreamBasicDescription & formatDesc,bool isDigital) const333 AEDataFormatList AEDeviceEnumerationOSX::caFormatToAE(const AudioStreamBasicDescription &formatDesc, bool isDigital) const
334 {
335   AEDataFormatList formatList;
336   // add stream format info
337   switch (formatDesc.mFormatID)
338   {
339     case kAudioFormatAC3:
340     case kAudioFormat60958AC3:
341       break;
342     default:
343       switch(formatDesc.mBitsPerChannel)
344     {
345       case 16:
346         if (formatDesc.mFormatFlags & kAudioFormatFlagIsBigEndian)
347         {
348           formatList.push_back(AE_FMT_S16BE);
349         }
350         else
351         {
352           formatList.push_back(AE_FMT_S16LE);
353         }
354         formatList.push_back(AE_FMT_S16NE); // if we support either LE or BE - we always support NE too...
355         break;
356       case 24:
357         if (formatDesc.mFormatFlags & kAudioFormatFlagIsBigEndian)
358         {
359           formatList.push_back(AE_FMT_S24BE3);
360         }
361         else
362         {
363           formatList.push_back(AE_FMT_S24LE3);
364         }
365         formatList.push_back(AE_FMT_S24NE3); // if we support either LE or BE - we always support NE too...
366         break;
367       case 32:
368         if (formatDesc.mFormatFlags & kAudioFormatFlagIsFloat)
369         {
370           formatList.push_back(AE_FMT_FLOAT);
371         }
372         else
373         {
374           if (formatDesc.mFormatFlags & kAudioFormatFlagIsBigEndian)
375           {
376             formatList.push_back(AE_FMT_S32BE);
377           }
378           else
379           {
380             formatList.push_back(AE_FMT_S32LE);
381           }
382           formatList.push_back(AE_FMT_S32NE); // if we support either LE or BE - we always support NE too...
383         }
384         break;
385     }
386       break;
387   }
388   return formatList;
389 }
390 
caFormatToAEType(const AudioStreamBasicDescription & formatDesc,bool isDigital) const391 AEDataTypeList AEDeviceEnumerationOSX::caFormatToAEType(const AudioStreamBasicDescription &formatDesc, bool isDigital) const
392 {
393   AEDataTypeList typeList;
394   // add stream format info
395   switch (formatDesc.mFormatID)
396   {
397     case kAudioFormatAC3:
398     case kAudioFormat60958AC3:
399       typeList.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
400       typeList.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
401       typeList.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
402       typeList.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
403       typeList.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
404       break;
405     default:
406       switch(formatDesc.mBitsPerChannel)
407     {
408       case 16:
409         if (formatDesc.mFormatFlags & kAudioFormatFlagIsBigEndian)
410           ;
411         else
412         {
413           /* Passthrough is possible with a 2ch digital output */
414           if (formatDesc.mChannelsPerFrame == 2 && isDigital)
415           {
416             if (formatDesc.mSampleRate == 48000)
417             {
418               typeList.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
419               typeList.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
420               typeList.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
421               typeList.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
422               typeList.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
423             }
424             else if (formatDesc.mSampleRate == 192000)
425             {
426               typeList.push_back(CAEStreamInfo::STREAM_TYPE_EAC3);
427             }
428           }
429         }
430         break;
431     }
432       break;
433   }
434   return typeList;
435 }
436 
getSampleRateListForStream(UInt32 streamIdx) const437 AESampleRateList AEDeviceEnumerationOSX::getSampleRateListForStream(UInt32 streamIdx) const
438 {
439   AESampleRateList returnSampleRateList;
440   if (streamIdx >= m_caStreamInfos.size())
441     return returnSampleRateList;
442 
443   // check the streams
444   const StreamFormatList &formatList = m_caStreamInfos[streamIdx].formatList;
445   for(UInt32 formatIdx = 0; formatIdx < formatList.size(); formatIdx++)
446   {
447     AudioStreamBasicDescription formatDesc = formatList[formatIdx].mFormat;
448     // add sample rate info
449     // for devices which return kAudioStreamAnyRatee
450     // we add 44.1khz and 48khz - user can use
451     // the "fixed" audio config to force one of them
452     if (formatDesc.mSampleRate == kAudioStreamAnyRate)
453     {
454       CLog::Log(LOGINFO, "%s reported samplerate is kAudioStreamAnyRate adding 44.1khz and 48khz", __FUNCTION__);
455       formatDesc.mSampleRate = 44100;
456       if (!hasSampleRate(returnSampleRateList, formatDesc.mSampleRate))
457         returnSampleRateList.push_back(formatDesc.mSampleRate);
458       formatDesc.mSampleRate = 48000;
459     }
460 
461     if (!hasSampleRate(returnSampleRateList, formatDesc.mSampleRate))
462       returnSampleRateList.push_back(formatDesc.mSampleRate);
463   }
464 
465   return returnSampleRateList;
466 }
467 
getDeviceNameForStream(UInt32 streamIdx) const468 std::string AEDeviceEnumerationOSX::getDeviceNameForStream(UInt32 streamIdx) const
469 {
470   std::stringstream deviceIdStr;
471   deviceIdStr << m_deviceID;
472 
473   // device name is the devicename from coreaudio + the device id to make it unique
474   // for example devicename could be DisplayPort and couldn't be distinguished if multiple
475   // DisplayPort devices are available
476   std::string deviceName = m_deviceName + "-" + deviceIdStr.str();
477   if (m_isPlanar)// planar devices are saved as :stream0
478   {
479     deviceName += ":stream0";
480   }
481   else
482   {
483     std::stringstream streamIdxStr;
484     streamIdxStr << streamIdx;
485     deviceName += ":stream" + streamIdxStr.str();
486   }
487   return deviceName;
488 }
489 
getExtraDisplayNameForStream(UInt32 streamIdx) const490 std::string AEDeviceEnumerationOSX::getExtraDisplayNameForStream(UInt32 streamIdx) const
491 {
492   // for distinguishing the streams inside one device we add
493   // the corresponding channels to the extraDisplayName
494   // planar devices are ignored here as their streams are
495   // the channels not different subdevices
496   if (m_caStreamInfos.size() > 1 && !m_isPlanar)
497   {
498     // build a string with the channels for this stream
499     UInt32 startChannel = 0;
500     CCoreAudioStream::GetStartingChannelInDevice(m_caStreamInfos[streamIdx].streamID, startChannel);
501     UInt32 numChannels = m_caDevice.GetNumChannelsOfStream(streamIdx);
502     std::stringstream extraName;
503     extraName << "Channels ";
504     extraName << startChannel;
505     extraName << " - ";
506     extraName << startChannel + numChannels - 1;
507     CLog::Log(LOGINFO,
508               "%s adding stream %d as pseudo device with start channel %d and %d channels total",
509               __FUNCTION__, (unsigned int)streamIdx, (unsigned int)startChannel,
510               (unsigned int)numChannels);
511     return extraName.str();
512   }
513 
514   //for all other devices use the datasource as extraname
515   return m_caDevice.GetCurrentDataSourceName();
516 }
517 
scoreSampleRate(Float64 destinationRate,unsigned int sourceRate) const518 float AEDeviceEnumerationOSX::scoreSampleRate(Float64 destinationRate, unsigned int sourceRate) const
519 {
520   float score = 0;
521   double intPortion;
522   double fracPortion = modf(destinationRate / sourceRate, &intPortion);
523 
524   score += (1 - fracPortion) * 1000;      // prefer sample rates that are multiples of the source sample rate
525   score += (intPortion == 1.0) ? 500 : 0;   // prefer exact matches over other multiples
526   score += (intPortion > 1 && intPortion < 100) ? (100 - intPortion) / 100 * 100 : 0; // prefer smaller multiples otherwise
527 
528   return score;
529 }
530 
ScoreFormat(const AudioStreamBasicDescription & formatDesc,const AEAudioFormat & format) const531 float AEDeviceEnumerationOSX::ScoreFormat(const AudioStreamBasicDescription &formatDesc, const AEAudioFormat &format) const
532 {
533   float score = 0;
534 
535   if (format.m_streamInfo.m_type != CAEStreamInfo::STREAM_TYPE_NULL)
536   {
537     if (formatDesc.mBitsPerChannel != 16)
538       return score;
539     if (formatDesc.mSampleRate !=  format.m_sampleRate)
540       return score;
541     if (formatDesc.mChannelsPerFrame != format.m_channelLayout.Count())
542       return score;
543     score += 5;
544 
545     if (formatDesc.mFormatID == kAudioFormat60958AC3 ||
546         formatDesc.mFormatID == 'IAC3' ||
547         formatDesc.mFormatID == kAudioFormatAC3)
548     {
549       score += 1;
550     }
551   }
552   // non-passthrough, whatever works is fine
553   else if (formatDesc.mFormatID == kAudioFormatLinearPCM)
554   {
555     score += scoreSampleRate(formatDesc.mSampleRate, format.m_sampleRate);
556 
557     if (formatDesc.mChannelsPerFrame == format.m_channelLayout.Count())
558       score += 5;
559     else if (formatDesc.mChannelsPerFrame > format.m_channelLayout.Count())
560       score += 1;
561     if (format.m_dataFormat == AE_FMT_FLOAT || format.m_dataFormat == AE_FMT_FLOATP)
562     { // for float, prefer the highest bitdepth we have
563       if (formatDesc.mBitsPerChannel >= 16)
564         score += (formatDesc.mBitsPerChannel / 8);
565     }
566     else
567     {
568       if (formatDesc.mBitsPerChannel == CAEUtil::DataFormatToBits(format.m_dataFormat))
569         score += 5;
570       else if (formatDesc.mBitsPerChannel > CAEUtil::DataFormatToBits(format.m_dataFormat))
571         score += 1;
572     }
573   }
574 
575   return score;
576 }
577 
FindSuitableFormatForStream(UInt32 & streamIdx,const AEAudioFormat & format,bool virt,AudioStreamBasicDescription & outputFormat,AudioStreamID & outputStream) const578 bool AEDeviceEnumerationOSX::FindSuitableFormatForStream(UInt32 &streamIdx, const AEAudioFormat &format, bool virt, AudioStreamBasicDescription &outputFormat, AudioStreamID &outputStream) const
579 {
580   CLog::Log(LOGDEBUG, "%s: Finding stream for format %s", __FUNCTION__, CAEUtil::DataFormatToStr(format.m_dataFormat));
581 
582   bool                        formatFound  = false;
583   float                       outputScore  = 0;
584   UInt32                      streamIdxStart = streamIdx;
585   UInt32                      streamIdxEnd   = streamIdx + 1;
586   UInt32                      streamIdxCurrent = streamIdx;
587 
588   if (streamIdx == INT_MAX)
589   {
590     streamIdxStart = 0;
591     streamIdxEnd = m_caStreamInfos.size();
592     streamIdxCurrent = 0;
593   }
594 
595   if (streamIdxCurrent >= m_caStreamInfos.size())
596     return false;
597 
598   // loop over all streams or over given streams (depends on initial value of param streamIdx
599   for(streamIdxCurrent = streamIdxStart; streamIdxCurrent < streamIdxEnd; streamIdxCurrent++)
600   {
601 
602     // Probe physical or virtual  formats
603     const StreamFormatList *formats = &m_caStreamInfos[streamIdxCurrent].formatList;
604     if (virt)
605       formats = &m_caStreamInfos[streamIdxCurrent].formatListVirt;
606 
607     for (StreamFormatList::const_iterator j = formats->begin(); j != formats->end(); ++j)
608     {
609       AudioStreamBasicDescription formatDesc = j->mFormat;
610 
611       // for devices with kAudioStreamAnyRate
612       // assume that the user uses a fixed config
613       // and knows what he is doing - so we use
614       // the requested samplerate here
615       if (formatDesc.mSampleRate == kAudioStreamAnyRate)
616         formatDesc.mSampleRate = format.m_sampleRate;
617 
618       float score = ScoreFormat(formatDesc, format);
619 
620       std::string formatString;
621       if (!virt)
622         CLog::Log(LOGDEBUG, "%s: Physical Format: %s rated %f", __FUNCTION__, StreamDescriptionToString(formatDesc, formatString), score);
623       else
624         CLog::Log(LOGDEBUG, "%s: Virtual Format: %s rated %f", __FUNCTION__, StreamDescriptionToString(formatDesc, formatString), score);
625 
626       if (score > outputScore)
627       {
628         outputScore  = score;
629         outputFormat = formatDesc;
630         outputStream = m_caStreamInfos[streamIdxCurrent].streamID;
631         streamIdx = streamIdxCurrent;// return the streamIdx for the caller
632         formatFound = true;
633       }
634     }
635   }
636 
637   if (m_isPlanar)
638     outputFormat.mChannelsPerFrame = std::min((size_t)format.m_channelLayout.Count(), m_caStreamInfos.size());
639 
640   return formatFound;
641 }
642 
643 // map coraudio channel labels to activeae channel labels
caChannelToAEChannel(const AudioChannelLabel & CAChannelLabel) const644 enum AEChannel AEDeviceEnumerationOSX::caChannelToAEChannel(const AudioChannelLabel &CAChannelLabel) const
645 {
646   enum AEChannel ret = AE_CH_NULL;
647   static unsigned int unknownChannel = AE_CH_UNKNOWN1;
648   switch(CAChannelLabel)
649   {
650     case kAudioChannelLabel_Left:
651       ret = AE_CH_FL;
652       break;
653     case kAudioChannelLabel_Right:
654       ret = AE_CH_FR;
655       break;
656     case kAudioChannelLabel_Center:
657       ret = AE_CH_FC;
658       break;
659     case kAudioChannelLabel_LFEScreen:
660       ret = AE_CH_LFE;
661       break;
662     case kAudioChannelLabel_LeftSurroundDirect:
663       ret = AE_CH_SL;
664       break;
665     case kAudioChannelLabel_RightSurroundDirect:
666       ret = AE_CH_SR;
667       break;
668     case kAudioChannelLabel_LeftCenter:
669       ret = AE_CH_FLOC;
670       break;
671     case kAudioChannelLabel_RightCenter:
672       ret = AE_CH_FROC;
673       break;
674     case kAudioChannelLabel_CenterSurround:
675       ret = AE_CH_TC;
676       break;
677     case kAudioChannelLabel_LeftSurround:
678       ret = AE_CH_SL;
679       break;
680     case kAudioChannelLabel_RightSurround:
681       ret = AE_CH_SR;
682       break;
683     case kAudioChannelLabel_VerticalHeightLeft:
684       ret = AE_CH_TFL;
685       break;
686     case kAudioChannelLabel_VerticalHeightRight:
687       ret = AE_CH_TFR;
688       break;
689     case kAudioChannelLabel_VerticalHeightCenter:
690       ret = AE_CH_TFC;
691       break;
692     case kAudioChannelLabel_TopCenterSurround:
693       ret = AE_CH_TC;
694       break;
695     case kAudioChannelLabel_TopBackLeft:
696       ret = AE_CH_TBL;
697       break;
698     case kAudioChannelLabel_TopBackRight:
699       ret = AE_CH_TBR;
700       break;
701     case kAudioChannelLabel_TopBackCenter:
702       ret = AE_CH_TBC;
703       break;
704     case kAudioChannelLabel_RearSurroundLeft:
705       ret = AE_CH_BL;
706       break;
707     case kAudioChannelLabel_RearSurroundRight:
708       ret = AE_CH_BR;
709       break;
710     case kAudioChannelLabel_LeftWide:
711       ret = AE_CH_BLOC;
712       break;
713     case kAudioChannelLabel_RightWide:
714       ret = AE_CH_BROC;
715       break;
716     case kAudioChannelLabel_LFE2:
717       ret = AE_CH_LFE;
718       break;
719     case kAudioChannelLabel_LeftTotal:
720       ret = AE_CH_FL;
721       break;
722     case kAudioChannelLabel_RightTotal:
723       ret = AE_CH_FR;
724       break;
725     case kAudioChannelLabel_HearingImpaired:
726       ret = AE_CH_FC;
727       break;
728     case kAudioChannelLabel_Narration:
729       ret = AE_CH_FC;
730       break;
731     case kAudioChannelLabel_Mono:
732       ret = AE_CH_FC;
733       break;
734     case kAudioChannelLabel_DialogCentricMix:
735       ret = AE_CH_FC;
736       break;
737     case kAudioChannelLabel_CenterSurroundDirect:
738       ret = AE_CH_TC;
739       break;
740     case kAudioChannelLabel_Haptic:
741       ret = AE_CH_FC;
742       break;
743     default:
744       ret = (enum AEChannel)unknownChannel++;
745   }
746   if (unknownChannel == AE_CH_MAX)
747     unknownChannel = AE_CH_UNKNOWN1;
748 
749   return ret;
750 }
751 
752 //Note: in multichannel mode CA will either pull 2 channels of data (stereo) or 6/8 channels of data
753 //(every speaker setup with more then 2 speakers). The difference between the number of real speakers
754 //and 6/8 channels needs to be padded with unknown channels so that the sample size fits 6/8 channels
755 //
756 //device [in] - the device whose channel layout should be used
757 //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
758 //channelsPerFrame [in] - the number of channels this device is configured to (e.x. 2 or 6/8)
GetAEChannelMap(CAEChannelInfo & channelMap,unsigned int channelsPerFrame) const759 void AEDeviceEnumerationOSX::GetAEChannelMap(CAEChannelInfo &channelMap, unsigned int channelsPerFrame) const
760 {
761   CCoreAudioChannelLayout calayout;
762   bool logMapping = channelMap.Count() > 0; // only log if the engine requests a layout during init
763   bool mapAvailable = false;
764   unsigned int numberChannelsInDeviceLayout = CA_MAX_CHANNELS; // default number of channels from CAChannelMap
765   AudioChannelLayout *layout = NULL;
766 
767   // try to fetch either the multichannel or the stereo channel layout from the device
768   if (channelsPerFrame == 2 || channelMap.Count() == 2)
769     mapAvailable = m_caDevice.GetPreferredChannelLayoutForStereo(calayout);
770   else
771     mapAvailable = m_caDevice.GetPreferredChannelLayout(calayout);
772 
773   // if a map was fetched - check if it is usable
774   if (mapAvailable)
775   {
776     layout = calayout;
777     if (layout == NULL || layout->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions)
778       mapAvailable = false;// wrong map format
779     else
780       numberChannelsInDeviceLayout = layout->mNumberChannelDescriptions;
781   }
782 
783   // start the mapping action
784   // the number of channels to be added to the outgoing channelmap
785   // this is CA_MAX_CHANNELS at max and might be lower for some output devices (channelsPerFrame)
786   unsigned int numChannelsToMap = std::min((unsigned int)CA_MAX_CHANNELS, (unsigned int)channelsPerFrame);
787 
788   // if there was a map fetched we force the number of
789   // channels to map to channelsPerFrame (this allows mapping
790   // of more then CA_MAX_CHANNELS if needed)
791   if (mapAvailable)
792     numChannelsToMap = channelsPerFrame;
793 
794   std::string layoutStr;
795 
796   if (logMapping)
797   {
798     CLog::Log(LOGDEBUG, "%s Engine requests layout %s", __FUNCTION__, ((std::string)channelMap).c_str());
799 
800     if (mapAvailable)
801       CLog::Log(LOGDEBUG, "%s trying to map to %s layout: %s", __FUNCTION__, channelsPerFrame == 2 ? "stereo" : "multichannel", calayout.ChannelLayoutToString(*layout, layoutStr));
802     else
803       CLog::Log(LOGDEBUG, "%s no map available - using static multichannel map layout", __FUNCTION__);
804   }
805 
806   channelMap.Reset();// start with an empty map
807 
808   for (unsigned int channel = 0; channel < numChannelsToMap; channel++)
809   {
810     // we only try to map channels which are defined in the device layout
811     enum AEChannel currentChannel;
812     if (channel < numberChannelsInDeviceLayout)
813     {
814       // get the channel from the fetched map
815       if (mapAvailable)
816         currentChannel = caChannelToAEChannel(layout->mChannelDescriptions[channel].mChannelLabel);
817       else// get the channel from the default map
818         currentChannel = CAChannelMap[channel];
819 
820     }
821     else// fill with unknown channels
822       currentChannel = caChannelToAEChannel(kAudioChannelLabel_Unknown);
823 
824     if(!channelMap.HasChannel(currentChannel))// only add if not already added
825       channelMap += currentChannel;
826   }
827 
828   if (logMapping)
829     CLog::Log(LOGDEBUG, "%s mapped channels to layout %s", __FUNCTION__, ((std::string)channelMap).c_str());
830 }
831