@@ -79,12 +79,31 @@ void AVAudioSessionInterruptionNotificationCallback(CFNotificationCenterRef cent
7979{
8080 // Create the audio processing graph
8181 engine_ = [[AVAudioEngine alloc ] init ];
82+ if (!engine_) {
83+ os_log_error (log_, " Unable to create AVAudioEngine instance" );
84+ throw std::runtime_error (" Unable to create AVAudioEngine" );
85+ }
86+
8287 AVAudioFormat *format = [[AVAudioFormat alloc ] initStandardFormatWithSampleRate: 44100 channels: 2 ];
83- if (!ConfigureProcessingGraphForFormat ( format, false ) ) {
84- os_log_error (log_, " Unable to create audio processing graph for 44.1 kHz stereo" );
85- throw std::runtime_error (" ConfigureProcessingGraphForFormat failed " );
88+ if (!format) {
89+ os_log_error (log_, " Unable to create AVAudioFormat for 44.1 kHz stereo" );
90+ throw std::runtime_error (" Unable to create AVAudioFormat " );
8691 }
8792
93+ playerNode_ = CreatePlayerNode (format);
94+ if (!playerNode_)
95+ throw std::runtime_error (" Unable to create audio player node" );
96+
97+ [engine_ attachNode: playerNode_];
98+ [engine_ connect: playerNode_ to: engine_.mainMixerNode format: format];
99+ [engine_ prepare ];
100+
101+ // TODO: Is it necessary to adjust the player node's maximum frames to render for 44.1?
102+
103+ #if DEBUG
104+ LogProcessingGraphDescription (log_, OS_LOG_TYPE_DEBUG);
105+ #endif /* DEBUG */
106+
88107 // Register for configuration change notifications
89108 auto notificationCenter = CFNotificationCenterGetLocalCenter ();
90109 CFNotificationCenterAddObserver (notificationCenter, this , AVAudioEngineConfigurationChangeNotificationCallback, (__bridge CFStringRef)AVAudioEngineConfigurationChangeNotification, (__bridge void *)engine_, CFNotificationSuspensionBehaviorDeliverImmediately);
@@ -150,7 +169,7 @@ void AVAudioSessionInterruptionNotificationCallback(CFNotificationCenterRef cent
150169 // would result in playback order A, AA, B
151170
152171 if (InternalDecoderQueueIsEmpty ()) {
153- // Enqueue the decoder on mPlayerNode if the decoder's processing format is supported
172+ // Enqueue the decoder on playerNode_ if the decoder's processing format is supported
154173 if (playerNode_->_node ->SupportsFormat (decoder.processingFormat )) {
155174 flags_.fetch_or (static_cast <unsigned int >(Flags::havePendingDecoder), std::memory_order_acq_rel);
156175 const auto result = playerNode_->_node ->EnqueueDecoder (decoder, false , error);
@@ -164,7 +183,7 @@ void AVAudioSessionInterruptionNotificationCallback(CFNotificationCenterRef cent
164183 if (!playerNode_->_node ->CurrentDecoder ())
165184 return configureForAndEnqueueDecoder (false );
166185
167- // mPlayerNode has a current decoder; fall through and push the decoder to the internal queue
186+ // playerNode_ has a current decoder; fall through and push the decoder to the internal queue
168187 }
169188
170189 // Otherwise push the decoder to the internal queue
@@ -417,8 +436,7 @@ void AVAudioSessionInterruptionNotificationCallback(CFNotificationCenterRef cent
417436 try {
418437 std::lock_guard lock (queueLock_);
419438 queuedDecoders_.push_back (decoder);
420- }
421- catch (const std::exception& e) {
439+ } catch (const std::exception& e) {
422440 os_log_error (log_, " Error pushing %{public}@ to queuedDecoders_: %{public}s" , decoder, e.what ());
423441 return false ;
424442 }
@@ -526,6 +544,52 @@ void AVAudioSessionInterruptionNotificationCallback(CFNotificationCenterRef cent
526544}
527545#endif /* TARGET_OS_IPHONE */
528546
547+ SFBAudioPlayerNode * SFB::AudioPlayer::CreatePlayerNode (AVAudioFormat *format) noexcept
548+ {
549+ #if DEBUG
550+ assert (format != nil );
551+ #endif /* DEBUG */
552+
553+ SFBAudioPlayerNode *playerNode = [[SFBAudioPlayerNode alloc ] initWithFormat: format];
554+ if (!playerNode) {
555+ os_log_error (log_, " Unable to create SFBAudioPlayerNode with format %{public}@" , SFB::StringDescribingAVAudioFormat (format));
556+ return nil ;
557+ }
558+
559+ // Avoid keeping a strong reference to `playerNode` in the event notification blocks
560+ // to prevent a retain cycle. This is safe in this case because the AudioPlayerNode
561+ // destructor synchronously waits for all event notifications to complete so the blocks
562+ // will never be called with an invalid reference.
563+ const auto & node = *(playerNode->_node );
564+
565+ playerNode->_node ->decodingStartedBlock_ = ^(Decoder decoder){
566+ HandleDecodingStarted (node, decoder);
567+ };
568+ playerNode->_node ->decodingCompleteBlock_ = ^(Decoder decoder){
569+ HandleDecodingComplete (node, decoder);
570+ };
571+
572+ playerNode->_node ->renderingWillStartBlock_ = ^(Decoder decoder, uint64_t hostTime){
573+ HandleRenderingWillStart (node, decoder, hostTime);
574+ };
575+ playerNode->_node ->renderingDecoderWillChangeBlock_ = ^(Decoder decoder, Decoder nextDecoder, uint64_t hostTime) {
576+ HandleRenderingDecoderWillChange (node, decoder, nextDecoder, hostTime);
577+ };
578+ playerNode->_node ->renderingWillCompleteBlock_ = ^(Decoder decoder, uint64_t hostTime){
579+ HandleRenderingWillComplete (node, decoder, hostTime);
580+ };
581+
582+ playerNode->_node ->decoderCanceledBlock_ = ^(Decoder decoder, AVAudioFramePosition framesRendered) {
583+ HandleDecoderCanceled (node, decoder, framesRendered);
584+ };
585+
586+ playerNode->_node ->asynchronousErrorBlock_ = ^(NSError *error) {
587+ HandleAsynchronousError (node, error);
588+ };
589+
590+ return playerNode;
591+ }
592+
529593bool SFB::AudioPlayer::ConfigureForAndEnqueueDecoder (Decoder decoder, bool clearQueueAndReset, NSError **error) noexcept
530594{
531595#if DEBUG
@@ -598,8 +662,7 @@ void AVAudioSessionInterruptionNotificationCallback(CFNotificationCenterRef cent
598662 format = standardEquivalentFormat;
599663 }
600664
601- // mPlayerNode may be nil since this method is called from the constructor
602- const auto formatsEqual = playerNode_ && [format isEqual: playerNode_->_node->RenderingFormat ()];
665+ const auto formatsEqual = [format isEqual: playerNode_->_node->RenderingFormat ()];
603666 if (formatsEqual && !forceUpdate)
604667 return true ;
605668
@@ -614,44 +677,8 @@ void AVAudioSessionInterruptionNotificationCallback(CFNotificationCenterRef cent
614677
615678 // Avoid creating a new AudioPlayerNode if not necessary
616679 SFBAudioPlayerNode *playerNode = nil ;
617- if (!formatsEqual) {
618- playerNode = [[SFBAudioPlayerNode alloc ] initWithFormat: format];
619- if (!playerNode) {
620- os_log_error (log_, " Unable to create SFBAudioPlayerNode with format %{public}@" , SFB::StringDescribingAVAudioFormat (format));
621- return false ;
622- }
623-
624- // Avoid keeping a strong reference to `playerNode` in the event notification blocks
625- // to prevent a retain cycle. This is safe in this case because the AudioPlayerNode
626- // destructor synchronously waits for all event notifications to complete so the blocks
627- // will never be called with an invalid reference.
628- const auto & node = *(playerNode->_node );
629-
630- playerNode->_node ->decodingStartedBlock_ = ^(Decoder decoder){
631- HandleDecodingStarted (node, decoder);
632- };
633- playerNode->_node ->decodingCompleteBlock_ = ^(Decoder decoder){
634- HandleDecodingComplete (node, decoder);
635- };
636-
637- playerNode->_node ->renderingWillStartBlock_ = ^(Decoder decoder, uint64_t hostTime){
638- HandleRenderingWillStart (node, decoder, hostTime);
639- };
640- playerNode->_node ->renderingDecoderWillChangeBlock_ = ^(Decoder decoder, Decoder nextDecoder, uint64_t hostTime) {
641- HandleRenderingDecoderWillChange (node, decoder, nextDecoder, hostTime);
642- };
643- playerNode->_node ->renderingWillCompleteBlock_ = ^(Decoder decoder, uint64_t hostTime){
644- HandleRenderingWillComplete (node, decoder, hostTime);
645- };
646-
647- playerNode->_node ->decoderCanceledBlock_ = ^(Decoder decoder, AVAudioFramePosition framesRendered) {
648- HandleDecoderCanceled (node, decoder, framesRendered);
649- };
650-
651- playerNode->_node ->asynchronousErrorBlock_ = ^(NSError *error) {
652- HandleAsynchronousError (node, error);
653- };
654- }
680+ if (!formatsEqual && !(playerNode = CreatePlayerNode (format)))
681+ return false ;
655682
656683 AVAudioOutputNode *outputNode = engine_.outputNode ;
657684 AVAudioMixerNode *mixerNode = engine_.mainMixerNode ;
@@ -706,11 +733,9 @@ void AVAudioSessionInterruptionNotificationCallback(CFNotificationCenterRef cent
706733 // Ensure the delegate returned a valid node
707734 assert (node != nil && " nil AVAudioNode returned by -audioPlayer:reconfigureProcessingGraph:withFormat:" );
708735 [engine_ connect: playerNode_ to: node format: format];
709- }
710- else
736+ } else
711737 [engine_ connect: playerNode_ to: playerNodeOutputConnectionPoint.node format: format];
712- }
713- else
738+ } else
714739 [engine_ connect: playerNode_ to: mixerNode format: format];
715740 }
716741
@@ -906,9 +931,8 @@ void AVAudioSessionInterruptionNotificationCallback(CFNotificationCenterRef cent
906931 if (error && [player_.delegate respondsToSelector: @selector (audioPlayer:encounteredError: )])
907932 [player_.delegate audioPlayer: player_ encounteredError: error];
908933 }
909- }
910- // End of audio
911- else {
934+ } else {
935+ // End of audio
912936#if DEBUG
913937 os_log_debug (log_, " End of audio reached" );
914938#endif /* DEBUG */
0 commit comments