OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
sound_libav.hpp
1#pragma once
2#include <ossia/detail/config.hpp>
3
4#include <ossia/audio/audio_parameter.hpp>
5#include <ossia/dataflow/audio_stretch_mode.hpp>
6#include <ossia/dataflow/graph_node.hpp>
7#include <ossia/dataflow/nodes/media.hpp>
8#include <ossia/dataflow/nodes/sound.hpp>
9#include <ossia/dataflow/port.hpp>
10#include <ossia/detail/libav.hpp>
11#include <ossia/detail/pod_vector.hpp>
12
13#include <algorithm>
14#include <type_traits>
15
16extern "C" {
17#include <libavcodec/avcodec.h>
18#include <libavformat/avformat.h>
19#include <libavutil/frame.h>
20#include <libavutil/mem.h>
21#include <libswresample/swresample.h>
22}
23
24namespace ossia::nodes
25{
26class sound_libav final : public ossia::sound_node
27{
28 AVPacket* packet{};
29 AVFrame* frame{};
30
31public:
32 sound_libav()
33 : packet{av_packet_alloc()}
34 , frame{av_frame_alloc()}
35 {
36 m_outlets.push_back(&audio_out);
37 }
38
39 ~sound_libav()
40 {
41 m_handle.cleanup();
42
43 av_frame_free(&frame);
44 av_packet_free(&packet);
45 }
46
47 std::string label() const noexcept override { return "sound_libav"; }
48
49 void set_start(std::size_t v) { start = v; }
50
51 void set_upmix(std::size_t v) { upmix = v; }
52
53 void set_sound(libav_handle hdl)
54 {
55 using namespace snd;
56 m_handle.cleanup();
57 m_handle = std::move(hdl);
58
59 m_tmp.clear();
60 m_channel_q = boost::circular_buffer<float>(8192 * m_handle.channels());
61 }
62
63 void transport(time_value flicks) override
64 {
65 m_channel_q.clear();
66 ossia::seek_to_flick(
67 m_handle.format, m_handle.codec, m_handle.stream, flicks.impl, AVSEEK_FLAG_ANY);
68 }
69
70 void fetch_from_libav(int samples_to_write)
71 {
72 const std::size_t channels = this->channels();
73 if(channels == 0)
74 return;
75
76 auto floats_to_write = channels * samples_to_write;
77 while(m_channel_q.size() < floats_to_write)
78 {
79 // FIXME this is buggy
80 if(m_channel_q.capacity() < 4 * floats_to_write)
81 {
82 m_channel_q.set_capacity(4 * floats_to_write);
83 }
84
85 // Need to fetch more data
86 auto fmt_ctx = m_handle.format;
87 auto codec_ctx = m_handle.codec;
88 auto stream = m_handle.stream;
89 {
90 int ret{};
91 {
92 av_packet_unref(packet);
93 ret = av_read_frame(fmt_ctx, packet);
94
95 while(ret >= 0 && ret != AVERROR(EOF) && packet->stream_index != stream->index)
96 {
97 av_packet_unref(packet);
98 ret = av_read_frame(fmt_ctx, packet);
99 }
100 if(ret == AVERROR(EOF))
101 {
102 break;
103 }
104 }
105 if(ret < 0)
106 {
107 return;
108 }
109
110 ret = avcodec_send_packet(codec_ctx, packet);
111 if(ret == 0)
112 {
113 ret = avcodec_receive_frame(codec_ctx, frame);
114 if(ret == 0)
115 {
116 const int samples = frame->nb_samples;
117 m_tmp.resize(samples * channels, boost::container::default_init);
118 float* out_ptr = m_tmp.data();
119 const int read_samples = swr_convert(
120 m_handle.resample, (uint8_t**)&out_ptr, samples,
121 (const uint8_t**)frame->extended_data, samples);
122
123 m_channel_q.insert(
124 m_channel_q.end(), out_ptr, out_ptr + read_samples * channels);
125 }
126 }
127 }
128 }
129 }
130
131 template <typename T>
132 void
133 fetch_audio(int64_t start, int64_t samples_to_write, T** audio_array_base) noexcept
134 {
135 const std::size_t channels = this->channels();
136 if(channels == 0)
137 return;
138
139 fetch_from_libav(samples_to_write);
140
141 // FIXME start offset
142 for(int k = 0; k < samples_to_write; k++)
143 {
144 for(std::size_t chan = 0; chan < channels; chan++)
145 {
146 if(m_channel_q.size() > 0)
147 {
148 audio_array_base[chan][k] = m_channel_q.front();
149 m_channel_q.pop_front();
150 }
151 else
152 {
153 audio_array_base[chan][k] = 0.;
154 }
155 }
156 }
157 }
158
159 template <typename T>
160 void fetch_audio_backward(
161 int64_t start, int64_t samples_to_write, T** audio_array_base) noexcept
162 {
163 const std::size_t channels = this->channels();
164 if(channels == 0)
165 return;
166
167 // For backward playback with libav:
168 // 1. Calculate the position we need to start reading from (going backwards)
169 // 2. Seek to that earlier position
170 // 3. Read forward
171 // 4. Reverse the samples
172
173 // Calculate backward start position
174 int64_t backward_start = start - samples_to_write + 1;
175 if(backward_start < 0)
176 backward_start = 0;
177
178 // Seek to the backward position
179 // Convert sample position to flicks for seeking
180 const int64_t sample_rate = m_handle.stream->codecpar->sample_rate;
181 if(sample_rate > 0)
182 {
183 // flicks = samples * flicks_per_second / sample_rate
184 constexpr int64_t flicks_per_second = 705600000LL;
185 int64_t flicks_pos = backward_start * flicks_per_second / sample_rate;
186
187 m_channel_q.clear();
188 ossia::seek_to_flick(
189 m_handle.format, m_handle.codec, m_handle.stream, flicks_pos, AVSEEK_FLAG_BACKWARD);
190 }
191
192 // Fetch the audio forward
193 fetch_from_libav(samples_to_write);
194
195 // Read into output, then reverse
196 for(int64_t k = 0; k < samples_to_write; k++)
197 {
198 for(std::size_t chan = 0; chan < channels; chan++)
199 {
200 if(m_channel_q.size() > 0)
201 {
202 audio_array_base[chan][k] = m_channel_q.front();
203 m_channel_q.pop_front();
204 }
205 else
206 {
207 audio_array_base[chan][k] = 0.;
208 }
209 }
210 }
211
212 // Reverse each channel in-place
213 for(std::size_t chan = 0; chan < channels; chan++)
214 {
215 std::reverse(audio_array_base[chan], audio_array_base[chan] + samples_to_write);
216 }
217 }
218
219 void run(const ossia::token_request& t, ossia::exec_state_facade e) noexcept override
220 {
221 if(!m_handle)
222 return;
223
224 const auto channels = m_handle.channels();
225 const auto len = m_handle.totalPCMFrameCount();
226
227 ossia::audio_port& ap = *audio_out;
228 ap.set_channels(std::max((std::size_t)upmix, (std::size_t)channels));
229
230 const auto [samples_to_read, samples_to_write]
231 = snd::sample_info(e.bufferSize(), e.modelToSamples(), t);
232 if(samples_to_write <= 0)
233 return;
234
235 assert(samples_to_write > 0);
236
237 const auto samples_offset = t.physical_start(e.modelToSamples());
238
239 // Handle transport for both forward and backward playback
240 if(t.forward())
241 {
242 if(t.prev_date < m_prev_date)
243 {
244 // Sentinel: we never played.
245 if(m_prev_date == ossia::time_value{ossia::time_value::infinite_min})
246 {
247 if(t.prev_date != 0_tv)
248 {
249 transport(t.prev_date);
250 }
251 else
252 {
253 // Otherwise we don't need transport, everything is already at 0
254 m_prev_date = 0_tv;
255 }
256 }
257 else
258 {
259 transport(t.prev_date);
260 }
261 }
262 }
263 else
264 {
265 // Backward playback transport handling
266 if(t.prev_date > m_prev_date)
267 {
268 // Sentinel: we never played.
269 if(m_prev_date == ossia::time_value{ossia::time_value::infinite_min})
270 {
271 transport(t.prev_date);
272 }
273 else
274 {
275 transport(t.prev_date);
276 }
277 }
278 }
279
280 for(int chan = 0; chan < channels; chan++)
281 {
282 ap.channel(chan).resize(e.bufferSize());
283 }
284
285 const double stretch_ratio = update_stretch(t, e);
286 const double abs_stretch_ratio = std::abs(stretch_ratio);
287
288 // Resample (handles both forward and backward internally)
289 m_resampler.run(
290 *this, t, e, stretch_ratio, channels, len, samples_to_read, samples_to_write,
291 samples_offset, ap);
292
293 const bool start_discontinuous = t.start_discontinuous || (m_last_stretch > 70.);
294 const bool end_discontinuous = t.end_discontinuous || (abs_stretch_ratio > 70.);
295 if(abs_stretch_ratio > 70. && m_last_stretch > 70.)
296 {
297 [[unlikely]];
298 for(std::size_t i = 0; i < channels; i++)
299 {
300 ossia::snd::do_zero(ap.channel(i), samples_offset, samples_to_write);
301 }
302 }
303 else
304 {
305 [[likely]];
306 for(int chan = 0; chan < channels; chan++)
307 {
308 // fade
309 snd::do_fade(
310 start_discontinuous, end_discontinuous, ap.channel(chan), samples_offset,
311 samples_to_write);
312 }
313 }
314
315 ossia::snd::perform_upmix(this->upmix, channels, ap);
316 ossia::snd::perform_start_offset(this->start, ap);
317
318 m_prev_date = t.date;
319 m_last_stretch = abs_stretch_ratio;
320 }
321
322 [[nodiscard]] std::size_t channels() const
323 {
324 return m_handle ? m_handle.channels() : 0;
325 }
326 [[nodiscard]] std::size_t duration() const
327 {
328 return m_handle ? m_handle.totalPCMFrameCount() : 0;
329 }
330
331private:
332 libav_handle m_handle{};
333
334 ossia::audio_outlet audio_out;
335
336 std::size_t start{};
337 std::size_t upmix{};
338
339 ossia::pod_vector<float> m_tmp{};
340 boost::circular_buffer<float> m_channel_q;
341};
342
343}
The time_value class.
Definition ossia/editor/scenario/time_value.hpp:30