blob: 9ee64b79370494b5353997472a91e78fc0efb766 [file] [log] [blame]
/*
* Copyright (C) 2011, Google Inc. All rights reserved.
* Copyright (C) 2020-2021, Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#if ENABLE(WEB_AUDIO)
#include "DefaultAudioDestinationNode.h"
#include "AudioContext.h"
#include "AudioDestination.h"
#include "AudioWorklet.h"
#include "AudioWorkletMessagingProxy.h"
#include "Logging.h"
#include "MediaStrategy.h"
#include "PlatformStrategies.h"
#include "ScriptExecutionContext.h"
#include "WorkerRunLoop.h"
#include <wtf/IsoMallocInlines.h>
#include <wtf/MainThread.h>
constexpr unsigned EnabledInputChannels = 2;
namespace WebCore {
WTF_MAKE_ISO_ALLOCATED_IMPL(DefaultAudioDestinationNode);
DefaultAudioDestinationNode::DefaultAudioDestinationNode(AudioContext& context, std::optional<float> sampleRate)
: AudioDestinationNode(context, sampleRate.value_or(AudioDestination::hardwareSampleRate()))
{
ASSERT(BaseAudioContext::isSupportedSampleRate(AudioDestination::hardwareSampleRate()));
initializeDefaultNodeOptions(2, ChannelCountMode::Explicit, ChannelInterpretation::Speakers);
}
DefaultAudioDestinationNode::~DefaultAudioDestinationNode()
{
uninitialize();
}
AudioContext& DefaultAudioDestinationNode::context()
{
return downcast<AudioContext>(AudioDestinationNode::context());
}
const AudioContext& DefaultAudioDestinationNode::context() const
{
return downcast<AudioContext>(AudioDestinationNode::context());
}
void DefaultAudioDestinationNode::initialize()
{
ASSERT(isMainThread());
if (isInitialized())
return;
ALWAYS_LOG(LOGIDENTIFIER);
createDestination();
AudioNode::initialize();
}
void DefaultAudioDestinationNode::uninitialize()
{
ASSERT(isMainThread());
if (!isInitialized())
return;
ALWAYS_LOG(LOGIDENTIFIER);
clearDestination();
m_numberOfInputChannels = 0;
AudioNode::uninitialize();
}
void DefaultAudioDestinationNode::clearDestination()
{
ASSERT(m_destination);
if (m_wasDestinationStarted) {
m_destination->stop();
m_wasDestinationStarted = false;
}
m_destination->clearCallback();
m_destination = nullptr;
}
void DefaultAudioDestinationNode::createDestination()
{
ALWAYS_LOG(LOGIDENTIFIER, "contextSampleRate = ", sampleRate(), ", hardwareSampleRate = ", AudioDestination::hardwareSampleRate());
ASSERT(!m_destination);
m_destination = platformStrategies()->mediaStrategy().createAudioDestination(*this, m_inputDeviceId, m_numberOfInputChannels, channelCount(), sampleRate());
}
void DefaultAudioDestinationNode::recreateDestination()
{
bool wasDestinationStarted = m_wasDestinationStarted;
clearDestination();
createDestination();
if (wasDestinationStarted) {
m_wasDestinationStarted = true;
m_destination->start(dispatchToRenderThreadFunction());
}
}
void DefaultAudioDestinationNode::enableInput(const String& inputDeviceId)
{
ALWAYS_LOG(LOGIDENTIFIER);
ASSERT(isMainThread());
if (m_numberOfInputChannels != EnabledInputChannels) {
m_numberOfInputChannels = EnabledInputChannels;
m_inputDeviceId = inputDeviceId;
if (isInitialized())
recreateDestination();
}
}
Function<void(Function<void()>&&)> DefaultAudioDestinationNode::dispatchToRenderThreadFunction()
{
if (auto* workletProxy = context().audioWorklet().proxy()) {
return [workletProxy = Ref { *workletProxy }](Function<void()>&& function) {
workletProxy->postTaskForModeToWorkletGlobalScope([function = WTFMove(function)](ScriptExecutionContext&) mutable {
function();
}, WorkerRunLoop::defaultMode());
};
}
return nullptr;
}
void DefaultAudioDestinationNode::startRendering(CompletionHandler<void(std::optional<Exception>&&)>&& completionHandler)
{
ASSERT(isInitialized());
if (!isInitialized())
return completionHandler(Exception { InvalidStateError, "AudioDestinationNode is not initialized"_s });
auto innerCompletionHandler = [completionHandler = WTFMove(completionHandler)](bool success) mutable {
completionHandler(success ? std::nullopt : std::make_optional(Exception { InvalidStateError, "Failed to start the audio device"_s }));
};
m_wasDestinationStarted = true;
m_destination->start(dispatchToRenderThreadFunction(), WTFMove(innerCompletionHandler));
}
void DefaultAudioDestinationNode::resume(CompletionHandler<void(std::optional<Exception>&&)>&& completionHandler)
{
ASSERT(isInitialized());
if (!isInitialized()) {
context().postTask([completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(Exception { InvalidStateError, "AudioDestinationNode is not initialized"_s });
});
return;
}
m_wasDestinationStarted = true;
m_destination->start(dispatchToRenderThreadFunction(), [completionHandler = WTFMove(completionHandler)](bool success) mutable {
completionHandler(success ? std::nullopt : std::make_optional(Exception { InvalidStateError, "Failed to start the audio device"_s }));
});
}
void DefaultAudioDestinationNode::suspend(CompletionHandler<void(std::optional<Exception>&&)>&& completionHandler)
{
ASSERT(isInitialized());
if (!isInitialized()) {
context().postTask([completionHandler = WTFMove(completionHandler)]() mutable {
completionHandler(Exception { InvalidStateError, "AudioDestinationNode is not initialized"_s });
});
return;
}
m_wasDestinationStarted = false;
m_destination->stop([completionHandler = WTFMove(completionHandler)](bool success) mutable {
completionHandler(success ? std::nullopt : std::make_optional(Exception { InvalidStateError, "Failed to stop the audio device"_s }));
});
}
void DefaultAudioDestinationNode::restartRendering()
{
if (!m_wasDestinationStarted)
return;
m_destination->stop();
m_destination->start(dispatchToRenderThreadFunction());
}
void DefaultAudioDestinationNode::close(CompletionHandler<void()>&& completionHandler)
{
ASSERT(isInitialized());
uninitialize();
context().postTask(WTFMove(completionHandler));
}
unsigned DefaultAudioDestinationNode::maxChannelCount() const
{
return AudioDestination::maxChannelCount();
}
ExceptionOr<void> DefaultAudioDestinationNode::setChannelCount(unsigned channelCount)
{
// The channelCount for the input to this node controls the actual number of channels we
// send to the audio hardware. It can only be set depending on the maximum number of
// channels supported by the hardware.
ASSERT(isMainThread());
ALWAYS_LOG(LOGIDENTIFIER, channelCount);
if (channelCount > maxChannelCount())
return Exception { IndexSizeError, "Channel count exceeds maximum limit"_s };
auto oldChannelCount = this->channelCount();
auto result = AudioNode::setChannelCount(channelCount);
if (result.hasException())
return result;
if (this->channelCount() != oldChannelCount && isInitialized())
recreateDestination();
return { };
}
unsigned DefaultAudioDestinationNode::framesPerBuffer() const
{
return m_destination ? m_destination->framesPerBuffer() : 0;
}
void DefaultAudioDestinationNode::render(AudioBus*, AudioBus* destinationBus, size_t numberOfFrames, const AudioIOPosition& outputPosition)
{
renderQuantum(destinationBus, numberOfFrames, outputPosition);
setIsSilent(destinationBus->isSilent());
// The reason we are handling mute after the call to setIsSilent() is because the muted state does
// not affect the audio destination node's effective playing state.
if (m_muted)
destinationBus->zero();
}
void DefaultAudioDestinationNode::setIsSilent(bool isSilent)
{
if (m_isSilent == isSilent)
return;
m_isSilent = isSilent;
updateIsEffectivelyPlayingAudio();
}
void DefaultAudioDestinationNode::isPlayingDidChange()
{
updateIsEffectivelyPlayingAudio();
}
void DefaultAudioDestinationNode::updateIsEffectivelyPlayingAudio()
{
bool isEffectivelyPlayingAudio = m_destination && m_destination->isPlaying() && !m_isSilent;
if (m_isEffectivelyPlayingAudio == isEffectivelyPlayingAudio)
return;
m_isEffectivelyPlayingAudio = isEffectivelyPlayingAudio;
context().isPlayingAudioDidChange();
}
} // namespace WebCore
#endif // ENABLE(WEB_AUDIO)