Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 109 additions & 26 deletions apps/fabric-example/ios/FabricExampleTests/AudioEngineTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,7 @@ @interface AudioEngineTests : XCTestCase

@implementation AudioEngineTests

+ (BOOL)testInvocationsAreParallelizable
{
+ (BOOL)testInvocationsAreParallelizable {
return NO;
}

Expand Down Expand Up @@ -421,8 +420,10 @@ - (void)testDetachSourceNodeRemovesTrackedNodeAndClearsGraphWhenEmpty {

- (void)testDetachSourceNodeKeepsGraphNeedsRebuildWhenInputRemains {
NSString *sourceNodeId = [self attachSourceNodeToAudioEngine];
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO onInputConfigurationChange:nil];
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO
onInputConfigurationChange:nil];
self.audioEngine.graphNeedsRebuild = YES;

[self.audioEngine detachSourceNodeWithId:sourceNodeId];
Expand All @@ -434,8 +435,10 @@ - (void)testDetachSourceNodeKeepsGraphNeedsRebuildWhenInputRemains {

- (void)testAttachInputNodeStoresAndConnectsInput {
FakeAudioEngine *fakeEngine = self.audioEngine.currentFakeAudioEngine;
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO onInputConfigurationChange:nil];
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO
onInputConfigurationChange:nil];

AVAudioSinkNode *inputNode = self.audioEngine.inputNode;
XCTAssertNotNil(inputNode);
Expand All @@ -454,8 +457,10 @@ - (void)testAttachInputNodeDefersConnectionUntilLiveInputFormatIsAvailable {
FakeAudioEngine *fakeEngine = self.audioEngine.currentFakeAudioEngine;
fakeEngine.fakeInputNode.outputFormat = nil;

[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO onInputConfigurationChange:nil];
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO
onInputConfigurationChange:nil];

XCTAssertNil(self.audioEngine.inputNode);
XCTAssertEqual(fakeEngine.attachNodeCallCount, 0);
Expand All @@ -482,8 +487,10 @@ - (void)testDetachInputNodeWithoutInputDoesNothing {
}

- (void)testDetachInputNodeClearsGraphOnlyWhenNoSourcesRemain {
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO onInputConfigurationChange:nil];
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO
onInputConfigurationChange:nil];
self.audioEngine.graphNeedsRebuild = YES;

[self.audioEngine detachInputNode];
Expand All @@ -492,8 +499,10 @@ - (void)testDetachInputNodeClearsGraphOnlyWhenNoSourcesRemain {
XCTAssertFalse(self.audioEngine.graphNeedsRebuild);

[self attachSourceNodeToAudioEngine];
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO onInputConfigurationChange:nil];
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO
onInputConfigurationChange:nil];
self.audioEngine.graphNeedsRebuild = YES;

[self.audioEngine detachInputNode];
Expand All @@ -506,8 +515,10 @@ - (void)testDetachInputNodePreservesSessionDeactivationInvalidation {
FakeAudioEngine *fakeEngine = self.audioEngine.currentFakeAudioEngine;
fakeEngine.fakeRunning = YES;
self.audioEngine.state = AudioEngineStateRunning;
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO onInputConfigurationChange:nil];
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO
onInputConfigurationChange:nil];

[self.audioEngine onSessionDeactivated];
[self.audioEngine detachInputNode];
Expand Down Expand Up @@ -729,8 +740,10 @@ - (void)testStartIfNecessaryRebuildsWhenGraphNeedsRebuild {

- (void)
testStartIfNecessaryRebuildsAfterSessionDeactivationEvenWhenTeardownClearsGraph {
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO onInputConfigurationChange:nil];
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO
onInputConfigurationChange:nil];

FakeAudioEngine *oldEngine = self.audioEngine.currentFakeAudioEngine;
oldEngine.fakeRunning = YES;
Expand All @@ -747,8 +760,10 @@ - (void)testStartIfNecessaryRebuildsWhenGraphNeedsRebuild {
AVAudioFormat *recoveredInputFormat =
[self testInputFormatWithSampleRate:48000 channelCount:1];
self.audioEngine.nextCreatedEngineInputFormat = recoveredInputFormat;
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO onInputConfigurationChange:nil];
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO
onInputConfigurationChange:nil];
AVAudioSinkNode *recoveredInputNode = self.audioEngine.inputNode;

XCTAssertTrue([self.audioEngine startIfNecessary]);
Expand All @@ -769,8 +784,10 @@ - (void)testStartIfNecessaryRebuildsWhenGraphNeedsRebuild {
}

