FFmpegfs Fuse Multi Media Filesystem 2.19
Loading...
Searching...
No Matches
ffmpeg_base.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de)
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 * On Debian systems, the complete text of the GNU General Public License
19 * Version 3 can be found in `/usr/share/common-licenses/GPL-3'.
20 */
21
32#ifdef __cplusplus
33extern "C" {
34#endif
35// Disable annoying warnings outside our code
36#pragma GCC diagnostic push
37#pragma GCC diagnostic ignored "-Wconversion"
38#pragma GCC diagnostic ignored "-Wsign-conversion"
39#include <libavutil/opt.h>
40#include <libavutil/mathematics.h>
41#include <libavutil/pixdesc.h>
42#include <libavutil/channel_layout.h>
43#include <libavcodec/avcodec.h>
44#pragma GCC diagnostic pop
45#ifdef __cplusplus
46}
47#endif
48
49#include "ffmpeg_base.h"
50#include "logging.h"
51
52
54 : m_virtualfile(nullptr)
55{
56}
57
58
59void FFmpeg_Base::video_stream_setup(AVCodecContext *output_codec_ctx, AVStream* output_stream, AVCodecContext *input_codec_ctx, AVRational framerate, AVPixelFormat enc_hw_pix_fmt) const
60{
61 AVRational time_base_tbn;
62 AVRational time_base_tbc;
63
64 if (!framerate.num || !framerate.den)
65 {
66 framerate.num = 25;
67 framerate.den = 1;
68 Logging::warning(nullptr, "No information about the input framerate is available. Falling back to a default value of 25fps for the output stream.");
69 }
70
71 // timebase: This is the fundamental unit of time (in seconds) in terms
72 // of which frame timestamps are represented. For fixed-fps content,
73 // timebase should be 1/framerate and timestamp increments should be
74 // identical to 1.
75 //time_base = m_in.m_pVideo_stream->time_base;
76
77 // tbn: must be set differently for the target format. Otherwise produces strange results.
78 switch (output_codec_ctx->codec_id)
79 {
80 case AV_CODEC_ID_THEORA: // ogg
81 case AV_CODEC_ID_MPEG1VIDEO:
82 case AV_CODEC_ID_MPEG2VIDEO:
83 {
84 time_base_tbn = av_inv_q(framerate);
85 time_base_tbc = time_base_tbn;
86 break;
87 }
88 case AV_CODEC_ID_VP9: // webm
89 {
90 time_base_tbn.num = 1;
91 time_base_tbn.den = 1000;
92 time_base_tbc = time_base_tbn;
93 break;
94 }
95 case AV_CODEC_ID_H264: // h264
96 case AV_CODEC_ID_H265: // h265
97 {
98 time_base_tbn.num = 1;
99 time_base_tbn.den = 90000;
100 time_base_tbc = av_inv_q(framerate);
101 break;
102 }
103 default: // mp4 and all others
104 {
105 time_base_tbn.num = 1;
106 time_base_tbn.den = 90000;
107 time_base_tbc = time_base_tbn;
108 break;
109 }
110 }
111
112 // tbn
113 output_stream->time_base = time_base_tbn;
114 // tbc
115 output_codec_ctx->time_base = time_base_tbc;
116
117 // tbr
118 // output_stream->r_frame_rate = m_in.m_pVideo_stream->r_frame_rate;
119 output_stream->r_frame_rate = framerate;
120
121 // fps
122 output_stream->avg_frame_rate = framerate;
123 // output_codec_ctx->framerate = framerate;
124
125 if (enc_hw_pix_fmt == AV_PIX_FMT_NONE)
126 {
127 // Automatic pix_fmt selection
128 int loss = 0;
129
130 AVPixelFormat src_pix_fmt = input_codec_ctx->pix_fmt;
131#if LAVC_USE_SUPPORTED_CFG
132 {
133 const enum AVPixelFormat *pix_list = nullptr;
134 int npix = 0;
135 int ret_cfg = avcodec_get_supported_config(output_codec_ctx, output_codec_ctx->codec,
136 AV_CODEC_CONFIG_PIX_FORMAT,
137 0, (const void**)&pix_list, &npix);
138 if (ret_cfg >= 0 && pix_list && npix > 0)
139 {
140 int alpha = 0;
141 enc_hw_pix_fmt = avcodec_find_best_pix_fmt_of_list(pix_list, src_pix_fmt, alpha, &loss);
142 }
143 }
144#else
145 if (output_codec_ctx->codec->pix_fmts != nullptr)
146 {
147 int alpha = 0;
148 enc_hw_pix_fmt = avcodec_find_best_pix_fmt_of_list(output_codec_ctx->codec->pix_fmts, src_pix_fmt, alpha, &loss);
149 }
150#endif
151
152 if (enc_hw_pix_fmt == AV_PIX_FMT_NONE)
153 {
154 // Fail safe if avcodec_find_best_pix_fmt_of_list has no idea what to use.
155 switch (output_codec_ctx->codec_id)
156 {
157 case AV_CODEC_ID_PRORES: // mov/prores
158 {
159 // yuva444p10le
160 // ProRes 4:4:4 if the source is RGB and ProRes 4:2:2 if the source is YUV.
161 enc_hw_pix_fmt = AV_PIX_FMT_YUV422P10LE;
162 break;
163 }
164 default: // all others
165 {
166 // At this moment the output format must be AV_PIX_FMT_YUV420P;
167 enc_hw_pix_fmt = AV_PIX_FMT_YUV420P;
168 break;
169 }
170 }
171 }
172 }
173
174 output_codec_ctx->pix_fmt = enc_hw_pix_fmt;
175 output_codec_ctx->gop_size = 12; // emit one intra frame every twelve frames at most
176}
177
178int FFmpeg_Base::dict_set_with_check(AVDictionary **pm, const char *key, const char *value, int flags, const char * filename, bool nodelete) const
179{
180 if (nodelete && !*value)
181 {
182 return 0;
183 }
184
185 int ret = av_dict_set(pm, key, value, flags);
186
187 if (ret < 0)
188 {
189 Logging::error(filename, "Error setting dictionary option key(%1)='%2' (error '%3').", key, value, ffmpeg_geterror(ret).c_str());
190 }
191
192 return ret;
193}
194
195int FFmpeg_Base::dict_set_with_check(AVDictionary **pm, const char *key, int64_t value, int flags, const char * filename, bool nodelete) const
196{
197 if (nodelete && !value)
198 {
199 return 0;
200 }
201
202 int ret = av_dict_set_int(pm, key, value, flags);
203
204 if (ret < 0)
205 {
206 Logging::error(filename, "Error setting dictionary option key(%1)='%2' (error '%3').", key, value, ffmpeg_geterror(ret).c_str());
207 }
208
209 return ret;
210}
211
212int FFmpeg_Base::opt_set_with_check(void *obj, const char *key, const char *value, int flags, const char * filename) const
213{
214 int ret = av_opt_set(obj, key, value, flags);
215
216 if (ret < 0)
217 {
218 Logging::error(filename, "Error setting dictionary option key(%1)='%2' (error '%3').", key, value, ffmpeg_geterror(ret).c_str());
219 }
220
221 return ret;
222}
223
224void FFmpeg_Base::video_info(bool out_file, const AVFormatContext *format_ctx, const AVStream *stream) const
225{
226 if (stream != nullptr && stream->codecpar != nullptr)
227 {
228 int64_t duration = AV_NOPTS_VALUE;
229
230 if (stream->duration != AV_NOPTS_VALUE)
231 {
232 duration = ffmpeg_rescale_q_rnd(stream->duration, stream->time_base);
233 }
234
235 Logging::debug(out_file ? virtname() : filename(), "Video %1 #%2: %3@%4 [%5]",
236 out_file ? "out" : "in",
237 stream->index,
238 get_codec_name(stream->codecpar->codec_id),
239 format_bitrate((stream->codecpar->bit_rate != 0) ? stream->codecpar->bit_rate : format_ctx->bit_rate).c_str(),
240 format_duration(duration).c_str());
241 }
242 else
243 {
244 Logging::debug(out_file ? virtname() : filename(), "Video %1: invalid stream",
245 out_file ? "out" : "in");
246 }
247}
248
249void FFmpeg_Base::audio_info(bool out_file, const AVFormatContext *format_ctx, const AVStream *stream) const
250{
251 if (stream != nullptr && stream->codecpar != nullptr)
252 {
253 int64_t duration = AV_NOPTS_VALUE;
254
255 if (stream->duration != AV_NOPTS_VALUE)
256 {
257 duration = ffmpeg_rescale_q_rnd(stream->duration, stream->time_base);
258 }
259
260 Logging::debug(out_file ? virtname() : filename(), "Audio %1 #2: %3@%4 %5 Channels %6 [%7]",
261 out_file ? "out" : "in",
262 stream->index,
263 get_codec_name(stream->codecpar->codec_id),
264 format_bitrate((stream->codecpar->bit_rate != 0) ? stream->codecpar->bit_rate : format_ctx->bit_rate).c_str(),
265 get_channels(stream->codecpar),
266 format_samplerate(stream->codecpar->sample_rate).c_str(),
267 format_duration(duration).c_str());
268 }
269 else
270 {
271 Logging::debug(out_file ? virtname() : filename(), "Audio %1: invalid stream",
272 out_file ? "out" : "in");
273 }
274}
275
276void FFmpeg_Base::subtitle_info(bool out_file, const AVFormatContext * /*format_ctx*/, const AVStream *stream) const
277{
278 if (stream != nullptr && stream->codecpar != nullptr)
279 {
280 Logging::debug(out_file ? virtname() : filename(), "Subtitle %1 #%2: %3",
281 out_file ? "out" : "in",
282 stream->index,
283 get_codec_name(stream->codecpar->codec_id));
284 }
285 else
286 {
287 Logging::debug(out_file ? virtname() : filename(), "Subtitle %1: invalid stream",
288 out_file ? "out" : "in");
289 }
290}
291
292std::string FFmpeg_Base::get_pix_fmt_name(enum AVPixelFormat pix_fmt)
293{
294 const char *fmt_name = av_get_pix_fmt_name(pix_fmt);
295 return (fmt_name != nullptr ? fmt_name : "none");
296}
297
298std::string FFmpeg_Base::get_sample_fmt_name(AVSampleFormat sample_fmt)
299{
300 return av_get_sample_fmt_name(sample_fmt);
301}
302
303#if LAVU_DEP_OLD_CHANNEL_LAYOUT
304std::string FFmpeg_Base::get_channel_layout_name(const AVChannelLayout * ch_layout)
305{
306 std::array<char, 1024> buffer;
307 av_channel_layout_describe(ch_layout, buffer.data(), buffer.size() - 1);
308 return buffer.data();
309}
310#else // !LAVU_DEP_OLD_CHANNEL_LAYOUT
311std::string FFmpeg_Base::get_channel_layout_name(int nb_channels, uint64_t channel_layout)
312{
313 std::array<char, 1024> buffer;
314 av_get_channel_layout_string(buffer.data(), buffer.size() - 1, nb_channels, channel_layout);
315 return buffer.data();
316}
317#endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT
318
319uint32_t FFmpeg_Base::pts_to_frame(AVStream* stream, int64_t pts) const
320{
321 if (pts == AV_NOPTS_VALUE)
322 {
323 return 0;
324 }
325 int64_t start_time = (stream->start_time != AV_NOPTS_VALUE) ? stream->start_time : 0;
326 AVRational factor = av_mul_q(stream->avg_frame_rate, stream->time_base);
327 return static_cast<uint32_t>(av_rescale(pts - start_time, factor.num, factor.den) + 1);
328}
329
330int64_t FFmpeg_Base::frame_to_pts(AVStream* stream, uint32_t frame_no) const
331{
332 int64_t start_time = (stream->start_time != AV_NOPTS_VALUE) ? stream->start_time : 0;
333 AVRational factor = av_mul_q(stream->avg_frame_rate, stream->time_base);
334 return static_cast<uint32_t>(av_rescale(frame_no - 1, factor.den, factor.num) + start_time);
335}
336
337int FFmpeg_Base::get_channels(const AVCodecParameters *codecpar) const
338{
339#if LAVU_DEP_OLD_CHANNEL_LAYOUT
340 return codecpar->ch_layout.nb_channels;
341#else // !LAVU_DEP_OLD_CHANNEL_LAYOUT
342 return codecpar->channels;
343#endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT
344}
345
346void FFmpeg_Base::set_channels(AVCodecParameters *codecpar_out, const AVCodecParameters *codecpar_in) const
347{
348#if LAVU_DEP_OLD_CHANNEL_LAYOUT
349 codecpar_out->ch_layout.nb_channels = codecpar_in->ch_layout.nb_channels;
350#else // !LAVU_DEP_OLD_CHANNEL_LAYOUT
351 codecpar_out->channels = codecpar_in->channels;
352#endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT
353}
354
355int FFmpeg_Base::get_channels(const AVCodecContext *codec_ctx) const
356{
357#if LAVU_DEP_OLD_CHANNEL_LAYOUT
358 return codec_ctx->ch_layout.nb_channels;
359#else // !LAVU_DEP_OLD_CHANNEL_LAYOUT
360 return codec_ctx->channels;
361#endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT
362}
363
364void FFmpeg_Base::set_channels(AVCodecContext *codec_ctx_out, const AVCodecContext *codec_ctx_in) const
365{
366#if LAVU_DEP_OLD_CHANNEL_LAYOUT
367 codec_ctx_out->ch_layout.nb_channels= codec_ctx_in->ch_layout.nb_channels;
368#else // !LAVU_DEP_OLD_CHANNEL_LAYOUT
369 codec_ctx_out->channels = codec_ctx_in->channels;
370#endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT
371}
372
373void FFmpeg_Base::set_channels(AVCodecContext *codec_ctx_out, int channels) const
374{
375#if LAVU_DEP_OLD_CHANNEL_LAYOUT
376 codec_ctx_out->ch_layout.nb_channels = channels;
377#else // !LAVU_DEP_OLD_CHANNEL_LAYOUT
378 codec_ctx_out->channels = channels;
379#endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT
380}
381
382// See...
383//
384// https://en.wikipedia.org/wiki/SubStation_Alpha
385// https://datatracker.ietf.org/doc/html/draft-ietf-cellar-codec-02
386// https://fileformats.fandom.com/wiki/SubStation_Alpha
387
388int FFmpeg_Base::get_script_info(AVCodecContext *codec_ctx, int play_res_x, int play_res_y, const char *font, int font_size, int primary_color, int secondary_color, int outline_color, int back_color, int bold, int italic, int underline, int border_style, int alignment) const
389{
390 const char *format =
391 "[Script Info]\r\n" //
392 "; Script generated by ffmpegfs " FFMPEFS_VERSION "\r\n" //
393 "; https://github.com/nschlia/ffmpegfs\r\n" //
394 "ScriptType: v4.00+\r\n" //
395 "PlayResX: %d\r\n" //
396 "PlayResY: %d\r\n" //
397 "ScaledBorderAndShadow: yes\r\n" //
398
399 // Some other tags...
400 //"Title: NAME (Language)\r\n" //
401 //"Original Script: ???\r\n" //
402 //"Script Updated By: version 2.8.01\r\n" //
403 //"Collisions: Normal\r\n" //
404 //"PlayDepth: 0\r\n" //
405 //"Timer: 100,0000\r\n" //
406 //"Video Aspect Ratio: 0\r\n" //
407 //"Video Zoom: 6\r\n" //
408 //"Video Position: 0\r\n" //
409
410 "\r\n" //
411 "[V4+ Styles]\r\n" //
412
413 "Format: " //
414 "Name, " //
415 "Fontname, Fontsize, " //
416 "PrimaryColour, SecondaryColour, OutlineColour, BackColour, " //
417 "Bold, Italic, Underline, StrikeOut, " //
418 "ScaleX, ScaleY, " //
419 "Spacing, Angle, " //
420 "BorderStyle, Outline, Shadow, " //
421 "Alignment, MarginL, MarginR, MarginV, " //
422 "Encoding\r\n" //
423
424 "Style: " //
425 "Default," // Name
426 "%s,%d," // Font{name,size}
427 "&H%x,&H%x,&H%x,&H%x," // {Primary,Secondary,Outline,Back}Colour
428 "%d,%d,%d,0," // Bold, Italic, Underline, StrikeOut
429 "100,100," // Scale{X,Y}
430 "0,0," // Spacing, Angle
431 "%d,1,0," // BorderStyle, Outline, Shadow
432 "%d,10,10,10," // Alignment, Margin[LRV]
433 "0\r\n" // Encoding
434 "\r\n" //
435
436 "[Events]\r\n" //
437 "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n";
438
439 size_t size = static_cast<size_t>(snprintf(nullptr, 0, format, play_res_x, play_res_y, font, font_size,
440 primary_color, secondary_color, outline_color, back_color,
441 -bold, -italic, -underline, border_style, alignment)) + 1; // Extra space for '\0'
442
443 codec_ctx->subtitle_header = reinterpret_cast<uint8_t *>(av_malloc(size + 1));
444
445 if (codec_ctx->subtitle_header == nullptr)
446 {
447 return AVERROR(ENOMEM);
448 }
449
450 snprintf(reinterpret_cast<char *>(codec_ctx->subtitle_header), size, format,
451 play_res_x, play_res_y, font, font_size,
452 primary_color, secondary_color, outline_color, back_color,
453 -bold, -italic, -underline, border_style, alignment);
454
455 codec_ctx->subtitle_header_size = static_cast<int>(size);
456
457 return 0;
458}
static std::string get_sample_fmt_name(AVSampleFormat sample_fmt)
Calls av_get_sample_fmt_name and returns a std::string with the format name.
int get_channels(const AVCodecParameters *codecpar) const
Get the number of channels from AVCodecParameters.
int opt_set_with_check(void *obj, const char *key, const char *value, int flags, const char *filename=nullptr) const
Call av_opt_set and check result code. Displays an error message if appropriate.
int get_script_info(AVCodecContext *codec_ctx, int play_res_x, int play_res_y, const char *font, int font_size, int primary_color, int secondary_color, int outline_color, int back_color, int bold, int italic, int underline, int border_style, int alignment) const
Generate a suitable AVCodecContext.subtitle_header for SUBTITLE_ASS. Nicked from the FFmpeg API funct...
void audio_info(bool out_file, const AVFormatContext *format_ctx, const AVStream *stream) const
Print data from the audio stream to log.
int64_t frame_to_pts(AVStream *stream, uint32_t frame_no) const
Convert frame number to PTS value.
uint32_t pts_to_frame(AVStream *stream, int64_t pts) const
Convert PTS value to frame number.
virtual const char * virtname() const =0
Return virtual filename. Must be implemented in child class.
void video_info(bool out_file, const AVFormatContext *format_ctx, const AVStream *stream) const
Print data from the video stream to a log.
void subtitle_info(bool out_file, const AVFormatContext *format_ctx, const AVStream *stream) const
Print data from the subtitle stream to log.
void video_stream_setup(AVCodecContext *output_codec_ctx, AVStream *output_stream, AVCodecContext *input_codec_ctx, AVRational framerate, AVPixelFormat enc_hw_pix_fmt) const
Set up a video stream.
static std::string get_channel_layout_name(const AVChannelLayout *ch_layout)
Calls av_channel_layout_describe and returns a std::string with the channel layout.
static std::string get_pix_fmt_name(AVPixelFormat pix_fmt)
Calls av_get_pix_fmt_name and returns a std::string with the pix format name.
int dict_set_with_check(AVDictionary **pm, const char *key, const char *value, int flags, const char *filename=nullptr, bool nodelete=false) const
Call av_dict_set and check the result code. It displays an error message if appropriate.
void set_channels(AVCodecParameters *codecpar_out, const AVCodecParameters *codecpar_in) const
Set the number of channels from AVCodecParameters.
FFmpeg_Base()
Construct FFmpeg_Base object.
virtual const char * filename() const =0
Return source filename. Must be implemented in child class.
static void warning(const T filename, const std::string &format_string, Args &&...args)
Write warning level log entry.
Definition logging.h:220
static void debug(const T filename, const std::string &format_string, Args &&...args)
Write debug level log entry.
Definition logging.h:182
static void error(const T filename, const std::string &format_string, Args &&...args)
Write error level log entry.
Definition logging.h:239
FFmpeg transcoder base.
const char * get_codec_name(AVCodecID codec_id, bool long_name)
Safe way to get the codec name. Function never fails, will return "unknown" on error.
std::string format_duration(int64_t value, uint32_t fracs)
Format a time in format HH:MM:SS.fract.
int64_t ffmpeg_rescale_q_rnd(int64_t ts, const AVRational &timebase_in, const AVRational &timebase_out)
Convert a FFmpeg time from in timebase to out timebase with rounding.
std::string format_samplerate(int value)
Format a samplerate.
std::string format_bitrate(BITRATE value)
Format a bit rate.
std::string ffmpeg_geterror(int errnum)
Get FFmpeg error string for errnum. Internally calls av_strerror().
#define FFMPEFS_VERSION
FFmpegfs version number.
Provide various log facilities to stderr, disk or syslog.