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 transport_scaled(flicks, 0.);
66 }
67
68 void transport(time_value flicks, const ossia::tick_transport_info& tinfo) override
69 {
70 transport_scaled(flicks, tinfo.current_tempo);
71 }
72
73 // Same mapping as file_sample_for_model_time, in flicks: when stretching
74 // the file position is model_time * root_tempo / file_tempo whatever the
75 // transport is doing; in raw mode it is the model time unscaled by the
76 // live tempo when that is known.
77 void transport_scaled(time_value flicks, double timeline_tempo)
78 {
79 m_channel_q.clear();
80 int64_t target_flicks = flicks.impl;
81 if(m_resampler.stretch() && tempo > 0.0)
82 {
83 target_flicks
84 = int64_t(std::llround(double(flicks.impl) * ossia::root_tempo / tempo));
85 }
86 else if(const double abs_tempo = std::abs(timeline_tempo); abs_tempo > 0.0)
87 {
88 target_flicks
89 = int64_t(std::llround(double(flicks.impl) * ossia::root_tempo / abs_tempo));
90 }
91 ossia::seek_to_flick(
92 m_handle.format, m_handle.codec, m_handle.stream, target_flicks,
93 AVSEEK_FLAG_ANY);
94 }
95
96 void fetch_from_libav(int samples_to_write)
97 {
98 const std::size_t channels = this->channels();
99 if(channels == 0)
100 return;
101
102 auto floats_to_write = channels * samples_to_write;
103 while(m_channel_q.size() < floats_to_write)
104 {
105 // FIXME this is buggy
106 if(m_channel_q.capacity() < 4 * floats_to_write)
107 {
108 m_channel_q.set_capacity(4 * floats_to_write);
109 }
110
111 // Need to fetch more data
112 auto fmt_ctx = m_handle.format;
113 auto codec_ctx = m_handle.codec;
114 auto stream = m_handle.stream;
115 {
116 int ret{};
117 {
118 av_packet_unref(packet);
119 ret = av_read_frame(fmt_ctx, packet);
120
121 while(ret >= 0 && ret != AVERROR(EOF) && packet->stream_index != stream->index)
122 {
123 av_packet_unref(packet);
124 ret = av_read_frame(fmt_ctx, packet);
125 }
126 if(ret == AVERROR(EOF))
127 {
128 break;
129 }
130 }
131 if(ret < 0)
132 {
133 return;
134 }
135
136 ret = avcodec_send_packet(codec_ctx, packet);
137 if(ret == 0)
138 {
139 ret = avcodec_receive_frame(codec_ctx, frame);
140 if(ret == 0)
141 {
142 const int samples = frame->nb_samples;
143 m_tmp.resize(samples * channels, boost::container::default_init);
144 float* out_ptr = m_tmp.data();
145 const int read_samples = swr_convert(
146 m_handle.resample, (uint8_t**)&out_ptr, samples,
147 (const uint8_t**)frame->extended_data, samples);
148
149 m_channel_q.insert(
150 m_channel_q.end(), out_ptr, out_ptr + read_samples * channels);
151 }
152 }
153 }
154 }
155 }
156
157 template <typename T>
158 void
159 fetch_audio(int64_t start, int64_t samples_to_write, T** audio_array_base) noexcept
160 {
161 const std::size_t channels = this->channels();
162 if(channels == 0)
163 return;
164
165 fetch_from_libav(samples_to_write);
166
167 // FIXME start offset
168 for(int k = 0; k < samples_to_write; k++)
169 {
170 for(std::size_t chan = 0; chan < channels; chan++)
171 {
172 if(m_channel_q.size() > 0)
173 {
174 audio_array_base[chan][k] = m_channel_q.front();
175 m_channel_q.pop_front();
176 }
177 else
178 {
179 audio_array_base[chan][k] = 0.;
180 }
181 }
182 }
183 }
184
185 template <typename T>
186 void fetch_audio_backward(
187 int64_t start, int64_t samples_to_write, T** audio_array_base) noexcept
188 {
189 const std::size_t channels = this->channels();
190 if(channels == 0)
191 return;
192
193 // For backward playback with libav:
194 // 1. Calculate the position we need to start reading from (going backwards)
195 // 2. Seek to that earlier position
196 // 3. Read forward
197 // 4. Reverse the samples
198
199 // Calculate backward start position
200 int64_t backward_start = start - samples_to_write + 1;
201 if(backward_start < 0)
202 backward_start = 0;
203
204 // Seek to the backward position
205 // Convert sample position to flicks for seeking
206 const int64_t sample_rate = m_handle.stream->codecpar->sample_rate;
207 if(sample_rate > 0)
208 {
209 // flicks = samples * flicks_per_second / sample_rate
210 constexpr int64_t flicks_per_second = 705600000LL;
211 int64_t flicks_pos = backward_start * flicks_per_second / sample_rate;
212
213 m_channel_q.clear();
214 ossia::seek_to_flick(
215 m_handle.format, m_handle.codec, m_handle.stream, flicks_pos, AVSEEK_FLAG_BACKWARD);
216 }
217
218 // Fetch the audio forward
219 fetch_from_libav(samples_to_write);
220
221 // Read into output, then reverse
222 for(int64_t k = 0; k < samples_to_write; k++)
223 {
224 for(std::size_t chan = 0; chan < channels; chan++)
225 {
226 if(m_channel_q.size() > 0)
227 {
228 audio_array_base[chan][k] = m_channel_q.front();
229 m_channel_q.pop_front();
230 }
231 else
232 {
233 audio_array_base[chan][k] = 0.;
234 }
235 }
236 }
237
238 // Reverse each channel in-place
239 for(std::size_t chan = 0; chan < channels; chan++)
240 {
241 std::reverse(audio_array_base[chan], audio_array_base[chan] + samples_to_write);
242 }
243 }
244
245 void run(const ossia::token_request& t, ossia::exec_state_facade e) noexcept override
246 {
247 if(!m_handle)
248 return;
249
250 const auto channels = m_handle.channels();
251 const auto len = m_handle.totalPCMFrameCount();
252
253 ossia::audio_port& ap = *audio_out;
254 ap.set_channels(std::max((std::size_t)upmix, (std::size_t)channels));
255
256 const auto [samples_to_read, samples_to_write]
257 = snd::sample_info(e.bufferSize(), e.modelToSamples(), t);
258 if(samples_to_write <= 0)
259 return;
260
261 assert(samples_to_write > 0);
262
263 const auto samples_offset = t.physical_start(e.modelToSamples());
264
265 if(t.forward())
266 {
267 if(t.prev_date < m_prev_date)
268 {
269 // First run after add_time_process() left the stretcher already
270 // primed; calling transport() again would reset it.
271 if(m_prev_date == ossia::time_value{ossia::time_value::infinite_min})
272 m_prev_date = t.prev_date;
273 else
274 transport(t.prev_date);
275 }
276 }
277 else
278 {
279 if(t.prev_date > m_prev_date)
280 {
281 if(m_prev_date == ossia::time_value{ossia::time_value::infinite_min})
282 m_prev_date = t.prev_date;
283 else
284 transport(t.prev_date);
285 }
286 }
287
288 for(int chan = 0; chan < channels; chan++)
289 {
290 ap.channel(chan).resize(e.bufferSize());
291 }
292
293 const double stretch_ratio = update_stretch(t, e);
294 const double abs_stretch_ratio = std::abs(stretch_ratio);
295
296 m_resampler.run(
297 *this, t, e, stretch_ratio, channels, len, samples_to_read, samples_to_write,
298 samples_offset, ap);
299
300 const bool start_discontinuous = t.start_discontinuous || (m_last_stretch > 70.);
301 const bool end_discontinuous = t.end_discontinuous || (abs_stretch_ratio > 70.);
302 if(abs_stretch_ratio > 70. && m_last_stretch > 70.)
303 {
304 [[unlikely]];
305 for(std::size_t i = 0; i < channels; i++)
306 {
307 ossia::snd::do_zero(ap.channel(i), samples_offset, samples_to_write);
308 }
309 }
310 else
311 {
312 [[likely]];
313 for(int chan = 0; chan < channels; chan++)
314 {
315 // fade
316 snd::do_fade(
317 start_discontinuous, end_discontinuous, ap.channel(chan), samples_offset,
318 samples_to_write);
319 }
320 }
321
322 ossia::snd::perform_upmix(this->upmix, channels, ap);
323 ossia::snd::perform_start_offset(this->start, ap);
324
325 m_prev_date = t.date;
326 m_last_stretch = abs_stretch_ratio;
327 }
328
329 [[nodiscard]] std::size_t channels() const
330 {
331 return m_handle ? m_handle.channels() : 0;
332 }
333 [[nodiscard]] std::size_t duration() const
334 {
335 return m_handle ? m_handle.totalPCMFrameCount() : 0;
336 }
337
338private:
339 libav_handle m_handle{};
340
341 ossia::audio_outlet audio_out;
342
343 std::size_t start{};
344 std::size_t upmix{};
345
346 ossia::pod_vector<float> m_tmp{};
347 boost::circular_buffer<float> m_channel_q;
348};
349
350}
The time_value class.
Definition ossia/editor/scenario/time_value.hpp:30