FFmpegfs Fuse Multi Media Filesystem 2.16
ffmpeg_base.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 2017-2024 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#if !LAVC_DEP_AV_INIT_PACKET
59void FFmpeg_Base::init_packet(AVPacket *pkt) const
60{
61 av_init_packet(pkt);
62 // Set the packet data and size so that it is recognised as being empty.
63 pkt->data = nullptr;
64 pkt->size = 0;
65}
66#endif // !LAVC_DEP_AV_INIT_PACKET
67
68void FFmpeg_Base::video_stream_setup(AVCodecContext *output_codec_ctx, AVStream* output_stream, AVCodecContext *input_codec_ctx, AVRational framerate, AVPixelFormat enc_hw_pix_fmt) const
69{
70 AVRational time_base_tbn;
71 AVRational time_base_tbc;
72
73 if (!framerate.num || !framerate.den)
74 {
75 framerate.num = 25;
76 framerate.den = 1;
77 Logging::warning(nullptr, "No information about the input framerate is available. Falling back to a default value of 25fps for the output stream.");
78 }
79
80 // timebase: This is the fundamental unit of time (in seconds) in terms
81 // of which frame timestamps are represented. For fixed-fps content,
82 // timebase should be 1/framerate and timestamp increments should be
83 // identical to 1.
84 //time_base = m_in.m_pVideo_stream->time_base;
85
86 // tbn: must be set differently for the target format. Otherwise produces strange results.
87 switch (output_codec_ctx->codec_id)
88 {
89 case AV_CODEC_ID_THEORA: // ogg
90 case AV_CODEC_ID_MPEG1VIDEO:
91 case AV_CODEC_ID_MPEG2VIDEO:
92 {
93 time_base_tbn = av_inv_q(framerate);
94 time_base_tbc = time_base_tbn;
95 break;
96 }
97 case AV_CODEC_ID_VP9: // webm
98 {
99 time_base_tbn.num = 1;
100 time_base_tbn.den = 1000;
101 time_base_tbc = time_base_tbn;
102 break;
103 }
104 case AV_CODEC_ID_H264: // h264
105 case AV_CODEC_ID_H265: // h265
106 {
107 time_base_tbn.num = 1;
108 time_base_tbn.den = 90000;
109 time_base_tbc = av_inv_q(framerate);
110 break;
111 }
112 default: // mp4 and all others
113 {
114 time_base_tbn.num = 1;
115 time_base_tbn.den = 90000;
116 time_base_tbc = time_base_tbn;
117 break;
118 }
119 }
120
121 // tbn
122 output_stream->time_base = time_base_tbn;
123 // tbc
124 output_codec_ctx->time_base = time_base_tbc;
125
126 // tbr
127 // output_stream->r_frame_rate = m_in.m_pVideo_stream->r_frame_rate;
128 output_stream->r_frame_rate = framerate;
129
130 // fps
131 output_stream->avg_frame_rate = framerate;
132 // output_codec_ctx->framerate = framerate;
133
134 if (enc_hw_pix_fmt == AV_PIX_FMT_NONE)
135 {
136 // Automatic pix_fmt selection
137 int loss = 0;
138
139 AVPixelFormat src_pix_fmt = input_codec_ctx->pix_fmt;
140 if (output_codec_ctx->codec->pix_fmts != nullptr)
141 {
142 int alpha = 0;
143 enc_hw_pix_fmt = avcodec_find_best_pix_fmt_of_list(output_codec_ctx->codec->pix_fmts, src_pix_fmt, alpha, &loss);
144 }
145
146 if (enc_hw_pix_fmt == AV_PIX_FMT_NONE)
147 {
148 // Fail safe if avcodec_find_best_pix_fmt_of_list has no idea what to use.
149 switch (output_codec_ctx->codec_id)
150 {
151 case AV_CODEC_ID_PRORES: // mov/prores
152 {
153 // yuva444p10le
154 // ProRes 4:4:4 if the source is RGB and ProRes 4:2:2 if the source is YUV.
155 enc_hw_pix_fmt = AV_PIX_FMT_YUV422P10LE;
156 break;
157 }
158 default: // all others
159 {
160 // At this moment the output format must be AV_PIX_FMT_YUV420P;
161 enc_hw_pix_fmt = AV_PIX_FMT_YUV420P;
162 break;
163 }
164 }
165 }
166 }
167
168 output_codec_ctx->pix_fmt = enc_hw_pix_fmt;
169 output_codec_ctx->gop_size = 12; // emit one intra frame every twelve frames at most
170}
171
172int FFmpeg_Base::dict_set_with_check(AVDictionary **pm, const char *key, const char *value, int flags, const char * filename, bool nodelete) const
173{
174 if (nodelete && !*value)
175 {
176 return 0;
177 }
178
179 int ret = av_dict_set(pm, key, value, flags);
180
181 if (ret < 0)
182 {
183 Logging::error(filename, "Error setting dictionary option key(%1)='%2' (error '%3').", key, value, ffmpeg_geterror(ret).c_str());
184 }
185
186 return ret;
187}
188
189int FFmpeg_Base::dict_set_with_check(AVDictionary **pm, const char *key, int64_t value, int flags, const char * filename, bool nodelete) const
190{
191 if (nodelete && !value)
192 {
193 return 0;
194 }
195
196 int ret = av_dict_set_int(pm, key, value, flags);
197
198 if (ret < 0)
199 {
200 Logging::error(filename, "Error setting dictionary option key(%1)='%2' (error '%3').", key, value, ffmpeg_geterror(ret).c_str());
201 }
202
203 return ret;
204}
205
206int FFmpeg_Base::opt_set_with_check(void *obj, const char *key, const char *value, int flags, const char * filename) const
207{
208 int ret = av_opt_set(obj, key, value, flags);
209
210 if (ret < 0)
211 {
212 Logging::error(filename, "Error setting dictionary option key(%1)='%2' (error '%3').", key, value, ffmpeg_geterror(ret).c_str());
213 }
214
215 return ret;
216}
217
218void FFmpeg_Base::video_info(bool out_file, const AVFormatContext *format_ctx, const AVStream *stream) const
219{
220 if (stream != nullptr && stream->codecpar != nullptr)
221 {
222 int64_t duration = AV_NOPTS_VALUE;
223
224 if (stream->duration != AV_NOPTS_VALUE)
225 {
226 duration = ffmpeg_rescale_q_rnd(stream->duration, stream->time_base);
227 }
228
229 Logging::debug(out_file ? virtname() : filename(), "Video %1 #%2: %3@%4 [%5]",
230 out_file ? "out" : "in",
231 stream->index,
232 get_codec_name(stream->codecpar->codec_id),
233 format_bitrate((stream->codecpar->bit_rate != 0) ? stream->codecpar->bit_rate : format_ctx->bit_rate).c_str(),
234 format_duration(duration).c_str());
235 }
236 else
237 {
238 Logging::debug(out_file ? virtname() : filename(), "Video %1: invalid stream",
239 out_file ? "out" : "in");
240 }
241}
242
243void FFmpeg_Base::audio_info(bool out_file, const AVFormatContext *format_ctx, const AVStream *stream) const
244{
245 if (stream != nullptr && stream->codecpar != nullptr)
246 {
247 int64_t duration = AV_NOPTS_VALUE;
248
249 if (stream->duration != AV_NOPTS_VALUE)
250 {
251 duration = ffmpeg_rescale_q_rnd(stream->duration, stream->time_base);
252 }
253
254 Logging::debug(out_file ? virtname() : filename(), "Audio %1 #2: %3@%4 %5 Channels %6 [%7]",
255 out_file ? "out" : "in",
256 stream->index,
257 get_codec_name(stream->codecpar->codec_id),
258 format_bitrate((stream->codecpar->bit_rate != 0) ? stream->codecpar->bit_rate : format_ctx->bit_rate).c_str(),
259 get_channels(stream->codecpar),
260 format_samplerate(stream->codecpar->sample_rate).c_str(),
261 format_duration(duration).c_str());
262 }
263 else
264 {
265 Logging::debug(out_file ? virtname() : filename(), "Audio %1: invalid stream",
266 out_file ? "out" : "in");
267 }
268}
269
270void FFmpeg_Base::subtitle_info(bool out_file, const AVFormatContext * /*format_ctx*/, const AVStream *stream) const
271{
272 if (stream != nullptr && stream->codecpar != nullptr)
273 {
274 Logging::debug(out_file ? virtname() : filename(), "Subtitle %1 #%2: %3",
275 out_file ? "out" : "in",
276 stream->index,
277 get_codec_name(stream->codecpar->codec_id));
278 }
279 else
280 {
281 Logging::debug(out_file ? virtname() : filename(), "Subtitle %1: invalid stream",
282 out_file ? "out" : "in");
283 }
284}
285
286std::string FFmpeg_Base::get_pix_fmt_name(enum AVPixelFormat pix_fmt)
287{
288 const char *fmt_name = av_get_pix_fmt_name(pix_fmt);
289 return (fmt_name != nullptr ? fmt_name : "none");
290}
291
292std::string FFmpeg_Base::get_sample_fmt_name(AVSampleFormat sample_fmt)
293{
294 return av_get_sample_fmt_name(sample_fmt);
295}
296
297#if LAVU_DEP_OLD_CHANNEL_LAYOUT
298std::string FFmpeg_Base::get_channel_layout_name(const AVChannelLayout * ch_layout)
299{
300 std::array<char, 1024> buffer;
301 av_channel_layout_describe(ch_layout, buffer.data(), buffer.size() - 1);
302 return buffer.data();
303}
304#else // !LAVU_DEP_OLD_CHANNEL_LAYOUT
305std::string FFmpeg_Base::get_channel_layout_name(int nb_channels, uint64_t channel_layout)
306{
307 std::array<char, 1024> buffer;
308 av_get_channel_layout_string(buffer.data(), buffer.size() - 1, nb_channels, channel_layout);
309 return buffer.data();
310}
311#endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT
312
313uint32_t FFmpeg_Base::pts_to_frame(AVStream* stream, int64_t pts) const
314{
315 if (pts == AV_NOPTS_VALUE)
316 {
317 return 0;
318 }
319 int64_t start_time = (stream->start_time != AV_NOPTS_VALUE) ? stream->start_time : 0;
320 AVRational factor = av_mul_q(stream->avg_frame_rate, stream->time_base);
321 return static_cast<uint32_t>(av_rescale(pts - start_time, factor.num, factor.den) + 1);
322}
323
324int64_t FFmpeg_Base::frame_to_pts(AVStream* stream, uint32_t frame_no) const
325{
326 int64_t start_time = (stream->start_time != AV_NOPTS_VALUE) ? stream->start_time : 0;
327 AVRational factor = av_mul_q(stream->avg_frame_rate, stream->time_base);
328 return static_cast<uint32_t>(av_rescale(frame_no - 1, factor.den, factor.num) + start_time);
329}
330
331int FFmpeg_Base::get_channels(const AVCodecParameters *codecpar) const
332{
333#if LAVU_DEP_OLD_CHANNEL_LAYOUT
334 return codecpar->ch_layout.nb_channels;
335#else // !LAVU_DEP_OLD_CHANNEL_LAYOUT
336 return codecpar->channels;
337#endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT
338}
339
340void FFmpeg_Base::set_channels(AVCodecParameters *codecpar_out, const AVCodecParameters *codecpar_in) const
341{
342#if LAVU_DEP_OLD_CHANNEL_LAYOUT
343 codecpar_out->ch_layout.nb_channels = codecpar_in->ch_layout.nb_channels;
344#else // !LAVU_DEP_OLD_CHANNEL_LAYOUT
345 codecpar_out->channels = codecpar_in->channels;
346#endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT
347}
348
349int FFmpeg_Base::get_channels(const AVCodecContext *codec_ctx) const
350{
351#if LAVU_DEP_OLD_CHANNEL_LAYOUT
352 return codec_ctx->ch_layout.nb_channels;
353#else // !LAVU_DEP_OLD_CHANNEL_LAYOUT
354 return codec_ctx->channels;
355#endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT
356}
357
358void FFmpeg_Base::set_channels(AVCodecContext *codec_ctx_out, const AVCodecContext *codec_ctx_in) const
359{
360#if LAVU_DEP_OLD_CHANNEL_LAYOUT
361 codec_ctx_out->ch_layout.nb_channels= codec_ctx_in->ch_layout.nb_channels;
362#else // !LAVU_DEP_OLD_CHANNEL_LAYOUT
363 codec_ctx_out->channels = codec_ctx_in->channels;
364#endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT
365}
366
367void FFmpeg_Base::set_channels(AVCodecContext *codec_ctx_out, int channels) const
368{
369#if LAVU_DEP_OLD_CHANNEL_LAYOUT
370 codec_ctx_out->ch_layout.nb_channels = channels;
371#else // !LAVU_DEP_OLD_CHANNEL_LAYOUT
372 codec_ctx_out->channels = channels;
373#endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT
374}
375
376// See...
377//
378// https://en.wikipedia.org/wiki/SubStation_Alpha
379// https://datatracker.ietf.org/doc/html/draft-ietf-cellar-codec-02
380// https://fileformats.fandom.com/wiki/SubStation_Alpha
381
382int 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
383{
384 const char *format =
385 "[Script Info]\r\n" //
386 "; Script generated by ffmpegfs " FFMPEFS_VERSION "\r\n" //
387 "; https://github.com/nschlia/ffmpegfs\r\n" //
388 "ScriptType: v4.00+\r\n" //
389 "PlayResX: %d\r\n" //
390 "PlayResY: %d\r\n" //
391 "ScaledBorderAndShadow: yes\r\n" //
392
393 // Some other tags...
394 //"Title: NAME (Language)\r\n" //
395 //"Original Script: ???\r\n" //
396 //"Script Updated By: version 2.8.01\r\n" //
397 //"Collisions: Normal\r\n" //
398 //"PlayDepth: 0\r\n" //
399 //"Timer: 100,0000\r\n" //
400 //"Video Aspect Ratio: 0\r\n" //
401 //"Video Zoom: 6\r\n" //
402 //"Video Position: 0\r\n" //
403
404 "\r\n" //
405 "[V4+ Styles]\r\n" //
406
407 "Format: " //
408 "Name, " //
409 "Fontname, Fontsize, " //
410 "PrimaryColour, SecondaryColour, OutlineColour, BackColour, " //
411 "Bold, Italic, Underline, StrikeOut, " //
412 "ScaleX, ScaleY, " //
413 "Spacing, Angle, " //
414 "BorderStyle, Outline, Shadow, " //
415 "Alignment, MarginL, MarginR, MarginV, " //
416 "Encoding\r\n" //
417
418 "Style: " //
419 "Default," // Name
420 "%s,%d," // Font{name,size}
421 "&H%x,&H%x,&H%x,&H%x," // {Primary,Secondary,Outline,Back}Colour
422 "%d,%d,%d,0," // Bold, Italic, Underline, StrikeOut
423 "100,100," // Scale{X,Y}
424 "0,0," // Spacing, Angle
425 "%d,1,0," // BorderStyle, Outline, Shadow
426 "%d,10,10,10," // Alignment, Margin[LRV]
427 "0\r\n" // Encoding
428 "\r\n" //
429
430 "[Events]\r\n" //
431 "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n";
432
433 size_t size = static_cast<size_t>(snprintf(nullptr, 0, format, play_res_x, play_res_y, font, font_size,
434 primary_color, secondary_color, outline_color, back_color,
435 -bold, -italic, -underline, border_style, alignment)) + 1; // Extra space for '\0'
436
437 codec_ctx->subtitle_header = reinterpret_cast<uint8_t *>(av_malloc(size + 1));
438
439 if (codec_ctx->subtitle_header == nullptr)
440 {
441 return AVERROR(ENOMEM);
442 }
443
444 snprintf(reinterpret_cast<char *>(codec_ctx->subtitle_header), size, format,
445 play_res_x, play_res_y, font, font_size,
446 primary_color, secondary_color, outline_color, back_color,
447 -bold, -italic, -underline, border_style, alignment);
448
449 codec_ctx->subtitle_header_size = static_cast<int>(size);
450
451 return 0;
452}
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.
Definition: ffmpeg_base.cc:292
int get_channels(const AVCodecParameters *codecpar) const
Get the number of channels from AVCodecParameters.
Definition: ffmpeg_base.cc:331
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.
Definition: ffmpeg_base.cc:206
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...
Definition: ffmpeg_base.cc:382
void audio_info(bool out_file, const AVFormatContext *format_ctx, const AVStream *stream) const
Print data from the audio stream to log.
Definition: ffmpeg_base.cc:243
int64_t frame_to_pts(AVStream *stream, uint32_t frame_no) const
Convert frame number to PTS value.
Definition: ffmpeg_base.cc:324
uint32_t pts_to_frame(AVStream *stream, int64_t pts) const
Convert PTS value to frame number.
Definition: ffmpeg_base.cc:313
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.
Definition: ffmpeg_base.cc:218
void subtitle_info(bool out_file, const AVFormatContext *format_ctx, const AVStream *stream) const
Print data from the subtitle stream to log.
Definition: ffmpeg_base.cc:270
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.
Definition: ffmpeg_base.cc:68
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.
Definition: ffmpeg_base.cc:298
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.
Definition: ffmpeg_base.cc:286
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.
Definition: ffmpeg_base.cc:172
void set_channels(AVCodecParameters *codecpar_out, const AVCodecParameters *codecpar_in) const
Set the number of channels from AVCodecParameters.
Definition: ffmpeg_base.cc:340
FFmpeg_Base()
Construct FFmpeg_Base object.
Definition: ffmpeg_base.cc:53
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.
Definition: ffmpeg_utils.h:42
Provide various log facilities to stderr, disk or syslog.