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