284 lines
9.5 KiB
C++
284 lines
9.5 KiB
C++
/*
|
|
* 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 = makeRef(*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)
|