// Copyright 2018 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 "services/audio/delay_buffer.h" #include #include #include "base/numerics/safe_conversions.h" #include "media/base/audio_bus.h" #include "media/base/vector_math.h" namespace audio { DelayBuffer::DelayBuffer(int history_size) : history_size_(history_size) {} DelayBuffer::~DelayBuffer() = default; void DelayBuffer::Write(FrameTicks position, const media::AudioBus& input_bus, double volume) { DCHECK(chunks_.empty() || chunks_.back().GetEndPosition() <= position); // Prune-out the oldest InputChunks, but ensure that this DelayBuffer is // maintaining at least |history_size_| frames in total when this method // returns (i.e., after the current chunk is inserted). const FrameTicks prune_position = position + input_bus.frames() - history_size_; while (!chunks_.empty() && chunks_.front().GetEndPosition() <= prune_position) { chunks_.pop_front(); } // Make a copy of the AudioBus for later consumption. Apply the volume setting // by scaling the audio signal during the copy. auto copy = media::AudioBus::Create(input_bus.channels(), input_bus.frames()); for (int ch = 0; ch < input_bus.channels(); ++ch) { media::vector_math::FMUL(input_bus.channel(ch), volume, input_bus.frames(), copy->channel(ch)); } chunks_.emplace_back(position, std::move(copy)); } void DelayBuffer::Read(FrameTicks from, int frames_to_read, media::AudioBus* output_bus) { DCHECK_LE(frames_to_read, output_bus->frames()); // Remove all of the oldest chunks until the one in front contains the |from| // position (or is the first chunk after it). while (!chunks_.empty() && chunks_.front().GetEndPosition() <= from) { chunks_.pop_front(); } // Loop, transferring data from each InputChunk to the output AudioBus until // the requested number of frames have been read. for (int frames_remaining = frames_to_read; frames_remaining > 0;) { const int dest_offset = frames_to_read - frames_remaining; // If attempting to read past the end of the recorded signal, zero-pad the // rest of the output and return. if (chunks_.empty()) { output_bus->ZeroFramesPartial(dest_offset, frames_remaining); return; } const InputChunk& chunk = chunks_.front(); // This is the offset to the frame within the chunk's AudioBus that // corresponds to the offset in the output AudioBus. If this calculated // value is out-of-range, there is a gap (i.e., a missing piece of audio // signal) in the recording. const int source_offset = base::saturated_cast(from + dest_offset - chunk.position); if (source_offset < 0) { // There is a gap in the recording. Fill zeroes in the corresponding part // of the output. const int frames_to_zero_fill = (source_offset + frames_remaining <= 0) ? frames_remaining : -source_offset; output_bus->ZeroFramesPartial(dest_offset, frames_to_zero_fill); frames_remaining -= frames_to_zero_fill; continue; } DCHECK_LE(source_offset, chunk.bus->frames()); // Copy some or all of the frames in the current chunk to the output; the // lesser of: a) the frames available in the chunk, or b) the frames // remaining to output. const int frames_to_copy_from_chunk = chunk.bus->frames() - source_offset; if (frames_to_copy_from_chunk <= frames_remaining) { chunk.bus->CopyPartialFramesTo(source_offset, frames_to_copy_from_chunk, dest_offset, output_bus); frames_remaining -= frames_to_copy_from_chunk; chunks_.pop_front(); // All frames from this chunk have been consumed. } else { chunk.bus->CopyPartialFramesTo(source_offset, frames_remaining, dest_offset, output_bus); return; // The |output_bus| has been fully populated. } } } DelayBuffer::FrameTicks DelayBuffer::GetBeginPosition() const { return chunks_.empty() ? 0 : chunks_.front().position; } DelayBuffer::FrameTicks DelayBuffer::GetEndPosition() const { return chunks_.empty() ? 0 : chunks_.back().GetEndPosition(); } DelayBuffer::InputChunk::InputChunk(FrameTicks p, std::unique_ptr b) : position(p), bus(std::move(b)) {} DelayBuffer::InputChunk::InputChunk(DelayBuffer::InputChunk&&) = default; DelayBuffer::InputChunk& DelayBuffer::InputChunk::operator=( DelayBuffer::InputChunk&&) = default; DelayBuffer::InputChunk::~InputChunk() = default; DelayBuffer::FrameTicks DelayBuffer::InputChunk::GetEndPosition() const { return position + bus->frames(); } } // namespace audio