OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
libav.hpp
1#pragma once
2#include <ossia/detail/config.hpp>
3
4#if __has_include(<libavcodec/avcodec.h>) && \
5 __has_include(<libavformat/avformat.h>) && \
6 __has_include(<libavdevice/avdevice.h>) && \
7 __has_include(<libavutil/frame.h>) && \
8 __has_include(<libswresample/swresample.h>) && \
9 __has_include(<libswscale/swscale.h>)
10
11#define OSSIA_HAS_LIBAV 1
12#include <ossia/detail/flicks.hpp>
13
14extern "C" {
15#include <libavcodec/avcodec.h>
16#include <libavformat/avformat.h>
17#include <libavutil/channel_layout.h>
18#include <libavutil/version.h>
19#include <libswresample/swresample.h>
20}
21
22namespace ossia
23{
24// Try to find a flag that is likely not going to be taken by AVSEEK_FLAG_... ever
25static constexpr const int OSSIA_LIBAV_SEEK_ROUGH = 0b00000100000000000000000000000000;
26inline bool seek_to_flick(
27 AVFormatContext* format, AVCodecContext* codec, AVStream* stream, int64_t flicks,
28 int flags = 0)
29{
30 constexpr auto flicks_tb = AVRational{1, ossia::flicks_per_second<int64_t>};
31 constexpr auto av_tb = AVRational{1, AV_TIME_BASE};
32
33 const auto dts = av_rescale_q_rnd(flicks, flicks_tb, av_tb, AVRounding::AV_ROUND_DOWN);
34
35 // Demuxers that do their own I/O (AVFMT_NOFILE: image sequences, devices)
36 // have no pb.
37 if(format->pb)
38 avio_flush(format->pb);
39 avformat_flush(format);
40 if(codec)
41 avcodec_flush_buffers(codec);
42
43 if(flags & OSSIA_LIBAV_SEEK_ROUGH)
44 {
45 // Seems to work better for seeking for thumbnails
46 flags &= ~OSSIA_LIBAV_SEEK_ROUGH;
47 if(av_seek_frame(format, -1, dts, flags) < 0)
48 return false;
49 }
50 else
51 {
52 // Seems to work better for seeking for video & audio
53 if(avformat_seek_file(format, -1, INT64_MIN, dts, INT64_MAX, flags) < 0)
54 return false;
55 }
56
57 // Decoder only: flushing the format context here resets the read to the
58 // start of the stream, silently undoing the seek.
59 if(codec)
60 avcodec_flush_buffers(codec);
61
62 return true;
63}
64
65static inline int avstream_get_audio_channels(AVStream& stream) noexcept
66{
67#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 24, 100)
68 return stream.codecpar->ch_layout.nb_channels;
69#else
70 return stream.codecpar->channels;
71#endif
72}
73
74// Opens path over custom IO; returns nullptr if it does not handle that path.
75// io_owner/io_free hand back the streaming resources for cleanup() to release.
76using libav_open_hook
77 = AVFormatContext* (*)(const char* path, void*& io_owner, void (*&io_free)(void*));
78inline libav_open_hook& libav_custom_open() noexcept
79{
80 static libav_open_hook hook = nullptr;
81 return hook;
82}
83
84struct libav_handle
85{
86 AVFormatContext* format{};
87 AVStream* stream{};
88 AVCodecContext* codec{};
89 SwrContext* resample{};
90 // Rate `resample` converts to; rate() stays the file's own.
91 int output_rate{};
92 void* io_owner{};
93 void (*io_free)(void*){};
94
95 libav_handle() = default;
96 libav_handle(const libav_handle& other) = delete;
97 libav_handle(libav_handle&& other)
98 {
99 format = other.format;
100 other.format = nullptr;
101 stream = other.stream;
102 other.stream = nullptr;
103 codec = other.codec;
104 other.codec = nullptr;
105 resample = other.resample;
106 other.resample = nullptr;
107 output_rate = other.output_rate;
108 other.output_rate = 0;
109 io_owner = other.io_owner;
110 other.io_owner = nullptr;
111 io_free = other.io_free;
112 other.io_free = nullptr;
113 }
114 libav_handle& operator=(const libav_handle& other) = delete;
115 libav_handle& operator=(libav_handle&& other)
116 {
117 format = other.format;
118 other.format = nullptr;
119 stream = other.stream;
120 other.stream = nullptr;
121 codec = other.codec;
122 other.codec = nullptr;
123 resample = other.resample;
124 other.resample = nullptr;
125 output_rate = other.output_rate;
126 other.output_rate = 0;
127 io_owner = other.io_owner;
128 other.io_owner = nullptr;
129 io_free = other.io_free;
130 other.io_free = nullptr;
131 return *this;
132 }
133 ~libav_handle()
134 {
135 if(format)
136 cleanup();
137 }
138
139 void cleanup()
140 {
141 stream = nullptr;
142 output_rate = 0;
143 if(resample)
144 swr_free(&resample);
145 if(codec)
146 {
147 avcodec_free_context(&codec);
148 }
149 if(format)
150 {
151 avformat_close_input(&format);
152 avformat_free_context(format);
153 format = nullptr;
154 }
155 if(io_owner)
156 {
157 io_free(io_owner);
158 io_owner = nullptr;
159 io_free = nullptr;
160 }
161 }
162
163 void open(const std::string& path, int stream_index, int target_rate) noexcept
164 {
165 if(auto hook = libav_custom_open())
166 format = hook(path.c_str(), io_owner, io_free);
167
168 if(!format
169 && avformat_open_input(&format, path.c_str(), nullptr, nullptr) != 0)
170 {
171 cleanup();
172 return;
173 }
174 if(avformat_find_stream_info(format, nullptr) < 0)
175 {
176 cleanup();
177 return;
178 }
179 if(stream_index < 0 || stream_index >= int(format->nb_streams))
180 {
181 cleanup();
182 return;
183 }
184 if(format->streams[stream_index]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO)
185 {
186 cleanup();
187 return;
188 }
189
190 stream = format->streams[stream_index];
191
192 auto cdc = avcodec_find_decoder(stream->codecpar->codec_id);
193 if(!cdc)
194 {
195 cleanup();
196 return;
197 }
198
199 codec = avcodec_alloc_context3(cdc);
200 if(avcodec_parameters_to_context(codec, stream->codecpar) < 0)
201 {
202 cleanup();
203 return;
204 }
205
206 if(avcodec_open2(codec, cdc, nullptr) < 0)
207 {
208 cleanup();
209 return;
210 }
211
212 AVSampleFormat out_sample_fmt{AV_SAMPLE_FMT_FLT};
213 AVSampleFormat in_sample_fmt{(AVSampleFormat)stream->codecpar->format};
214 int in_sample_rate = stream->codecpar->sample_rate;
215 int out_sample_rate = target_rate == 0 ? in_sample_rate : target_rate;
216
217#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 24, 100)
218 AVChannelLayout out_layout{stream->codecpar->ch_layout};
219 AVChannelLayout in_layout{stream->codecpar->ch_layout};
220
221 swr_alloc_set_opts2(
222 &resample, &out_layout, out_sample_fmt, out_sample_rate, &in_layout,
223 in_sample_fmt, in_sample_rate, 0, nullptr);
224#else
225 int64_t out_layout = stream->codecpar->channel_layout;
226 int64_t in_layout = stream->codecpar->channel_layout;
227
228 resample = swr_alloc_set_opts(
229 resample, out_layout, out_sample_fmt, out_sample_rate, in_layout, in_sample_fmt,
230 in_sample_rate, 0, nullptr);
231#endif
232 // A handle whose resampler failed to build would hand swr_convert() a null
233 // context on the audio thread.
234 if(!resample || swr_init(resample) < 0)
235 {
236 cleanup();
237 return;
238 }
239 output_rate = out_sample_rate;
240 }
241
242 int rate() const noexcept { return stream->codecpar->sample_rate; }
243
245 int out_rate() const noexcept { return output_rate > 0 ? output_rate : rate(); }
246
248 int out_capacity_for(int in_samples) const noexcept
249 {
250 const int in_rate = rate();
251 if(!resample || in_rate <= 0)
252 return in_samples;
253 const int64_t delay = swr_get_delay(resample, in_rate);
254 return int(av_rescale_rnd(delay + in_samples, out_rate(), in_rate, AV_ROUND_UP));
255 }
256
258 void flush_resampler() noexcept
259 {
260 if(resample)
261 swr_init(resample);
262 }
263 int channels() const noexcept { return avstream_get_audio_channels(*stream); }
264
265 int64_t totalPCMFrameCount() const noexcept
266 {
267 if(stream->duration > 0)
268 {
269 return stream->duration;
270 }
271 else if(format->duration > 0)
272 {
273 double seconds = format->duration / double(AV_TIME_BASE);
274 double frames = seconds * stream->codecpar->sample_rate;
275 return frames;
276 }
277 else if(stream->nb_frames > 0)
278 {
279 return av_rescale_q(
280 stream->nb_frames, stream->r_frame_rate,
281 AVRational{1, stream->codecpar->sample_rate});
282 }
283 else
284 {
285 return 0;
286 }
287 }
288
289 operator bool() const noexcept { return bool(format); }
290
291 void fetch(int64_t frame, int samples_to_write, auto func)
292 {
293 // At or before: the loop below only walks forward.
294 ossia::seek_to_flick(
295 format, codec, stream,
296 ossia::flicks_per_second<double> * frame / stream->codecpar->sample_rate,
297 AVSEEK_FLAG_BACKWARD);
298
299 const std::size_t channels = this->channels();
300 std::vector<float> tmp;
301 if(channels == 0)
302 return;
303
304 int processed = 0;
305 while(processed < samples_to_write)
306 {
307 // Need to fetch more data
308 {
309 auto packet = av_packet_alloc();
310 int ret{};
311 {
312 ret = av_read_frame(format, packet);
313
314 while(ret >= 0 && ret != AVERROR(EOF) && packet->stream_index != stream->index)
315 {
316 av_packet_unref(packet);
317 ret = av_read_frame(format, packet);
318 }
319 if(ret == AVERROR(EOF))
320 {
321 break;
322 }
323 }
324 if(ret < 0)
325 {
326 return;
327 }
328
329 ret = avcodec_send_packet(codec, packet);
330 if(ret == 0)
331 {
332 auto avframe = av_frame_alloc();
333 ret = avcodec_receive_frame(codec, avframe);
334 if(ret == 0)
335 {
336 // Sample positions, not an int: past two hours of 48k audio a
337 // frame index no longer fits in 32 bits and the offset computed
338 // from it wraps into the middle of the file.
339 const int64_t av_frame_start = avframe->best_effort_timestamp;
340 const int samples = avframe->nb_samples;
341
342 // It's possible that we get a frame that is just after what we have asked, thus
343 // frame - av_frame_start can sometimes be negative
344 const int64_t offset = (frame < av_frame_start) ? 0 : (frame - av_frame_start);
345 if(offset >= samples)
346 {
347 // ffmpeg didn't even manage to seek to the correct frame, we have to read another
348 av_frame_free(&avframe);
349 av_packet_unref(packet);
350 av_packet_free(&packet);
351 continue;
352 }
353
354 // The actual samples we can read here:
355 //samples = samples - (frame - av_frame_start);
356
357 tmp.resize(samples * channels);
358 float* out_ptr = tmp.data();
359
360 // Note: this function is only called with swr_convert's rate not changing
361 int read_samples = swr_convert(
362 resample, (uint8_t**)&out_ptr, samples,
363 (const uint8_t**)avframe->extended_data, samples);
364
365 auto end = tmp.data() + tmp.size();
366
367 read_samples -= offset;
368 if(read_samples <= 0)
369 {
370 av_frame_free(&avframe);
371 av_packet_unref(packet);
372 av_packet_free(&packet);
373 return;
374 }
375
376 out_ptr += offset * channels;
377
378 for(int i = 0; i < read_samples; i++)
379 {
380 func(out_ptr, end);
381
382 processed++;
383 if(processed == samples_to_write)
384 {
385 av_frame_free(&avframe);
386 av_packet_unref(packet);
387 av_packet_free(&packet);
388 return;
389 }
390
391 out_ptr += channels;
392 }
393
394 // We read an entire packet: set "frame" to the beginning of next packet
395 // so that we read it in full
396 frame = avframe->best_effort_timestamp + avframe->nb_samples;
397 }
398 av_frame_free(&avframe);
399 }
400 av_packet_unref(packet);
401 av_packet_free(&packet);
402 }
403 }
404 }
405};
406
407}
408#endif
Definition git_info.h:7