#include "services/audio/delay_buffer.h"
#include <algorithm>
#include <utility>
#include "base/numerics/safe_conversions.h"
#include "base/trace_event/trace_event.h"
#include "base/types/zip.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);
const FrameTicks prune_position =
position + input_bus.frames() - history_size_;
while (!chunks_.empty() &&
chunks_.front().GetEndPosition() <= prune_position) {
chunks_.pop_front();
}
auto copy = media::AudioBus::Create(input_bus.channels(), input_bus.frames());
for (auto [src_ch, dest_ch] :
base::zip(input_bus.AllChannels(), copy->AllChannels())) {
media::vector_math::FMUL(src_ch, volume, dest_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());
while (!chunks_.empty() && chunks_.front().GetEndPosition() <= from) {
chunks_.pop_front();
}
for (int frames_remaining = frames_to_read; frames_remaining > 0;) {
const int dest_offset = frames_to_read - frames_remaining;
if (chunks_.empty()) {
TRACE_EVENT_INSTANT1("audio", "DelayBuffer::Read underrun",
TRACE_EVENT_SCOPE_THREAD, "frames missing",
frames_remaining);
output_bus->ZeroFramesPartial(dest_offset, frames_remaining);
return;
}
const InputChunk& chunk = chunks_.front();
const int source_offset =
base::saturated_cast<int>(from + dest_offset - chunk.position);
if (source_offset < 0) {
const int frames_to_zero_fill = (source_offset + frames_remaining <= 0)
? frames_remaining
: -source_offset;
TRACE_EVENT_INSTANT1("audio", "DelayBuffer::Read gap",
TRACE_EVENT_SCOPE_THREAD, "frames missing",
frames_to_zero_fill);
output_bus->ZeroFramesPartial(dest_offset, frames_to_zero_fill);
frames_remaining -= frames_to_zero_fill;
continue;
}
DCHECK_LE(source_offset, chunk.bus->frames());
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();
} else {
chunk.bus->CopyPartialFramesTo(source_offset, frames_remaining,
dest_offset, output_bus);
return;
}
}
}
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<media::AudioBus> 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();
}
}