summaryrefslogtreecommitdiff
path: root/chromium/services/audio/delay_buffer.h
blob: 2d1482c88dcb8c55f1479bd1096b54eb33b66aa1 (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
// 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.

#ifndef SERVICES_AUDIO_DELAY_BUFFER_H_
#define SERVICES_AUDIO_DELAY_BUFFER_H_

#include <memory>

#include "base/containers/circular_deque.h"
#include "base/macros.h"

namespace media {
class AudioBus;
}  // namespace media

namespace audio {

// Records and maintains a recent history of an audio signal, then allows
// read-back starting from part of the recording. While this looks a lot like a
// FIFO, it is not because Read() will typically not be reading from one end of
// a queue.
//
// The audio format is the same throughout all operations, as this DelayBuffer
// does not resample or remix the audio. Also, for absolute precision, it uses
// frame counts to track the timing of the audio recorded and read.
//
// The typical use case is the loopback audio system: In this scenario, the
// service has an audio output stream running for local playback, and the
// stream's audio is timed to play back in the near future (usually, 1 ms to 20
// ms, depending on the platform). When loopback is active, that audio will be
// copied into this DelayBuffer via calls to Write(). Then, the loopback audio
// stream implementation will Read() the audio at a time in the recent past
// (approximately 20 ms before "now," but this will vary slightly). Because of
// clock drift concerns, the loopback implementation will slightly compress/
// stretch the audio signal it pulls out of this buffer, to maintain
// synchronization, and this will cause it to vary the number of frames read for
// each successive Read() call.
class DelayBuffer {
 public:
  // Use sample counts as a measure of audio signal position.
  using FrameTicks = int64_t;

  // Construct a DelayBuffer that keeps at least |history_size| un-read frames
  // recorded.
  explicit DelayBuffer(int history_size);

  ~DelayBuffer();

  // Inserts a copy of the given audio into the buffer. |position| must be
  // monotonically increasing, and the audio must not overlap any
  // previously-written audio. The length of the |input_bus| may vary, but the
  // channel layout may not. The given |volume| will be used to scale the audio
  // during the copy to the internal buffer.
  void Write(FrameTicks position,
             const media::AudioBus& input_bus,
             double volume);

  // Reads audio from the buffer, starting at the given |from| position, which
  // must not overlap any previously-read audio. |frames_to_read| is the number
  // of frames to read, which may be any amount less than or equal to the size
  // of the |output_bus|. No part of the |output_bus| beyond the first
  // |frames_to_read| will be modified. If there are gaps (i.e., missing pieces)
  // of the recording, zeros will be filled in the output.
  void Read(FrameTicks from, int frames_to_read, media::AudioBus* output_bus);

  // Returns the current buffered range of the recording, ala the usual C++
  // [begin,end)  semantics.
  FrameTicks GetBeginPosition() const;
  FrameTicks GetEndPosition() const;

 private:
  struct InputChunk {
    // The position of the first frame in this chunk.
    FrameTicks position;

    // The storage for the audio frames.
    std::unique_ptr<media::AudioBus> bus;

    // Constructor for an InputChunk with data.
    InputChunk(FrameTicks p, std::unique_ptr<media::AudioBus> b);

    // Move constructor/assignment.
    InputChunk(InputChunk&& other);
    InputChunk& operator=(InputChunk&& other);

    ~InputChunk();

    // Returns the position just after the last frame's position.
    FrameTicks GetEndPosition() const;

   private:
    DISALLOW_COPY_AND_ASSIGN(InputChunk);
  };

  // The minimum number of un-read frames that must be kept.
  const int history_size_;

  // A queue storing each chunk of recorded audio. The elements in the queue are
  // always in-order, chronologically increasing by InputChunk::position, and do
  // not overlap.
  base::circular_deque<InputChunk> chunks_;

  DISALLOW_COPY_AND_ASSIGN(DelayBuffer);
};

}  // namespace audio

#endif  // SERVICES_AUDIO_DELAY_BUFFER_H_