Skip to content
Merged
2 changes: 1 addition & 1 deletion apps/common-app/src/singletons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import { AudioContext, AudioRecorder } from 'react-native-audio-api';
export const audioContext = new AudioContext();
export const audioRecorder = new AudioRecorder({
androidInputPreset: 'voiceCommunication',
iosVoiceProcessing: true,
iosVoiceProcessing: false,
});
29 changes: 14 additions & 15 deletions apps/fabric-example/ios/FabricExampleTests/AudioPlayerTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,7 @@

class IOSAudioPlayer : public CommonPlayer {
public:
IOSAudioPlayer(
const std::function<void(DSPAudioBuffer *, int)> &renderAudio,
float sampleRate,
int channelCount,
std::atomic<uint32_t> &currentRenders,
std::weak_ptr<AudioContext> context,
std::mutex *driverMutex);
using CommonPlayer::CommonPlayer;
~IOSAudioPlayer() override;

bool start() override;
Expand All @@ -43,10 +37,12 @@
[[nodiscard]] double getOutputLatency() const override;

protected:
NativeAudioPlayer *audioPlayer_;
std::atomic<bool> flushOverflowNextPull_;
int pendingSavedCount_;
DSPAudioBuffer pendingSaved_;
NativeAudioPlayer *createNativePlayer();

NativeAudioPlayer *audioPlayer_ = createNativePlayer();
std::atomic<bool> flushOverflowNextPull_{false};
int pendingSavedCount_{0};
DSPAudioBuffer pendingSaved_{RENDER_QUANTUM_SIZE, channelCount_, sampleRate_};
};

} // namespace audioapi
Expand Down Expand Up @@ -239,7 +235,8 @@ - (void)cleanup
channelCount,
currentRendersStorage_,
std::weak_ptr<AudioContext>{},
nullptr) {}
nullptr,
AudioContextLatencyHint::INTERACTIVE) {}

NativeAudioPlayer *replaceAudioPlayer(NativeAudioPlayer *audioPlayer) {
NativeAudioPlayer *previous = audioPlayer_;
Expand Down Expand Up @@ -321,7 +318,8 @@ - (NativeAudioPlayer *)createPlayerWithRenderCallCount:(NSInteger *)renderCallCo
}
}
sampleRate:48000
channelCount:2];
channelCount:2
preferredIOBufferFrames:RENDER_QUANTUM_SIZE];
}

- (void)assertStartLikeOperationRunsGraphForSelector:(SEL)selector
Expand Down Expand Up @@ -384,8 +382,9 @@ - (void)testRenderBlockInvokesRenderAudioWhenBufferCountMatches
renderedFrameCount = static_cast<AVAudioFrameCount>(numFrames);
renderedBufferList = outputBuffer;
}
sampleRate:48000
channelCount:2];
sampleRate:48000
channelCount:2
preferredIOBufferFrames:RENDER_QUANTUM_SIZE];
TestAudioOutput output(2, 64, 0.0f);
BOOL isSilence = NO;
AudioTimeStamp timestamp = {};
Expand Down
1 change: 1 addition & 0 deletions packages/audiodocs/docs/core/audio-context.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ constructor(options?: AudioContextOptions)
| Parameter | Type | Default | |
| :---: | :---: | :----: | :---- |
| `sampleRate` <Optional /> | `number` | - | The preferred sample rate for the context. |
| `latencyHint` <Optional /> | `'interactive' \| 'balanced' \| 'playback'` | `'interactive'` | What the context should optimize its output stream for. `interactive` requests the lowest latency the platform offers; `balanced` and `playback` trade output latency for a deeper buffer, which favours glitch-free sustained playback. |

#### Errors

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,30 @@

