[WASAPI] set stream audio category
[xbmc.git] / xbmc / cores / AudioEngine / Sinks / AESinkDARWINTVOS.mm
blob162b5c9650adf5a2de2d412621b6b1239486a82d
1 /*
2  *  Copyright (C) 2005-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  */
9 #include "AESinkDARWINTVOS.h"
11 #include "ServiceBroker.h"
12 #include "cores/AudioEngine/AESinkFactory.h"
13 #include "cores/AudioEngine/Sinks/darwin/CoreAudioHelpers.h"
14 #include "cores/AudioEngine/Utils/AERingBuffer.h"
15 #include "cores/AudioEngine/Utils/AEUtil.h"
16 #include "threads/Condition.h"
17 #include "threads/SystemClock.h"
18 #include "utils/StringUtils.h"
19 #include "utils/log.h"
20 #include "windowing/WinSystem.h"
22 #include "platform/darwin/DarwinUtils.h"
24 #include <mutex>
25 #include <sstream>
27 #import <AVFoundation/AVAudioSession.h>
28 #include <AudioToolbox/AudioToolbox.h>
29 #include <unistd.h>
31 using namespace std::chrono_literals;
33 enum CAChannelIndex
35   CAChannel_PCM_6CHAN = 0,
36   CAChannel_PCM_8CHAN = 1,
37   CAChannel_PCM_DD5_1 = 2,
40 static enum AEChannel CAChannelMap[3][9] = {
41     {AE_CH_FL, AE_CH_FR, AE_CH_LFE, AE_CH_FC, AE_CH_BL, AE_CH_BR, AE_CH_NULL},
42     {AE_CH_FL, AE_CH_FR, AE_CH_LFE, AE_CH_FC, AE_CH_SL, AE_CH_SR, AE_CH_BL, AE_CH_BR, AE_CH_NULL},
43     {AE_CH_FL, AE_CH_FC, AE_CH_FR, AE_CH_BL, AE_CH_BR, AE_CH_LFE, AE_CH_NULL},
46 static std::string getAudioRoute()
48   std::string route;
49   AVAudioSession* myAudioSession = [AVAudioSession sharedInstance];
50   AVAudioSessionRouteDescription* currentRoute = [myAudioSession currentRoute];
51   NSString* output = [[currentRoute.outputs firstObject] portType];
52   if (output)
53     route = [output UTF8String];
55   return route;
58 static void dumpAVAudioSessionProperties()
60   std::string route = getAudioRoute();
61   CLog::Log(LOGINFO, "{} audio route = {}", __PRETTY_FUNCTION__, route.empty() ? "NONE" : route);
63   AVAudioSession* mySession = [AVAudioSession sharedInstance];
65   CLog::Log(LOGINFO, "{} sampleRate {:f}", __PRETTY_FUNCTION__, [mySession sampleRate]);
66   CLog::Log(LOGINFO, "{} outputLatency {:f}", __PRETTY_FUNCTION__, [mySession outputLatency]);
67   CLog::Log(LOGINFO, "{} IOBufferDuration {:f}", __PRETTY_FUNCTION__, [mySession IOBufferDuration]);
68   CLog::Log(LOGINFO, "{} outputNumberOfChannels {}", __PRETTY_FUNCTION__,
69             static_cast<long>([mySession outputNumberOfChannels]));
70   // maximumOutputNumberOfChannels provides hints to tvOS audio settings
71   // if 2, then audio is set to two channel stereo. iOS return this unless hdmi connected
72   // if 6, then audio is set to Digital Dolby 5.1 OR hdmi path detected sink can only handle 6 channels.
73   // if 8, then audio is set to Best Quality AND hdmi path detected sink can handle 8 channels.
74   CLog::Log(LOGINFO, "{} maximumOutputNumberOfChannels {}", __PRETTY_FUNCTION__,
75             static_cast<long>([mySession maximumOutputNumberOfChannels]));
77   //CDarwinUtils::DumpAudioDescriptions(__PRETTY_FUNCTION__);
80 static bool deactivateAudioSession(int count)
82   if (--count < 0)
83     return false;
85   bool rtn = false;
86   NSError* err = nullptr;
87   // deactvivate the session
88   AVAudioSession* mySession = [AVAudioSession sharedInstance];
89   if (![mySession setActive:NO error:&err])
90   {
91     CLog::Log(LOGWARNING, "AVAudioSession setActive NO failed, count {}", count);
92     usleep(10 * 1000);
93     rtn = deactivateAudioSession(count);
94   }
95   else
96   {
97     rtn = true;
98   }
99   return rtn;
102 static void setAVAudioSessionProperties(NSTimeInterval bufferseconds,
103                                         double samplerate,
104                                         int channels)
106   // darwin docs and technotes say,
107   // deavtivate the session before changing the values
108   AVAudioSession* mySession = [AVAudioSession sharedInstance];
110   // need to fetch maximumOutputNumberOfChannels when active
111   NSInteger maxchannels = [mySession maximumOutputNumberOfChannels];
113   NSError* err = nil;
114   // deactvivate the session
115   if (!deactivateAudioSession(10))
116     CLog::Log(LOGWARNING, "AVAudioSession setActive NO failed: {}", static_cast<long>(err.code));
118   // change the number of channels
119   if (channels > maxchannels)
120     channels = static_cast<UInt32>(maxchannels);
121   err = nil;
122   [mySession setPreferredOutputNumberOfChannels:channels error:&err];
123   if (err != nil)
124     CLog::Log(LOGWARNING, "{} setPreferredOutputNumberOfChannels failed", __PRETTY_FUNCTION__);
126   // change the sameple rate
127   err = nil;
128   [mySession setPreferredSampleRate:samplerate error:&err];
129   if (err != nil)
130     CLog::Log(LOGWARNING, "{} setPreferredSampleRate failed", __PRETTY_FUNCTION__);
132   // change the i/o buffer duration
133   err = nil;
134   [mySession setPreferredIOBufferDuration:bufferseconds error:&err];
135   if (err != nil)
136     CLog::Log(LOGWARNING, "{} setPreferredIOBufferDuration failed", __PRETTY_FUNCTION__);
138   // reactivate the session
139   err = nil;
140   if (![mySession setActive:YES error:&err])
141     CLog::Log(LOGWARNING, "AVAudioSession setActive YES failed: {}", static_cast<long>(err.code));
143   // check that we got the samperate what we asked for
144   if (samplerate != [mySession sampleRate])
145     CLog::Log(LOGWARNING, "sampleRate does not match: asked {:f}, is {:f}", samplerate,
146               [mySession sampleRate]);
148   // check that we got the number of channels what we asked for
149   if (channels != [mySession outputNumberOfChannels])
150     CLog::Log(LOGWARNING, "number of channels do not match: asked {}, is {}", channels,
151               static_cast<long>([mySession outputNumberOfChannels]));
154 #pragma mark - SineWaveGenerator
155 /***************************************************************************************/
156 /***************************************************************************************/
157 #if DO_440HZ_TONE_TEST
158 static void SineWaveGeneratorInitWithFrequency(SineWaveGenerator* ctx,
159                                                double frequency,
160                                                double samplerate)
162   // Given:
163   //   frequency in cycles per second
164   //   2*PI radians per sine wave cycle
165   //   sample rate in samples per second
166   //
167   // Then:
168   //   cycles     radians     seconds     radians
169   //   ------  *  -------  *  -------  =  -------
170   //   second      cycle      sample      sample
171   ctx->currentPhase = 0.0;
172   ctx->phaseIncrement = frequency * 2 * M_PI / samplerate;
175 static int16_t SineWaveGeneratorNextSampleInt16(SineWaveGenerator* ctx)
177   int16_t sample = INT16_MAX * sinf(ctx->currentPhase);
179   ctx->currentPhase += ctx->phaseIncrement;
180   // Keep the value between 0 and 2*M_PI
181   while (ctx->currentPhase > 2 * M_PI)
182     ctx->currentPhase -= 2 * M_PI;
184   return sample / 4;
186 static float SineWaveGeneratorNextSampleFloat(SineWaveGenerator* ctx)
188   float sample = MAXFLOAT * sinf(ctx->currentPhase);
190   ctx->currentPhase += ctx->phaseIncrement;
191   // Keep the value between 0 and 2*M_PI
192   while (ctx->currentPhase > 2 * M_PI)
193     ctx->currentPhase -= 2 * M_PI;
195   return sample / 4;
197 #endif
199 #pragma mark - CAAudioUnitSink
200 /***************************************************************************************/
201 /***************************************************************************************/
202 class CAAudioUnitSink
204 public:
205   CAAudioUnitSink();
206   ~CAAudioUnitSink();
208   bool open(AudioStreamBasicDescription outputFormat, size_t buffer_size);
209   bool close();
210   bool activate();
211   bool deactivate();
212   void updatedelay(AEDelayStatus& status);
213   double buffertime();
214   unsigned int sampletrate() { return m_outputFormat.mSampleRate; };
215   unsigned int write(uint8_t* data, unsigned int frames, unsigned int framesize);
216   void drain();
218 private:
219   bool setupAudio();
221   // callbacks
222   static OSStatus renderCallback(void* inRefCon,
223                                  AudioUnitRenderActionFlags* ioActionFlags,
224                                  const AudioTimeStamp* inTimeStamp,
225                                  UInt32 inOutputBusNumber,
226                                  UInt32 inNumberFrames,
227                                  AudioBufferList* ioData);
229   bool m_setup;
230   bool m_activated = false;
231   AudioUnit m_audioUnit;
232   AudioStreamBasicDescription m_outputFormat;
233   AERingBuffer* m_buffer = nullptr;
235   Float32 m_totalLatency;
236   Float32 m_inputLatency;
237   Float32 m_outputLatency;
238   Float32 m_bufferDuration;
240   unsigned int m_sampleRate;
241   unsigned int m_frameSize;
243   std::atomic<bool> m_started;
245   CAESpinSection m_render_section;
246   std::atomic<int64_t> m_render_timestamp;
249 CAAudioUnitSink::CAAudioUnitSink() : m_started(false), m_render_timestamp(0)
253 CAAudioUnitSink::~CAAudioUnitSink()
255   close();
258 bool CAAudioUnitSink::open(AudioStreamBasicDescription outputFormat, size_t buffer_size)
260   m_setup = false;
261   m_outputFormat = outputFormat;
262   m_outputLatency = 0.0;
263   m_bufferDuration = 0.0;
264   m_sampleRate = static_cast<unsigned int>(outputFormat.mSampleRate);
265   m_frameSize = outputFormat.mChannelsPerFrame * outputFormat.mBitsPerChannel / 8;
267   m_buffer = new AERingBuffer(buffer_size);
269   return setupAudio();
272 bool CAAudioUnitSink::close()
274   deactivate();
275   delete m_buffer;
276   m_buffer = NULL;
278   m_started = false;
279   return true;
282 bool CAAudioUnitSink::activate()
284   if (!m_activated)
285   {
286     if (setupAudio())
287     {
288       AudioOutputUnitStart(m_audioUnit);
289       m_activated = true;
290     }
291   }
293   return m_activated;
296 bool CAAudioUnitSink::deactivate()
298   if (m_activated)
299   {
300     AudioUnitReset(m_audioUnit, kAudioUnitScope_Global, 0);
302     // this is a delayed call, the OS will block here
303     // until the autio unit actually is stopped.
304     AudioOutputUnitStop(m_audioUnit);
306     // detach the render callback on the unit
307     AURenderCallbackStruct callbackStruct = {};
308     AudioUnitSetProperty(m_audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input,
309                          0, &callbackStruct, sizeof(callbackStruct));
311     AudioUnitUninitialize(m_audioUnit);
312     AudioComponentInstanceDispose(m_audioUnit), m_audioUnit = nullptr;
314     m_setup = false;
315     m_activated = false;
316   }
318   return m_activated;
321 void CAAudioUnitSink::updatedelay(AEDelayStatus& status)
323   // return the number of audio frames in buffer, in seconds
324   // use internal framesize, once written,
325   // bytes in buffer are owned by CAAudioUnitSink.
326   unsigned int size;
327   CAESpinLock lock(m_render_section);
328   do
329   {
330     status.tick = m_render_timestamp;
331     status.delay = 0;
332     if (m_buffer)
333       size = m_buffer->GetReadSize();
334     else
335       size = 0;
336   } while (lock.retry());
338   // bytes to seconds
339   status.delay += static_cast<double>(size) / static_cast<double>(m_frameSize) /
340                   static_cast<double>(m_sampleRate);
341   // add in hw delay and total latency (in seconds)
342   status.delay += static_cast<double>(m_totalLatency);
345 double CAAudioUnitSink::buffertime()
347   // return the number of audio frames for the total buffer size, in seconds
348   // use internal framesize, buffer is owned by CAAudioUnitSink.
349   double buffertime;
350   buffertime =
351       static_cast<double>(m_buffer->GetMaxSize()) / static_cast<double>(m_frameSize * m_sampleRate);
352   return buffertime;
355 CCriticalSection mutex;
356 XbmcThreads::ConditionVariable condVar;
358 unsigned int CAAudioUnitSink::write(uint8_t* data, unsigned int frames, unsigned int framesize)
360   // use the passed in framesize instead of internal,
361   // writes are relative to AE formats. once written,
362   // CAAudioUnitSink owns them.
363   if (m_buffer->GetWriteSize() < frames * framesize)
364   { // no space to write - wait for a bit
365     std::unique_lock<CCriticalSection> lock(mutex);
366     auto timeout = std::chrono::milliseconds(900 * frames / m_sampleRate);
367     if (!m_started)
368       timeout = 4500ms;
370     // we are using a timer here for being sure for timeouts
371     // condvar can be woken spuriously as signaled
372     XbmcThreads::EndTime<> timer(timeout);
373     condVar.wait(mutex, timeout);
374     if (!m_started && timer.IsTimePast())
375     {
376       CLog::Log(LOGERROR, "{} engine didn't start in {} ms!", __FUNCTION__, timeout.count());
377       return INT_MAX;
378     }
379   }
381   unsigned int write_frames = std::min(frames, m_buffer->GetWriteSize() / framesize);
382   if (write_frames)
383     m_buffer->Write(data, write_frames * framesize);
385   return write_frames;
388 void CAAudioUnitSink::drain()
390   unsigned int bytes = m_buffer->GetReadSize();
391   unsigned int totalBytes = bytes;
392   int maxNumTimeouts = 3;
393   auto timeout = std::chrono::milliseconds(static_cast<int>(buffertime()));
395   while (bytes && maxNumTimeouts > 0)
396   {
397     std::unique_lock<CCriticalSection> lock(mutex);
398     XbmcThreads::EndTime<> timer(timeout);
399     condVar.wait(mutex, timeout);
401     bytes = m_buffer->GetReadSize();
402     // if we timeout and do not consume bytes,
403     // decrease maxNumTimeouts and try again.
404     if (timer.IsTimePast() && bytes == totalBytes)
405       maxNumTimeouts--;
406     totalBytes = bytes;
407   }
410 bool CAAudioUnitSink::setupAudio()
412   if (m_setup && m_audioUnit)
413     return true;
415   // Audio Unit Setup
416   // Describe a default output unit.
417   AudioComponentDescription description = {};
418   description.componentType = kAudioUnitType_Output;
419   description.componentSubType = kAudioUnitSubType_RemoteIO;
420   description.componentManufacturer = kAudioUnitManufacturer_Apple;
422   // Get component
423   AudioComponent component;
424   component = AudioComponentFindNext(nullptr, &description);
425   OSStatus status = AudioComponentInstanceNew(component, &m_audioUnit);
426   if (status != noErr)
427   {
428     CLog::Log(LOGERROR, "{} error creating audioUnit (error: {})", __PRETTY_FUNCTION__,
429               static_cast<int>(status));
430     return false;
431   }
433   // set the hw buffer size (in seconds), this affects the number of samples
434   // that get rendered every time the audio callback is fired.
435   double samplerate = m_outputFormat.mSampleRate;
436   int channels = m_outputFormat.mChannelsPerFrame;
437   NSTimeInterval bufferseconds =
438       1024 * m_outputFormat.mChannelsPerFrame / m_outputFormat.mSampleRate;
439   CLog::Log(LOGINFO, "{} setting channels {}", __PRETTY_FUNCTION__, channels);
440   CLog::Log(LOGINFO, "{} setting samplerate {:f}", __PRETTY_FUNCTION__, samplerate);
441   CLog::Log(LOGINFO, "{} setting buffer duration to {:f}", __PRETTY_FUNCTION__, bufferseconds);
442   setAVAudioSessionProperties(bufferseconds, samplerate, channels);
444   // Get the real output samplerate, the requested might not available
445   Float64 realisedSampleRate = [[AVAudioSession sharedInstance] sampleRate];
446   if (m_outputFormat.mSampleRate != realisedSampleRate)
447   {
448     CLog::Log(LOGINFO,
449               "{} couldn't set requested samplerate {}, AudioUnit will resample to {} instead",
450               __PRETTY_FUNCTION__, static_cast<int>(m_outputFormat.mSampleRate),
451               static_cast<int>(realisedSampleRate));
452     // if we don't want AudioUnit to resample - but instead let activeae resample -
453     // reflect the realised samplerate to the output format here
454     // well maybe it is handy in the future - as of writing this
455     // AudioUnit was about 6 times faster then activeae ;)
456     //m_outputFormat.mSampleRate = realisedSampleRate;
457     //m_sampleRate = realisedSampleRate;
458   }
460   // Set the output stream format
461   UInt32 ioDataSize = sizeof(AudioStreamBasicDescription);
462   status = AudioUnitSetProperty(m_audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
463                                 0, &m_outputFormat, ioDataSize);
464   if (status != noErr)
465   {
466     CLog::Log(LOGERROR, "{} error setting stream format on audioUnit (error: {})",
467               __PRETTY_FUNCTION__, static_cast<int>(status));
468     return false;
469   }
471   // Attach a render callback on the unit
472   AURenderCallbackStruct callbackStruct = {};
473   callbackStruct.inputProc = renderCallback;
474   callbackStruct.inputProcRefCon = this;
475   status = AudioUnitSetProperty(m_audioUnit, kAudioUnitProperty_SetRenderCallback,
476                                 kAudioUnitScope_Input, 0, &callbackStruct, sizeof(callbackStruct));
477   if (status != noErr)
478   {
479     CLog::Log(LOGERROR, "{} error setting render callback for AudioUnit (error: {})",
480               __PRETTY_FUNCTION__, static_cast<int>(status));
481     return false;
482   }
484   status = AudioUnitInitialize(m_audioUnit);
485   if (status != noErr)
486   {
487     CLog::Log(LOGERROR, "{} error initializing AudioUnit (error: {})", __PRETTY_FUNCTION__,
488               static_cast<int>(status));
489     return false;
490   }
492   AVAudioSession* mySession = [AVAudioSession sharedInstance];
493   m_inputLatency = [mySession inputLatency];
494   m_outputLatency = [mySession outputLatency];
495   m_bufferDuration = [mySession IOBufferDuration];
496   m_totalLatency = m_outputLatency + m_bufferDuration;
497   CLog::Log(LOGINFO, "{} total latency = {:f}", __PRETTY_FUNCTION__, m_totalLatency);
499   m_setup = true;
500   std::string formatString;
501   CLog::Log(LOGINFO, "{} setup audio format: {}", __PRETTY_FUNCTION__,
502             StreamDescriptionToString(m_outputFormat, formatString));
504   dumpAVAudioSessionProperties();
506   return m_setup;
509 inline void LogLevel(unsigned int got, unsigned int wanted)
511   static unsigned int lastReported = INT_MAX;
512   if (got != wanted)
513   {
514     if (got != lastReported)
515     {
516       CLog::Log(LOGWARNING, "DARWINIOS: {}flow ({} vs {} bytes)", got > wanted ? "over" : "under",
517                 got, wanted);
518       lastReported = got;
519     }
520   }
521   else
522     lastReported = INT_MAX; // indicate we were good at least once
525 OSStatus CAAudioUnitSink::renderCallback(void* inRefCon,
526                                          AudioUnitRenderActionFlags* ioActionFlags,
527                                          const AudioTimeStamp* inTimeStamp,
528                                          UInt32 inOutputBusNumber,
529                                          UInt32 inNumberFrames,
530                                          AudioBufferList* ioData)
532   CAAudioUnitSink* sink = (CAAudioUnitSink*)inRefCon;
534   sink->m_render_section.enter();
535   sink->m_started = true;
537   for (unsigned int i = 0; i < ioData->mNumberBuffers; i++)
538   {
539     unsigned int wanted = ioData->mBuffers[i].mDataByteSize;
540     unsigned int bytes = std::min(sink->m_buffer->GetReadSize(), wanted);
541     sink->m_buffer->Read(static_cast<unsigned char*>(ioData->mBuffers[i].mData), bytes);
542     LogLevel(bytes, wanted);
544     if (bytes == 0)
545     {
546       // Apple iOS docs say kAudioUnitRenderAction_OutputIsSilence provides a hint to
547       // the audio unit that there is no audio to process. and you must also explicitly
548       // set the buffers contents pointed at by the ioData parameter to 0.
549       memset(ioData->mBuffers[i].mData, 0x00, ioData->mBuffers[i].mDataByteSize);
550       *ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence;
551     }
552     else if (bytes < wanted)
553     {
554       // zero out what we did not copy over (underflow)
555       uint8_t* empty = static_cast<uint8_t*>(ioData->mBuffers[i].mData) + bytes;
556       memset(empty, 0x00, wanted - bytes);
557     }
558   }
560   sink->m_render_timestamp = inTimeStamp->mHostTime;
561   sink->m_render_section.leave();
562   // tell the sink we're good for more data
563   condVar.notifyAll();
565   return noErr;
568 #pragma mark - EnumerateDevices
569 /***************************************************************************************/
570 /***************************************************************************************/
571 static void EnumerateDevices(AEDeviceInfoList& list)
573   CAEDeviceInfo device;
575   device.m_deviceName = "default";
576   device.m_displayName = "Default";
577   device.m_displayNameExtra = "";
579   // if not hdmi,  CAESinkDARWINIOS::Initialize will kick back to 2 channel PCM
580   device.m_deviceType = AE_DEVTYPE_HDMI;
581   device.m_wantsIECPassthrough = true;
583   // Passthrough only working < tvos 11.2??
584   device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
585   device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_EAC3);
586   device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
587   device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
588   device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
589   device.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
591   device.m_sampleRates.push_back(44100);
592   device.m_sampleRates.push_back(48000);
594   device.m_dataFormats.push_back(AE_FMT_RAW);
595   device.m_dataFormats.push_back(AE_FMT_S16LE);
596   device.m_dataFormats.push_back(AE_FMT_FLOAT);
598   // add channel info
599   NSInteger maxChannels = [[AVAudioSession sharedInstance] maximumOutputNumberOfChannels];
600   if (maxChannels > 6)
601     device.m_channels = AE_CH_LAYOUT_7_1;
602   else
603     device.m_channels = AE_CH_LAYOUT_5_1;
605   CLog::Log(LOGDEBUG, "EnumerateDevices:Device({})", device.m_deviceName);
607   list.push_back(device);
610 #pragma mark - AEDeviceInfoList
611 /***************************************************************************************/
612 /***************************************************************************************/
613 AEDeviceInfoList CAESinkDARWINTVOS::m_devices;
615 CAESinkDARWINTVOS::CAESinkDARWINTVOS()
619 void CAESinkDARWINTVOS::Register()
621   AE::AESinkRegEntry reg;
622   reg.sinkName = "DARWINTVOS";
623   reg.createFunc = CAESinkDARWINTVOS::Create;
624   reg.enumerateFunc = CAESinkDARWINTVOS::EnumerateDevicesEx;
625   AE::CAESinkFactory::RegisterSink(reg);
628 std::unique_ptr<IAESink> CAESinkDARWINTVOS::Create(std::string& device,
629                                                    AEAudioFormat& desiredFormat)
631   auto sink = std::make_unique<CAESinkDARWINTVOS>();
632   if (sink->Initialize(desiredFormat, device))
633     return sink;
635   return {};
638 bool CAESinkDARWINTVOS::Initialize(AEAudioFormat& format, std::string& device)
640   std::string route = getAudioRoute();
641   // no route, no audio. bail and let AE kick back to NULL device
642   if (route.empty())
643     return false;
645   // no device, bail and let AE kick back to NULL device
646   bool found = false;
647   std::string devicelower = device;
648   StringUtils::ToLower(devicelower);
649   for (size_t i = 0; i < m_devices.size(); i++)
650   {
651     if (devicelower.find(m_devices[i].m_deviceName) != std::string::npos)
652     {
653       m_info = m_devices[i];
654       found = true;
655       break;
656     }
657   }
658   if (!found)
659     return false;
661   AudioStreamBasicDescription audioFormat = {};
662   audioFormat.mFormatID = kAudioFormatLinearPCM;
664   // check if are we dealing with raw formats or pcm
665   bool passthrough = false;
666   switch (format.m_dataFormat)
667   {
668     case AE_FMT_RAW:
669       // this will be selected when AE wants AC3 or DTS or anything other then float
670       format.m_dataFormat = AE_FMT_S16LE;
671       audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
672       if (route.find("HDMI") != std::string::npos)
673         passthrough = true;
674       else
675       {
676         // this should never happen but we cover it just in case
677         // for iOS/tvOS, if we are not hdmi, we cannot do raw
678         // so kick back to pcm.
679         format.m_dataFormat = AE_FMT_FLOAT;
680         audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
681       }
682       break;
683     default:
684       // AE lies, even when we register formats we can handle,
685       // it shoves everything down and it is up to the sink
686       // to check/verify and kick back to what the sink supports
687       format.m_dataFormat = AE_FMT_FLOAT;
688       audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
689       break;
690   }
692   // check and correct sample rates to what we support,
693   // remember, AE is a lier and we need to check/verify
694   // and kick back to what the sink supports
695   switch (format.m_sampleRate)
696   {
697     case 11025:
698     case 22050:
699     case 44100:
700     case 88200:
701     case 176400:
702       if (route.find("HDMI") != std::string::npos)
703         audioFormat.mSampleRate = 48000;
704       else
705         audioFormat.mSampleRate = 44100;
706       break;
707     default:
708     case 8000:
709     case 12000:
710     case 16000:
711     case 24000:
712     case 32000:
713     case 48000:
714     case 96000:
715     case 192000:
716     case 384000:
717       audioFormat.mSampleRate = 48000;
718       break;
719   }
721   if (passthrough)
722   {
723     // passthrough is special, PCM encapsulated IEC61937 packets.
724     // make sure input and output samplerate match for preventing resampling
725     audioFormat.mSampleRate = [[AVAudioSession sharedInstance] sampleRate];
726     audioFormat.mFramesPerPacket = 1; // must be 1
727     audioFormat.mChannelsPerFrame = 2; // passthrough needs 2 channels
728     audioFormat.mBitsPerChannel = 16;
729     audioFormat.mBytesPerFrame = audioFormat.mChannelsPerFrame * (audioFormat.mBitsPerChannel >> 3);
730     audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame * audioFormat.mFramesPerPacket;
731     audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsPacked;
732   }
733   else
734   {
735     NSInteger maxChannels = [[AVAudioSession sharedInstance] maximumOutputNumberOfChannels];
736     audioFormat.mFramesPerPacket = 1; // must be 1
738     // tvos supports up to 8 channels
739     audioFormat.mChannelsPerFrame = format.m_channelLayout.Count();
740     // clamp number of channels to what tvOS reports
741     if (maxChannels == 2)
742       audioFormat.mChannelsPerFrame = (UInt32)maxChannels;
744     audioFormat.mBitsPerChannel = CAEUtil::DataFormatToBits(format.m_dataFormat);
745     audioFormat.mBytesPerFrame = audioFormat.mChannelsPerFrame * (audioFormat.mBitsPerChannel >> 3);
746     audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame * audioFormat.mFramesPerPacket;
747     audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsPacked;
749     CAEChannelInfo channel_info;
750     CAChannelIndex channel_index = CAChannel_PCM_6CHAN;
751     if (maxChannels == 6 && format.m_channelLayout.Count() == 6)
752     {
753       // if 6, then audio is set to Digital Dolby 5.1, need to use DD mapping
754       channel_index = CAChannel_PCM_DD5_1;
755     }
756     else if (format.m_channelLayout.Count() == 5)
757     {
758       // if 5, then audio is set to Digital Dolby 5.0, need to use DD mapping
759       channel_index = CAChannel_PCM_DD5_1;
760     }
761     else
762     {
763       if (format.m_channelLayout.Count() > 6)
764         channel_index = CAChannel_PCM_8CHAN;
765     }
766     for (size_t chan = 0; chan < format.m_channelLayout.Count(); ++chan)
767     {
768       if (chan < maxChannels)
769         channel_info += CAChannelMap[channel_index][chan];
770     }
771     format.m_channelLayout = channel_info;
772   }
774   std::string formatString;
775   CLog::Log(LOGDEBUG, "{}: AudioStreamBasicDescription: {} {}", __PRETTY_FUNCTION__,
776             StreamDescriptionToString(audioFormat, formatString),
777             passthrough ? "passthrough" : "pcm");
779 #if DO_440HZ_TONE_TEST
780   SineWaveGeneratorInitWithFrequency(&m_SineWaveGenerator, 440.0, audioFormat.mSampleRate);
781 #endif
783   size_t buffer_size;
784   switch (format.m_streamInfo.m_type)
785   {
786     case CAEStreamInfo::STREAM_TYPE_AC3:
787       if (!format.m_streamInfo.m_frameSize)
788         format.m_streamInfo.m_frameSize = 1536;
789       format.m_frames = format.m_streamInfo.m_frameSize;
790       buffer_size = format.m_frames * 8;
791       break;
792     case CAEStreamInfo::STREAM_TYPE_EAC3:
793       if (!format.m_streamInfo.m_frameSize)
794         format.m_streamInfo.m_frameSize = 1536;
795       format.m_frames = format.m_streamInfo.m_frameSize;
796       buffer_size = format.m_frames * 8;
797       break;
798     case CAEStreamInfo::STREAM_TYPE_DTS_512:
799     case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE:
800       format.m_frames = 512;
801       buffer_size = 16384;
802       break;
803     case CAEStreamInfo::STREAM_TYPE_DTS_1024:
804       format.m_frames = 1024;
805       buffer_size = 16384;
806       break;
807     case CAEStreamInfo::STREAM_TYPE_DTS_2048:
808       format.m_frames = 2048;
809       buffer_size = 16384;
810       break;
811     default:
812       format.m_frames = 1024;
813       buffer_size = (512 * audioFormat.mBytesPerFrame) * 8;
814       break;
815   }
816   m_audioSink = new CAAudioUnitSink;
817   m_audioSink->open(audioFormat, buffer_size);
818   // reset to the realised samplerate
819   format.m_sampleRate = m_audioSink->sampletrate();
820   format.m_frameSize =
821       format.m_channelLayout.Count() * (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3);
823   m_format = format;
825   if (!m_audioSink->activate())
826     return false;
828   return true;
831 void CAESinkDARWINTVOS::Deinitialize()
833   delete m_audioSink;
834   m_audioSink = nullptr;
837 void CAESinkDARWINTVOS::GetDelay(AEDelayStatus& status)
839   if (m_audioSink)
840     m_audioSink->updatedelay(status);
841   else
842     status.SetDelay(0.0);
845 double CAESinkDARWINTVOS::GetCacheTotal()
847   if (m_audioSink)
848     return m_audioSink->buffertime();
849   return 0.0;
852 unsigned int CAESinkDARWINTVOS::AddPackets(uint8_t** data, unsigned int frames, unsigned int offset)
854   uint8_t* buffer = data[0] + (offset * m_format.m_frameSize);
855 #if DO_440HZ_TONE_TEST
856   if (m_format.m_dataFormat == AE_FMT_FLOAT)
857   {
858     float* samples = static_cast<float*>(buffer);
859     for (unsigned int j = 0; j < frames; j++)
860     {
861       float sample = SineWaveGeneratorNextSampleFloat(&m_SineWaveGenerator);
862       *samples++ = sample;
863       *samples++ = sample;
864     }
865   }
866   else
867   {
868     int16_t* samples = (int16_t*)buffer;
869     for (unsigned int j = 0; j < frames; j++)
870     {
871       int16_t sample = SineWaveGeneratorNextSampleInt16(&m_SineWaveGenerator);
872       *samples++ = sample;
873       *samples++ = sample;
874     }
875   }
876 #endif
877   if (m_audioSink)
878     return m_audioSink->write(buffer, frames, m_format.m_frameSize);
879   return 0;
882 void CAESinkDARWINTVOS::Drain()
884   if (m_audioSink)
885     m_audioSink->drain();
888 bool CAESinkDARWINTVOS::HasVolume()
890   return false;
893 void CAESinkDARWINTVOS::EnumerateDevicesEx(AEDeviceInfoList& list, bool force)
895   m_devices.clear();
896   EnumerateDevices(m_devices);
897   list = m_devices;