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
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,
});
19 changes: 19 additions & 0 deletions packages/audiodocs/docs/core/audio-context.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,25 @@ 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 (many simultaneous sources, low-end devices). See the table below for what each platform does with it. Numeric hints are not supported yet. |

#### `latencyHint` per platform

| `latencyHint` | Android (Oboe `PerformanceMode`) | iOS (`AVAudioSession.preferredIOBufferDuration`) | Web |
| :---: | :---- | :---- | :---- |
| `'interactive'` | `LowLatency` | 128 frames | forwarded |
| `'balanced'` | `None` | 1024 frames | forwarded |
| `'playback'` | `PowerSaving` | 4096 frames | forwarded |

As in the Web Audio API, omitting the hint means `'interactive'`. An unrecognized string is
treated as `'interactive'` too on Android and iOS, where a browser would throw a `TypeError`.

On iOS the buffer duration belongs to the process-wide `AVAudioSession` rather than to one stream,
so concurrent contexts cannot each get their own. The shortest duration any live context asked for
wins, which makes `'balanced'` and `'playback'` best-effort: either loses to a concurrent
`'interactive'`. Releasing the last request asks for 128 frames again. Every
value is a request the route may refuse; `outputLatency` reports what the session grants, which a
running engine may not have adopted yet.

#### Errors

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,38 @@

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

AudioPlayer::AudioPlayer(
const std::function<void(DSPAudioBuffer *, int)> &renderAudio,
float sampleRate,
int channelCount,
std::mutex *driverMutex,
const std::shared_ptr<AudioContext> &context,
std::atomic<uint32_t> &currentRenders)
std::atomic<uint32_t> &currentRenders,
AudioContextLatencyHint latencyHint)
: renderAudio_(renderAudio),
currentRenders_(currentRenders),
sampleRate_(sampleRate),
channelCount_(channelCount),
isRunning_(false),
driverMutex_(driverMutex),
context_(context) {}
context_(context),
performanceMode_(performanceModeFor(latencyHint)) {}

bool AudioPlayer::openAudioStream() {
std::scoped_lock lock(streamMutex_);
Expand All @@ -35,7 +53,7 @@ bool AudioPlayer::openAudioStream() {
builder.setSharingMode(SharingMode::Exclusive)
->setFormat(AudioFormat::Float)
->setFormatConversionAllowed(true)
->setPerformanceMode(PerformanceMode::LowLatency)
->setPerformanceMode(performanceMode_)
->setChannelCount(channelCount_)
->setSampleRateConversionQuality(SampleRateConversionQuality::Medium)
->setFramesPerDataCallback(RENDER_QUANTUM_SIZE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <mutex>

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

namespace audioapi {
Expand All @@ -29,7 +30,8 @@ class AudioPlayer : public CommonPlayer,
int channelCount,
std::mutex *driverMutex,
const std::shared_ptr<AudioContext> &context,
std::atomic<uint32_t> &currentRenders);
std::atomic<uint32_t> &currentRenders,
AudioContextLatencyHint latencyHint);

~AudioPlayer() override {
cleanup();
Expand Down Expand Up @@ -67,6 +69,7 @@ class AudioPlayer : public CommonPlayer,
std::atomic<int32_t> lastCallbackFrameCount_{0};
std::mutex *driverMutex_;
std::weak_ptr<AudioContext> context_;
const PerformanceMode performanceMode_;

bool openAudioStream();
};
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);

JSI_HOST_FUNCTION_DECL(close);
JSI_HOST_FUNCTION_DECL(resume);
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 @@ -15,8 +15,11 @@
namespace audioapi {
AudioContext::AudioContext(
float sampleRate,
const std::shared_ptr<IAudioEventHandlerRegistry> &audioEventHandlerRegistry)
: BaseAudioContext(sampleRate, audioEventHandlerRegistry), isInitialized_(false) {
const std::shared_ptr<IAudioEventHandlerRegistry> &audioEventHandlerRegistry,
AudioContextLatencyHint latencyHint)
: BaseAudioContext(sampleRate, audioEventHandlerRegistry),
latencyHint_(latencyHint),
isInitialized_(false) {
// Context starts SUSPENDED with no audio-thread consumer. Let the producer
// drain the channels itself until start()/resume() hands draining to the
// audio callback (same pattern as OfflineAudioContext before rendering).
Expand Down Expand Up @@ -45,13 +48,15 @@ void AudioContext::initialize(const AudioDestinationNode *destination) {
destination_->getChannelCount(),
&driverMutex_,
std::static_pointer_cast<AudioContext>(shared_from_this()),
currentRenders_);
currentRenders_,
latencyHint_);
#else
audioPlayer_ = std::make_shared<IOSAudioPlayer>(
[this](DSPAudioBuffer *buf, int n) { processGraph(buf, n); },
getSampleRate(),
destination_->getChannelCount(),
currentRenders_);
currentRenders_,
latencyHint_);
#endif
}

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/jsi/ContextPromiseResolver.hpp>
#include <audioapi/utils/AudioBuffer.hpp>
#include <audioapi/utils/Macros.h>
Expand All @@ -15,7 +16,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 All @@ -39,6 +41,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
@@ -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 @@ -24,7 +25,8 @@ class IOSAudioPlayer : public CommonPlayer {
const std::function<void(DSPAudioBuffer *, int)> &renderAudio,
float sampleRate,
int channelCount,
std::atomic<uint32_t> &currentRenders);
std::atomic<uint32_t> &currentRenders,
AudioContextLatencyHint latencyHint);
~IOSAudioPlayer() override;

