1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
// Copyright 2018 The Chromium Authors
// 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 <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);
// 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 (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());
// 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()) {
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();
// 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<int>(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;
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());
// 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<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();
}
} // namespace audio
|