Loading...
Searching...
No Matches
GStreamerAudioBuffer.hpp
Go to the documentation of this file.
1#pragma once
2
12#include <ossia/detail/pod_vector.hpp>
13#include <ossia/detail/small_vector.hpp>
14
15#include <algorithm>
16#include <atomic>
17#include <cstddef>
18#include <span>
19#include <vector>
20
21namespace Gfx::GStreamer
22{
23
24// Audio: GStreamer delivers large chunks (e.g. 1024 samples).
25// The audio engine reads small chunks (e.g. 64 samples).
26// We use a lock-free ring buffer to bridge the two.
28{
29 int sample_rate{48000};
30 int num_channels{2};
31
32 // Ring buffer per channel, written by GStreamer thread, read by audio engine
33 static constexpr std::size_t ring_size = 65536;
34
35 // Max block the audio thread may resize the output storage to; the
36 // parameter reserves this up front so the per-tick resize never reallocates
37 // (a realloc would free a buffer the audio thread is reading through).
38 static constexpr std::size_t max_block = 1 << 15;
39 std::vector<std::vector<float>> ring; // [channel][ring_size]
40 std::atomic<std::size_t> write_pos{0};
41 std::atomic<std::size_t> read_pos{0};
42
43 // Backing storage for audio spans — audio engine reads from here
44 std::vector<ossia::float_vector>* output_data{};
45
46 void init(int nchannels)
47 {
48 num_channels = nchannels;
49 ring.resize(nchannels);
50 for(auto& ch : ring)
51 ch.resize(ring_size, 0.f);
52 }
53
54 // Called by GStreamer thread: write deinterleaved samples into ring
55 void write(const float* interleaved, int num_samples, int channels)
56 {
57 int nch = std::min(channels, num_channels);
58 auto wp = write_pos.load(std::memory_order_relaxed);
59 for(int s = 0; s < num_samples; s++)
60 {
61 for(int ch = 0; ch < nch; ch++)
62 ring[ch][(wp + s) % ring_size] = interleaved[s * channels + ch];
63 }
64 write_pos.store(wp + num_samples, std::memory_order_release);
65 }
66
67 // Points at the parameter's audio spans so read_into_output can re-point
68 // them after a resize. A raw pointer (not a std::function) so that clearing
69 // or using it during teardown can never throw on the audio thread.
70 ossia::small_vector<std::span<float>, 8>* output_spans{};
71
72 // Called by audio engine (indirectly): copy from ring into output spans
73 void read_into_output(int block_size)
74 {
75 if(!output_data)
76 return;
77
78 // The engine tick size can differ from the configured buffer size
79 // (e.g. PipeWire dynamic quantum); the storage follows it, but never
80 // beyond the capacity reserved at construction (so no reallocation).
81 if(block_size > (int)max_block)
82 block_size = max_block;
83 bool resized = false;
84 for(auto& v : *output_data)
85 {
86 if(std::ssize(v) != block_size)
87 {
88 v.resize(block_size);
89 resized = true;
90 }
91 }
92 if(resized && output_spans && output_data)
93 {
94 const std::size_t n = std::min(output_spans->size(), output_data->size());
95 for(std::size_t i = 0; i < n; i++)
96 (*output_spans)[i] = (*output_data)[i];
97 }
98
99 auto rp = read_pos.load(std::memory_order_relaxed);
100 auto wp = write_pos.load(std::memory_order_acquire);
101
102 std::size_t available = (wp >= rp) ? (wp - rp) : 0;
103
104 int nch = std::min((int)output_data->size(), num_channels);
105 if(available >= (std::size_t)block_size)
106 {
107 for(int ch = 0; ch < nch; ch++)
108 {
109 auto& dst = (*output_data)[ch];
110 auto& src = ring[ch];
111 for(int s = 0; s < block_size; s++)
112 dst[s] = src[(rp + s) % ring_size];
113 }
114 read_pos.store(rp + block_size, std::memory_order_release);
115 }
116 else
117 {
118 // Underrun: output silence
119 for(int ch = 0; ch < nch; ch++)
120 std::fill_n((*output_data)[ch].data(), block_size, 0.f);
121 }
122 }
123};
124
125}
Definition GStreamerAudioBuffer.hpp:28