|  | /* | 
|  | * Copyright (C) 2010, Google 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 "modules/webaudio/DeferredTaskHandler.h" | 
|  | #include "modules/webaudio/AudioNode.h" | 
|  | #include "modules/webaudio/AudioNodeOutput.h" | 
|  | #include "modules/webaudio/OfflineAudioContext.h" | 
|  | #include "platform/CrossThreadFunctional.h" | 
|  | #include "platform/WebTaskRunner.h" | 
|  | #include "public/platform/Platform.h" | 
|  |  | 
|  | namespace blink { | 
|  |  | 
|  | void DeferredTaskHandler::lock() { | 
|  | // Don't allow regular lock in real-time audio thread. | 
|  | DCHECK(!IsAudioThread()); | 
|  | context_graph_mutex_.lock(); | 
|  | } | 
|  |  | 
|  | bool DeferredTaskHandler::TryLock() { | 
|  | // Try to catch cases of using try lock on main thread | 
|  | // - it should use regular lock. | 
|  | DCHECK(IsAudioThread()); | 
|  | if (!IsAudioThread()) { | 
|  | // In release build treat tryLock() as lock() (since above | 
|  | // DCHECK(isAudioThread) never fires) - this is the best we can do. | 
|  | lock(); | 
|  | return true; | 
|  | } | 
|  | return context_graph_mutex_.TryLock(); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::unlock() { | 
|  | context_graph_mutex_.unlock(); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::OfflineLock() { | 
|  | // CHECK is here to make sure to explicitly crash if this is called from | 
|  | // other than the offline render thread, which is considered as the audio | 
|  | // thread in OfflineAudioContext. | 
|  | CHECK(IsAudioThread()) << "DeferredTaskHandler::offlineLock() must be called " | 
|  | "within the offline audio thread."; | 
|  |  | 
|  | context_graph_mutex_.lock(); | 
|  | } | 
|  |  | 
|  | bool DeferredTaskHandler::IsGraphOwner() { | 
|  | #if DCHECK_IS_ON() | 
|  | return context_graph_mutex_.Locked(); | 
|  | #else | 
|  | // The method is only used inside of DCHECK() so it must be no-op in the | 
|  | // release build. Returning false so we can catch when it happens. | 
|  | return false; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::AddDeferredBreakConnection(AudioHandler& node) { | 
|  | DCHECK(IsAudioThread()); | 
|  | deferred_break_connection_list_.push_back(&node); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::BreakConnections() { | 
|  | DCHECK(IsAudioThread()); | 
|  | DCHECK(IsGraphOwner()); | 
|  |  | 
|  | for (unsigned i = 0; i < deferred_break_connection_list_.size(); ++i) | 
|  | deferred_break_connection_list_[i]->BreakConnectionWithLock(); | 
|  | deferred_break_connection_list_.clear(); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::MarkSummingJunctionDirty( | 
|  | AudioSummingJunction* summing_junction) { | 
|  | DCHECK(IsGraphOwner()); | 
|  | dirty_summing_junctions_.insert(summing_junction); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::RemoveMarkedSummingJunction( | 
|  | AudioSummingJunction* summing_junction) { | 
|  | DCHECK(IsMainThread()); | 
|  | GraphAutoLocker locker(*this); | 
|  | dirty_summing_junctions_.erase(summing_junction); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::MarkAudioNodeOutputDirty(AudioNodeOutput* output) { | 
|  | DCHECK(IsGraphOwner()); | 
|  | DCHECK(IsMainThread()); | 
|  | dirty_audio_node_outputs_.insert(output); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::RemoveMarkedAudioNodeOutput(AudioNodeOutput* output) { | 
|  | DCHECK(IsGraphOwner()); | 
|  | DCHECK(IsMainThread()); | 
|  | dirty_audio_node_outputs_.erase(output); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::HandleDirtyAudioSummingJunctions() { | 
|  | DCHECK(IsGraphOwner()); | 
|  |  | 
|  | for (AudioSummingJunction* junction : dirty_summing_junctions_) | 
|  | junction->UpdateRenderingState(); | 
|  | dirty_summing_junctions_.clear(); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::HandleDirtyAudioNodeOutputs() { | 
|  | DCHECK(IsGraphOwner()); | 
|  |  | 
|  | HashSet<AudioNodeOutput*> dirty_outputs; | 
|  | dirty_audio_node_outputs_.swap(dirty_outputs); | 
|  |  | 
|  | // Note: the updating of rendering state may cause output nodes | 
|  | // further down the chain to be marked as dirty. These will not | 
|  | // be processed in this render quantum. | 
|  | for (AudioNodeOutput* output : dirty_outputs) | 
|  | output->UpdateRenderingState(); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::AddAutomaticPullNode(AudioHandler* node) { | 
|  | DCHECK(IsGraphOwner()); | 
|  |  | 
|  | if (!automatic_pull_nodes_.Contains(node)) { | 
|  | automatic_pull_nodes_.insert(node); | 
|  | automatic_pull_nodes_need_updating_ = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::RemoveAutomaticPullNode(AudioHandler* node) { | 
|  | DCHECK(IsGraphOwner()); | 
|  |  | 
|  | if (automatic_pull_nodes_.Contains(node)) { | 
|  | automatic_pull_nodes_.erase(node); | 
|  | automatic_pull_nodes_need_updating_ = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::UpdateAutomaticPullNodes() { | 
|  | DCHECK(IsGraphOwner()); | 
|  |  | 
|  | if (automatic_pull_nodes_need_updating_) { | 
|  | CopyToVector(automatic_pull_nodes_, rendering_automatic_pull_nodes_); | 
|  | automatic_pull_nodes_need_updating_ = false; | 
|  | } | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::ProcessAutomaticPullNodes(size_t frames_to_process) { | 
|  | DCHECK(IsAudioThread()); | 
|  |  | 
|  | for (unsigned i = 0; i < rendering_automatic_pull_nodes_.size(); ++i) | 
|  | rendering_automatic_pull_nodes_[i]->ProcessIfNecessary(frames_to_process); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::AddChangedChannelCountMode(AudioHandler* node) { | 
|  | DCHECK(IsGraphOwner()); | 
|  | DCHECK(IsMainThread()); | 
|  | deferred_count_mode_change_.insert(node); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::RemoveChangedChannelCountMode(AudioHandler* node) { | 
|  | DCHECK(IsGraphOwner()); | 
|  |  | 
|  | deferred_count_mode_change_.erase(node); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::AddChangedChannelInterpretation(AudioHandler* node) { | 
|  | DCHECK(IsGraphOwner()); | 
|  | DCHECK(IsMainThread()); | 
|  | deferred_channel_interpretation_change_.insert(node); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::RemoveChangedChannelInterpretation( | 
|  | AudioHandler* node) { | 
|  | DCHECK(IsGraphOwner()); | 
|  |  | 
|  | deferred_channel_interpretation_change_.erase(node); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::UpdateChangedChannelCountMode() { | 
|  | DCHECK(IsGraphOwner()); | 
|  |  | 
|  | for (AudioHandler* node : deferred_count_mode_change_) | 
|  | node->UpdateChannelCountMode(); | 
|  | deferred_count_mode_change_.clear(); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::UpdateChangedChannelInterpretation() { | 
|  | DCHECK(IsGraphOwner()); | 
|  |  | 
|  | for (AudioHandler* node : deferred_channel_interpretation_change_) | 
|  | node->UpdateChannelInterpretation(); | 
|  | deferred_channel_interpretation_change_.clear(); | 
|  | } | 
|  |  | 
|  | DeferredTaskHandler::DeferredTaskHandler() | 
|  | : automatic_pull_nodes_need_updating_(false), audio_thread_(0) {} | 
|  |  | 
|  | scoped_refptr<DeferredTaskHandler> DeferredTaskHandler::Create() { | 
|  | return base::AdoptRef(new DeferredTaskHandler()); | 
|  | } | 
|  |  | 
|  | DeferredTaskHandler::~DeferredTaskHandler() { | 
|  | DCHECK(!automatic_pull_nodes_.size()); | 
|  | if (automatic_pull_nodes_need_updating_) | 
|  | rendering_automatic_pull_nodes_.resize(automatic_pull_nodes_.size()); | 
|  | DCHECK(!rendering_automatic_pull_nodes_.size()); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::HandleDeferredTasks() { | 
|  | UpdateChangedChannelCountMode(); | 
|  | UpdateChangedChannelInterpretation(); | 
|  | HandleDirtyAudioSummingJunctions(); | 
|  | HandleDirtyAudioNodeOutputs(); | 
|  | UpdateAutomaticPullNodes(); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::ContextWillBeDestroyed() { | 
|  | for (auto& handler : rendering_orphan_handlers_) | 
|  | handler->ClearContext(); | 
|  | for (auto& handler : deletable_orphan_handlers_) | 
|  | handler->ClearContext(); | 
|  | ClearHandlersToBeDeleted(); | 
|  | // Some handlers might live because of their cross thread tasks. | 
|  | } | 
|  |  | 
|  | DeferredTaskHandler::GraphAutoLocker::GraphAutoLocker(BaseAudioContext* context) | 
|  | : handler_(context->GetDeferredTaskHandler()) { | 
|  | handler_.lock(); | 
|  | } | 
|  |  | 
|  | DeferredTaskHandler::OfflineGraphAutoLocker::OfflineGraphAutoLocker( | 
|  | OfflineAudioContext* context) | 
|  | : handler_(context->GetDeferredTaskHandler()) { | 
|  | handler_.OfflineLock(); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::AddRenderingOrphanHandler( | 
|  | scoped_refptr<AudioHandler> handler) { | 
|  | DCHECK(handler); | 
|  | DCHECK(!rendering_orphan_handlers_.Contains(handler)); | 
|  | rendering_orphan_handlers_.push_back(std::move(handler)); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::RequestToDeleteHandlersOnMainThread() { | 
|  | DCHECK(IsGraphOwner()); | 
|  | DCHECK(IsAudioThread()); | 
|  | if (rendering_orphan_handlers_.IsEmpty()) | 
|  | return; | 
|  | deletable_orphan_handlers_.AppendVector(rendering_orphan_handlers_); | 
|  | rendering_orphan_handlers_.clear(); | 
|  | PostCrossThreadTask( | 
|  | *Platform::Current()->MainThread()->GetTaskRunner(), FROM_HERE, | 
|  | CrossThreadBind(&DeferredTaskHandler::DeleteHandlersOnMainThread, | 
|  | scoped_refptr<DeferredTaskHandler>(this))); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::DeleteHandlersOnMainThread() { | 
|  | DCHECK(IsMainThread()); | 
|  | GraphAutoLocker locker(*this); | 
|  | deletable_orphan_handlers_.clear(); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::ClearHandlersToBeDeleted() { | 
|  | DCHECK(IsMainThread()); | 
|  | GraphAutoLocker locker(*this); | 
|  | rendering_orphan_handlers_.clear(); | 
|  | deletable_orphan_handlers_.clear(); | 
|  | } | 
|  |  | 
|  | void DeferredTaskHandler::SetAudioThreadToCurrentThread() { | 
|  | DCHECK(!IsMainThread()); | 
|  | ThreadIdentifier thread = CurrentThread(); | 
|  | ReleaseStore(&audio_thread_, thread); | 
|  | } | 
|  |  | 
|  | }  // namespace blink |