Skip to content
Open
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
30 changes: 20 additions & 10 deletions apps/fabric-example/ios/FabricExampleTests/AudioEngineTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,8 @@ - (void)testDetachSourceNodeRemovesTrackedNodeAndClearsGraphWhenEmpty {
- (void)testDetachSourceNodeKeepsGraphNeedsRebuildWhenInputRemains {
NSString *sourceNodeId = [self attachSourceNodeToAudioEngine];
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]];
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
onInputConfigurationChange:nil];
self.audioEngine.graphNeedsRebuild = YES;

[self.audioEngine detachSourceNodeWithId:sourceNodeId];
Expand All @@ -435,7 +436,8 @@ - (void)testDetachSourceNodeKeepsGraphNeedsRebuildWhenInputRemains {
- (void)testAttachInputNodeStoresAndConnectsInput {
FakeAudioEngine *fakeEngine = self.audioEngine.currentFakeAudioEngine;
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]];
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
onInputConfigurationChange:nil];

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

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

XCTAssertNil(self.audioEngine.inputNode);
XCTAssertEqual(fakeEngine.attachNodeCallCount, 0);
Expand Down Expand Up @@ -483,7 +486,8 @@ - (void)testDetachInputNodeWithoutInputDoesNothing {

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

[self.audioEngine detachInputNode];
Expand All @@ -493,7 +497,8 @@ - (void)testDetachInputNodeClearsGraphOnlyWhenNoSourcesRemain {

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

[self.audioEngine detachInputNode];
Expand All @@ -507,7 +512,8 @@ - (void)testDetachInputNodePreservesSessionDeactivationInvalidation {
fakeEngine.fakeRunning = YES;
self.audioEngine.state = AudioEngineStateRunning;
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]];
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
onInputConfigurationChange:nil];

[self.audioEngine onSessionDeactivated];
[self.audioEngine detachInputNode];
Expand Down Expand Up @@ -730,7 +736,8 @@ - (void)testStartIfNecessaryRebuildsWhenGraphNeedsRebuild {
- (void)
testStartIfNecessaryRebuildsAfterSessionDeactivationEvenWhenTeardownClearsGraph {
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]];
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
onInputConfigurationChange:nil];

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

