summaryrefslogtreecommitdiff
path: root/chromium/services/audio/delay_buffer.cc
blob: 17d2a04b2771a195e1ec019fd15715bc78c591ee (plain)
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
// 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 <algorithm>
#include <utility>

#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<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;
      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