blob: 92bee137033034c19dc955bc1fab430122b37825 [file] [log] [blame]
// Copyright 2017 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 "third_party/blink/renderer/platform/audio/push_pull_fifo.h"
#include <memory>
#include "build/build_config.h"
#include "third_party/blink/renderer/platform/audio/audio_utilities.h"
#include "third_party/blink/renderer/platform/histogram.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
namespace blink {
namespace {
// Suppress the warning log if over/underflow happens more than 100 times.
const unsigned kMaxMessagesToLog = 100;
}
const size_t PushPullFIFO::kMaxFIFOLength = 65536;
PushPullFIFO::PushPullFIFO(unsigned number_of_channels, size_t fifo_length)
: fifo_length_(fifo_length) {
CHECK_LE(fifo_length_, kMaxFIFOLength);
fifo_bus_ = AudioBus::Create(number_of_channels, fifo_length_);
}
PushPullFIFO::~PushPullFIFO() {
// Capture metrics only after the FIFO is actually pulled.
if (pull_count_ == 0)
return;
// TODO(hongchan): The fast-shutdown process prevents the data below from
// being collected correctly. Consider using "outside metric collector" that
// survives the fast-shutdown.
// Capture the percentage of underflow happened based on the total pull count.
// (100 buckets of size 1) This is equivalent of
// "Media.AudioRendererMissedDeadline" metric for WebAudio.
DEFINE_STATIC_LOCAL(
LinearHistogram,
fifo_underflow_percentage_histogram,
("WebAudio.PushPullFIFO.UnderflowPercentage", 1, 100, 101));
fifo_underflow_percentage_histogram.Count(
static_cast<int32_t>(100.0 * underflow_count_ / pull_count_));
// We only collect the underflow count because no overflow can happen in the
// current implementation. This is similar to
// "Media.AudioRendererAudioGlitches" metric for WebAudio, which is a simple
// flag indicates any instance of glitches during FIFO's lifetime.
DEFINE_STATIC_LOCAL(BooleanHistogram, fifo_underflow_glitches_histogram,
("WebAudio.PushPullFIFO.UnderflowGlitches"));
fifo_underflow_glitches_histogram.Count(underflow_count_ > 0);
}
// Push the data from |input_bus| to FIFO. The size of push is determined by
// the length of |input_bus|.
void PushPullFIFO::Push(const AudioBus* input_bus) {
TRACE_EVENT1("webaudio", "PushPullFIFO::Push",
"input_bus length", input_bus->length());
MutexLocker locker(lock_);
CHECK(input_bus);
CHECK_EQ(input_bus->length(), audio_utilities::kRenderQuantumFrames);
SECURITY_CHECK(input_bus->length() <= fifo_length_);
SECURITY_CHECK(index_write_ < fifo_length_);
const size_t input_bus_length = input_bus->length();
const size_t remainder = fifo_length_ - index_write_;
for (unsigned i = 0; i < fifo_bus_->NumberOfChannels(); ++i) {
float* fifo_bus_channel = fifo_bus_->Channel(i)->MutableData();
const float* input_bus_channel = input_bus->Channel(i)->Data();
if (remainder >= input_bus_length) {
// The remainder is big enough for the input data.
memcpy(fifo_bus_channel + index_write_, input_bus_channel,
input_bus_length * sizeof(*fifo_bus_channel));
} else {
// The input data overflows the remainder size. Wrap around the index.
memcpy(fifo_bus_channel + index_write_, input_bus_channel,
remainder * sizeof(*fifo_bus_channel));
memcpy(fifo_bus_channel, input_bus_channel + remainder,
(input_bus_length - remainder) * sizeof(*fifo_bus_channel));
}
}
// Update the write index; wrap it around if necessary.
index_write_ = (index_write_ + input_bus_length) % fifo_length_;
// In case of overflow, move the |index_read_| to the updated |index_write_|
// to avoid reading overwritten frames by the next pull.
if (input_bus_length > fifo_length_ - frames_available_) {
index_read_ = index_write_;
if (++overflow_count_ < kMaxMessagesToLog) {
LOG(WARNING) << "PushPullFIFO: overflow while pushing ("
<< "overflowCount=" << overflow_count_
<< ", availableFrames=" << frames_available_
<< ", inputFrames=" << input_bus_length
<< ", fifoLength=" << fifo_length_ << ")";
}
}
// Update the number of frames available in FIFO.
frames_available_ =
std::min(frames_available_ + input_bus_length, fifo_length_);
DCHECK_EQ((index_read_ + frames_available_) % fifo_length_, index_write_);
}
// Pull the data out of FIFO to |output_bus|. If remaining frame in the FIFO
// is less than the frames to pull, provides remaining frame plus the silence.
size_t PushPullFIFO::Pull(AudioBus* output_bus, size_t frames_requested) {
TRACE_EVENT2("webaudio", "PushPullFIFO::Pull",
"output_bus length", output_bus->length(),
"frames_requested", frames_requested);
MutexLocker locker(lock_);
#if defined(OS_ANDROID)
if (!output_bus) {
// Log when outputBus or FIFO object is invalid. (crbug.com/692423)
LOG(WARNING) << "[WebAudio/PushPullFIFO::pull <" << static_cast<void*>(this)
<< ">] |outputBus| is invalid.";
// Silently return to avoid crash.
return 0;
}
// The following checks are in place to catch the inexplicable crash.
// (crbug.com/692423)
if (frames_requested > output_bus->length()) {
LOG(WARNING) << "[WebAudio/PushPullFIFO::pull <" << static_cast<void*>(this)
<< ">] framesRequested > outputBus->length() ("
<< frames_requested << " > " << output_bus->length() << ")";
}
if (frames_requested > fifo_length_) {
LOG(WARNING) << "[WebAudio/PushPullFIFO::pull <" << static_cast<void*>(this)
<< ">] framesRequested > fifo_length_ (" << frames_requested
<< " > " << fifo_length_ << ")";
}
if (index_read_ >= fifo_length_) {
LOG(WARNING) << "[WebAudio/PushPullFIFO::pull <" << static_cast<void*>(this)
<< ">] index_read_ >= fifo_length_ (" << index_read_
<< " >= " << fifo_length_ << ")";
}
#endif
CHECK(output_bus);
SECURITY_CHECK(frames_requested <= output_bus->length());
SECURITY_CHECK(frames_requested <= fifo_length_);
SECURITY_CHECK(index_read_ < fifo_length_);
const size_t remainder = fifo_length_ - index_read_;
const size_t frames_to_fill = std::min(frames_available_, frames_requested);
for (unsigned i = 0; i < fifo_bus_->NumberOfChannels(); ++i) {
const float* fifo_bus_channel = fifo_bus_->Channel(i)->Data();
float* output_bus_channel = output_bus->Channel(i)->MutableData();
// Fill up the output bus with the available frames first.
if (remainder >= frames_to_fill) {
// The remainder is big enough for the frames to pull.
memcpy(output_bus_channel, fifo_bus_channel + index_read_,
frames_to_fill * sizeof(*fifo_bus_channel));
} else {
// The frames to pull is bigger than the remainder size.
// Wrap around the index.
memcpy(output_bus_channel, fifo_bus_channel + index_read_,
remainder * sizeof(*fifo_bus_channel));
memcpy(output_bus_channel + remainder, fifo_bus_channel,
(frames_to_fill - remainder) * sizeof(*fifo_bus_channel));
}
// The frames available was not enough to fulfill the requested frames. Fill
// the rest of the channel with silence.
if (frames_requested > frames_to_fill) {
memset(output_bus_channel + frames_to_fill, 0,
(frames_requested - frames_to_fill) * sizeof(*output_bus_channel));
}
}
// Update the read index; wrap it around if necessary.
index_read_ = (index_read_ + frames_to_fill) % fifo_length_;
// In case of underflow, move the |indexWrite| to the updated |indexRead|.
if (frames_requested > frames_to_fill) {
index_write_ = index_read_;
if (underflow_count_++ < kMaxMessagesToLog) {
LOG(WARNING) << "PushPullFIFO: underflow while pulling ("
<< "underflowCount=" << underflow_count_
<< ", availableFrames=" << frames_available_
<< ", requestedFrames=" << frames_requested
<< ", fifoLength=" << fifo_length_ << ")";
}
}
// Update the number of frames in FIFO.
frames_available_ -= frames_to_fill;
DCHECK_EQ((index_read_ + frames_available_) % fifo_length_, index_write_);
pull_count_++;
// |frames_requested > frames_available_| means the frames in FIFO is not
// enough to fulfill the requested frames from the audio device.
return frames_requested > frames_available_
? frames_requested - frames_available_
: 0;
}
const PushPullFIFOStateForTest PushPullFIFO::GetStateForTest() const {
return {length(), NumberOfChannels(), frames_available_, index_read_,
index_write_, overflow_count_, underflow_count_};
}
} // namespace blink