DELETE_COPY_AND_MOVE(IOSAudioPlayer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,34 @@

namespace audioapi {

namespace {

/// In frames of the session's own rate, which may differ from the context's.
int preferredIOBufferFramesFor(AudioContextLatencyHint latencyHint)
{
switch (latencyHint) {
case AudioContextLatencyHint::INTERACTIVE:
return RENDER_QUANTUM_SIZE;
case AudioContextLatencyHint::BALANCED:
return 8 * RENDER_QUANTUM_SIZE;
case AudioContextLatencyHint::PLAYBACK:
return 32 * RENDER_QUANTUM_SIZE;
}
return RENDER_QUANTUM_SIZE;
}

} // namespace

IOSAudioPlayer::IOSAudioPlayer(
const std::function<void(DSPAudioBuffer *, int)> &renderAudio,
float sampleRate,
int channelCount,
std::atomic<uint32_t> &currentRenders)
std::atomic<uint32_t> &currentRenders,
AudioContextLatencyHint latencyHint)
: audioBuffer_(nullptr),
audioPlayer_(nullptr),
renderAudio_(renderAudio),
sampleRate_(sampleRate),
renderAudio_(renderAudio),
currentRenders_(currentRenders),
channelCount_(channelCount),
isRunning_(false),
Expand All @@ -31,9 +50,11 @@
deliverOutputBuffers(outputData, numFrames);
};

audioPlayer_ = [[NativeAudioPlayer alloc] initWithRenderAudio:renderAudioBlock
sampleRate:sampleRate
channelCount:channelCount_];
audioPlayer_ =
[[NativeAudioPlayer alloc] initWithRenderAudio:renderAudioBlock
sampleRate:sampleRate
channelCount:channelCount_
preferredIOBufferFrames:preferredIOBufferFramesFor(latencyHint)];
audioBuffer_ = std::make_shared<DSPAudioBuffer>(RENDER_QUANTUM_SIZE, channelCount_, sampleRate);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ typedef void (^RenderAudioBlock)(AudioBufferList *outputBuffer, int numFrames);

- (instancetype)initWithRenderAudio:(RenderAudioBlock)renderAudio
sampleRate:(float)sampleRate
channelCount:(int)channelCount;
channelCount:(int)channelCount
preferredIOBufferFrames:(int)preferredIOBufferFrames;

- (bool)start;

Expand Down
Loading
Loading