blob: 9f5b613be55f28706ff18f86851467e9c9a3249e [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/logging.h"
#include "services/media/audio/audio_output.h"
#include "services/media/audio/audio_track_impl.h"
#include "services/media/audio/audio_track_to_output_link.h"
namespace mojo {
namespace media {
namespace audio {
AudioTrackToOutputLink::Bookkeeping::~Bookkeeping() {}
AudioTrackToOutputLink::AudioTrackToOutputLink(AudioTrackImplWeakPtr track,
AudioOutputWeakPtr output)
: track_(track),
output_(output),
pending_queue_(new PacketQueue) {
#if !(defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON))
flush_lock_held_ = false;
#endif
}
AudioTrackToOutputLink::~AudioTrackToOutputLink() {
ReleaseQueue(pending_queue_);
}
void AudioTrackToOutputLink::UpdateGain() {
AudioTrackImplPtr track(GetTrack());
AudioOutputPtr output(GetOutput());
// If either side of this relationship is going away, then we are shutting
// down. Don't bother to re-calculate the amplitude scale factor.
if (!track || !output) { return; }
// Obtain the track gain and, if it is at or below the muted threshold, force
// the track to be muted and get out.
double db_gain = track->DbGain();
if (db_gain <= AudioTrack::kMutedGain) {
gain_.ForceMute();
return;
}
// Add in the output gain and clamp to the maximum allowed total gain.
db_gain += output->DbGain();
if (db_gain > AudioTrack::kMaxGain) {
db_gain = AudioTrack::kMaxGain;
}
gain_.Set(db_gain);
}
AudioTrackToOutputLinkPtr AudioTrackToOutputLink::New(
AudioTrackImplWeakPtr track,
AudioOutputWeakPtr output) {
return AudioTrackToOutputLinkPtr(new AudioTrackToOutputLink(track, output));
}
void AudioTrackToOutputLink::PushToPendingQueue(
const AudioPipe::AudioPacketRefPtr& pkt) {
base::AutoLock lock(pending_queue_lock_);
pending_queue_->emplace_back(pkt);
}
void AudioTrackToOutputLink::FlushPendingQueue() {
// Create a new (empty) queue before obtaining any locks. This will allow us
// to quickly swap the empty queue for the current queue and get out of all
// the locks, and then release the packets at our leisure instead of
// potentially holding off a high priority mixing thread while releasing
// packets.
//
// Note: the safety of this technique depends on Flush only ever being called
// from the AudioTrack, and the AudioTrack's actions being serialized on the
// AudioServer's message loop thread. If multiple flushes are allowed to be
// invoked simultaneously, or if a packet is permitted to be added to the
// queue while a flush operation is in progress, it is possible to return
// packets to the user in an order different than the one that they were
// queued in.
PacketQueuePtr new_queue(new PacketQueue);
{
base::AutoLock lock(flush_lock_);
{
// TODO(johngro): Assuming that it is impossible to push a new packet
// while a flush is in progress, it's pretty easy to show that this lock
// can never be contended. Because of this, we could consider removing
// this lock operation (although, flush is a relatively rare operation, so
// the extra overhead is pretty insignificant.
base::AutoLock lock(pending_queue_lock_);
pending_queue_.swap(new_queue);
}
flushed_ = true;
}
ReleaseQueue(new_queue);
}
AudioPipe::AudioPacketRefPtr AudioTrackToOutputLink::LockPendingQueueFront(
bool* was_flushed) {
#if !(defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON))
// No one had better be holding the flush lock right now. If someone is, then
// we either have multiple threads hitting this operation at the same time
// (should be impossible), or an audio output forgot to unlock the front of
// the queue before attempting to lock it again.
bool was_held = false;
flush_lock_held_.compare_exchange_strong(was_held, true);
DCHECK(!was_held);
#endif
flush_lock_.Acquire();
DCHECK(was_flushed);
*was_flushed = flushed_;
flushed_ = false;
{
base::AutoLock lock(pending_queue_lock_);
if (pending_queue_->size()) {
return pending_queue_->front();
} else {
return nullptr;
}
}
}
void AudioTrackToOutputLink::UnlockPendingQueueFront(
AudioPipe::AudioPacketRefPtr* pkt,
bool release_packet) {
{
base::AutoLock lock(pending_queue_lock_);
// Assert that the user either got no packet when they locked the queue
// (because the queue was empty), or that they got the front of the queue
// and that the front of the queue has not changed.
DCHECK(pkt);
DCHECK((*pkt == nullptr) ||
(pending_queue_->size() && (*pkt == pending_queue_->front())));
if (*pkt) {
*pkt = nullptr;
if (release_packet) {
pending_queue_->pop_front();
}
}
}
flush_lock_.Release();
#if !(defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON))
bool was_held = true;
flush_lock_held_.compare_exchange_strong(was_held, false);
DCHECK(was_held);
#endif
}
void AudioTrackToOutputLink::ReleaseQueue(const PacketQueuePtr& queue) {
if (!queue) {
return;
}
for (auto iter = queue->begin(); iter != queue->end(); ++iter) {
(*iter)->SetResult(MediaConsumer::SendResult::FLUSHED);
(*iter).reset();
}
queue->clear();
}
} // namespace audio
} // namespace media
} // namespace mojo