XCTAssertTrue([self.audioEngine startIfNecessary]);
Expand All @@ -770,7 +778,8 @@ - (void)testStartIfNecessaryRebuildsWhenGraphNeedsRebuild {

- (void)testStartIfNecessaryRebuildsInputNodeWithFreshInstance {
[self.audioEngine
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]];
attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
onInputConfigurationChange:nil];
FakeAudioEngine *oldEngine = self.audioEngine.currentFakeAudioEngine;
AVAudioSinkNode *oldInputNode = self.audioEngine.inputNode;
AVAudioFormat *replacementInputFormat =
Expand Down Expand Up @@ -991,7 +1000,8 @@ - (void)testConcurrentRecordAndPlayPathsDoNotCrash {
for (NSInteger index = 0; index < 10; index += 1) {
dispatch_group_enter(group);
dispatch_async(queue, ^{
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]];
[self.audioEngine attachInputNodeWithReceiverBlock:[self testInputReceiverBlock]
onInputConfigurationChange:nil];
[self.audioEngine startIfNecessary];
dispatch_group_leave(group);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@

[[nodiscard]] bool isRunning() const override;

[[nodiscard]] double getBaseLatency() const override;
[[nodiscard]] double getOutputLatency() const override;

protected:
std::shared_ptr<DSPAudioBuffer> audioBuffer_;
NativeAudioPlayer *audioPlayer_;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#import <XCTest/XCTest.h>

#import <audioapi/core/OfflineAudioContext.h>
#import <audioapi/ios/core/IOSAudioRecorder.h>
#import <audioapi/ios/core/NativeAudioRecorder.h>
#import <audioapi/ios/system/AudioEngine.h>
#import <audioapi/ios/system/AudioSessionManager.h>
Expand All @@ -22,41 +23,6 @@

namespace audioapi {

class IAudioEventHandlerRegistry;

class IOSAudioRecorder : public AudioRecorder {
public:
IOSAudioRecorder(const std::shared_ptr<IAudioEventHandlerRegistry> &audioEventHandlerRegistry);
~IOSAudioRecorder() override;

Result<NoneType, std::string> start(const std::string &fileNameOverride = "") override;
Result<std::tuple<std::vector<std::string>, double, double>, std::string> stop() override;

Result<NoneType, std::string> enableFileOutput(
std::shared_ptr<AudioFileProperties> properties) override;
void disableFileOutput() override;

void connect(const std::shared_ptr<utils::graph::NodeHandle> &node) override;
void disconnect() override;

void pause() override;
void resume() override;

bool isRecording() const override;
bool isPaused() const override;
bool isIdle() const override;

Result<NoneType, std::string> setOnAudioReadyCallback(
float sampleRate,
size_t bufferLength,
int channelCount,
uint64_t callbackId) override;
void clearOnAudioReadyCallback() override;

protected:
NativeAudioRecorder *nativeRecorder_;
};

struct RecorderAdapterTestFixture {
std::shared_ptr<OfflineAudioContext> context;
std::shared_ptr<utils::graph::NodeHandle> handle;
Expand Down Expand Up @@ -317,6 +283,7 @@ - (void)setUp

_recorder = std::make_unique<TestableIOSAudioRecorder>(std::shared_ptr<IAudioEventHandlerRegistry>());
self.originalNativeRecorder = _recorder->replaceNativeRecorder(self.nativeRecorder);
self.nativeRecorder.onInputConfigurationChange = self.originalNativeRecorder.onInputConfigurationChange;
}

- (void)tearDown
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,13 @@ - (void)stopIfNecessary
}

- (void)attachInputNodeWithReceiverBlock:(AVAudioSinkNodeReceiverBlock)receiverBlock
onInputConfigurationChange:(void (^)(void))onInputConfigurationChange
{
self.attachInputNodeCallCount += 1;
self.inputNode = [[AVAudioSinkNode alloc] initWithReceiverBlock:receiverBlock];
self.lastAttachedInputNode = self.inputNode;
self.lastAttachedReceiverBlock = receiverBlock;
(void)onInputConfigurationChange;
}

- (bool)startIfNecessary
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
#include <audioapi/core/sources/RecorderAdapterNode.h>
#include <audioapi/core/types/ChannelInterpretation.h>
#include <audioapi/core/utils/Constants.h>
#include <audioapi/core/utils/CurrentRenderScope.h>
#include <audioapi/utils/AudioArray.hpp>

#include <algorithm>
#include <cmath>
#include <cstring>
#include <memory>
#include <thread>
#include <vector>

namespace audioapi {
Expand Down Expand Up @@ -59,15 +61,24 @@ void RecorderAdapterNode::init(size_t bufferSize, int channelCount, float sample
}

void RecorderAdapterNode::adapterCleanup() {
isInitialized_.store(false, std::memory_order_release);
waitForProcessQuiescence();

needsResampling_ = false;
buff_.clear();
resampler_.reset();
overflowSize_ = 0;
}

isInitialized_.store(false, std::memory_order_release);
void RecorderAdapterNode::waitForProcessQuiescence() const {
while (currentProcesses_.load(std::memory_order_acquire) != 0) {
std::this_thread::yield();
}
}

void RecorderAdapterNode::processNode(int framesToProcess) {
const CurrentRenderScope processScope(currentProcesses_);

if (!isInitialized_.load(std::memory_order_acquire)) {
audioBuffer_->zero();
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <audioapi/dsp/r8brain/Resampler.hpp>
#include <audioapi/utils/AudioBuffer.hpp>
#include <audioapi/utils/CircularOverflowableAudioArray.h>
#include <atomic>
#include <cstdint>
#include <memory>
#include <vector>

Expand Down Expand Up @@ -39,6 +41,7 @@ class RecorderAdapterNode : public AudioNode {
private:
void readFrames(AudioBuffer &target, size_t framesToRead);
void processResampled(int framesToProcess);
void waitForProcessQuiescence() const;

std::unique_ptr<r8b::MultiChannelResampler> resampler_;
bool needsResampling_ = false;
Expand All @@ -53,6 +56,8 @@ class RecorderAdapterNode : public AudioNode {
size_t overflowSize_ = 0;

std::atomic<bool> isInitialized_{false};
/// Incremented around each processNode() call; adapterCleanup waits for quiescence.
std::atomic<uint32_t> currentProcesses_{0};
};

} // namespace audioapi
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#pragma once

#ifdef __OBJC__ // when compiled as Objective-C
#import <NativeAudioRecorder.h>
#import <audioapi/ios/core/NativeAudioRecorder.h>
#else
typedef struct objc_object NSURL;
typedef struct objc_object AVAudioFile;
typedef struct objc_object AudioBufferList;
typedef struct objc_object NativeAudioRecorder;
typedef struct objc_object AVAudioFormat;
#endif // __OBJC__

#include <audioapi/core/inputs/AudioRecorder.h>
Expand Down Expand Up @@ -67,6 +68,16 @@ class IOSAudioRecorder : public AudioRecorder {
Result<std::string, std::string> setupFileWriter(
const std::shared_ptr<AudioFileProperties> &properties,
const std::string &fileNameOverride = "");
Result<NoneType, std::string> reprepareForLiveInput();
void handleInputConfigurationChange();
Result<NoneType, std::string> reprepareFileWriter(
AVAudioFormat *inputFormat,
int maxInputBufferLength);
Result<NoneType, std::string> reprepareCallback(
AVAudioFormat *inputFormat,
int maxInputBufferLength);
void reprepareAdapter(AVAudioFormat *inputFormat, int maxInputBufferLength);
void runSideEffects(const AudioBufferList *inputBuffer, int numFrames);

std::vector<std::string> recordingSegmentPaths_;
std::atomic<float> streamSampleRate_{0.0f};
Expand Down
Loading
Loading