namespace audioapi {

namespace {

PerformanceMode performanceModeFor(AudioContextLatencyHint latencyHint) {
switch (latencyHint) {
case AudioContextLatencyHint::INTERACTIVE:
return PerformanceMode::LowLatency;
case AudioContextLatencyHint::BALANCED:
return PerformanceMode::None;
case AudioContextLatencyHint::PLAYBACK:
return PerformanceMode::PowerSaving;
}
return PerformanceMode::LowLatency;
}

} // namespace

bool AudioPlayer::openAudioStream() {
std::scoped_lock lock(streamMutex_);
AudioStreamBuilder builder;

builder.setSharingMode(SharingMode::Exclusive)
->setFormat(AudioFormat::Float)
->setFormatConversionAllowed(true)
->setPerformanceMode(PerformanceMode::LowLatency)
->setPerformanceMode(performanceModeFor(latencyHint_))
->setChannelCount(channelCount_)
->setSampleRateConversionQuality(SampleRateConversionQuality::Medium)
->setFramesPerDataCallback(RENDER_QUANTUM_SIZE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <audioapi/HostObjects/sources/AudioBufferHostObject.h>
#include <audioapi/HostObjects/utils/AudioDecoderHostObject.h>
#include <audioapi/HostObjects/utils/AudioFileUtilsHostObject.h>
#include <audioapi/HostObjects/utils/JsEnumParser.h>
#include <audioapi/core/AudioContext.h>
#include <audioapi/core/OfflineAudioContext.h>
#include <audioapi/core/inputs/ActiveRecorderHandle.h>
Expand Down Expand Up @@ -69,16 +70,22 @@ class AudioAPIModuleInstaller {
return jsi::Function::createFromHostFunction(
*jsiRuntime,
jsi::PropNameID::forAscii(*jsiRuntime, "createAudioContext"),
1,
2,
[jsCallInvoker, audioEventHandlerRegistry](
jsi::Runtime &runtime,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count) -> jsi::Value {
auto sampleRate = static_cast<float>(args[0].getNumber());

auto latencyHint = AudioContextLatencyHint::INTERACTIVE;
if (count > 1 && args[1].isString()) {
latencyHint =
js_enum_parser::latencyHintFromString(args[1].getString(runtime).utf8(runtime));
}

auto audioContextHostObject = std::make_shared<AudioContextHostObject>(
sampleRate, audioEventHandlerRegistry, &runtime, jsCallInvoker);
sampleRate, audioEventHandlerRegistry, &runtime, jsCallInvoker, latencyHint);

return jsi::Object::createFromHostObject(runtime, audioContextHostObject);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ AudioContextHostObject::AudioContextHostObject(
float sampleRate,
const std::shared_ptr<IAudioEventHandlerRegistry> &audioEventHandlerRegistry,
jsi::Runtime *runtime,
const std::shared_ptr<react::CallInvoker> &callInvoker)
const std::shared_ptr<react::CallInvoker> &callInvoker,
AudioContextLatencyHint latencyHint)
: BaseAudioContextHostObject(
std::make_shared<AudioContext>(sampleRate, audioEventHandlerRegistry),
std::make_shared<AudioContext>(sampleRate, audioEventHandlerRegistry, latencyHint),
runtime,
callInvoker) {
addGetters(JSI_EXPORT_PROPERTY_GETTER(AudioContextHostObject, outputLatency));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#pragma once

#include <audioapi/HostObjects/BaseAudioContextHostObject.h>
#include <audioapi/core/types/AudioContextLatencyHint.h>
#include <audioapi/events/IAudioEventHandlerRegistry.h>

#include <jsi/jsi.h>

#include <memory>

namespace audioapi {
Expand All @@ -17,7 +19,8 @@ class AudioContextHostObject : public BaseAudioContextHostObject {
float sampleRate,
const std::shared_ptr<IAudioEventHandlerRegistry> &audioEventHandlerRegistry,
jsi::Runtime *runtime,
const std::shared_ptr<react::CallInvoker> &callInvoker);
const std::shared_ptr<react::CallInvoker> &callInvoker,
AudioContextLatencyHint latencyHint);
~AudioContextHostObject() override;

JSI_HOST_FUNCTION_DECL(close);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ std::string filterTypeToString(BiquadFilterType type) {
}
}

AudioContextLatencyHint latencyHintFromString(const std::string &hint) {
if (hint == "balanced")
return AudioContextLatencyHint::BALANCED;
if (hint == "playback")
return AudioContextLatencyHint::PLAYBACK;

return AudioContextLatencyHint::INTERACTIVE;
}

OverSampleType overSampleTypeFromString(const std::string &type) {
if (type == "2x")
return OverSampleType::OVERSAMPLE_2X;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <audioapi/core/analysis/AnalyserNode.h>
#include <audioapi/core/types/AudioContextLatencyHint.h>
#include <audioapi/core/types/BiquadFilterType.h>
#include <audioapi/core/types/ChannelCountMode.h>
#include <audioapi/core/types/ChannelInterpretation.h>
Expand Down Expand Up @@ -29,4 +30,6 @@ std::string distanceModelToString(DistanceModelType model);
DistanceModelType distanceModelFromString(const std::string &model);
ChannelInterpretation channelInterpretationFromString(const std::string &interpretation);
std::string contextStateToString(ContextState state);
/// Interactive, the spec default, for an unrecognised string; a browser would throw a TypeError.
AudioContextLatencyHint latencyHintFromString(const std::string &hint);
} // namespace audioapi::js_enum_parser
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ using PlatformAudioPlayer = IOSAudioPlayer;

AudioContext::AudioContext(
float sampleRate,
const std::shared_ptr<IAudioEventHandlerRegistry> &audioEventHandlerRegistry)
const std::shared_ptr<IAudioEventHandlerRegistry> &audioEventHandlerRegistry,
AudioContextLatencyHint latencyHint)
: BaseAudioContext(sampleRate, audioEventHandlerRegistry),
latencyHint_(latencyHint),
isInitialized_(false),
onErrorEvent_(audioEventHandlerRegistry) {
// Context starts SUSPENDED with no audio-thread consumer. Let the producer
Expand All @@ -50,7 +52,8 @@ void AudioContext::initialize(const AudioDestinationNode *destination) {
destination_->getChannelCount(),
currentRenders_,
std::static_pointer_cast<AudioContext>(shared_from_this()),
&driverMutex_);
&driverMutex_,
latencyHint_);
}

bool AudioContext::tryStartDriver() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <audioapi/core/BaseAudioContext.h>
#include <audioapi/core/CommonPlayer.h>
#include <audioapi/core/types/AudioContextLatencyHint.h>
#include <audioapi/events/AudioEvent.h>
#include <audioapi/events/EventCaller.hpp>
#include <audioapi/jsi/ContextPromiseResolver.hpp>
Expand All @@ -17,7 +18,8 @@ class AudioContext : public BaseAudioContext {
public:
explicit AudioContext(
float sampleRate,
const std::shared_ptr<IAudioEventHandlerRegistry> &audioEventHandlerRegistry);
const std::shared_ptr<IAudioEventHandlerRegistry> &audioEventHandlerRegistry,
AudioContextLatencyHint latencyHint);
~AudioContext() override;
DELETE_COPY_AND_MOVE(AudioContext);

Expand Down Expand Up @@ -47,6 +49,7 @@ class AudioContext : public BaseAudioContext {

private:
std::shared_ptr<CommonPlayer> audioPlayer_;
AudioContextLatencyHint latencyHint_;
std::atomic<bool> isInitialized_{false};
/// Audio I/O callback thread increments around each platform render callback;
/// control thread waits on suspend/close.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <audioapi/core/types/AudioContextLatencyHint.h>
#include <audioapi/core/utils/Constants.h>
#include <audioapi/utils/AudioBuffer.hpp>
#include <audioapi/utils/Macros.h>
Expand All @@ -23,15 +24,17 @@ class CommonPlayer {
int channelCount,
std::atomic<uint32_t> &currentRenders,
std::weak_ptr<AudioContext> context,
std::mutex *driverMutex)
std::mutex *driverMutex,
AudioContextLatencyHint latencyHint)
: renderAudio_(renderAudio),
renderBuffer_(
std::make_shared<DSPAudioBuffer>(RENDER_QUANTUM_SIZE, channelCount, sampleRate)),
sampleRate_(sampleRate),
channelCount_(channelCount),
currentRenders_(currentRenders),
context_(std::move(context)),
driverMutex_(driverMutex) {}
driverMutex_(driverMutex),
latencyHint_(latencyHint) {}
DELETE_COPY_AND_MOVE(CommonPlayer);
virtual ~CommonPlayer() = default;

Expand Down Expand Up @@ -65,6 +68,7 @@ class CommonPlayer {
/// The owning context's driver mutex; `nullptr` disables stream-failure reporting.
std::mutex *driverMutex_;
std::atomic<bool> isRunning_{false};
AudioContextLatencyHint latencyHint_;
};

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

#include <cstdint>

namespace audioapi {

enum class AudioContextLatencyHint : std::uint8_t { INTERACTIVE, BALANCED, PLAYBACK };

} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ typedef struct objc_object AudioBufferList;
#endif // __OBJC__

#include <audioapi/core/CommonPlayer.h>
#include <audioapi/core/types/AudioContextLatencyHint.h>
#include <audioapi/utils/AudioBuffer.hpp>
#include <audioapi/utils/Macros.h>

Expand All @@ -21,13 +22,7 @@ namespace audioapi {

class IOSAudioPlayer : public CommonPlayer {
public:
IOSAudioPlayer(
const std::function<void(DSPAudioBuffer *, int)> &renderAudio,
float sampleRate,
int channelCount,
std::atomic<uint32_t> &currentRenders,
std::weak_ptr<AudioContext> context,
std::mutex *driverMutex);
using CommonPlayer::CommonPlayer;
~IOSAudioPlayer() override;

DELETE_COPY_AND_MOVE(IOSAudioPlayer);
Expand All @@ -51,12 +46,17 @@ class IOSAudioPlayer : public CommonPlayer {
/// is kept (max 128 frames) and played at the start of the next callback.
void deliverOutputBuffers(AudioBufferList *outputData, int numFrames);

NativeAudioPlayer *audioPlayer_;
/// Builds the native player and points its render and stream-failure blocks at this object.
/// Runs as a member initializer, before the members below exist: the blocks only capture
/// `this` and must not be invoked until construction has finished.
NativeAudioPlayer *createNativePlayer();

NativeAudioPlayer *audioPlayer_ = createNativePlayer();
/// Set from main thread on start/resume; consumed on audio thread to drop stale pending audio.
std::atomic<bool> flushOverflowNextPull_{false};
/// Frames valid at the front of each `pendingSaved_[ch]` (0 … RENDER_QUANTUM_SIZE).
int pendingSavedCount_{0};
DSPAudioBuffer pendingSaved_;
DSPAudioBuffer pendingSaved_{RENDER_QUANTUM_SIZE, channelCount_, sampleRate_};
};

} // namespace audioapi
Loading
Loading