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 <optional>
15#include <type_traits>
16
17extern "C" {
18#include <libavcodec/avcodec.h>
19#include <libavformat/avformat.h>
20#include <libavutil/frame.h>
21#include <libavutil/mem.h>
22#include <libswresample/swresample.h>
23}
24
25namespace ossia::nodes
26{
27class sound_libav final : public ossia::sound_node
28{
29 AVPacket* packet{};
30 AVFrame* frame{};
31
32public:
33 sound_libav()
34 : packet{av_packet_alloc()}
35 , frame{av_frame_alloc()}
36 {
37 m_outlets.push_back(&audio_out);
38 }
39
40 ~sound_libav()
41 {
42 m_handle.cleanup();
43
44 av_frame_free(&frame);
45 av_packet_free(&packet);
46 }
47
48 std::string label() const noexcept override { return "sound_libav"; }
49
50 void set_start(std::size_t v) { start = v; }
51
52 void set_upmix(std::size_t v) { upmix = v; }
53
54 void set_sound(libav_handle hdl)
55 {
56 using namespace snd;
57 m_handle.cleanup();
58 m_handle = std::move(hdl);
59
60 m_tmp.clear();
61 m_channel_q = boost::circular_buffer<float>(
62 m_handle ? 8192 * m_handle.channels() : 0);
63 m_window_start = 0;
64 m_positioned = false;
65 }
66
67 void transport(time_value flicks) override { transport_scaled(flicks, 0.); }
68
69 void transport(time_value flicks, const ossia::tick_transport_info& tinfo) override
70 {
71 transport_scaled(flicks, tinfo.current_tempo);
72 }
73
75 void transport_scaled(time_value flicks, double timeline_tempo)
76 {
77 if(!m_handle)
78 return;
79 m_resampler.transport(
80 file_sample_for_model_time(flicks, timeline_tempo, m_handle.out_rate()));
81 }
82
85 bool decode_one(std::optional<int64_t>& pts) noexcept
86 {
87 auto fmt_ctx = m_handle.format;
88 auto codec_ctx = m_handle.codec;
89 auto stream = m_handle.stream;
90 const std::size_t channels = this->channels();
91 if(!fmt_ctx || !codec_ctx || !stream || channels == 0)
92 return false;
93
94 for(;;)
95 {
96 // Drain first: a packet can hold several frames.
97 int ret = avcodec_receive_frame(codec_ctx, frame);
98 if(ret == 0)
99 {
100 const int in_samples = frame->nb_samples;
101 if(frame->best_effort_timestamp != AV_NOPTS_VALUE)
102 pts = frame->best_effort_timestamp;
103
104 const int capacity = m_handle.out_capacity_for(in_samples);
105 m_tmp.resize(std::size_t(capacity) * channels, boost::container::default_init);
106 float* out_ptr = m_tmp.data();
107 const int out_samples = swr_convert(
108 m_handle.resample, (uint8_t**)&out_ptr, capacity,
109 (const uint8_t**)frame->extended_data, in_samples);
110
111 if(out_samples > 0)
112 push_window(out_ptr, out_samples);
113 return true;
114 }
115 if(ret != AVERROR(EAGAIN))
116 return false;
117
118 if(m_drained)
119 return false;
120
121 av_packet_unref(packet);
122 ret = av_read_frame(fmt_ctx, packet);
123 while(ret >= 0 && packet->stream_index != stream->index)
124 {
125 av_packet_unref(packet);
126 ret = av_read_frame(fmt_ctx, packet);
127 }
128
129 if(ret < 0)
130 {
131 // Flush, or the tail of the file is lost.
132 m_drained = true;
133 avcodec_send_packet(codec_ctx, nullptr);
134 }
135 else if(avcodec_send_packet(codec_ctx, packet) < 0)
136 {
137 return false;
138 }
139 }
140 }
141
143 void push_window(const float* data, int64_t frames) noexcept
144 {
145 const int64_t channels = int64_t(this->channels());
146 const int64_t capacity = int64_t(m_channel_q.capacity()) / channels;
147 const int64_t held = int64_t(m_channel_q.size()) / channels;
148
149 if(frames >= capacity)
150 {
151 // Larger than the whole window: keep its tail.
152 const int64_t keep = capacity;
153 m_channel_q.clear();
154 m_window_start += held + (frames - keep);
155 m_channel_q.insert(
156 m_channel_q.end(), data + (frames - keep) * channels,
157 data + frames * channels);
158 return;
159 }
160
161 const int64_t overflow = std::max<int64_t>(0, held + frames - capacity);
162 if(overflow > 0)
163 {
164 m_channel_q.erase_begin(std::size_t(overflow * channels));
165 m_window_start += overflow;
166 }
167 m_channel_q.insert(m_channel_q.end(), data, data + frames * channels);
168 }
169
171 bool seek_window(int64_t frame) noexcept
172 {
173 m_channel_q.clear();
174 m_window_start = 0;
175 m_positioned = false;
176 m_drained = false;
177
178 const int64_t orate = m_handle.out_rate();
179 if(orate <= 0 || !m_handle.stream)
180 return false;
181
182 // At or before: ensure_window() can only walk forward.
183 const int64_t flicks = int64_t(
184 std::llround(ossia::flicks_per_second<double> * double(frame) / double(orate)));
185 if(!ossia::seek_to_flick(
186 m_handle.format, m_handle.codec, m_handle.stream, flicks,
187 AVSEEK_FLAG_BACKWARD))
188 return false;
189
190 m_handle.flush_resampler();
191
192 // The timestamp only lines up with the first output sample right after a
193 // flush; from here the position is counted.
194 std::optional<int64_t> pts;
195 if(!decode_one(pts) || !pts)
196 return false;
197
198 const int64_t start_time = (m_handle.stream->start_time != AV_NOPTS_VALUE)
199 ? m_handle.stream->start_time
200 : 0;
201 const int64_t pos = av_rescale_q(
202 *pts - start_time, m_handle.stream->time_base, AVRational{1, int(orate)});
203 if(pos < 0)
204 return false;
205
206 // decode_one may have trimmed the front to fit; m_window_start holds how
207 // much, and the seek position is on top of it.
208 m_window_start += pos;
209 m_positioned = true;
210 return true;
211 }
212
214 void ensure_window(int64_t frame, int64_t count) noexcept
215 {
216 const int64_t channels = int64_t(this->channels());
217 if(channels == 0 || count <= 0)
218 return;
219
220 // History, so a stretcher re-reading behind what it consumed does not seek.
221 const std::size_t want = std::size_t((2 * count + 32768) * channels);
222 if(m_channel_q.capacity() < want)
223 m_channel_q.set_capacity(want);
224
225 const int64_t held = int64_t(m_channel_q.size()) / channels;
226 const bool in_reach = m_positioned && frame >= m_window_start
227 && frame <= m_window_start + held + count;
228 if(!in_reach && !seek_window(frame))
229 return;
230
231 // Or a stream whose timestamps jump back decodes the rest of the file here.
232 const int64_t limit = int64_t(m_channel_q.capacity()) / channels;
233 int64_t decoded = 0;
234 while(m_window_start + int64_t(m_channel_q.size()) / channels < frame + count)
235 {
236 std::optional<int64_t> pts;
237 if(!decode_one(pts))
238 break;
239 if(++decoded > limit)
240 break;
241 }
242 }
243
245 template <typename T>
246 void read_window(int64_t frame, int64_t count, T** out) const noexcept
247 {
248 const int64_t channels = int64_t(this->channels());
249 if(channels == 0)
250 return;
251 const int64_t held = int64_t(m_channel_q.size()) / channels;
252 const int64_t offset = frame - m_window_start;
253
254 for(int64_t k = 0; k < count; k++)
255 {
256 const int64_t i = offset + k;
257 if(i >= 0 && i < held)
258 {
259 auto it = m_channel_q.begin() + std::size_t(i * channels);
260 for(int64_t chan = 0; chan < channels; chan++)
261 out[chan][k] = T(*it++);
262 }
263 else
264 {
265 for(int64_t chan = 0; chan < channels; chan++)
266 out[chan][k] = T(0);
267 }
268 }
269 }
270
271 template <typename T>
272 void
273 fetch_audio(int64_t start, int64_t samples_to_write, T** audio_array_base) noexcept
274 {
275 if(this->channels() == 0 || samples_to_write <= 0)
276 return;
277 if(start < 0)
278 start = 0;
279
280 ensure_window(start, samples_to_write);
281 read_window(start, samples_to_write, audio_array_base);
282 }
283
284 template <typename T>
285 void fetch_audio_backward(
286 int64_t start, int64_t samples_to_write, T** audio_array_base) noexcept
287 {
288 const int64_t channels = int64_t(this->channels());
289 if(channels == 0 || samples_to_write <= 0)
290 return;
291
292 // `start` is the newest sample; read_window() zeroes what precedes the
293 // file, so `first` is left negative.
294 const int64_t first = start - samples_to_write + 1;
295
296 ensure_window(std::max<int64_t>(0, first), samples_to_write);
297 read_window(first, samples_to_write, audio_array_base);
298
299 for(int64_t chan = 0; chan < channels; chan++)
300 std::reverse(audio_array_base[chan], audio_array_base[chan] + samples_to_write);
301 }
302
303 void run(const ossia::token_request& t, ossia::exec_state_facade e) noexcept override
304 {
305 if(!m_handle)
306 return;
307
308 const auto channels = m_handle.channels();
309 const auto len = int64_t(this->duration());
310
311 ossia::audio_port& ap = *audio_out;
312 ap.set_channels(std::max((std::size_t)upmix, (std::size_t)channels));
313
314 const auto [samples_to_read, samples_to_write]
315 = snd::sample_info(e.bufferSize(), e.modelToSamples(), t);
316 if(samples_to_write <= 0)
317 return;
318
319 assert(samples_to_write > 0);
320
321 const auto samples_offset = t.physical_start(e.modelToSamples());
322
323 if(t.forward())
324 {
325 if(t.prev_date < m_prev_date)
326 {
327 // First run after add_time_process() left the stretcher already
328 // primed; calling transport() again would reset it.
329 if(m_prev_date == ossia::time_value{ossia::time_value::infinite_min})
330 m_prev_date = t.prev_date;
331 else
332 transport(t.prev_date);
333 }
334 }
335 else
336 {
337 if(t.prev_date > m_prev_date)
338 {
339 if(m_prev_date == ossia::time_value{ossia::time_value::infinite_min})
340 m_prev_date = t.prev_date;
341 else
342 transport(t.prev_date);
343 }
344 }
345
346 for(int chan = 0; chan < channels; chan++)
347 {
348 ap.channel(chan).resize(e.bufferSize());
349 }
350
351 const double stretch_ratio = update_stretch(t, e);
352 const double abs_stretch_ratio = std::abs(stretch_ratio);
353
354 // swr already outputs at the graph's rate.
355 m_resampler.run(
356 *this, t, e, stretch_ratio, 1., channels, len, samples_to_read,
357 samples_to_write, samples_offset, ap);
358
359 const bool start_discontinuous = t.start_discontinuous || (m_last_stretch > 70.);
360 const bool end_discontinuous = t.end_discontinuous || (abs_stretch_ratio > 70.);
361 if(abs_stretch_ratio > 70. && m_last_stretch > 70.)
362 {
363 [[unlikely]];
364 for(std::size_t i = 0; i < channels; i++)
365 {
366 ossia::snd::do_zero(ap.channel(i), samples_offset, samples_to_write);
367 }
368 }
369 else
370 {
371 [[likely]];
372 for(int chan = 0; chan < channels; chan++)
373 {
374 // fade
375 snd::do_fade(
376 start_discontinuous, end_discontinuous, ap.channel(chan), samples_offset,
377 samples_to_write);
378 }
379 }
380
381 ossia::snd::perform_upmix(this->upmix, channels, ap);
382 ossia::snd::perform_start_offset(this->start, ap);
383
384 m_prev_date = t.date;
385 m_last_stretch = abs_stretch_ratio;
386 }
387
388 [[nodiscard]] std::size_t channels() const
389 {
390 return m_handle ? m_handle.channels() : 0;
391 }
393 [[nodiscard]] std::size_t duration() const
394 {
395 if(!m_handle)
396 return 0;
397 const int64_t frames = m_handle.totalPCMFrameCount();
398 const int64_t in_rate = m_handle.rate();
399 const int64_t out_rate = m_handle.out_rate();
400 if(in_rate <= 0 || out_rate <= 0 || in_rate == out_rate)
401 return std::size_t(frames);
402 return std::size_t(av_rescale_rnd(frames, out_rate, in_rate, AV_ROUND_DOWN));
403 }
404
405private:
406 libav_handle m_handle{};
407
408 ossia::audio_outlet audio_out;
409
410 std::size_t start{};
411 std::size_t upmix{};
412
413 ossia::pod_vector<float> m_tmp{};
414
417 boost::circular_buffer<float> m_channel_q;
418 int64_t m_window_start{};
419 bool m_positioned{};
420 bool m_drained{};
421};
422
423}
The time_value class.
Definition ossia/editor/scenario/time_value.hpp:30