MythTV master
audiooutputca.cpp
Go to the documentation of this file.
1/*****************************************************************************
2 * = NAME
3 * audiooutputca.cpp
4 *
5 * = DESCRIPTION
6 * Core Audio glue for Mac OS X.
7 *
8 * = REVISION
9 * $Id$
10 *
11 * = AUTHORS
12 * Jeremiah Morris, Andrew Kimpton, Nigel Pearson, Jean-Yves Avenard
13 *****************************************************************************/
14
15#include <array>
16#include <thread>
17#include <vector>
18
19#include <CoreServices/CoreServices.h>
20#include <CoreAudio/CoreAudio.h>
21#include <AudioUnit/AudioUnit.h>
22#include <AudioToolbox/AudioFormat.h>
23#include <AvailabilityMacros.h>
24
27#include "audiooutputca.h"
28
29// kAudioObjectPropertyElementMaster was deprecated in OS_X 12
30// kAudioObjectPropertyElementMain defaults to a main/master port value of 0
31static constexpr int8_t kMythAudioObjectPropertyElementMain { 0 };
32
33#define LOC QString("CoreAudio: ")
34
35#define CHANNELS_MIN 1
36#define CHANNELS_MAX 8
37
38using AudioStreamIDVec = std::vector<AudioStreamID>;
39using AudioStreamRangedVec = std::vector<AudioStreamRangedDescription>;
40using AudioValueRangeVec = std::vector<AudioValueRange>;
41using RatesVec = std::vector<int>;
42using ChannelsArr = std::array<bool,CHANNELS_MAX>;
43
44#define OSS_STATUS(x) UInt32ToFourCC((UInt32*)&(x))
45char* UInt32ToFourCC(const UInt32* pVal)
46{
47 UInt32 inVal = *pVal;
48 char* pIn = (char*)&inVal;
49 static char fourCC[5];
50 fourCC[4] = 0;
51 fourCC[3] = pIn[0];
52 fourCC[2] = pIn[1];
53 fourCC[1] = pIn[2];
54 fourCC[0] = pIn[3];
55 return fourCC;
56}
57
58QString StreamDescriptionToString(AudioStreamBasicDescription desc)
59{
60 UInt32 formatId = desc.mFormatID;
61 char* fourCC = UInt32ToFourCC(&formatId);
62 QString str;
63
64 switch (desc.mFormatID)
65 {
66 case kAudioFormatLinearPCM:
67 str = QString("[%1] %2%3 Channel %4-bit %5 %6 (%7Hz) %8 Channels")
68 .arg(fourCC)
69 .arg((desc.mFormatFlags & kAudioFormatFlagIsNonMixable) ? "" : "Mixable ")
70 .arg(desc.mChannelsPerFrame)
71 .arg(desc.mBitsPerChannel)
72 .arg((desc.mFormatFlags & kAudioFormatFlagIsFloat) ? "Floating Point" : "Signed Integer")
73 .arg((desc.mFormatFlags & kAudioFormatFlagIsBigEndian) ? "BE" : "LE")
74 .arg((UInt32)desc.mSampleRate)
75 .arg(desc.mChannelsPerFrame);
76 break;
77 case kAudioFormatAC3:
78 str = QString("[%1] AC-3/DTS (%2Hz) %3 Channels")
79 .arg(fourCC)
80 .arg((UInt32)desc.mSampleRate)
81 .arg(desc.mChannelsPerFrame);
82 break;
83 case kAudioFormat60958AC3:
84 str = QString("[%1] AC-3/DTS for S/PDIF %2 (%3Hz) %4 Channels")
85 .arg(fourCC)
86 .arg((desc.mFormatFlags & kAudioFormatFlagIsBigEndian) ? "BE" : "LE")
87 .arg((UInt32)desc.mSampleRate)
88 .arg(desc.mChannelsPerFrame);
89 break;
90 default:
91 str = QString("[%1]").arg(fourCC);
92 break;
93 }
94 return str;
95}
96
102public:
103 explicit CoreAudioData(AudioOutputCA *parent);
104 CoreAudioData(AudioOutputCA *parent, AudioDeviceID deviceID);
105 CoreAudioData(AudioOutputCA *parent, QString deviceName);
106
107 static AudioDeviceID GetDefaultOutputDevice();
109 QString *GetName();
110 static AudioDeviceID GetDeviceWithName(const QString& deviceName);
111
113 int OpenAnalog();
114 void CloseAnalog();
115 bool OpenSPDIF ();
116 void CloseSPDIF();
117
118 static void SetAutoHogMode(bool enable);
119 static bool GetAutoHogMode();
120 static pid_t GetHogStatus();
121 bool SetHogStatus(bool hog);
122 bool SetMixingSupport(bool mix);
123 bool GetMixingSupport();
124
125 bool FindAC3Stream();
126 static void ResetAudioDevices();
127 static void ResetStream(AudioStreamID s);
128 static RatesVec RatesList(AudioDeviceID d);
129 bool ChannelsList(AudioDeviceID d, bool passthru, ChannelsArr& chans);
130
131 static AudioStreamIDVec StreamsList(AudioDeviceID d);
132 static AudioStreamRangedVec FormatsList(AudioStreamID s);
133
134 static int AudioStreamChangeFormat(AudioStreamID s,
135 AudioStreamBasicDescription format);
136
137 AudioOutputCA *mCA {nullptr}; // We could subclass, but this ends up tidier
138
139 // Analog output specific
140 AudioUnit mOutputUnit {nullptr};
141
142 // SPDIF mode specific
143 bool mDigitalInUse {false}; // Is the digital (SPDIF) output in use?
144 pid_t mHog {-1};
146 AudioDeviceID mDeviceID {0};
147 AudioStreamID mStreamID {}; // StreamID that has a cac3 streamformat
148 int mStreamIndex {-1}; // Index of mStreamID in an AudioBufferList
149 UInt32 mBytesPerPacket {UINT32_MAX};
150 AudioStreamBasicDescription mFormatOrig {}; // The original format the stream
151 AudioStreamBasicDescription mFormatNew {}; // The format we changed the stream to
152 bool mRevertFormat {false}; // Do we need to revert the stream format?
153 bool mIoProc {false};
154 bool mInitialized {false};
155 bool mStarted {false};
156 bool mWasDigital {false};
157 AudioDeviceIOProcID mIoProcID {};
158};
159
160// These callbacks communicate with Core Audio.
161static OSStatus RenderCallbackAnalog(void *inRefCon,
162 AudioUnitRenderActionFlags *ioActionFlags,
163 const AudioTimeStamp *inTimeStamp,
164 UInt32 inBusNumber,
165 UInt32 inNumberFrames,
166 AudioBufferList *ioData);
167static OSStatus RenderCallbackSPDIF(AudioDeviceID inDevice,
168 const AudioTimeStamp *inNow,
169 const void *inInputData,
170 const AudioTimeStamp *inInputTime,
171 AudioBufferList *outOutputData,
172 const AudioTimeStamp *inOutputTime,
173 void *inRefCon);
174
180AudioOutputBase(settings)
181{
182 m_mainDevice.remove(0, 10);
183 LOG(VB_AUDIO, LOG_INFO, LOC + QString("AudioOutputCA::AudioOutputCA searching %1").arg(m_mainDevice));
184 d = new CoreAudioData(this, m_mainDevice);
185
186 InitSettings(settings);
187 if (settings.m_init)
188 Reconfigure(settings);
189}
190
192{
193 KillAudio();
194
195 delete d;
196}
197
199{
200 auto *settings = new AudioOutputSettings();
201
202 // Seek hardware sample rate available
204
205 if (rates.empty())
206 {
207 // Error retrieving rates, assume 48kHz
208 settings->AddSupportedRate(48000);
209 }
210 else
211 {
212 while (int rate = settings->GetNextRate())
213 {
214 for (auto entry : rates)
215 {
216 if (entry != rate)
217 continue;
218 settings->AddSupportedRate(entry);
219 }
220 }
221 }
222
223 // Supported format: 16 bits audio or float
224 settings->AddSupportedFormat(FORMAT_S16);
225 settings->AddSupportedFormat(FORMAT_FLT);
226
228 if (!d->ChannelsList(d->mDeviceID, digital, channels))
229 {
230 // Error retrieving list of supported channels, assume stereo only
231 settings->AddSupportedChannels(2);
232 }
233 else
234 {
235 for (int i = CHANNELS_MIN; i <= CHANNELS_MAX; i++)
236 {
237 if (channels[i-1])
238 {
239 LOG(VB_AUDIO, LOG_INFO, QString("AudioOutputCA::Support %1 channels").arg(i));
240 // In case 8 channels are supported but not 6, fake 6
241 if (i == 8 && !channels[6-1])
242 settings->AddSupportedChannels(6);
243 settings->AddSupportedChannels(i);
244 }
245 }
246 }
247
248 if (d->FindAC3Stream())
249 {
250 settings->setPassthrough(1); // yes passthrough
251 }
252 return settings;
253}
254
256{
257 bool deviceOpened = false;
258
259 if (d->mWasDigital)
260 {
261 }
262 LOG(VB_AUDIO, LOG_INFO, "AudioOutputCA::OpenDevice: Entering");
263 if (m_passthru || m_enc)
264 {
265 LOG(VB_AUDIO, LOG_INFO, "AudioOutputCA::OpenDevice() Trying Digital.");
266 if (!(deviceOpened = d->OpenSPDIF()))
267 d->CloseSPDIF();
268 }
269
270 if (!deviceOpened)
271 {
272 LOG(VB_AUDIO, LOG_INFO, "AudioOutputCA::OpenDevice() Trying Analog.");
273 int result = -1;
274 //for (int i=0; result < 0 && i < 10; i++)
275 {
276 result = d->OpenAnalog();
277 LOG(VB_AUDIO, LOG_INFO, QString("AudioOutputCA::OpenDevice: OpenAnalog = %1").arg(result));
278 if (result < 0)
279 {
280 d->CloseAnalog();
281 std::this_thread::sleep_for(1s);
282 }
283 }
284 deviceOpened = (result > 0);
285 }
286
287 if (!deviceOpened)
288 {
289 LOG(VB_GENERAL, LOG_ERR, "AudioOutputCA Error: Couldn't open any audio device!");
290 d->CloseAnalog();
291 return false;
292 }
293
295 {
296 QString controlLabel = gCoreContext->GetSetting("MixerControl", "PCM");
297 controlLabel += "MixerVolume";
298 SetCurrentVolume(gCoreContext->GetNumSetting(controlLabel, 80));
299 }
300
301 return true;
302}
303
305{
306 LOG(VB_AUDIO, LOG_INFO, LOC + QString("CloseDevice [%1]: Entering")
307 .arg(d->mDigitalInUse ? "SPDIF" : "Analog"));
308 if (d->mDigitalInUse)
309 d->CloseSPDIF();
310 else
311 d->CloseAnalog();
312}
313
314template <class AudioDataType>
315static inline void _ReorderSmpteToCA(AudioDataType *buf, uint frames)
316{
317 AudioDataType tmpLS;
318 AudioDataType tmpRS;
319 AudioDataType tmpRLs;
320 AudioDataType tmpRRs;
321 AudioDataType *buf2;
322 for (uint i = 0; i < frames; i++)
323 {
324 buf = buf2 = buf + 4;
325 tmpRLs = *buf++;
326 tmpRRs = *buf++;
327 tmpLS = *buf++;
328 tmpRS = *buf++;
329
330 *buf2++ = tmpLS;
331 *buf2++ = tmpRS;
332 *buf2++ = tmpRLs;
333 *buf2++ = tmpRRs;
334 }
335}
336
337static inline void ReorderSmpteToCA(void *buf, uint frames, AudioFormat format)
338{
340 {
341 case 8: _ReorderSmpteToCA((uchar *)buf, frames); break;
342 case 16: _ReorderSmpteToCA((short *)buf, frames); break;
343 default: _ReorderSmpteToCA((int *)buf, frames); break;
344 }
345}
346
348bool AudioOutputCA::RenderAudio(unsigned char *aubuf,
349 int size, unsigned long long timestamp)
350{
352 {
353 m_actuallyPaused = true;
354 return false;
355 }
356
357 /* This callback is called when the sound system requests
358 data. We don't want to block here, because that would
359 just cause dropouts anyway, so we always return whatever
360 data is available. If we haven't received enough, either
361 because we've finished playing or we have a buffer
362 underrun, we play silence to fill the unused space. */
363
364 int written_size = GetAudioData(aubuf, size, false);
365 if (written_size && (size > written_size))
366 {
367 // play silence on buffer underrun
368 memset(aubuf + written_size, 0, size - written_size);
369 }
370
371 //Audio received is in SMPTE channel order, reorder to CA unless passthru
372 if (!m_passthru && m_channels == 8)
373 {
375 }
376
377 /* update audiotime (m_bufferedBytes is read by GetBufferedOnSoundcard) */
378 UInt64 nanos = AudioConvertHostTimeToNanos(timestamp -
379 AudioGetCurrentHostTime());
380 m_bufferedBytes = (int)((nanos / 1000000000.0) * // secs
381 (m_effDsp / 100.0) * // frames/sec
382 m_outputBytesPerFrame); // bytes/frame
383
384 return (written_size > 0);
385}
386
387// unneeded and unused in CA
388void AudioOutputCA::WriteAudio([[maybe_unused]] unsigned char *aubuf,
389 [[maybe_unused]] int size)
390{
391}
392
394{
395 return m_bufferedBytes;
396}
397
401std::chrono::milliseconds AudioOutputCA::GetAudiotime(void)
402{
403 std::chrono::milliseconds audbuf_timecode = GetBaseAudBufTimeCode();
404
405 if (audbuf_timecode == 0ms)
406 return 0ms;
407
408 int totalbuffer = audioready() + GetBufferedOnSoundcard();
409
410 return audbuf_timecode - millisecondsFromFloat(totalbuffer * 100000.0 /
413}
414
415/* This callback provides converted audio data to the default output device. */
416OSStatus RenderCallbackAnalog(void *inRefCon,
417 AudioUnitRenderActionFlags *ioActionFlags,
418 const AudioTimeStamp *inTimeStamp,
419 [[maybe_unused]] UInt32 inBusNumber,
420 [[maybe_unused]] UInt32 inNumberFrames,
421 AudioBufferList *ioData)
422{
423 AudioOutputCA *inst = (static_cast<CoreAudioData *>(inRefCon))->mCA;
424
425 if (!inst->RenderAudio((unsigned char *)(ioData->mBuffers[0].mData),
426 ioData->mBuffers[0].mDataByteSize,
427 inTimeStamp->mHostTime))
428 {
429 // play silence if RenderAudio returns false
430 memset(ioData->mBuffers[0].mData, 0, ioData->mBuffers[0].mDataByteSize);
431 *ioActionFlags = kAudioUnitRenderAction_OutputIsSilence;
432 }
433 return noErr;
434}
435
436int AudioOutputCA::GetVolumeChannel([[maybe_unused]] int channel) const
437{
438 // FIXME: this only returns global volume
439 Float32 volume;
440
441 if (!AudioUnitGetParameter(d->mOutputUnit,
442 kHALOutputParam_Volume,
443 kAudioUnitScope_Global, 0, &volume))
444 return (int)lroundf(volume * 100.0F);
445
446 return 0; // error case
447}
448
449void AudioOutputCA::SetVolumeChannel([[maybe_unused]] int channel, int volume)
450{
451 // FIXME: this only sets global volume
452 AudioUnitSetParameter(d->mOutputUnit, kHALOutputParam_Volume,
453 kAudioUnitScope_Global, 0, (volume * 0.01F), 0);
454}
455
456// IOProc style callback for SPDIF audio output
457static OSStatus RenderCallbackSPDIF([[maybe_unused]] AudioDeviceID inDevice,
458 [[maybe_unused]] const AudioTimeStamp *inNow,
459 [[maybe_unused]] const void *inInputData,
460 [[maybe_unused]] const AudioTimeStamp *inInputTime,
461 AudioBufferList *outOutputData,
462 const AudioTimeStamp *inOutputTime,
463 void *inRefCon)
464{
465 auto *d = static_cast<CoreAudioData *>(inRefCon);
466 AudioOutputCA *a = d->mCA;
467 int index = d->mStreamIndex;
468
469 /*
470 * HACK: No idea why this would be the case, but after the second run, we get
471 * incorrect value
472 */
473 if (d->mBytesPerPacket > 0 &&
474 outOutputData->mBuffers[index].mDataByteSize > d->mBytesPerPacket)
475 {
476 outOutputData->mBuffers[index].mDataByteSize = d->mBytesPerPacket;
477 }
478 if (!a->RenderAudio((unsigned char *)(outOutputData->mBuffers[index].mData),
479 outOutputData->mBuffers[index].mDataByteSize,
480 inOutputTime->mHostTime))
481 {
482 // play silence if RenderAudio returns false
483 memset(outOutputData->mBuffers[index].mData, 0,
484 outOutputData->mBuffers[index].mDataByteSize);
485 }
486 return noErr;
487}
488
490{
491 // Reset all the devices to a default 'non-hog' and mixable format.
492 // If we don't do this we may be unable to find the Default Output device.
493 // (e.g. if we crashed last time leaving it stuck in AC-3 mode)
495
497}
498
499CoreAudioData::CoreAudioData(AudioOutputCA *parent, AudioDeviceID deviceID) :
500 mCA(parent)
501{
503 mDeviceID = deviceID;
504}
505
506CoreAudioData::CoreAudioData(AudioOutputCA *parent, QString deviceName) :
507 mCA(parent)
508{
510 mDeviceID = GetDeviceWithName(deviceName);
511 if (!mDeviceID)
512 {
513 // Didn't find specified device, use default one
515 if (deviceName != "Default Output Device")
516 {
517 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:CoreAudioData: \"%1\" not found, using default device %2.")
518 .arg(deviceName).arg(mDeviceID));
519 }
520 }
521 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::CoreAudioData: device number is %1")
522 .arg(mDeviceID));
523}
524
525AudioDeviceID CoreAudioData::GetDeviceWithName(const QString &deviceName)
526{
527 UInt32 size = 0;
528 AudioDeviceID deviceID = 0;
529 AudioObjectPropertyAddress pa
530 {
531 kAudioHardwarePropertyDevices,
532 kAudioObjectPropertyScopeGlobal,
534 };
535
536 OSStatus err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &pa,
537 0, nullptr, &size);
538 if (err)
539 {
540 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetPropertyDataSize: Unable to retrieve the property sizes. "
541 "Error [%1]")
542 .arg(err));
543 return deviceID;
544 }
545
546 UInt32 deviceCount = size / sizeof(AudioDeviceID);
547 std::vector<AudioDeviceID> devices = {};
548 devices.resize(deviceCount);
549
550 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
551 0, nullptr, &size, devices.data());
552 if (err)
553 {
554 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetDeviceWithName: Unable to retrieve the list of available devices. "
555 "Error [%1]")
556 .arg(err));
557 }
558 else
559 {
560 for (const auto & dev : devices)
561 {
562 CoreAudioData device(nullptr, dev);
563 if (device.GetTotalOutputChannels() == 0)
564 continue;
565 QString *name = device.GetName();
566 if (name && *name == deviceName)
567 {
568 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::GetDeviceWithName: Found: %1").arg(*name));
569 deviceID = dev;
570 delete name;
571 }
572 if (deviceID)
573 break;
574 }
575 }
576 return deviceID;
577}
578
580{
581 UInt32 paramSize;
582 AudioDeviceID deviceId = 0;
583 AudioObjectPropertyAddress pa
584 {
585 kAudioHardwarePropertyDefaultOutputDevice,
586 kAudioObjectPropertyScopeGlobal,
588 };
589
590 // Find the ID of the default Device
591 paramSize = sizeof(deviceId);
592 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
593 0, nullptr, &paramSize, &deviceId);
594 if (err == noErr)
595 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::GetDefaultOutputDevice: default device ID = %1").arg(deviceId));
596 else
597 {
598 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetDefaultOutputDevice: could not get default audio device: [%1]")
599 .arg(OSS_STATUS(err)));
600 deviceId = 0;
601 }
602 return deviceId;
603}
604
606{
607 if (!mDeviceID)
608 return 0;
609 UInt32 channels = 0;
610 UInt32 size = 0;
611 AudioObjectPropertyAddress pa
612 {
613 kAudioDevicePropertyStreamConfiguration,
614 kAudioDevicePropertyScopeOutput, // Scope needs to be set to output to find output streams
616 };
617
618 OSStatus err = AudioObjectGetPropertyDataSize(mDeviceID, &pa,
619 0, nullptr, &size);
620 if (err)
621 {
622 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetTotalOutputChannels: Unable to get "
623 "size of device output channels - id: %1 Error = [%2]")
624 .arg(mDeviceID)
625 .arg(err));
626 return 0;
627 }
628
629 auto *pList = (AudioBufferList *)malloc(size);
630 err = AudioObjectGetPropertyData(mDeviceID, &pa,
631 0, nullptr, &size, pList);
632 if (!err)
633 {
634 for (UInt32 buffer = 0; buffer < pList->mNumberBuffers; buffer++)
635 channels += pList->mBuffers[buffer].mNumberChannels;
636 }
637 else
638 {
639 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetTotalOutputChannels: Unable to get "
640 "total device output channels - id: %1 Error = [%2]")
641 .arg(mDeviceID)
642 .arg(err));
643 }
644 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::GetTotalOutputChannels: Found %1 channels in %2 buffers")
645 .arg(channels).arg(pList->mNumberBuffers));
646 free(pList);
647 return channels;
648}
649
651{
652 if (!mDeviceID)
653 return nullptr;
654
655 AudioObjectPropertyAddress pa
656 {
657 kAudioObjectPropertyName,
658 kAudioObjectPropertyScopeGlobal,
660 };
661
662 CFStringRef name;
663 UInt32 propertySize = sizeof(CFStringRef);
664 OSStatus err = AudioObjectGetPropertyData(mDeviceID, &pa,
665 0, nullptr, &propertySize, &name);
666 if (err)
667 {
668 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:AudioObjectGetPropertyData for kAudioObjectPropertyName error: [%1]")
669 .arg(err));
670 return nullptr;
671 }
672 char *cname = new char[CFStringGetLength(name) + 1];
673 CFStringGetCString(name, cname, CFStringGetLength(name) + 1, kCFStringEncodingUTF8);
674 auto *qname = new QString(cname);
675 delete[] cname;
676 return qname;
677}
678
680{
681 UInt32 val = 0;
682 UInt32 size = sizeof(val);
683 AudioObjectPropertyAddress pa
684 {
685 kAudioHardwarePropertyHogModeIsAllowed,
686 kAudioObjectPropertyScopeGlobal,
688 };
689
690 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa, 0, nullptr, &size, &val);
691 if (err)
692 {
693 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetAutoHogMode: Unable to get auto 'hog' mode. Error = [%1]")
694 .arg(err));
695 return false;
696 }
697 return (val == 1);
698}
699
701{
702 UInt32 val = enable ? 1 : 0;
703 AudioObjectPropertyAddress pa
704 {
705 kAudioHardwarePropertyHogModeIsAllowed,
706 kAudioObjectPropertyScopeGlobal,
708 };
709
710 OSStatus err = AudioObjectSetPropertyData(kAudioObjectSystemObject, &pa, 0, nullptr,
711 sizeof(val), &val);
712 if (err)
713 {
714 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:SetAutoHogMode: Unable to set auto 'hog' mode. Error = [%1]")
715 .arg(err));
716 }
717}
718
720{
721 pid_t PID;
722 UInt32 PIDsize = sizeof(PID);
723 AudioObjectPropertyAddress pa
724 {
725 kAudioDevicePropertyHogMode,
726 kAudioObjectPropertyScopeGlobal,
728 };
729
730 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa, 0, nullptr,
731 &PIDsize, &PID);
732 if (err != noErr)
733 {
734 // This is not a fatal error.
735 // Some drivers simply don't support this property
736 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::GetHogStatus: unable to check: [%1]")
737 .arg(err));
738 return -1;
739 }
740 return PID;
741}
742
744{
745 AudioObjectPropertyAddress pa
746 {
747 kAudioDevicePropertyHogMode,
748 kAudioObjectPropertyScopeGlobal,
750 };
751
752 // According to Jeff Moore (Core Audio, Apple), Setting kAudioDevicePropertyHogMode
753 // is a toggle and the only way to tell if you do get hog mode is to compare
754 // the returned pid against getpid, if the match, you have hog mode, if not you don't.
755 if (!mDeviceID)
756 return false;
757
758 if (hog)
759 {
760 if (mHog == -1) // Not already set
761 {
762 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::SetHogStatus: Setting 'hog' status on device %1")
763 .arg(mDeviceID));
764 OSStatus err = AudioObjectSetPropertyData(mDeviceID, &pa, 0, nullptr,
765 sizeof(mHog), &mHog);
766 if (err || mHog != getpid())
767 {
768 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:SetHogStatus: Unable to set 'hog' status. Error = [%1]")
769 .arg(OSS_STATUS(err)));
770 return false;
771 }
772 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::SetHogStatus: Successfully set 'hog' status on device %1")
773 .arg(mDeviceID));
774 }
775 }
776 else
777 {
778 if (mHog > -1) // Currently Set
779 {
780 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::SetHogStatus: Releasing 'hog' status on device %1")
781 .arg(mDeviceID));
782 pid_t hogPid = -1;
783 OSStatus err = AudioObjectSetPropertyData(mDeviceID, &pa, 0, nullptr,
784 sizeof(hogPid), &hogPid);
785 if (err || hogPid == getpid())
786 {
787 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:SetHogStatus: Unable to release 'hog' status. Error = [%1]")
788 .arg(OSS_STATUS(err)));
789 return false;
790 }
791 mHog = hogPid; // Reset internal state
792 }
793 }
794 return true;
795}
796
798{
799 if (!mDeviceID)
800 return false;
801 int restore = -1;
802 if (mMixerRestore == -1) // This is our first change to this setting. Store the original setting for restore
803 restore = (GetMixingSupport() ? 1 : 0);
804 UInt32 mixEnable = mix ? 1 : 0;
805 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::SetMixingSupport: %1abling mixing for device %2")
806 .arg(mix ? "En" : "Dis")
807 .arg(mDeviceID));
808
809 AudioObjectPropertyAddress pa
810 {
811 kAudioDevicePropertySupportsMixing,
812 kAudioObjectPropertyScopeGlobal,
814 };
815 OSStatus err = AudioObjectSetPropertyData(mDeviceID, &pa, 0, nullptr,
816 sizeof(mixEnable), &mixEnable);
817 if (err)
818 {
819 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:SetMixingSupport: Unable to set MixingSupport to %1. Error = [%2]")
820 .arg(mix ? "'On'" : "'Off'")
821 .arg(OSS_STATUS(err)));
822 return false;
823 }
824 if (mMixerRestore == -1)
825 mMixerRestore = restore;
826 return true;
827}
828
830{
831 if (!mDeviceID)
832 return false;
833 UInt32 val = 0;
834 UInt32 size = sizeof(val);
835 AudioObjectPropertyAddress pa
836 {
837 kAudioDevicePropertySupportsMixing,
838 kAudioObjectPropertyScopeGlobal,
840 };
841 OSStatus err = AudioObjectGetPropertyData(mDeviceID, &pa, 0, nullptr,
842 &size, &val);
843 if (err)
844 return false;
845 return (val > 0);
846}
847
852{
853 OSStatus err;
854 UInt32 listSize;
855 AudioStreamIDVec vec {};
856
857 AudioObjectPropertyAddress pa
858 {
859 kAudioDevicePropertyStreams,
860 kAudioObjectPropertyScopeGlobal,
862 };
863
864 err = AudioObjectGetPropertyDataSize(d, &pa,
865 0, nullptr, &listSize);
866 if (err != noErr)
867 {
868 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:StreamsList: could not get list size: [%1]")
869 .arg(OSS_STATUS(err)));
870 return {};
871 }
872 try
873 {
874 // Bugfix: vec.reserve will not change size of vector since contents are updated directly via memory copy
875 // In general all std::vector arrays have been changed from reserve to resize.
876 vec.resize(listSize / sizeof(AudioStreamID));
877 }
878 catch (...)
879 {
880 LOG(VB_GENERAL, LOG_ERR, "CoreAudioData Error:StreamsList(): out of memory?");
881 return {};
882 }
883
884 err = AudioObjectGetPropertyData(d, &pa,
885 0, nullptr, &listSize, vec.data());
886 if (err != noErr)
887 {
888 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:StreamsList: could not get list: [%1]")
889 .arg(OSS_STATUS(err)));
890 return {};
891 }
892
893 return vec;
894}
895
897{
898 OSStatus err;
900 UInt32 listSize;
901
902 AudioObjectPropertyAddress pa
903 {
904 // Bugfix: kAudioStreamPropertyPhysicalFormats, is meant to only give array of AudioStreamBasicDescription
905 kAudioStreamPropertyAvailablePhysicalFormats, // gives array of AudioStreamRangedDescription
906 kAudioObjectPropertyScopeGlobal,
908 };
909
910 // Retrieve all the stream formats supported by this output stream
911 err = AudioObjectGetPropertyDataSize(s, &pa, 0, nullptr, &listSize);
912 if (err != noErr)
913 {
914 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:FormatsList(): couldn't get list size: [%1]")
915 .arg(OSS_STATUS(err)));
916 return {};
917 }
918
919 try
920 {
921 vec.resize(listSize / sizeof(AudioStreamRangedDescription));
922 }
923 catch (...)
924 {
925 LOG(VB_GENERAL, LOG_ERR, "CoreAudioData Error:FormatsList(): out of memory?");
926 return {};
927 }
928
929 err = AudioObjectGetPropertyData(s, &pa, 0, nullptr, &listSize, vec.data());
930 if (err != noErr)
931 {
932 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:FormatsList: couldn't get list: [%1]")
933 .arg(OSS_STATUS(err)));
934 return {};
935 }
936
937 return vec;
938}
939
940static UInt32 sNumberCommonSampleRates = 15;
941static Float64 sCommonSampleRates[] = {
942 8000.0, 11025.0, 12000.0,
943 16000.0, 22050.0, 24000.0,
944 32000.0, 44100.0, 48000.0,
945 64000.0, 88200.0, 96000.0,
946 128000.0, 176400.0, 192000.0 };
947
948static bool IsRateCommon(Float64 inRate)
949{
950 bool theAnswer = false;
951 for(UInt32 i = 0; !theAnswer && (i < sNumberCommonSampleRates); i++)
952 {
953 theAnswer = inRate == sCommonSampleRates[i];
954 }
955 return theAnswer;
956}
957
959{
960 OSStatus err;
961 AudioValueRangeVec ranges;
962 RatesVec finalvec;
963 UInt32 listSize;
964
965 AudioObjectPropertyAddress pa
966 {
967 kAudioDevicePropertyAvailableNominalSampleRates,
968 kAudioObjectPropertyScopeGlobal,
970 };
971
972 // retrieve size of rate list
973 err = AudioObjectGetPropertyDataSize(d, &pa, 0, nullptr, &listSize);
974 if (err != noErr)
975 {
976 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:RatesList(): couldn't get data rate list size: [%1]")
977 .arg(err));
978 return {};
979 }
980
981 try
982 {
983 ranges.resize(listSize / sizeof(AudioValueRange));
984 finalvec.reserve(listSize / sizeof(AudioValueRange));
985 }
986 catch (...)
987 {
988 LOG(VB_GENERAL, LOG_ERR, "CoreAudioData Error:RatesList(): out of memory?");
989 return {};
990 }
991
992 err = AudioObjectGetPropertyData(d, &pa, 0, nullptr, &listSize, ranges.data());
993 if (err != noErr)
994 {
995 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:RatesList(): couldn't get list: [%1]")
996 .arg(err));
997 return {};
998 }
999
1000 // iterate through the ranges and add the minimum, maximum, and common rates in between
1001 UInt32 theFirstIndex;
1002 UInt32 theLastIndex = 0;
1003 for(auto range : ranges)
1004 {
1005 theFirstIndex = theLastIndex;
1006 // find the index of the first common rate greater than or equal to the minimum
1007 while((theFirstIndex < sNumberCommonSampleRates) && (sCommonSampleRates[theFirstIndex] < range.mMinimum))
1008 theFirstIndex++;
1009
1010 if (theFirstIndex >= sNumberCommonSampleRates)
1011 break;
1012
1013 theLastIndex = theFirstIndex;
1014 // find the index of the first common rate greater than or equal to the maximum
1015 while((theLastIndex < sNumberCommonSampleRates) && (sCommonSampleRates[theLastIndex] < range.mMaximum))
1016 {
1017 finalvec.push_back(sCommonSampleRates[theLastIndex]);
1018 theLastIndex++;
1019 }
1020 if (IsRateCommon(range.mMinimum))
1021 finalvec.push_back(range.mMinimum);
1022 else if (IsRateCommon(range.mMaximum))
1023 finalvec.push_back(range.mMaximum);
1024 }
1025
1026 return finalvec;
1027}
1028
1029bool CoreAudioData::ChannelsList(AudioDeviceID /*d*/, bool passthru, ChannelsArr& chans)
1030{
1031 AudioStreamIDVec streams;
1033 bool founddigital = false;
1034
1035 chans.fill(false);
1036
1037 streams = StreamsList(mDeviceID);
1038 if (streams.empty())
1039 return false;
1040
1041 if (passthru)
1042 {
1043 for (auto stream : streams)
1044 {
1045 formats = FormatsList(stream);
1046 if (formats.empty())
1047 continue;
1048
1049 // Find a stream with a cac3 stream
1050 for (auto format : formats)
1051 {
1052 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::ChannelsList: (passthru) found format: %1")
1053 .arg(StreamDescriptionToString(format.mFormat)));
1054 // Add supported number of channels
1055 if (format.mFormat.mChannelsPerFrame <= CHANNELS_MAX)
1056 chans[format.mFormat.mChannelsPerFrame-1] = true;
1057
1058 if (format.mFormat.mFormatID == 'IAC3' ||
1059 format.mFormat.mFormatID == kAudioFormat60958AC3)
1060 {
1061 // By default AC3 has 6 (5.1) channels but CoreAudio seems to set mChannelsPerFrame to 2
1062 // and considers it a "2 channel Encoded Digital Audio"
1063 chans[6-1] = true;
1064 // chans[format.mFormat.mChannelsPerFrame-1] = true;
1065 founddigital = true;
1066 }
1067 }
1068 }
1069 }
1070
1071 if (!founddigital)
1072 {
1073 for (auto stream : streams)
1074 {
1075 formats = FormatsList(stream);
1076 if (formats.empty())
1077 continue;
1078 for (auto format : formats)
1079 {
1080 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::ChannelsList: (!founddigital) found format: %1")
1081 .arg(StreamDescriptionToString(format.mFormat)));
1082 if (format.mFormat.mChannelsPerFrame <= CHANNELS_MAX)
1083 chans[format.mFormat.mChannelsPerFrame-1] = true;
1084 }
1085 }
1086 }
1087 return true;
1088}
1089
1091{
1092 AudioComponentDescription desc;
1093 AudioStreamBasicDescription DeviceFormat;
1094 AudioChannelLayout *layout;
1095 AudioChannelLayout new_layout;
1096 AudioDeviceID defaultDevice = GetDefaultOutputDevice();
1097 AudioObjectPropertyAddress pa
1098 {
1099 kAudioHardwarePropertyDevices,
1100 kAudioObjectPropertyScopeGlobal,
1102 };
1103
1104 LOG(VB_AUDIO, LOG_INFO, "CoreAudioData::OpenAnalog: Entering");
1105
1106 desc.componentType = kAudioUnitType_Output;
1107 if (defaultDevice == mDeviceID)
1108 {
1109 desc.componentSubType = kAudioUnitSubType_DefaultOutput;
1110 }
1111 else
1112 {
1113 desc.componentSubType = kAudioUnitSubType_HALOutput;
1114 }
1115 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
1116 desc.componentFlags = 0;
1117 desc.componentFlagsMask = 0;
1118 mDigitalInUse = false;
1119
1120 AudioComponent comp = AudioComponentFindNext(nullptr, &desc);
1121 if (comp == nullptr)
1122 {
1123 LOG(VB_GENERAL, LOG_ERR, "CoreAudioData Error:OpenAnalog: AudioComponentFindNext failed");
1124 return false;
1125 }
1126
1127 OSErr err = AudioComponentInstanceNew(comp, &mOutputUnit);
1128 if (err)
1129 {
1130 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenAnalog: AudioComponentInstanceNew returned %1")
1131 .arg(err));
1132 return false;
1133 }
1134
1135 // Check if we have IO
1136 UInt32 hasIO = 0;
1137 UInt32 size_hasIO = sizeof(hasIO);
1138 err = AudioUnitGetProperty(mOutputUnit,
1139 kAudioOutputUnitProperty_HasIO,
1140 kAudioUnitScope_Output,
1141 0,
1142 &hasIO, &size_hasIO);
1143 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::OpenAnalog: HasIO (output) = %1").arg(hasIO));
1144 if (!hasIO)
1145 {
1146 UInt32 enableIO = 1;
1147 err = AudioUnitSetProperty(mOutputUnit,
1148 kAudioOutputUnitProperty_EnableIO,
1149 kAudioUnitScope_Global,
1150 0,
1151 &enableIO, sizeof(enableIO));
1152 if (err)
1153 {
1154 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:OpenAnalog: failed enabling IO: %1")
1155 .arg(err));
1156 }
1157 hasIO = 0;
1158 err = AudioUnitGetProperty(mOutputUnit,
1159 kAudioOutputUnitProperty_HasIO,
1160 kAudioUnitScope_Output,
1161 0,
1162 &hasIO, &size_hasIO);
1163 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::HasIO = %1").arg(hasIO));
1164 }
1165
1166 /*
1167 * We shouldn't have to do this distinction, however for some unknown reasons
1168 * assigning device to AudioUnit fail when switching from SPDIF mode
1169 */
1170 if (defaultDevice != mDeviceID)
1171 {
1172 err = AudioUnitSetProperty(mOutputUnit,
1173 kAudioOutputUnitProperty_CurrentDevice,
1174 kAudioUnitScope_Global,
1175 0,
1176 &mDeviceID, sizeof(mDeviceID));
1177 if (err)
1178 {
1179 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenAnalog: Unable to set current device to %1. Error = %2")
1180 .arg(mDeviceID)
1181 .arg(err));
1182 return -1;
1183 }
1184 }
1185 /* Get the current format */
1186 UInt32 param_size = sizeof(AudioStreamBasicDescription);
1187
1188 err = AudioUnitGetProperty(mOutputUnit,
1189 kAudioUnitProperty_StreamFormat,
1190 kAudioUnitScope_Input,
1191 0,
1192 &DeviceFormat,
1193 &param_size );
1194 if (err)
1195 {
1196 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:OpenAnalog: Unable to retrieve current stream format: [%1]")
1197 .arg(err));
1198 }
1199 else
1200 {
1201 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::OpenAnalog: current format is: %1")
1202 .arg(StreamDescriptionToString(DeviceFormat)));
1203 }
1204 /* Get the channel layout of the device side of the unit */
1205 pa.mSelector = kAudioDevicePropertyPreferredChannelLayout;
1206 err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &pa,
1207 0, nullptr, &param_size);
1208
1209 if(!err)
1210 {
1211 layout = (AudioChannelLayout *) malloc(param_size);
1212
1213 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
1214 0, nullptr, &param_size, layout);
1215
1216 /* We need to "fill out" the ChannelLayout, because there are multiple ways that it can be set */
1217 if(layout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap)
1218 {
1219 /* bitmap defined channellayout */
1220 err = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForBitmap,
1221 sizeof(UInt32), &layout->mChannelBitmap,
1222 &param_size,
1223 layout);
1224 if (err)
1225 LOG(VB_GENERAL, LOG_WARNING, "CoreAudioData Warning:OpenAnalog: Can't retrieve current channel layout");
1226 }
1227 else if(layout->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions )
1228 {
1229 /* layouttags defined channellayout */
1230 err = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForTag,
1231 sizeof(AudioChannelLayoutTag),
1232 &layout->mChannelLayoutTag,
1233 &param_size,
1234 layout);
1235 if (err)
1236 LOG(VB_GENERAL, LOG_WARNING, "CoreAudioData Warning:OpenAnalog: Can't retrieve current channel layout");
1237 }
1238
1239 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::OpenAnalog: Layout of AUHAL has %1 channels")
1240 .arg(layout->mNumberChannelDescriptions));
1241
1242 int channels_found = 0;
1243 for(UInt32 i = 0; i < layout->mNumberChannelDescriptions; i++)
1244 {
1245 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::OpenAnalog: this is channel: %1")
1246 .arg(layout->mChannelDescriptions[i].mChannelLabel));
1247
1248 switch( layout->mChannelDescriptions[i].mChannelLabel)
1249 {
1250 case kAudioChannelLabel_Left:
1251 case kAudioChannelLabel_Right:
1252 case kAudioChannelLabel_Center:
1253 case kAudioChannelLabel_LFEScreen:
1254 case kAudioChannelLabel_LeftSurround:
1255 case kAudioChannelLabel_RightSurround:
1256 case kAudioChannelLabel_RearSurroundLeft:
1257 case kAudioChannelLabel_RearSurroundRight:
1258 case kAudioChannelLabel_CenterSurround:
1259 channels_found++;
1260 break;
1261 default:
1262 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::unrecognized channel form provided by driver: %1")
1263 .arg(layout->mChannelDescriptions[i].mChannelLabel));
1264 }
1265 }
1266 if(channels_found == 0)
1267 {
1268 LOG(VB_GENERAL, LOG_WARNING, "CoreAudioData Warning:Audio device is not configured. "
1269 "You should configure your speaker layout with "
1270 "the \"Audio Midi Setup\" utility in /Applications/"
1271 "Utilities.");
1272 }
1273 free(layout);
1274 }
1275 else
1276 {
1277 LOG(VB_GENERAL, LOG_WARNING, "CoreAudioData Warning:this driver does not support kAudioDevicePropertyPreferredChannelLayout.");
1278 }
1279
1280 memset (&new_layout, 0, sizeof(new_layout));
1281 switch(mCA->m_channels)
1282 {
1283 case 1:
1284 new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
1285 break;
1286 case 2:
1287 new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
1288 break;
1289 case 6:
1290 // 3F2-LFE L R C LFE LS RS
1291 new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_AudioUnit_5_1;
1292 break;
1293 case 8:
1294 // We need
1295 // 3F4-LFE L R C LFE Rls Rrs LS RS
1296 // but doesn't exist, so we'll swap channels later
1297 new_layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_7_1_A; // L R C LFE Ls Rs Lc Rc
1298 break;
1299 }
1300 /* Set new_layout as the layout */
1301 err = AudioUnitSetProperty(mOutputUnit,
1302 kAudioUnitProperty_AudioChannelLayout,
1303 kAudioUnitScope_Input,
1304 0,
1305 &new_layout, sizeof(new_layout));
1306 if (err)
1307 {
1308 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:OpenAnalog: couldn't set channels layout [%1]")
1309 .arg(err));
1310 }
1311
1312 if(new_layout.mNumberChannelDescriptions > 0)
1313 free(new_layout.mChannelDescriptions);
1314
1315 // Set up the audio output unit
1316 int formatFlags;
1317 switch (mCA->m_outputFormat)
1318 {
1319 case FORMAT_S16:
1320 formatFlags = kLinearPCMFormatFlagIsSignedInteger;
1321 break;
1322 case FORMAT_FLT:
1323 formatFlags = kLinearPCMFormatFlagIsFloat;
1324 break;
1325 default:
1326 formatFlags = kLinearPCMFormatFlagIsSignedInteger;
1327 break;
1328 }
1329
1330 AudioStreamBasicDescription conv_in_desc;
1331 memset(&conv_in_desc, 0, sizeof(AudioStreamBasicDescription));
1332 conv_in_desc.mSampleRate = mCA->m_sampleRate;
1333 conv_in_desc.mFormatID = kAudioFormatLinearPCM;
1334 conv_in_desc.mFormatFlags = formatFlags |
1335 kAudioFormatFlagsNativeEndian |
1336 kLinearPCMFormatFlagIsPacked;
1337 conv_in_desc.mBytesPerPacket = mCA->m_outputBytesPerFrame;
1338 // This seems inefficient, does it hurt if we increase this?
1339 conv_in_desc.mFramesPerPacket = 1;
1340 conv_in_desc.mBytesPerFrame = mCA->m_outputBytesPerFrame;
1341 conv_in_desc.mChannelsPerFrame = mCA->m_channels;
1342 conv_in_desc.mBitsPerChannel =
1344
1345 /* Set AudioUnit input format */
1346 err = AudioUnitSetProperty(mOutputUnit,
1347 kAudioUnitProperty_StreamFormat,
1348 kAudioUnitScope_Input,
1349 0,
1350 &conv_in_desc,
1351 sizeof(AudioStreamBasicDescription));
1352 if (err)
1353 {
1354 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenAnalog: AudioUnitSetProperty returned [%1]")
1355 .arg(err));
1356 return false;
1357 }
1358 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::OpenAnalog: set format as %1")
1359 .arg(StreamDescriptionToString(conv_in_desc)));
1360 /* Retrieve actual format */
1361 err = AudioUnitGetProperty(mOutputUnit,
1362 kAudioUnitProperty_StreamFormat,
1363 kAudioUnitScope_Input,
1364 0,
1365 &DeviceFormat,
1366 &param_size);
1367
1368 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::OpenAnalog: the actual set AU format is %1")
1369 .arg(StreamDescriptionToString(DeviceFormat)));
1370
1371 // Attach callback to default output
1372 AURenderCallbackStruct input;
1373 input.inputProc = RenderCallbackAnalog;
1374 input.inputProcRefCon = this;
1375
1376 err = AudioUnitSetProperty(mOutputUnit,
1377 kAudioUnitProperty_SetRenderCallback,
1378 kAudioUnitScope_Input,
1379 0, &input, sizeof(input));
1380 if (err)
1381 {
1382 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenAnalog: AudioUnitSetProperty (callback) returned [%1]")
1383 .arg(err));
1384 return false;
1385 }
1386 mIoProc = true;
1387
1388 // We're all set up - start the audio output unit
1389 ComponentResult res = AudioUnitInitialize(mOutputUnit);
1390 if (res)
1391 {
1392 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenAnalog: AudioUnitInitialize error: [%1]")
1393 .arg(res));
1394 return false;
1395 }
1396 mInitialized = true;
1397
1398 err = AudioOutputUnitStart(mOutputUnit);
1399 if (err)
1400 {
1401 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenAnalog: AudioOutputUnitStart error: [%1]")
1402 .arg(err));
1403 return false;
1404 }
1405 mStarted = true;
1406 return true;
1407}
1408
1410{
1411 OSStatus err;
1412
1413 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::CloseAnalog: Entering: %1")
1414 .arg((long)mOutputUnit));
1415 if (mOutputUnit)
1416 {
1417 if (mStarted)
1418 {
1419 err = AudioOutputUnitStop(mOutputUnit);
1420 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::CloseAnalog: AudioOutputUnitStop %1")
1421 .arg(err));
1422 }
1423 if (mInitialized)
1424 {
1425 err = AudioUnitUninitialize(mOutputUnit);
1426 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::CloseAnalog: AudioUnitUninitialize %1")
1427 .arg(err));
1428 }
1429 err = AudioComponentInstanceDispose(mOutputUnit);
1430 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::CloseAnalog: CloseComponent %1")
1431 .arg(err));
1432 mOutputUnit = nullptr;
1433 }
1434 mIoProc = false;
1435 mInitialized = false;
1436 mStarted = false;
1437 mWasDigital = false;
1438}
1439
1441{
1442 OSStatus err;
1443 AudioStreamIDVec streams;
1444 AudioStreamBasicDescription outputFormat {};
1445
1446 LOG(VB_AUDIO, LOG_INFO, "CoreAudioData::OpenSPDIF: Entering");
1447
1448 streams = StreamsList(mDeviceID);
1449 if (streams.empty())
1450 {
1451 LOG(VB_GENERAL, LOG_WARNING, "CoreAudioData Warning:OpenSPDIF: Couldn't retrieve list of streams");
1452 return false;
1453 }
1454
1455 for (size_t i = 0; i < streams.size(); ++i)
1456 {
1458 if (formats.empty())
1459 continue;
1460
1461 // Find a stream with a cac3 stream
1462 for (auto format : formats)
1463 {
1464 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::OpenSPDIF: Considering Physical Format: %1")
1465 .arg(StreamDescriptionToString(format.mFormat)));
1466 if ((format.mFormat.mFormatID == 'IAC3' ||
1467 format.mFormat.mFormatID == kAudioFormat60958AC3) &&
1468 format.mFormat.mSampleRate == mCA->m_sampleRate)
1469 {
1470 LOG(VB_AUDIO, LOG_INFO, "CoreAudioData::OpenSPDIF: Found digital format");
1471 mStreamIndex = i;
1472 mStreamID = streams[i];
1473 outputFormat = format.mFormat;
1474 break;
1475 }
1476 }
1477 if (outputFormat.mFormatID)
1478 break;
1479 }
1480
1481 if (!outputFormat.mFormatID)
1482 {
1483 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenSPDIF: Couldn't find suitable output"));
1484 return false;
1485 }
1486
1487 if (!mRevertFormat)
1488 {
1489 AudioObjectPropertyAddress pa
1490 {
1491 kAudioStreamPropertyPhysicalFormat,
1492 kAudioObjectPropertyScopeGlobal,
1494 };
1495
1496 // Retrieve the original format of this stream first
1497 // if not done so already
1498 UInt32 paramSize = sizeof(mFormatOrig);
1499 err = AudioObjectGetPropertyData(mStreamID, &pa, 0, nullptr,
1500 &paramSize, &mFormatOrig);
1501 if (err != noErr)
1502 {
1503 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:OpenSPDIF - could not retrieve the original streamformat: [%1]")
1504 .arg(OSS_STATUS(err)));
1505 }
1506 else
1507 {
1508 mRevertFormat = true;
1509 }
1510 }
1511
1512 mDigitalInUse = true;
1513
1514 SetAutoHogMode(false);
1515 bool autoHog = GetAutoHogMode();
1516 if (!autoHog)
1517 {
1518 // Hog the device
1519 SetHogStatus(true);
1520 // Set mixable to false if we are allowed to
1521 SetMixingSupport(false);
1522 }
1523
1524 mFormatNew = outputFormat;
1526 {
1527 return false;
1528 }
1529 mBytesPerPacket = mFormatNew.mBytesPerPacket;
1530
1531 // Add IOProc callback
1532 err = AudioDeviceCreateIOProcID(mDeviceID,
1533 (AudioDeviceIOProc)RenderCallbackSPDIF,
1534 (void *)this, &mIoProcID);
1535 if (err != noErr)
1536 {
1537 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenSPDIF: AudioDeviceCreateIOProcID failed: [%1]")
1538 .arg(OSS_STATUS(err)));
1539 return false;
1540 }
1541 mIoProc = true;
1542
1543 // Start device
1544 err = AudioDeviceStart(mDeviceID, mIoProcID);
1545 if (err != noErr)
1546 {
1547 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:OpenSPDIF: AudioDeviceStart failed: [%1]")
1548 .arg(OSS_STATUS(err)));
1549 return false;
1550 }
1551 mStarted = true;
1552 return true;
1553}
1554
1556{
1557 OSStatus err;
1558
1559 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::CloseSPDIF: Entering [%1]").arg(mDigitalInUse));;
1560 if (!mDigitalInUse)
1561 return;
1562
1563 // Stop device
1564 if (mStarted)
1565 {
1566 err = AudioDeviceStop(mDeviceID, mIoProcID);
1567 if (err != noErr)
1568 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:CloseSPDIF: AudioDeviceStop failed: [%1]")
1569 .arg(OSS_STATUS(err)));
1570 mStarted = false;
1571 }
1572
1573 // Remove IOProc callback
1574 if (mIoProc)
1575 {
1576 err = AudioDeviceDestroyIOProcID(mDeviceID, mIoProcID);
1577 if (err != noErr)
1578 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:CloseSPDIF: AudioDeviceDestroyIOProcID failed: [%1]")
1579 .arg(OSS_STATUS(err)));
1580 mIoProc = false;
1581 }
1582
1583 if (mRevertFormat)
1584 {
1586 mRevertFormat = false;
1587 }
1588
1589 SetHogStatus(false);
1590 if (mMixerRestore > -1) // We changed the mixer status
1592 AudioHardwareUnload();
1593 mMixerRestore = -1;
1594 mBytesPerPacket = -1;
1595 mStreamIndex = -1;
1596 mWasDigital = true;
1597}
1598
1600 AudioStreamBasicDescription format)
1601{
1602 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::AudioStreamChangeFormat: %1 -> %2")
1603 .arg(s)
1604 .arg(StreamDescriptionToString(format)));
1605
1606 AudioObjectPropertyAddress pa
1607 {
1608 kAudioStreamPropertyPhysicalFormat,
1609 kAudioObjectPropertyScopeGlobal,
1611 };
1612 OSStatus err = AudioObjectSetPropertyData(s, &pa, 0, nullptr,
1613 sizeof(format), &format);
1614 if (err != noErr)
1615 {
1616 LOG(VB_GENERAL, LOG_ERR, QString("CoreAudioData Error:AudioStreamChangeFormat couldn't set stream format: [%1]")
1617 .arg(OSS_STATUS(err)));
1618 return false;
1619 }
1620 return true;
1621}
1622
1624{
1625 AudioStreamIDVec streams;
1626
1627
1628 // Get a list of all the streams on this device
1629 streams = StreamsList(mDeviceID);
1630 if (streams.empty())
1631 return false;
1632
1633 for (auto stream : streams)
1634 {
1636 if (formats.empty())
1637 continue;
1638
1639 // Find a stream with a cac3 stream
1640 for (auto format : formats) {
1641 if (format.mFormat.mFormatID == 'IAC3' ||
1642 format.mFormat.mFormatID == kAudioFormat60958AC3)
1643 {
1644 LOG(VB_AUDIO, LOG_INFO, "CoreAudioData::FindAC3Stream: found digital format");
1645 return true;
1646 }
1647 }
1648 }
1649
1650 return false;
1651}
1652
1658{
1659 UInt32 size;
1660 AudioObjectPropertyAddress pa
1661 {
1662 kAudioHardwarePropertyDevices,
1663 kAudioObjectPropertyScopeGlobal,
1665 };
1666
1667 OSStatus err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &pa,
1668 0, nullptr, &size);
1669 if (err)
1670 {
1671 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetPropertyDataSize: Unable to retrieve the property sizes. "
1672 "Error [%1]")
1673 .arg(err));
1674 return;
1675 }
1676
1677 std::vector<AudioDeviceID> devices = {};
1678 devices.resize(size / sizeof(AudioDeviceID));
1679 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
1680 0, nullptr, &size, devices.data());
1681 if (err)
1682 {
1683 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:GetPropertyData: Unable to retrieve the list of available devices. "
1684 "Error [%1]")
1685 .arg(err));
1686 return;
1687 }
1688
1689 for (const auto & dev : devices)
1690 {
1691 AudioStreamIDVec streams;
1692
1693 streams = StreamsList(dev);
1694 if (streams.empty())
1695 continue;
1696 for (auto stream : streams)
1697 ResetStream(stream);
1698 }
1699}
1700
1701void CoreAudioData::ResetStream(AudioStreamID s)
1702{
1703 AudioStreamBasicDescription currentFormat;
1704 OSStatus err;
1705 UInt32 paramSize;
1706 AudioObjectPropertyAddress pa
1707 {
1708 kAudioStreamPropertyPhysicalFormat,
1709 kAudioObjectPropertyScopeGlobal,
1711 };
1712
1713
1714 // Find the streams current physical format
1715 paramSize = sizeof(currentFormat);
1716 AudioObjectGetPropertyData(s, &pa, 0, nullptr,
1717 &paramSize, &currentFormat);
1718
1719 // If it's currently AC-3/SPDIF then reset it to some mixable format
1720 if (currentFormat.mFormatID == 'IAC3' ||
1721 currentFormat.mFormatID == kAudioFormat60958AC3)
1722 {
1724
1725
1726 if (formats.empty())
1727 return;
1728
1729 for (auto format : formats) {
1730 if (format.mFormat.mFormatID == kAudioFormatLinearPCM)
1731 {
1732 LOG(VB_AUDIO, LOG_INFO, QString("CoreAudioData::ResetStream: Resetting stream %1 to %2").arg(s).arg(StreamDescriptionToString(format.mFormat)));
1733 err = AudioObjectSetPropertyData(s, &pa, 0, nullptr,
1734 sizeof(format), &(format.mFormat));
1735 if (err != noErr)
1736 {
1737 LOG(VB_GENERAL, LOG_WARNING, QString("CoreAudioData Warning:ResetStream: could not set physical format: [%1]")
1738 .arg(OSS_STATUS(err)));
1739 continue;
1740 }
1741
1742 std::this_thread::sleep_for(1s); // For the change to take effect
1743 }
1744 }
1745 }
1746}
1747
1748QMap<QString, QString> *AudioOutputCA::GetDevices(const char */*type*/)
1749{
1750 auto *devs = new QMap<QString, QString>();
1751
1752 // Obtain a list of all available audio devices
1753 UInt32 size = 0;
1754
1755 AudioObjectPropertyAddress pa
1756 {
1757 kAudioHardwarePropertyDevices,
1758 kAudioObjectPropertyScopeGlobal,
1760 };
1761
1762 OSStatus err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &pa,
1763 0, nullptr, &size);
1764 if (err)
1765 {
1766 LOG(VB_AUDIO, LOG_INFO, LOC + QString("GetPropertyDataSize: Unable to retrieve the property sizes. "
1767 "Error [%1]")
1768 .arg(err));
1769 return devs;
1770 }
1771
1772 UInt32 deviceCount = size / sizeof(AudioDeviceID);
1773 std::vector<AudioDeviceID> devices = {};
1774 devices.resize(deviceCount);
1775 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa,
1776 0, nullptr, &size, devices.data());
1777 if (err)
1778 {
1779 LOG(VB_AUDIO, LOG_INFO, LOC + QString("AudioOutputCA::GetDevices: Unable to retrieve the list of "
1780 "available devices. Error [%1]")
1781 .arg(err));
1782 }
1783 else
1784 {
1785 LOG(VB_AUDIO, LOG_INFO, LOC + QString("GetDevices: Number of devices: %1").arg(deviceCount));
1786
1787 for (const auto & dev : devices)
1788 {
1789 CoreAudioData device(nullptr, dev);
1790 if (device.GetTotalOutputChannels() == 0)
1791 continue;
1792 QString *name = device.GetName();
1793 if (name)
1794 {
1795 devs->insert(*name, QString());
1796 delete name;
1797 }
1798 }
1799 }
1800 return devs;
1801}
std::vector< AudioStreamID > AudioStreamIDVec
std::vector< AudioValueRange > AudioValueRangeVec
#define LOC
static Float64 sCommonSampleRates[]
#define CHANNELS_MAX
std::vector< AudioStreamRangedDescription > AudioStreamRangedVec
QString StreamDescriptionToString(AudioStreamBasicDescription desc)
static OSStatus RenderCallbackAnalog(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
static OSStatus RenderCallbackSPDIF(AudioDeviceID inDevice, const AudioTimeStamp *inNow, const void *inInputData, const AudioTimeStamp *inInputTime, AudioBufferList *outOutputData, const AudioTimeStamp *inOutputTime, void *inRefCon)
#define OSS_STATUS(x)
#define CHANNELS_MIN
static bool IsRateCommon(Float64 inRate)
char * UInt32ToFourCC(const UInt32 *pVal)
std::vector< int > RatesVec
static void _ReorderSmpteToCA(AudioDataType *buf, uint frames)
static constexpr int8_t kMythAudioObjectPropertyElementMain
std::array< bool, CHANNELS_MAX > ChannelsArr
static void ReorderSmpteToCA(void *buf, uint frames, AudioFormat format)
static UInt32 sNumberCommonSampleRates
@ FORMAT_FLT
@ FORMAT_S16
static const std::array< const std::string, 8 > formats
void KillAudio(void)
Kill the output thread and cleanup.
void Reconfigure(const AudioSettings &settings) override
(Re)Configure AudioOutputBase
AudioFormat m_outputFormat
void InitSettings(const AudioSettings &settings)
std::chrono::milliseconds GetBaseAudBufTimeCode() const
int GetAudioData(uchar *buffer, int buf_size, bool full_buffer, volatile uint *local_raud=nullptr)
Copy frames from the audiobuffer into the buffer provided.
int audioready() const
Get the scaled number of bytes in the audiobuffer, i.e.
Implements Core Audio (Mac OS X Hardware Abstraction Layer) output.
Definition: audiooutputca.h:14
friend class CoreAudioData
Definition: audiooutputca.h:46
std::chrono::milliseconds GetAudiotime(void) override
Reimplement the base class's version of GetAudiotime() so that we don't use gettimeofday or Qt mutexe...
int GetVolumeChannel(int channel) const override
void CloseDevice(void) override
virtual ~AudioOutputCA()
void SetVolumeChannel(int channel, int volume) override
void WriteAudio(unsigned char *aubuf, int size) override
int GetBufferedOnSoundcard(void) const override
Return the size in bytes of frames currently in the audio buffer adjusted with the audio playback lat...
AudioOutputSettings * GetOutputSettings(bool digital) override
static QMap< QString, QString > * GetDevices(const char *type=nullptr)
CoreAudioData * d
Definition: audiooutputca.h:45
AudioOutputCA(const AudioSettings &settings)
bool OpenDevice(void) override
bool RenderAudio(unsigned char *aubuf, int size, unsigned long long timestamp)
Object-oriented part of callback.
static int FormatToBits(AudioFormat format)
const int & channels() const
Definition: audiooutput.h:254
bool m_init
If set to false, AudioOutput instance will not try to initially open the audio device.
Definition: audiosettings.h:85
This holds Core Audio member variables and low-level audio IO methods The name is now a misnomer,...
static void SetAutoHogMode(bool enable)
static AudioDeviceID GetDeviceWithName(const QString &deviceName)
AudioStreamBasicDescription mFormatOrig
static AudioStreamRangedVec FormatsList(AudioStreamID s)
static AudioDeviceID GetDefaultOutputDevice()
AudioDeviceIOProcID mIoProcID
bool GetMixingSupport()
static pid_t GetHogStatus()
static RatesVec RatesList(AudioDeviceID d)
static AudioStreamIDVec StreamsList(AudioDeviceID d)
Get a list of all the streams on this device.
AudioDeviceID mDeviceID
bool ChannelsList(AudioDeviceID d, bool passthru, ChannelsArr &chans)
UInt32 mBytesPerPacket
static int AudioStreamChangeFormat(AudioStreamID s, AudioStreamBasicDescription format)
AudioUnit mOutputUnit
static bool GetAutoHogMode()
bool SetHogStatus(bool hog)
bool SetMixingSupport(bool mix)
bool OpenDevice()
AudioStreamBasicDescription mFormatNew
CoreAudioData(AudioOutputCA *parent)
static void ResetStream(AudioStreamID s)
AudioStreamID mStreamID
AudioOutputCA * mCA
QString * GetName()
int GetTotalOutputChannels()
static void ResetAudioDevices()
Reset any devices with an AC3 stream back to a Linear PCM so that they can become a default output de...
QString GetSetting(const QString &key, const QString &defaultval="")
int GetNumSetting(const QString &key, int defaultval=0)
Contains Packet Identifier numeric values.
Definition: mpegtables.h:207
bool m_internalVol
Definition: volumebase.h:43
virtual void SetCurrentVolume(int value)
Definition: volumebase.cpp:124
unsigned int uint
Definition: compat.h:60
static const iso6937table * d
std::enable_if_t< std::is_floating_point_v< T >, std::chrono::milliseconds > millisecondsFromFloat(T value)
Helper function for convert a floating point number to a duration.
Definition: mythchrono.h:91
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39