Skip to content

Commit 93f6c95

Browse files
authored
Refactor audio engine setup and player node creation (#635)
1 parent bb213bf commit 93f6c95

File tree

2 files changed

+81
-54
lines changed

2 files changed

+81
-54
lines changed

Sources/CSFBAudioEngine/Player/AudioPlayer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,9 @@ class AudioPlayer final {
277277
#endif /* TARGET_OS_IPHONE */
278278

279279
private:
280+
/// Creates and returns an audio player node for `format` or `nil` on error
281+
SFBAudioPlayerNode * _Nullable CreatePlayerNode(AVAudioFormat * _Nonnull format) noexcept;
282+
280283
/// Configures the player to render audio from `decoder` and enqueues `decoder` on the player node
281284
/// - parameter clearQueueAndReset: If `true` the internal decoder queue is cleared and the player node is reset
282285
/// - parameter error: An optional pointer to an `NSError` object to receive error information

Sources/CSFBAudioEngine/Player/AudioPlayer.mm

Lines changed: 78 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
529593
bool 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

Comments
 (0)