2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
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"
27 #import <AVFoundation/AVAudioSession.h>
28 #include <AudioToolbox/AudioToolbox.h>
31 using namespace std::chrono_literals;
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()
49 AVAudioSession* myAudioSession = [AVAudioSession sharedInstance];
50 AVAudioSessionRouteDescription* currentRoute = [myAudioSession currentRoute];
51 NSString* output = [[currentRoute.outputs firstObject] portType];
53 route = [output UTF8String];
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)
86 NSError* err = nullptr;
87 // deactvivate the session
88 AVAudioSession* mySession = [AVAudioSession sharedInstance];
89 if (![mySession setActive:NO error:&err])
91 CLog::Log(LOGWARNING, "AVAudioSession setActive NO failed, count {}", count);
93 rtn = deactivateAudioSession(count);
102 static void setAVAudioSessionProperties(NSTimeInterval bufferseconds,
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];
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);
122 [mySession setPreferredOutputNumberOfChannels:channels error:&err];
124 CLog::Log(LOGWARNING, "{} setPreferredOutputNumberOfChannels failed", __PRETTY_FUNCTION__);
126 // change the sameple rate
128 [mySession setPreferredSampleRate:samplerate error:&err];
130 CLog::Log(LOGWARNING, "{} setPreferredSampleRate failed", __PRETTY_FUNCTION__);
132 // change the i/o buffer duration
134 [mySession setPreferredIOBufferDuration:bufferseconds error:&err];
136 CLog::Log(LOGWARNING, "{} setPreferredIOBufferDuration failed", __PRETTY_FUNCTION__);
138 // reactivate the session
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,
163 // frequency in cycles per second
164 // 2*PI radians per sine wave cycle
165 // sample rate in samples per second
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;
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;
199 #pragma mark - CAAudioUnitSink
200 /***************************************************************************************/
201 /***************************************************************************************/
202 class CAAudioUnitSink
208 bool open(AudioStreamBasicDescription outputFormat, size_t buffer_size);
212 void updatedelay(AEDelayStatus& status);
214 unsigned int sampletrate() { return m_outputFormat.mSampleRate; };
215 unsigned int write(uint8_t* data, unsigned int frames, unsigned int framesize);
222 static OSStatus renderCallback(void* inRefCon,
223 AudioUnitRenderActionFlags* ioActionFlags,
224 const AudioTimeStamp* inTimeStamp,
225 UInt32 inOutputBusNumber,
226 UInt32 inNumberFrames,
227 AudioBufferList* ioData);
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()
258 bool CAAudioUnitSink::open(AudioStreamBasicDescription outputFormat, size_t buffer_size)
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);
272 bool CAAudioUnitSink::close()
282 bool CAAudioUnitSink::activate()
288 AudioOutputUnitStart(m_audioUnit);
296 bool CAAudioUnitSink::deactivate()
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;
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.
327 CAESpinLock lock(m_render_section);
330 status.tick = m_render_timestamp;
333 size = m_buffer->GetReadSize();
336 } while (lock.retry());
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.
351 static_cast<double>(m_buffer->GetMaxSize()) / static_cast<double>(m_frameSize * m_sampleRate);
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);
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())
376 CLog::Log(LOGERROR, "{} engine didn't start in {} ms!", __FUNCTION__, timeout.count());
381 unsigned int write_frames = std::min(frames, m_buffer->GetWriteSize() / framesize);
383 m_buffer->Write(data, write_frames * framesize);
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)
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)
410 bool CAAudioUnitSink::setupAudio()
412 if (m_setup && m_audioUnit)
416 // Describe a default output unit.
417 AudioComponentDescription description = {};
418 description.componentType = kAudioUnitType_Output;
419 description.componentSubType = kAudioUnitSubType_RemoteIO;
420 description.componentManufacturer = kAudioUnitManufacturer_Apple;
423 AudioComponent component;
424 component = AudioComponentFindNext(nullptr, &description);
425 OSStatus status = AudioComponentInstanceNew(component, &m_audioUnit);
428 CLog::Log(LOGERROR, "{} error creating audioUnit (error: {})", __PRETTY_FUNCTION__,
429 static_cast<int>(status));
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)
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;
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);
466 CLog::Log(LOGERROR, "{} error setting stream format on audioUnit (error: {})",
467 __PRETTY_FUNCTION__, static_cast<int>(status));
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));
479 CLog::Log(LOGERROR, "{} error setting render callback for AudioUnit (error: {})",
480 __PRETTY_FUNCTION__, static_cast<int>(status));
484 status = AudioUnitInitialize(m_audioUnit);
487 CLog::Log(LOGERROR, "{} error initializing AudioUnit (error: {})", __PRETTY_FUNCTION__,
488 static_cast<int>(status));
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);
500 std::string formatString;
501 CLog::Log(LOGINFO, "{} setup audio format: {}", __PRETTY_FUNCTION__,
502 StreamDescriptionToString(m_outputFormat, formatString));
504 dumpAVAudioSessionProperties();
509 inline void LogLevel(unsigned int got, unsigned int wanted)
511 static unsigned int lastReported = INT_MAX;
514 if (got != lastReported)
516 CLog::Log(LOGWARNING, "DARWINIOS: {}flow ({} vs {} bytes)", got > wanted ? "over" : "under",
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++)
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);
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;
552 else if (bytes < wanted)
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);
560 sink->m_render_timestamp = inTimeStamp->mHostTime;
561 sink->m_render_section.leave();
562 // tell the sink we're good for more data
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);
599 NSInteger maxChannels = [[AVAudioSession sharedInstance] maximumOutputNumberOfChannels];
601 device.m_channels = AE_CH_LAYOUT_7_1;
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))
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
645 // no device, bail and let AE kick back to NULL device
647 std::string devicelower = device;
648 StringUtils::ToLower(devicelower);
649 for (size_t i = 0; i < m_devices.size(); i++)
651 if (devicelower.find(m_devices[i].m_deviceName) != std::string::npos)
653 m_info = m_devices[i];
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)
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)
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;
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;
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)
702 if (route.find("HDMI") != std::string::npos)
703 audioFormat.mSampleRate = 48000;
705 audioFormat.mSampleRate = 44100;
717 audioFormat.mSampleRate = 48000;
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;
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)
753 // if 6, then audio is set to Digital Dolby 5.1, need to use DD mapping
754 channel_index = CAChannel_PCM_DD5_1;
756 else if (format.m_channelLayout.Count() == 5)
758 // if 5, then audio is set to Digital Dolby 5.0, need to use DD mapping
759 channel_index = CAChannel_PCM_DD5_1;
763 if (format.m_channelLayout.Count() > 6)
764 channel_index = CAChannel_PCM_8CHAN;
766 for (size_t chan = 0; chan < format.m_channelLayout.Count(); ++chan)
768 if (chan < maxChannels)
769 channel_info += CAChannelMap[channel_index][chan];
771 format.m_channelLayout = channel_info;
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);
784 switch (format.m_streamInfo.m_type)
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;
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;
798 case CAEStreamInfo::STREAM_TYPE_DTS_512:
799 case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE:
800 format.m_frames = 512;
803 case CAEStreamInfo::STREAM_TYPE_DTS_1024:
804 format.m_frames = 1024;
807 case CAEStreamInfo::STREAM_TYPE_DTS_2048:
808 format.m_frames = 2048;
812 format.m_frames = 1024;
813 buffer_size = (512 * audioFormat.mBytesPerFrame) * 8;
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();
821 format.m_channelLayout.Count() * (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3);
825 if (!m_audioSink->activate())
831 void CAESinkDARWINTVOS::Deinitialize()
834 m_audioSink = nullptr;
837 void CAESinkDARWINTVOS::GetDelay(AEDelayStatus& status)
840 m_audioSink->updatedelay(status);
842 status.SetDelay(0.0);
845 double CAESinkDARWINTVOS::GetCacheTotal()
848 return m_audioSink->buffertime();
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)
858 float* samples = static_cast<float*>(buffer);
859 for (unsigned int j = 0; j < frames; j++)
861 float sample = SineWaveGeneratorNextSampleFloat(&m_SineWaveGenerator);
868 int16_t* samples = (int16_t*)buffer;
869 for (unsigned int j = 0; j < frames; j++)
871 int16_t sample = SineWaveGeneratorNextSampleInt16(&m_SineWaveGenerator);
878 return m_audioSink->write(buffer, frames, m_format.m_frameSize);
882 void CAESinkDARWINTVOS::Drain()
885 m_audioSink->drain();
888 bool CAESinkDARWINTVOS::HasVolume()
893 void CAESinkDARWINTVOS::EnumerateDevicesEx(AEDeviceInfoList& list, bool force)
896 EnumerateDevices(m_devices);