- (void)testStartIfNecessaryRebuildsInputNodeWithFreshInstance {
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO onInputConfigurationChange:nil];
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO
onInputConfigurationChange:nil];
FakeAudioEngine *oldEngine = self.audioEngine.currentFakeAudioEngine;
AVAudioSinkNode *oldInputNode = self.audioEngine.inputNode;
AVAudioFormat *replacementInputFormat =
Expand Down Expand Up @@ -936,6 +953,36 @@ - (void)testRestartAudioEngineStopsAndRestartsWhenStateRunning {
XCTAssertFalse(self.audioEngine.graphNeedsRebuild);
}

- (void)
testConfigurationChangeCallbackCanReadLiveInputFormatWhileRestartHoldsLock {
__block BOOL callbackRan = NO;
__block AVAudioFormat *formatSeenDuringRebuild = nil;

[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO
onInputConfigurationChange:^{
callbackRan = YES;
formatSeenDuringRebuild = [self.audioEngine getLiveInputFormat];
}];

self.audioEngine.state = AudioEngineStateRunning;
self.audioEngine.currentFakeAudioEngine.fakeRunning = YES;

XCTestExpectation *restartFinished =
[self expectationWithDescription:@"restartAudioEngine returned"];

dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
[self.audioEngine restartAudioEngine];
[restartFinished fulfill];
});

[self waitForExpectations:@[ restartFinished ] timeout:5.0];

XCTAssertTrue(callbackRan);
XCTAssertNotNil(formatSeenDuringRebuild);
}

- (void)testConcurrentStartIfNecessaryDoesNotCrash {
[self attachSourceNodeToAudioEngine];
self.audioEngine.state = AudioEngineStateIdle;
Expand All @@ -952,7 +999,8 @@ - (void)testConcurrentStartIfNecessaryDoesNotCrash {
});
}

dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
dispatch_group_wait(group,
dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
XCTAssertTrue([self.audioEngine startIfNecessary]);
}

Expand All @@ -978,7 +1026,8 @@ - (void)testConcurrentAttachDetachAndRestartDoesNotCrash {
});
}

dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
dispatch_group_wait(group,
dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
}

- (void)testConcurrentRecordAndPlayPathsDoNotCrash {
Expand All @@ -991,8 +1040,10 @@ - (void)testConcurrentRecordAndPlayPathsDoNotCrash {
for (NSInteger index = 0; index < 10; index += 1) {
dispatch_group_enter(group);
dispatch_async(queue, ^{
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO onInputConfigurationChange:nil];
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO
onInputConfigurationChange:nil];
[self.audioEngine startIfNecessary];
dispatch_group_leave(group);
});
Expand All @@ -1005,7 +1056,8 @@ - (void)testConcurrentRecordAndPlayPathsDoNotCrash {
});
}

dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
dispatch_group_wait(group,
dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
}

- (void)testConcurrentInterruptionAndStartDoesNotCrash {
Expand All @@ -1032,7 +1084,38 @@ - (void)testConcurrentInterruptionAndStartDoesNotCrash {
});
}

dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
dispatch_group_wait(group,
dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
}

- (void)testConcurrentLiveInputFormatReadAndRestartDoesNotCrash {
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
voiceProcessingEnabled:NO
onInputConfigurationChange:nil];
self.audioEngine.state = AudioEngineStateRunning;
self.audioEngine.currentFakeAudioEngine.fakeRunning = YES;

dispatch_queue_t queue =
dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0);
dispatch_group_t group = dispatch_group_create();

for (NSInteger index = 0; index < 50; index += 1) {
dispatch_group_enter(group);
dispatch_async(queue, ^{
[self.audioEngine getLiveInputFormat];
dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_async(queue, ^{
[self.audioEngine restartAudioEngine];
dispatch_group_leave(group);
});
}

dispatch_group_wait(group,
dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC));
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ @implementation AudioEngineInputRegistration
@end

@interface AudioEngine () {
std::mutex _engineLock;
std::recursive_mutex _engineLock;
BOOL _isRebuildingAudioEngine;
/// Tracks whether voice processing is currently engaged on the system input
/// node of the live engine instance. Reset whenever the engine is recreated.
Expand Down Expand Up @@ -170,6 +170,8 @@ - (void)materializeSourceNodeWithId:(NSString *)sourceNodeId

- (AVAudioFormat *)liveInputFormat
{
std::scoped_lock lock(_engineLock);

if (self.audioEngine == nil) {
return nil;
}
Expand Down
Loading