Loading...
Searching...
No Matches
LibavOutputStream.hpp
1#pragma once
2
3extern "C" {
4
5#include <libavcodec/avcodec.h>
6#include <libavdevice/avdevice.h>
7#include <libavformat/avformat.h>
8#include <libavutil/pixdesc.h>
9#include <libswresample/swresample.h>
10#include <libswscale/swscale.h>
11}
12
13#include <Audio/Settings/Model.hpp>
14#include <Gfx/Libav/AudioFrameEncoder.hpp>
15#include <Gfx/Libav/LibavOutputSettings.hpp>
16
17#include <score/application/ApplicationContext.hpp>
18#include <score/tools/Debug.hpp>
19
20#include <ossia/detail/flat_map.hpp>
21
22#include <QApplication>
23
24#include <CDSPResampler.h>
25
26#include <string>
27
28namespace Gfx
29{
30
32{
33 std::string name;
34 std::string codec;
35 ossia::flat_map<std::string, std::string> options;
36};
37
39{
40 const AVCodec* codec{};
41 AVStream* st{};
42 AVCodecContext* enc{};
43
44 /* pts of the next frame that will be generated */
45 int64_t next_pts{};
46 int samples_count{};
47
48 AVFrame* cache_input_frame{};
49 AVFrame* tmp_frame{};
50
51 AVPacket* tmp_pkt{};
52
53 struct SwsContext* sws_ctx{};
54 std::vector<std::unique_ptr<r8b::CDSPResampler>> resamplers;
55
56 std::unique_ptr<AudioFrameEncoder> encoder;
57
58 // Pre-allocated buffers for audio resampling (avoid per-frame heap allocs)
59 std::vector<std::vector<double>> resample_in_buf;
60 std::vector<ossia::float_vector> resample_out_buf;
61
62 bool m_valid{};
63
65 const LibavOutputSettings& set, AVFormatContext* oc, const StreamOptions& opts)
66 {
67 codec = avcodec_find_encoder_by_name(opts.codec.c_str());
68 if(!codec)
69 {
70 qDebug() << "Could not find encoder for " << opts.codec.c_str();
71 return;
72 }
73
74 this->tmp_pkt = av_packet_alloc();
75 if(!this->tmp_pkt)
76 {
77 qDebug() << "Could not allocate AVPacket";
78 return;
79 }
80
81 this->st = avformat_new_stream(oc, nullptr);
82 if(!this->st)
83 {
84 qDebug() << "Could not allocate stream";
85 return;
86 }
87 this->st->id = oc->nb_streams - 1;
88
89 this->enc = avcodec_alloc_context3(codec);
90 if(!this->enc)
91 {
92 qDebug() << "Could not alloc an encoding context";
93 return;
94 }
95
96 switch(codec->type)
97 {
98 case AVMEDIA_TYPE_AUDIO:
99 init_audio(set, this->enc);
100 break;
101 case AVMEDIA_TYPE_VIDEO:
102 init_video(set, this->enc);
103 break;
104
105 default:
106 break;
107 }
108
109 /* Some formats want stream headers to be separate. */
110 if(oc->oformat->flags & AVFMT_GLOBALHEADER)
111 {
112 this->enc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
113 }
114 }
115
116 void init_audio(const LibavOutputSettings& set, AVCodecContext* c)
117 {
118#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 24, 100)
119 c->sample_fmt = av_get_sample_fmt(set.audio_converted_smpfmt.toStdString().c_str());
120
121 {
122 const int* supported_samplerates{};
123#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 19, 100)
124 avcodec_get_supported_config(
125 c, codec, AV_CODEC_CONFIG_SAMPLE_RATE, 0, (const void**)&supported_samplerates,
126 nullptr);
127#else
128 supported_samplerates = codec->supported_samplerates;
129#endif
130 if(supported_samplerates)
131 {
132 c->sample_rate = supported_samplerates[0];
133 for(int i = 0; supported_samplerates[i]; i++)
134 {
135 if(supported_samplerates[i] == set.audio_sample_rate)
136 {
137 c->sample_rate = set.audio_sample_rate;
138 break;
139 }
140 }
141 }
142 else
143 {
144 c->sample_rate = set.audio_sample_rate;
145 }
146 }
147
148 // A NAMED layout, not AV_CHANNEL_ORDER_UNSPEC: encoders that publish a
149 // supported-layout list -- aac and libopus among them -- reject an
150 // unspecified order from avcodec_open2 with EINVAL, which is what made the
151 // "MP4 H.264 + AAC" preset refuse to start at all.
152 av_channel_layout_default(&c->ch_layout, set.audio_channels);
153 c->thread_count = set.threads > 0 ? set.threads : 0;
154 if(set.audio_encoder_short == "pcm_s24le" || set.audio_encoder_short == "pcm_s24be")
155 c->bits_per_raw_sample = 24;
156
157 this->st->time_base = AVRational{1, c->sample_rate};
158 c->time_base = AVRational{1, c->sample_rate};
159 c->framerate = AVRational{c->sample_rate, 1};
160 qDebug() << "Opening audio encoder with: rate: " << c->sample_rate;
161#endif
162 }
163
164 void init_video(const LibavOutputSettings& set, AVCodecContext* c)
165 {
166 c->codec_id = codec->id;
167 c->width = set.width;
168 c->height = set.height;
169
170 // Enable multi-threaded encoding (0 = auto-detect CPU count)
171 c->thread_count = set.threads > 0 ? set.threads : 0;
172 c->thread_type = FF_THREAD_FRAME | FF_THREAD_SLICE;
173 /* timebase: This is the fundamental unit of time (in seconds) in terms
174 * of which frame timestamps are represented. For fixed-fps content,
175 * timebase should be 1/framerate and timestamp increments should be
176 * identical to 1. */
177 this->st->time_base = AVRational{100000, int(100000 * set.rate)};
178 c->time_base = this->st->time_base;
179 c->framerate = AVRational{this->st->time_base.den, this->st->time_base.num};
180
181 // gop_size and max_b_frames: use FFmpeg/codec defaults.
182 // Users can override via the options dict (g=<N>, bf=<N>).
183 // FFmpeg default: g=12, bf=0. Presets set explicit values where needed.
184
185 c->pix_fmt = av_get_pix_fmt(set.video_converted_pixfmt.toStdString().c_str());
186 if(c->pix_fmt == AV_PIX_FMT_NONE)
187 {
188 // Default to first supported format of this codec
189 const AVPixelFormat* fmts = nullptr;
190#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 19, 100)
191 avcodec_get_supported_config(
192 c, codec, AV_CODEC_CONFIG_PIX_FORMAT, 0,
193 (const void**)&fmts, nullptr);
194#else
195 fmts = codec->pix_fmts;
196#endif
197 if(fmts && fmts[0] != AV_PIX_FMT_NONE)
198 c->pix_fmt = fmts[0];
199 else
200 c->pix_fmt = AV_PIX_FMT_YUV420P;
201 }
202 c->strict_std_compliance = FF_COMPLIANCE_NORMAL;
203 }
204
205 void open_audio(
206 const LibavOutputSettings& set, AVFormatContext* oc, const AVCodec* codec,
207 AVDictionary* opt_arg)
208 {
209#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 24, 100)
210 AVDictionary* opt = nullptr;
211
212 av_dict_copy(&opt, opt_arg, 0);
213 int ret = avcodec_open2(enc, codec, &opt);
214 av_dict_free(&opt);
215 if(ret < 0)
216 {
217 qDebug() << "Could not open audio codec: " << av_to_string(ret);
218 return;
219 }
220
221 int nb_samples = 0;
222 if(enc->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)
223 {
224 auto& audio_stgs = score::AppContext().settings<Audio::Settings::Model>();
225 nb_samples = audio_stgs.getBufferSize();
226 enc->frame_size = nb_samples;
227 qDebug() << "Setting frame_size: " << nb_samples;
228 }
229 else
230 {
231 nb_samples = enc->frame_size;
232 qDebug() << "Forcing frame_size: " << nb_samples;
233 }
234
235 cache_input_frame = alloc_audio_frame(
236 enc->sample_fmt, &enc->ch_layout, enc->sample_rate, nb_samples);
237
238 /* copy the stream parameters to the muxer */
239 ret = avcodec_parameters_from_context(this->st->codecpar, enc);
240 if(ret < 0)
241 {
242 qDebug() << "Could not copy the stream parameters";
243 return;
244 }
245
246 {
247 auto conv_fmt
248 = av_get_sample_fmt(set.audio_converted_smpfmt.toStdString().c_str());
249 if(conv_fmt == AV_SAMPLE_FMT_NONE)
250 {
251 qDebug() << "Invalid audio sample format:" << set.audio_converted_smpfmt;
252 return;
253 }
254
255 auto& ctx = score::AppContext().settings<Audio::Settings::Model>();
256
257 const int input_sample_rate = ctx.getRate();
258 if(enc->sample_rate != input_sample_rate)
259 {
260 for(int i = 0; i < set.audio_channels; i++)
261 this->resamplers.push_back(std::make_unique<r8b::CDSPResampler>(
262 input_sample_rate, enc->sample_rate, nb_samples * 2, 3.0, 206.91,
263 r8b::fprMinPhase));
264 }
265
266 switch(conv_fmt)
267 {
268 case AV_SAMPLE_FMT_NONE:
269 case AV_SAMPLE_FMT_U8:
270 case AV_SAMPLE_FMT_S16:
271 encoder = std::make_unique<S16IAudioFrameEncoder>(nb_samples);
272 break;
273 case AV_SAMPLE_FMT_S32:
274 // Always full-scale int32, even for a 24-bit container: libav's
275 // pcm_s24le takes AV_SAMPLE_FMT_S32 samples and writes their TOP 24
276 // bits, so scaling to 2^23 as a 24-bit sample would have made every
277 // WAV 24-bit recording 48 dB too quiet.
278 encoder = std::make_unique<S32IAudioFrameEncoder>(nb_samples);
279 break;
280 case AV_SAMPLE_FMT_FLT:
281 encoder = std::make_unique<FltIAudioFrameEncoder>(nb_samples);
282 break;
283 case AV_SAMPLE_FMT_DBL:
284 encoder = std::make_unique<DblIAudioFrameEncoder>(nb_samples);
285 break;
286
287 case AV_SAMPLE_FMT_U8P:
288 case AV_SAMPLE_FMT_S16P:
289 encoder = std::make_unique<S16PAudioFrameEncoder>(nb_samples);
290 break;
291 case AV_SAMPLE_FMT_S32P:
292 encoder = std::make_unique<S32PAudioFrameEncoder>(nb_samples);
293 break;
294 case AV_SAMPLE_FMT_FLTP:
295 encoder = std::make_unique<FltPAudioFrameEncoder>(nb_samples);
296 break;
297 case AV_SAMPLE_FMT_DBLP:
298 encoder = std::make_unique<DblPAudioFrameEncoder>(nb_samples);
299 break;
300 case AV_SAMPLE_FMT_S64:
301 case AV_SAMPLE_FMT_S64P:
302 qDebug() << "64-bit integer audio sample format not supported for encoding";
303 break;
304 default:
305 break;
306 }
307 }
308
309 m_valid = true;
310#endif
311 }
312
313#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 24, 100)
314 static AVFrame* alloc_audio_frame(
315 enum AVSampleFormat sample_fmt, const AVChannelLayout* channel_layout,
316 int sample_rate, int nb_samples)
317 {
318 AVFrame* frame = av_frame_alloc();
319 if(!frame)
320 {
321 qDebug() << "Error allocating an audio frame";
322 return nullptr;
323 }
324
325 frame->format = sample_fmt;
326 av_channel_layout_copy(&frame->ch_layout, channel_layout);
327 frame->sample_rate = sample_rate;
328 frame->nb_samples = nb_samples;
329
330 if(nb_samples)
331 {
332 if(av_frame_get_buffer(frame, 0) < 0)
333 {
334 qDebug() << "Error allocating an audio buffer";
335 av_frame_free(&frame);
336 return nullptr;
337 }
338 }
339
340 return frame;
341 }
342#endif
343
344 static AVFrame* alloc_video_frame(enum AVPixelFormat pix_fmt, int width, int height)
345 {
346 auto frame = av_frame_alloc();
347 if(!frame)
348 return NULL;
349
350 frame->format = pix_fmt;
351 frame->width = width;
352 frame->height = height;
353
354 /* allocate the buffers for the frame data */
355 const int ret = av_frame_get_buffer(frame, 0);
356 if(ret < 0)
357 {
358 qDebug() << "Could not allocate frame data.";
359 av_frame_free(&frame);
360 return nullptr;
361 }
362
363 return frame;
364 }
365
366 void open_video(
367 const LibavOutputSettings& set, AVFormatContext* oc, const AVCodec* codec,
368 AVDictionary* opt_arg)
369 {
370 AVCodecContext* c = this->enc;
371 AVDictionary* opt = nullptr;
372
373 av_dict_copy(&opt, opt_arg, 0);
374
375 /* open the codec */
376 int ret = avcodec_open2(this->enc, codec, &opt);
377 av_dict_free(&opt);
378 if(ret < 0)
379 {
380 qDebug() << "Could not open video codec: " << av_to_string(ret);
381 return;
382 }
383
384 /* allocate and init a re-usable frame */
385 this->cache_input_frame = alloc_video_frame(AV_PIX_FMT_RGBA, c->width, c->height);
386 if(!this->cache_input_frame)
387 {
388 qDebug() << "Could not allocate video frame";
389 return;
390 }
391
392 this->tmp_frame = nullptr;
393 {
394 auto input_fmt = av_get_pix_fmt(set.video_render_pixfmt.toStdString().c_str());
395 auto conv_fmt = av_get_pix_fmt(set.video_converted_pixfmt.toStdString().c_str());
396 if(input_fmt == AV_PIX_FMT_NONE || conv_fmt == AV_PIX_FMT_NONE)
397 {
398 qDebug() << "Invalid pixel format:" << set.video_render_pixfmt
399 << "->" << set.video_converted_pixfmt;
400 return;
401 }
402 sws_ctx = sws_getContext(
403 set.width, set.height, input_fmt, set.width, set.height, conv_fmt,
404 SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
405 if(!sws_ctx)
406 {
407 qDebug() << "Could not create swscale context";
408 return;
409 }
410 this->tmp_frame = alloc_video_frame(conv_fmt, c->width, c->height);
411 if(!this->tmp_frame)
412 {
413 qDebug() << "Could not allocate temporary video frame";
414 return;
415 }
416 }
417
418 /* copy the stream parameters to the muxer */
419 ret = avcodec_parameters_from_context(this->st->codecpar, c);
420 if(ret < 0)
421 {
422 qDebug() << "Could not copy the stream parameters";
423 return;
424 }
425
426 m_valid = true;
427 }
428
429 void open(
430 const LibavOutputSettings& set, AVFormatContext* oc, const AVCodec* codec,
431 AVDictionary* opt_arg)
432 {
433 SCORE_ASSERT(oc);
434 SCORE_ASSERT(codec);
435 if(codec->type == AVMEDIA_TYPE_AUDIO)
436 {
437 open_audio(set, oc, codec, opt_arg);
438 }
439 else if(codec->type == AVMEDIA_TYPE_VIDEO)
440 {
441 open_video(set, oc, codec, opt_arg);
442 }
443 }
444
445 void close(AVFormatContext* oc)
446 {
447 avcodec_free_context(&enc);
448 av_frame_free(&cache_input_frame);
449 av_frame_free(&tmp_frame);
450 av_packet_free(&tmp_pkt);
451 sws_freeContext(sws_ctx);
452 sws_ctx = nullptr;
453 }
454
455 AVFrame* get_video_frame()
456 {
457 if(!m_valid || !this->cache_input_frame)
458 return nullptr;
459 if(av_frame_make_writable(this->cache_input_frame) < 0)
460 return nullptr;
461
462 this->cache_input_frame->pts = this->next_pts++;
463 return this->cache_input_frame;
464 }
465
466 AVFrame* get_audio_frame()
467 {
468 if(!m_valid || !this->cache_input_frame)
469 return nullptr;
470 if(av_frame_make_writable(this->cache_input_frame) < 0)
471 return nullptr;
472
473 this->cache_input_frame->pts = this->next_pts;
474 this->next_pts += this->enc->frame_size;
475 return this->cache_input_frame;
476 }
477
478 int write_video_frame(AVFormatContext* fmt_ctx, AVFrame* input_frame)
479 {
480 if(!m_valid)
481 return -1;
482#if LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(7, 5, 100)
483 // Must unref before reuse — sws_scale_frame allocates internal buffers
484 av_frame_unref(tmp_frame);
485 tmp_frame->format = enc->pix_fmt;
486 tmp_frame->width = enc->width;
487 tmp_frame->height = enc->height;
488
489 // scale the frame
490 int ret = sws_scale_frame(sws_ctx, tmp_frame, input_frame);
491 if(ret < 0)
492 {
493 qDebug() << "Error during sws_scale_frame: " << av_to_string(ret);
494 return ret;
495 }
496
497 tmp_frame->pts = input_frame->pts;
498
499 // send the frame to the encoder
500 ret = avcodec_send_frame(enc, tmp_frame);
501 if(ret < 0)
502 {
503 qDebug() << "Error sending a frame to the encoder: " << av_to_string(ret);
504 return ret;
505 }
506
507 while(ret >= 0)
508 {
509 ret = avcodec_receive_packet(enc, tmp_pkt);
510 if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
511 break;
512 else if(ret < 0)
513 {
514 qDebug() << "Error encoding a frame: " << av_to_string(ret);
515 return ret;
516 }
517
518 /* rescale output packet timestamp values from codec to stream timebase */
519 av_packet_rescale_ts(tmp_pkt, enc->time_base, st->time_base);
520 tmp_pkt->stream_index = st->index;
521
522 ret = av_interleaved_write_frame(fmt_ctx, tmp_pkt);
523 if(ret < 0)
524 {
525 qDebug() << "Error while writing output packet: " << av_to_string(ret);
526 return ret;
527 }
528 }
529
530 return ret == AVERROR_EOF ? 1 : 0;
531#endif
532 return 1;
533 }
534
535 // Write a pre-converted frame directly to the encoder — no sws_scale.
536 // The frame must already be in enc->pix_fmt with correct dimensions.
537 int write_video_frame_direct(AVFormatContext* fmt_ctx, AVFrame* frame)
538 {
539 if(!m_valid)
540 return -1;
541
542 int ret = avcodec_send_frame(enc, frame);
543 if(ret < 0)
544 {
545 qDebug() << "Error sending a frame to the encoder: " << av_to_string(ret);
546 return ret;
547 }
548
549 while(ret >= 0)
550 {
551 ret = avcodec_receive_packet(enc, tmp_pkt);
552 if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
553 break;
554 else if(ret < 0)
555 {
556 qDebug() << "Error encoding a frame: " << av_to_string(ret);
557 return ret;
558 }
559
560 av_packet_rescale_ts(tmp_pkt, enc->time_base, st->time_base);
561 tmp_pkt->stream_index = st->index;
562
563 ret = av_interleaved_write_frame(fmt_ctx, tmp_pkt);
564 if(ret < 0)
565 {
566 qDebug() << "Error while writing output packet: " << av_to_string(ret);
567 return ret;
568 }
569 }
570
571 return ret == AVERROR_EOF ? 1 : 0;
572 }
573
574 int write_audio_frame(AVFormatContext* fmt_ctx, AVFrame* input_frame)
575 {
576 if(!m_valid)
577 return -1;
578 // send the frame to the encoder
579 int ret = avcodec_send_frame(enc, input_frame);
580 if(ret < 0)
581 {
582 qDebug() << "Error sending a frame to the encoder: " << av_to_string(ret);
583 return ret;
584 }
585
586 while(ret >= 0)
587 {
588 ret = avcodec_receive_packet(enc, tmp_pkt);
589 if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
590 break;
591 else if(ret < 0)
592 {
593 qDebug() << "Error encoding a frame: " << av_to_string(ret);
594 return ret;
595 }
596
597 /* rescale output packet timestamp values from codec to stream timebase */
598 av_packet_rescale_ts(tmp_pkt, enc->time_base, st->time_base);
599 tmp_pkt->stream_index = st->index;
600
601 ret = av_interleaved_write_frame(fmt_ctx, tmp_pkt);
602 if(ret < 0)
603 {
604 qDebug() << "Error while writing output packet: " << av_to_string(ret);
605 return ret;
606 }
607 }
608
609 return ret == AVERROR_EOF ? 1 : 0;
610 }
611};
612}
Definition score-plugin-audio/Audio/Settings/Model.hpp:22
Binds the rendering pipeline to ossia processes.
Definition AssetTable.cpp:7
Definition LibavOutputSettings.hpp:16
Definition LibavOutputStream.hpp:39
Definition LibavOutputStream.hpp:32
T & settings() const
Access a specific Settings model instance.
Definition ApplicationContext.hpp:41