mirror of
https://github.com/davisking/dlib.git
synced 2024-11-01 10:14:53 +08:00
[FFmpeg] added save_frame() and improved picking of codecs when not specified (#2800)
* WIP * - pick best codec - added save_frame() - fixed example * Add missing variable declaration * added extensions fixed tests * attempt to set quality as high as possible. lower expected PSNR * arrufat suggestion * refactoring --------- Co-authored-by: Your name <you@example.com> Co-authored-by: pf <pf@me> Co-authored-by: Adrià Arrufat <1671644+arrufat@users.noreply.github.com>
This commit is contained in:
parent
b86871241b
commit
eccb9675ff
@ -1627,7 +1627,7 @@ namespace dlib
|
|||||||
inline void load_frame(image_type& image, const std::string& file_name)
|
inline void load_frame(image_type& image, const std::string& file_name)
|
||||||
{
|
{
|
||||||
if (!demuxer({file_name, video_enabled, audio_disabled}).read(image))
|
if (!demuxer({file_name, video_enabled, audio_disabled}).read(image))
|
||||||
throw error("ffmpeg::load_frame: error while loading " + file_name);
|
throw error(EIMAGE_LOAD, "ffmpeg::load_frame: error while loading " + file_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------
|
||||||
|
@ -480,6 +480,47 @@ namespace dlib { namespace ffmpeg { namespace details
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline AVCodecID pick_codec_from_filename(const std::string& filename)
|
||||||
|
{
|
||||||
|
const auto ext_pos = filename.find_last_of(".");
|
||||||
|
|
||||||
|
if (ext_pos != std::string::npos)
|
||||||
|
{
|
||||||
|
const std::string ext = filename.substr(ext_pos + 1);
|
||||||
|
|
||||||
|
if (ext == "png" || ext == "PNG")
|
||||||
|
return AV_CODEC_ID_PNG;
|
||||||
|
else if (ext == "jpeg" || ext == "jpg" || ext == "JPEG")
|
||||||
|
return AV_CODEC_ID_MJPEG;
|
||||||
|
else if (ext == "tiff")
|
||||||
|
return AV_CODEC_ID_TIFF;
|
||||||
|
else if (ext == "webp")
|
||||||
|
return AV_CODEC_ID_WEBP;
|
||||||
|
else if (ext == "bmp")
|
||||||
|
return AV_CODEC_ID_BMP;
|
||||||
|
else if (ext == "h264")
|
||||||
|
return AV_CODEC_ID_H264;
|
||||||
|
else if (ext == "h265" || ext == "hevc")
|
||||||
|
return AV_CODEC_ID_H265;
|
||||||
|
else if (ext == "aac")
|
||||||
|
return AV_CODEC_ID_AAC;
|
||||||
|
else if (ext == "ac3")
|
||||||
|
return AV_CODEC_ID_AC3;
|
||||||
|
else if (ext == "jls")
|
||||||
|
return AV_CODEC_ID_JPEGLS;
|
||||||
|
else if (ext == "jp2")
|
||||||
|
return AV_CODEC_ID_JPEG2000;
|
||||||
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 37, 100)
|
||||||
|
else if (ext == "jxl")
|
||||||
|
return AV_CODEC_ID_JPEGXL;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return AV_CODEC_ID_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
}}}
|
}}}
|
||||||
|
@ -634,6 +634,24 @@ namespace dlib
|
|||||||
} st;
|
} st;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
template <
|
||||||
|
class image_type,
|
||||||
|
is_image_check<image_type> = true
|
||||||
|
>
|
||||||
|
void save_frame(
|
||||||
|
const image_type& image,
|
||||||
|
const std::string& file_name,
|
||||||
|
const std::unordered_map<std::string, std::string>& codec_options = {}
|
||||||
|
);
|
||||||
|
/*!
|
||||||
|
requires
|
||||||
|
- image_type must be a type conforming to the generic image interface.
|
||||||
|
ensures
|
||||||
|
- encodes the image into the file pointed by file_name using options described in codec_options.
|
||||||
|
!*/
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -822,10 +840,64 @@ namespace dlib
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline bool check_codecs (
|
||||||
|
const bool is_video,
|
||||||
|
const std::string& filename,
|
||||||
|
const AVOutputFormat* oformat,
|
||||||
|
encoder::args& args
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// Check the codec is supported by this muxer
|
||||||
|
const auto supported_codecs = list_codecs_for_muxer(oformat);
|
||||||
|
|
||||||
|
const auto codec_supported = [&](AVCodecID id, const std::string& name)
|
||||||
|
{
|
||||||
|
return std::find_if(begin(supported_codecs), end(supported_codecs), [&](const auto& supported) {
|
||||||
|
return id != AV_CODEC_ID_NONE ? id == supported.codec_id : name == supported.codec_name;
|
||||||
|
}) != end(supported_codecs);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (codec_supported(args.args_codec.codec, args.args_codec.codec_name))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
logger_dlib_wrapper() << LWARN
|
||||||
|
<< "Codec " << avcodec_get_name(args.args_codec.codec) << " or " << args.args_codec.codec_name
|
||||||
|
<< " cannot be stored in this file";
|
||||||
|
|
||||||
|
// Pick codec based on file extension
|
||||||
|
args.args_codec.codec = pick_codec_from_filename(filename);
|
||||||
|
|
||||||
|
if (codec_supported(args.args_codec.codec, ""))
|
||||||
|
{
|
||||||
|
logger_dlib_wrapper() << LWARN << "Picking codec " << avcodec_get_name(args.args_codec.codec);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick the default codec as suggested by FFmpeg
|
||||||
|
args.args_codec.codec = is_video ? oformat->video_codec : oformat->audio_codec;
|
||||||
|
|
||||||
|
if (args.args_codec.codec != AV_CODEC_ID_NONE)
|
||||||
|
{
|
||||||
|
logger_dlib_wrapper() << LWARN << "Picking default codec " << avcodec_get_name(args.args_codec.codec);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_dlib_wrapper() << LWARN
|
||||||
|
<< "List of supported codecs for muxer " << oformat->name << " in this installation of ffmpeg:";
|
||||||
|
|
||||||
|
for (const auto& supported : supported_codecs)
|
||||||
|
logger_dlib_wrapper() << LWARN << " " << supported.codec_name;
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
inline encoder::encoder(
|
inline encoder::encoder(
|
||||||
const args& a
|
const args& a
|
||||||
) : args_(a)
|
) : args_(a)
|
||||||
@ -1188,37 +1260,8 @@ namespace dlib
|
|||||||
if (st.pFormatCtx->oformat->flags & AVFMT_GLOBALHEADER)
|
if (st.pFormatCtx->oformat->flags & AVFMT_GLOBALHEADER)
|
||||||
args.args_codec.flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
args.args_codec.flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||||
|
|
||||||
// Before we create the encoder, check the codec is supported by this muxer
|
if (!check_codecs(is_video, st.args_.filepath, st.pFormatCtx->oformat, args))
|
||||||
const auto supported_codecs = list_codecs_for_muxer(st.pFormatCtx->oformat);
|
|
||||||
|
|
||||||
if (std::find_if(begin(supported_codecs), end(supported_codecs), [&](const auto& supported) {
|
|
||||||
return args.args_codec.codec != AV_CODEC_ID_NONE ?
|
|
||||||
supported.codec_id == args.args_codec.codec :
|
|
||||||
supported.codec_name == args.args_codec.codec_name;
|
|
||||||
}) == end(supported_codecs))
|
|
||||||
{
|
|
||||||
logger_dlib_wrapper() << LWARN
|
|
||||||
<< "Codec " << avcodec_get_name(args.args_codec.codec) << " or " << args.args_codec.codec_name
|
|
||||||
<< " cannot be stored in this file";
|
|
||||||
|
|
||||||
args.args_codec.codec = is_video ? st.pFormatCtx->oformat->video_codec :
|
|
||||||
st.pFormatCtx->oformat->audio_codec;
|
|
||||||
|
|
||||||
if (args.args_codec.codec != AV_CODEC_ID_NONE)
|
|
||||||
{
|
|
||||||
logger_dlib_wrapper() << LWARN
|
|
||||||
<< "Picking default codec " << avcodec_get_name(args.args_codec.codec);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger_dlib_wrapper() << LWARN
|
|
||||||
<< "List of supported codecs for muxer " << st.pFormatCtx->oformat->name << " in this installation of ffmpeg:";
|
|
||||||
for (const auto& supported : supported_codecs)
|
|
||||||
logger_dlib_wrapper() << LWARN << " " << supported.codec_name;
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Codec is supported by muxer, so create encoder
|
// Codec is supported by muxer, so create encoder
|
||||||
enc = encoder(args);
|
enc = encoder(args);
|
||||||
@ -1381,6 +1424,36 @@ namespace dlib
|
|||||||
inline AVCodecID muxer::get_audio_codec_id() const noexcept { return st.encoder_audio.get_codec_id(); }
|
inline AVCodecID muxer::get_audio_codec_id() const noexcept { return st.encoder_audio.get_codec_id(); }
|
||||||
inline std::string muxer::get_audio_codec_name() const noexcept { return st.encoder_audio.get_codec_name(); }
|
inline std::string muxer::get_audio_codec_name() const noexcept { return st.encoder_audio.get_codec_name(); }
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
template <
|
||||||
|
class image_type,
|
||||||
|
is_image_check<image_type>
|
||||||
|
>
|
||||||
|
inline void save_frame(
|
||||||
|
const image_type& image,
|
||||||
|
const std::string& file_name,
|
||||||
|
const std::unordered_map<std::string, std::string>& codec_options
|
||||||
|
)
|
||||||
|
{
|
||||||
|
muxer writer([&] {
|
||||||
|
muxer::args args;
|
||||||
|
args.filepath = file_name;
|
||||||
|
args.enable_image = true;
|
||||||
|
args.enable_audio = false;
|
||||||
|
args.args_image.h = num_rows(image);
|
||||||
|
args.args_image.w = num_columns(image);
|
||||||
|
args.args_image.framerate = 1;
|
||||||
|
args.args_image.fmt = pix_traits<pixel_type_t<image_type>>::fmt;
|
||||||
|
args.args_image.codec_options = codec_options;
|
||||||
|
args.format_options["update"] = "1";
|
||||||
|
return args;
|
||||||
|
}());
|
||||||
|
|
||||||
|
if (!writer.push(image))
|
||||||
|
throw error(EIMAGE_SAVE, "ffmpeg::save_frame: error while saving " + file_name);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -146,6 +146,21 @@ namespace
|
|||||||
DLIB_TEST_MSG(similarity > 25.0, "psnr " << similarity);
|
DLIB_TEST_MSG(similarity > 25.0, "psnr " << similarity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class pixel_type>
|
||||||
|
void test_load_save_frame(const std::string& filename)
|
||||||
|
{
|
||||||
|
matrix<pixel_type> img1, img2;
|
||||||
|
img1 = get_random_image<pixel_type>();
|
||||||
|
|
||||||
|
save_frame(img1, filename, {{"qmin", "1"}, {"qmax", "1"}});
|
||||||
|
load_frame(img2, filename);
|
||||||
|
|
||||||
|
DLIB_TEST(img1.nr() == img2.nr());
|
||||||
|
DLIB_TEST(img1.nc() == img2.nc());
|
||||||
|
const double similarity = psnr(img1, img2);
|
||||||
|
DLIB_TEST_MSG(similarity > 20.0, "psnr " << similarity);
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// DECODER
|
// DECODER
|
||||||
@ -784,6 +799,13 @@ namespace
|
|||||||
DLIB_TEST(samples <= (nsamples + rate));
|
DLIB_TEST(samples <= (nsamples + rate));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto codec_supported = [](const AVCodecID id)
|
||||||
|
{
|
||||||
|
return std::find_if(begin(list_codecs()), end(list_codecs()), [=](const auto& supported) {
|
||||||
|
return supported.codec_id == id && supported.supports_encoding;
|
||||||
|
}) != end(list_codecs());
|
||||||
|
};
|
||||||
|
|
||||||
class video_tester : public tester
|
class video_tester : public tester
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -802,6 +824,30 @@ namespace
|
|||||||
test_frame<bgr_pixel>();
|
test_frame<bgr_pixel>();
|
||||||
test_frame<rgb_alpha_pixel>();
|
test_frame<rgb_alpha_pixel>();
|
||||||
test_frame<bgr_alpha_pixel>();
|
test_frame<bgr_alpha_pixel>();
|
||||||
|
|
||||||
|
if (codec_supported(AV_CODEC_ID_PNG))
|
||||||
|
{
|
||||||
|
test_load_save_frame<rgb_pixel>("dummy.png");
|
||||||
|
test_load_save_frame<bgr_pixel>("dummy.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codec_supported(AV_CODEC_ID_MJPEG))
|
||||||
|
{
|
||||||
|
test_load_save_frame<rgb_pixel>("dummy.jpg");
|
||||||
|
test_load_save_frame<bgr_pixel>("dummy.jpg");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codec_supported(AV_CODEC_ID_BMP))
|
||||||
|
{
|
||||||
|
test_load_save_frame<rgb_pixel>("dummy.bmp");
|
||||||
|
test_load_save_frame<bgr_pixel>("dummy.bmp");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codec_supported(AV_CODEC_ID_TIFF))
|
||||||
|
{
|
||||||
|
test_load_save_frame<rgb_pixel>("dummy.tiff");
|
||||||
|
test_load_save_frame<bgr_pixel>("dummy.tiff");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dlib::file f(DLIB_FFMPEG_DATA);
|
dlib::file f(DLIB_FFMPEG_DATA);
|
||||||
|
@ -40,7 +40,7 @@ try
|
|||||||
|
|
||||||
const std::string filename = get_option(parser, "i", "");
|
const std::string filename = get_option(parser, "i", "");
|
||||||
const std::string device = get_option(parser, "o", "hw:0,0");
|
const std::string device = get_option(parser, "o", "hw:0,0");
|
||||||
const std::string codec = get_option(parser, "codec", "pcm_s16le");
|
const std::string codec = get_option(parser, "codec", "");
|
||||||
|
|
||||||
if (device.empty())
|
if (device.empty())
|
||||||
{
|
{
|
||||||
@ -65,7 +65,7 @@ try
|
|||||||
args.enable_image = false;
|
args.enable_image = false;
|
||||||
args.args_audio.codec_name = codec;
|
args.args_audio.codec_name = codec;
|
||||||
args.args_audio.sample_rate = 44100;
|
args.args_audio.sample_rate = 44100;
|
||||||
args.args_audio.channel_layout = cap.channel_layout();
|
args.args_audio.channel_layout = AV_CH_LAYOUT_STEREO;
|
||||||
args.args_audio.fmt = cap.sample_fmt();
|
args.args_audio.fmt = cap.sample_fmt();
|
||||||
return args;
|
return args;
|
||||||
}());
|
}());
|
||||||
|
Loading…
Reference in New Issue
Block a user