OpenShot Library | libopenshot 1.1.1
Loading...
Searching...
No Matches
CaptureAudioBuffer.h
Go to the documentation of this file.
1
2// Copyright (c) 2008-2026 OpenShot Studios, LLC
3// SPDX-License-Identifier: LGPL-3.0-or-later
4
5#ifndef OPENSHOT_CAPTURE_AUDIO_BUFFER_H
6#define OPENSHOT_CAPTURE_AUDIO_BUFFER_H
7
8#include <algorithm>
9#include <cstdint>
10#include <deque>
11#include <utility>
12#include <vector>
13
14extern "C" {
15#include <libavutil/mathematics.h>
16}
17
18namespace openshot {
19// Positions are sample frames relative to recording start. Callers synchronize
20// access. Delivery delays and dropped packets must never shift later samples.
22public:
23 void Reset(int channels, int64_t capacity)
24 {
25 channel_count = channels;
26 max_samples = capacity;
27 position = 0;
28 queued_samples = 0;
29 timestamp_started = false;
30 next_timestamp_sample = 0;
31 blocks.clear();
32 }
33
34 void Push(int64_t start, std::vector<std::vector<float>> samples)
35 {
36 if (samples.size() != static_cast<size_t>(channel_count) || samples.empty()) return;
37 const int64_t count = samples.front().size();
38 for (const auto& channel : samples) {
39 if (channel.size() != static_cast<size_t>(count)) return;
40 }
41 if (!count || start + count <= position) return;
42 // Trim samples from before recording start, already-written intervals,
43 // or a single packet larger than the bounded capture buffer.
44 const int64_t trim = std::max<int64_t>({0, position - start, count - max_samples});
45 for (auto& channel : samples) channel.erase(channel.begin(), channel.begin() + trim);
46 start += trim;
47 queued_samples += count - trim;
48 const auto insertion = std::upper_bound(blocks.begin(), blocks.end(), start,
49 [](int64_t value, const Block& block) { return value < block.start; });
50 blocks.insert(insertion, Block{start, std::move(samples)});
51 while (queued_samples > max_samples && !blocks.empty()) {
52 queued_samples -= blocks.front().samples.front().size();
53 blocks.pop_front();
54 }
55 }
56
57 void PushTimestamp(int64_t timestamp, int64_t epoch, AVRational time_base,
58 int sample_rate, std::vector<std::vector<float>> samples)
59 {
60 if (samples.empty() || samples.front().empty()) return;
61 int64_t start = av_rescale_q(timestamp - epoch, time_base, AVRational{1, sample_rate});
62 // Capture timestamps include clock-estimation jitter. Placing every
63 // packet independently can insert silence or discard PCM at each seam.
64 // Keep adjacent packets sample-contiguous within one millisecond of
65 // their absolute capture position. Compare against the accumulated end,
66 // not the previous timestamp, so tolerance cannot accumulate into drift.
67 const int64_t tolerance = std::max(1, sample_rate / 1000);
68 if (timestamp_started && start >= next_timestamp_sample - tolerance
69 && start <= next_timestamp_sample + tolerance) {
70 start = next_timestamp_sample;
71 }
72 next_timestamp_sample = start + static_cast<int64_t>(samples.front().size());
73 timestamp_started = true;
74 Push(start, std::move(samples));
75 }
76
77 bool Covers(int count) const
78 {
79 return std::any_of(blocks.begin(), blocks.end(), [&](const Block& block) {
80 return block.start + static_cast<int64_t>(block.samples.front().size()) >= position + count;
81 });
82 }
83
84 std::vector<std::vector<float>> Read(int count)
85 {
86 std::vector<std::vector<float>> result(channel_count, std::vector<float>(count, 0.0f));
87 const int64_t end = position + count;
88 for (const auto& block : blocks) {
89 const int64_t first = std::max(position, block.start);
90 const int64_t last = std::min(end, block.start + static_cast<int64_t>(block.samples.front().size()));
91 if (last <= first) continue;
92 for (int channel = 0; channel < channel_count; ++channel) {
93 std::copy_n(block.samples[channel].begin() + (first - block.start), last - first,
94 result[channel].begin() + (first - position));
95 }
96 }
97 position = end;
98 for (auto it = blocks.begin(); it != blocks.end();) {
99 if (it->start + static_cast<int64_t>(it->samples.front().size()) <= position) {
100 queued_samples -= it->samples.front().size();
101 it = blocks.erase(it);
102 } else ++it;
103 }
104 return result;
105 }
106
107private:
108 struct Block {
109 int64_t start;
110 std::vector<std::vector<float>> samples;
111 };
112 std::deque<Block> blocks;
113 int channel_count = 0;
114 int64_t max_samples = 0;
115 int64_t position = 0;
116 int64_t queued_samples = 0;
117 bool timestamp_started = false;
118 int64_t next_timestamp_sample = 0;
119};
120}
121#endif
void Push(int64_t start, std::vector< std::vector< float > > samples)
void Reset(int channels, int64_t capacity)
void PushTimestamp(int64_t timestamp, int64_t epoch, AVRational time_base, int sample_rate, std::vector< std::vector< float > > samples)
std::vector< std::vector< float > > Read(int count)
This namespace is the default namespace for all code in the openshot library.