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