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 <type_traits>
14
15extern "C" {
16#include <libavcodec/avcodec.h>
17#include <libavformat/avformat.h>
18#include <libavutil/frame.h>
19#include <libavutil/mem.h>
20#include <libswresample/swresample.h>
21}
22
23namespace ossia::nodes
24{
25class sound_libav final : public ossia::sound_node
26{
27 AVPacket* packet{};
28 AVFrame* frame{};
29
30public:
31 sound_libav()
32 : packet{av_packet_alloc()}
33 , frame{av_frame_alloc()}
34 {
35 m_outlets.push_back(&audio_out);
36 }
37
38 ~sound_libav()
39 {
40 m_handle.cleanup();
41
42 av_frame_free(&frame);
43 av_packet_free(&packet);
44 }
45
46 std::string label() const noexcept override { return "sound_libav"; }
47
48 void set_start(std::size_t v) { start = v; }
49
50 void set_upmix(std::size_t v) { upmix = v; }
51
52 void set_sound(libav_handle hdl)
53 {
54 using namespace snd;
55 m_handle.cleanup();
56 m_handle = std::move(hdl);
57
58 m_tmp.clear();
59 m_channel_q = boost::circular_buffer<float>(8192 * m_handle.channels());
60 }
61
62 void transport(time_value flicks) override
63 {
64 m_channel_q.clear();
65 ossia::seek_to_flick(
66 m_handle.format, m_handle.codec, m_handle.stream, flicks.impl, AVSEEK_FLAG_ANY);
67 }
68
69 void fetch_from_libav(int samples_to_write)
70 {
71 const std::size_t channels = this->channels();
72 if(channels == 0)
73 return;
74
75 auto floats_to_write = channels * samples_to_write;
76 while(m_channel_q.size() < floats_to_write)
77 {
78 // FIXME this is buggy
79 if(m_channel_q.capacity() < 4 * floats_to_write)
80 {
81 m_channel_q.set_capacity(4 * floats_to_write);
82 }
83
84 // Need to fetch more data
85 auto fmt_ctx = m_handle.format;
86 auto codec_ctx = m_handle.codec;
87 auto stream = m_handle.stream;
88 {
89 int ret{};
90 {
91 av_packet_unref(packet);
92 ret = av_read_frame(fmt_ctx, packet);
93
94 while(ret >= 0 && ret != AVERROR(EOF) && packet->stream_index != stream->index)
95 {
96 av_packet_unref(packet);
97 ret = av_read_frame(fmt_ctx, packet);
98 }
99 if(ret == AVERROR(EOF))
100 {
101 break;
102 }
103 }
104 if(ret < 0)
105 {
106 return;
107 }
108
109 ret = avcodec_send_packet(codec_ctx, packet);
110 if(ret == 0)
111 {
112 ret = avcodec_receive_frame(codec_ctx, frame);
113 if(ret == 0)
114 {
115 const int samples = frame->nb_samples;
116 m_tmp.resize(samples * channels, boost::container::default_init);
117 float* out_ptr = m_tmp.data();
118 const int read_samples = swr_convert(
119 m_handle.resample, (uint8_t**)&out_ptr, samples,
120 (const uint8_t**)frame->extended_data, samples);
121
122 m_channel_q.insert(
123 m_channel_q.end(), out_ptr, out_ptr + read_samples * channels);
124 }
125 }
126 }
127 }
128 }
129
130 template <typename T>
131 void
132 fetch_audio(int64_t start, int64_t samples_to_write, T** audio_array_base) noexcept
133 {
134 const std::size_t channels = this->channels();
135 if(channels == 0)
136 return;
137
138 fetch_from_libav(samples_to_write);
139
140 // FIXME start offset
141 for(int k = 0; k < samples_to_write; k++)
142 {
143 for(std::size_t chan = 0; chan < channels; chan++)
144 {
145 if(m_channel_q.size() > 0)
146 {
147 audio_array_base[chan][k] = m_channel_q.front();
148 m_channel_q.pop_front();
149 }
150 else
151 {
152 audio_array_base[chan][k] = 0.;
153 }
154 }
155 }
156 }
157
158 void run(const ossia::token_request& t, ossia::exec_state_facade e) noexcept override
159 {
160 if(!m_handle)
161 return;
162
163 // TODO do the backwards play head
164 if(!t.forward())
165 return;
166
167 const auto channels = m_handle.channels();
168 const auto len = m_handle.totalPCMFrameCount();
169
170 ossia::audio_port& ap = *audio_out;
171 ap.set_channels(std::max((std::size_t)upmix, (std::size_t)channels));
172
173 const auto [samples_to_read, samples_to_write]
174 = snd::sample_info(e.bufferSize(), e.modelToSamples(), t);
175 if(samples_to_write <= 0)
176 return;
177
178 assert(samples_to_write > 0);
179
180 const auto samples_offset = t.physical_start(e.modelToSamples());
181 if(t.tempo > 0)
182 {
183 if(t.prev_date < m_prev_date)
184 {
185 // Sentinel: we never played.
186 if(m_prev_date == ossia::time_value{ossia::time_value::infinite_min})
187 {
188 if(t.prev_date != 0_tv)
189 {
190 transport(t.prev_date);
191 }
192 else
193 {
194 // Otherwise we don't need transport, everything is already at 0
195 m_prev_date = 0_tv;
196 }
197 }
198 else
199 {
200 transport(t.prev_date);
201 }
202 }
203
204 for(int chan = 0; chan < channels; chan++)
205 {
206 ap.channel(chan).resize(e.bufferSize());
207 }
208
209 double stretch_ratio = update_stretch(t, e);
210
211 // Resample
212 m_resampler.run(
213 *this, t, e, stretch_ratio, channels, len, samples_to_read, samples_to_write,
214 samples_offset, ap);
215
216 for(int chan = 0; chan < channels; chan++)
217 {
218 // fade
219 snd::do_fade(
220 t.start_discontinuous, t.end_discontinuous, ap.channel(chan), samples_offset,
221 samples_to_write);
222 }
223
224 ossia::snd::perform_upmix(this->upmix, channels, ap);
225 ossia::snd::perform_start_offset(this->start, ap);
226
227 m_prev_date = t.date;
228 }
229 else
230 {
231 /* TODO */
232 }
233 }
234
235 [[nodiscard]] std::size_t channels() const
236 {
237 return m_handle ? m_handle.channels() : 0;
238 }
239 [[nodiscard]] std::size_t duration() const
240 {
241 return m_handle ? m_handle.totalPCMFrameCount() : 0;
242 }
243
244private:
245 libav_handle m_handle{};
246
247 ossia::audio_outlet audio_out;
248
249 std::size_t start{};
250 std::size_t upmix{};
251
252 ossia::pod_vector<float> m_tmp{};
253 boost::circular_buffer<float> m_channel_q;
254};
255
256}
The time_value class.
Definition ossia/editor/scenario/time_value.hpp:30