From 53c4be90269b607a225e434b6a703b1aa3ab4e94 Mon Sep 17 00:00:00 2001 From: pfeatherstone <45853521+pfeatherstone@users.noreply.github.com> Date: Wed, 19 Apr 2023 13:32:19 +0100 Subject: [PATCH] FFmpeg : logging (#2774) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added a couple more audio examples * logging DONE * bug fix on certain compilers * use a C-style stack allocated array. This reduces heap allocations * reduce stack string size. replace '\n' rather than remove * - register_ffmpeg() doesn't need to return anything - refactored loggers - added verbose option * bug fix * unused variable warning * Update docs * Update docs --------- Co-authored-by: pf Co-authored-by: AdriĆ  Arrufat <1671644+arrufat@users.noreply.github.com> --- dlib/media/ffmpeg_abstract.h | 16 +++ dlib/media/ffmpeg_demuxer.h | 65 +++++------- dlib/media/ffmpeg_muxer.h | 90 +++++++--------- dlib/media/ffmpeg_utils.h | 147 +++++++++++++++++--------- examples/ffmpeg_video_demuxing_ex.cpp | 20 +++- 5 files changed, 200 insertions(+), 138 deletions(-) diff --git a/dlib/media/ffmpeg_abstract.h b/dlib/media/ffmpeg_abstract.h index de7873f64..5f88f02ba 100644 --- a/dlib/media/ffmpeg_abstract.h +++ b/dlib/media/ffmpeg_abstract.h @@ -29,6 +29,22 @@ namespace dlib - Returns a string description of a channel layout, where layout is e.g. AV_CH_LAYOUT_STEREO !*/ +// --------------------------------------------------------------------------------------------------- + + dlib::logger& logger_ffmpeg(); + /*! + ensures + - Returns a global logger used by the internal ffmpeg libraries. + - You may set the logging level using .set_level() to supress or enable certain logs. + !*/ + + dlib::logger& logger_dlib_wrapper(); + /*! + ensures + - Returns a global logger used by dlib's ffmpeg wrappers. + - You may set the logging level using .set_level() to supress or enable certain logs. + !*/ + // --------------------------------------------------------------------------------------------------- class frame diff --git a/dlib/media/ffmpeg_demuxer.h b/dlib/media/ffmpeg_demuxer.h index ea6796b66..76618bc09 100644 --- a/dlib/media/ffmpeg_demuxer.h +++ b/dlib/media/ffmpeg_demuxer.h @@ -75,8 +75,7 @@ namespace dlib decoder_extractor( const args& a, av_ptr pCodecCtx_, - const AVCodec* codec, - std::shared_ptr log_ + const AVCodec* codec ); bool is_open() const noexcept; @@ -108,7 +107,6 @@ namespace dlib resizer resizer_image; resampler resizer_audio; std::queue frame_queue; - std::shared_ptr log; }; } @@ -154,7 +152,6 @@ namespace dlib details::av_ptr parser; details::av_ptr packet; details::decoder_extractor extractor; - std::shared_ptr log; }; // --------------------------------------------------------------------------------------------------- @@ -236,7 +233,6 @@ namespace dlib int stream_id_video{-1}; int stream_id_audio{-1}; std::queue frame_queue; - std::shared_ptr log; } st; }; @@ -262,9 +258,8 @@ namespace dlib inline decoder_extractor::decoder_extractor( const args& a, av_ptr pCodecCtx_, - const AVCodec* codec, - std::shared_ptr log_ - ) : log(log_) + const AVCodec* codec + ) { args_ = a; avframe = make_avframe(); @@ -279,7 +274,7 @@ namespace dlib if (ret < 0) { - (*log) << LERROR << "avcodec_open2() failed : " << get_av_error(ret).c_str(); + logger_dlib_wrapper() << LERROR << "avcodec_open2() failed : " << get_av_error(ret).c_str(); return; } @@ -358,7 +353,7 @@ namespace dlib } else { pCodecCtx = nullptr; state = EXTRACT_ERROR; - (*log) << LERROR << "avcodec_send_packet() failed : " << get_av_error(ret); + logger_dlib_wrapper() << LERROR << "avcodec_send_packet() failed : " << get_av_error(ret); } }; @@ -378,7 +373,7 @@ namespace dlib { pCodecCtx = nullptr; state = EXTRACT_ERROR; - (*log) << LERROR << "avcodec_receive_frame() failed : " << get_av_error(ret); + logger_dlib_wrapper() << LERROR << "avcodec_receive_frame() failed : " << get_av_error(ret); } else { @@ -454,24 +449,23 @@ namespace dlib // --------------------------------------------------------------------------------------------------- inline decoder::decoder(const args &a) - : log(std::make_shared("ffmpeg::decoder")) { using namespace details; DLIB_ASSERT(a.args_codec.codec != AV_CODEC_ID_NONE || a.args_codec.codec_name != "", "At least args_codec.codec or args_codec.codec_name must be set"); - const bool init = details::register_ffmpeg::get(); + details::register_ffmpeg(); const AVCodec* pCodec = nullptr; if (a.args_codec.codec != AV_CODEC_ID_NONE) - pCodec = init ? avcodec_find_decoder(a.args_codec.codec) : nullptr; + pCodec = avcodec_find_decoder(a.args_codec.codec); else if (!a.args_codec.codec_name.empty()) - pCodec = init ? avcodec_find_decoder_by_name(a.args_codec.codec_name.c_str()) : nullptr; + pCodec = avcodec_find_decoder_by_name(a.args_codec.codec_name.c_str()); if (!pCodec) { - (*log) << LERROR + logger_dlib_wrapper() << LERROR << "Codec " << avcodec_get_name(a.args_codec.codec) << " / " @@ -484,14 +478,14 @@ namespace dlib if (!pCodecCtx) { - (*log) << LERROR << "avcodec_alloc_context3() failed to allocate codec context for " << pCodec->name; + logger_dlib_wrapper() << LERROR << "avcodec_alloc_context3() failed to allocate codec context for " << pCodec->name; return; } if (pCodecCtx->codec_id == AV_CODEC_ID_AAC) pCodecCtx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; - extractor = decoder_extractor{{a.args_codec, a.args_image, a.args_audio, pCodecCtx->time_base}, std::move(pCodecCtx), pCodec, log}; + extractor = decoder_extractor{{a.args_codec, a.args_image, a.args_audio, pCodecCtx->time_base}, std::move(pCodecCtx), pCodec}; if (!extractor.is_open()) return; @@ -504,7 +498,7 @@ namespace dlib parser.reset(av_parser_init(pCodec->id)); if (!parser) { - (*log) << LERROR << "av_parser_init() failed codec " << pCodec->name << " not found"; + logger_dlib_wrapper() << LERROR << "av_parser_init() failed codec " << pCodec->name << " not found"; return; } } @@ -550,7 +544,7 @@ namespace dlib ); if (ret < 0) - return fail(*log, "AV : error while parsing encoded buffer"); + return fail("AV : error while parsing encoded buffer"); encoded += ret; nencoded -= ret; @@ -676,10 +670,9 @@ namespace dlib using namespace std::chrono; using namespace details; - const bool init = details::register_ffmpeg::get(); + details::register_ffmpeg(); st = {}; - st.log = std::make_shared("ffmpeg::demuxer"); st.args_ = a; AVFormatContext* pFormatCtx = avformat_alloc_context(); @@ -730,7 +723,7 @@ namespace dlib opts.get()); if (ret != 0) - return fail(*st.log, "avformat_open_input() failed with error : ", get_av_error(ret)); + return fail("avformat_open_input() failed with error : ", get_av_error(ret)); if (opts.size() > 0) { @@ -744,7 +737,7 @@ namespace dlib ret = avformat_find_stream_info(st.pFormatCtx.get(), NULL); if (ret < 0) - return fail(*st.log, "avformat_find_stream_info() failed with error : ", get_av_error(ret)); + return fail("avformat_find_stream_info() failed with error : ", get_av_error(ret)); const auto setup_stream = [&](bool is_video) { @@ -757,26 +750,26 @@ namespace dlib return true; //You might be asking for both video and audio but only video is available. That's OK. Just provide video. else if (stream_id == AVERROR_DECODER_NOT_FOUND) - return fail(*st.log, "av_find_best_stream() : decoder not found for stream type : ", av_get_media_type_string(media_type)); + return fail("av_find_best_stream() : decoder not found for stream type : ", av_get_media_type_string(media_type)); else if (stream_id < 0) - return fail(*st.log, "av_find_best_stream() failed : ", get_av_error(stream_id)); + return fail("av_find_best_stream() failed : ", get_av_error(stream_id)); av_ptr pCodecCtx{avcodec_alloc_context3(pCodec)}; if (!pCodecCtx) - return fail(*st.log, "avcodec_alloc_context3() failed to allocate codec context for ", pCodec->name); + return fail("avcodec_alloc_context3() failed to allocate codec context for ", pCodec->name); const int ret = avcodec_parameters_to_context(pCodecCtx.get(), st.pFormatCtx->streams[stream_id]->codecpar); if (ret < 0) - return fail(*st.log, "avcodec_parameters_to_context() failed : ", get_av_error(ret)); + return fail("avcodec_parameters_to_context() failed : ", get_av_error(ret)); if (pCodecCtx->codec_type == AVMEDIA_TYPE_VIDEO) { if (pCodecCtx->height == 0 || pCodecCtx->width == 0 || pCodecCtx->pix_fmt == AV_PIX_FMT_NONE) - return fail(*st.log, "Codec parameters look wrong : (h,w,pixel_fmt) : (", + return fail("Codec parameters look wrong : (h,w,pixel_fmt) : (", pCodecCtx->height, ",", pCodecCtx->width, ",", get_pixel_fmt_str(pCodecCtx->pix_fmt), ")"); @@ -788,13 +781,13 @@ namespace dlib if (pCodecCtx->sample_rate == 0 || pCodecCtx->sample_fmt == AV_SAMPLE_FMT_NONE || channel_layout_empty(pCodecCtx.get())) - return fail(*st.log,"Codec parameters look wrong :", + return fail("Codec parameters look wrong :", " sample_rate : ", pCodecCtx->sample_rate, " sample format : ", get_audio_fmt_str(pCodecCtx->sample_fmt), " channel layout : ", get_channel_layout_str(pCodecCtx.get())); } else - return fail(*st.log,"Unrecognized media type ", pCodecCtx->codec_type); + return fail("Unrecognized media type ", pCodecCtx->codec_type); if (is_video) { @@ -804,7 +797,7 @@ namespace dlib args.args_image = st.args_.args_image; args.time_base = st.pFormatCtx->streams[stream_id]->time_base; return args; - }(), std::move(pCodecCtx), pCodec, st.log}; + }(), std::move(pCodecCtx), pCodec}; st.stream_id_video = stream_id; } @@ -816,7 +809,7 @@ namespace dlib args.args_audio = st.args_.args_audio; args.time_base = st.pFormatCtx->streams[stream_id]->time_base; return args; - }(), std::move(pCodecCtx), pCodec, st.log}; + }(), std::move(pCodecCtx), pCodec}; st.stream_id_audio = stream_id; } @@ -831,12 +824,12 @@ namespace dlib return false; if (!st.channel_audio.is_open() && !st.channel_video.is_open()) - return fail(*st.log, "At least one of video and audio channels must be enabled"); + return fail("At least one of video and audio channels must be enabled"); populate_metadata(); st.packet = make_avpacket(); - return init; + return true; } inline bool demuxer::object_alive() const noexcept @@ -903,7 +896,7 @@ namespace dlib return false; else if (ret < 0) - return fail(*st.log, "av_read_frame() failed : ", get_av_error(ret)); + return fail("av_read_frame() failed : ", get_av_error(ret)); if (st.packet->stream_index == st.stream_id_video) channel = &st.channel_video; diff --git a/dlib/media/ffmpeg_muxer.h b/dlib/media/ffmpeg_muxer.h index 493f054fa..b404d2bfe 100644 --- a/dlib/media/ffmpeg_muxer.h +++ b/dlib/media/ffmpeg_muxer.h @@ -91,8 +91,7 @@ namespace dlib encoder( const args& a, - std::function sink, - std::shared_ptr log_ + std::function sink ); bool open(); @@ -106,7 +105,6 @@ namespace dlib details::resampler resizer_audio; details::audio_fifo fifo; std::function sink; - std::shared_ptr log; }; // --------------------------------------------------------------------------------------------------- @@ -177,7 +175,6 @@ namespace dlib std::chrono::system_clock::time_point connecting_time{}; std::chrono::system_clock::time_point connected_time{}; std::chrono::system_clock::time_point last_read_time{}; - std::shared_ptr log; } st; }; @@ -207,8 +204,7 @@ namespace dlib inline void check_properties( const AVCodec* pCodec, - AVCodecContext* pCodecCtx, - logger& log + AVCodecContext* pCodecCtx ) { // Video properties @@ -227,7 +223,7 @@ namespace dlib if (!framerate_supported) { - log << LINFO + logger_dlib_wrapper() << LINFO << "Requested framerate " << pCodecCtx->framerate.num / pCodecCtx->framerate.den << " not supported. Changing to default " @@ -252,7 +248,7 @@ namespace dlib if (!pix_fmt_supported) { - log << LINFO + logger_dlib_wrapper() << LINFO << "Requested pixel format " << av_get_pix_fmt_name(pCodecCtx->pix_fmt) << " not supported. Changing to default " @@ -278,7 +274,7 @@ namespace dlib if (!sample_rate_supported) { - log << LINFO + logger_dlib_wrapper() << LINFO << "Requested sample rate " << pCodecCtx->sample_rate << " not supported. Changing to default " @@ -303,7 +299,7 @@ namespace dlib if (!sample_fmt_supported) { - log << LINFO + logger_dlib_wrapper() << LINFO << "Requested sample format " << av_get_sample_fmt_name(pCodecCtx->sample_fmt) << " not supported. Changing to default " @@ -329,7 +325,7 @@ namespace dlib if (!channel_layout_supported) { - log << LINFO + logger_dlib_wrapper() << LINFO << "Channel layout " << details::get_channel_layout_str(pCodecCtx) << " not supported. Changing to default " @@ -354,7 +350,7 @@ namespace dlib if (!channel_layout_supported) { - log << LINFO + logger_dlib_wrapper() << LINFO << "Channel layout " << details::get_channel_layout_str(pCodecCtx) << " not supported. Changing to default " @@ -372,17 +368,15 @@ namespace dlib std::function sink ) : encoder(a, [sink](AVCodecContext*, AVPacket* pkt) { return sink(pkt->size, (const char*)pkt->data); - }, std::make_shared("ffmpeg::encoder")) + }) { } inline encoder::encoder( const args& a, - std::function sink_, - std::shared_ptr log_ + std::function sink_ ) : args_(a), - sink(std::move(sink_)), - log(log_) + sink(std::move(sink_)) { if (!open()) pCodecCtx = nullptr; @@ -400,22 +394,22 @@ namespace dlib DLIB_CASSERT(sink != nullptr, "must provide an appriate sink callback"); - const bool init = details::register_ffmpeg::get(); // This must be used somewhere otherwise compiler might optimize it away. + details::register_ffmpeg(); packet = make_avpacket(); const AVCodec* pCodec = nullptr; if (args_.args_codec.codec != AV_CODEC_ID_NONE) - pCodec = init ? avcodec_find_encoder(args_.args_codec.codec) : nullptr; + pCodec = avcodec_find_encoder(args_.args_codec.codec); else if (!args_.args_codec.codec_name.empty()) - pCodec = init ? avcodec_find_encoder_by_name(args_.args_codec.codec_name.c_str()) : nullptr; + pCodec = avcodec_find_encoder_by_name(args_.args_codec.codec_name.c_str()); if (!pCodec) - return fail(*log, "Codec ", avcodec_get_name(args_.args_codec.codec), " or ", args_.args_codec.codec_name, " not found"); + return fail("Codec ", avcodec_get_name(args_.args_codec.codec), " or ", args_.args_codec.codec_name, " not found"); pCodecCtx.reset(avcodec_alloc_context3(pCodec)); if (!pCodecCtx) - return fail(*log, "AV : failed to allocate codec context for ", pCodec->name, " : likely ran out of memory"); + return fail("AV : failed to allocate codec context for ", pCodec->name, " : likely ran out of memory"); if (args_.args_codec.bitrate > 0) pCodecCtx->bit_rate = args_.args_codec.bitrate; @@ -431,14 +425,14 @@ namespace dlib args_.args_image.fmt == AV_PIX_FMT_NONE || args_.args_image.framerate <= 0) { - return fail(*log, pCodec->name, " is an image codec. height, width, fmt (pixel format) and framerate must be set"); + return fail(pCodec->name, " is an image codec. height, width, fmt (pixel format) and framerate must be set"); } pCodecCtx->height = args_.args_image.h; pCodecCtx->width = args_.args_image.w; pCodecCtx->pix_fmt = args_.args_image.fmt; pCodecCtx->framerate = AVRational{args_.args_image.framerate, 1}; - check_properties(pCodec, pCodecCtx.get(), *log); + check_properties(pCodec, pCodecCtx.get()); pCodecCtx->time_base = inv(pCodecCtx->framerate); //don't know what src options are, but at least dst options are set @@ -451,13 +445,13 @@ namespace dlib args_.args_audio.channel_layout <= 0 || args_.args_audio.fmt == AV_SAMPLE_FMT_NONE) { - return fail(*log, pCodec->name, " is an audio codec. sample_rate, channel_layout and fmt (sample format) must be set"); + return fail(pCodec->name, " is an audio codec. sample_rate, channel_layout and fmt (sample format) must be set"); } pCodecCtx->sample_rate = args_.args_audio.sample_rate; pCodecCtx->sample_fmt = args_.args_audio.fmt; set_layout(pCodecCtx.get(), args_.args_audio.channel_layout); - check_properties(pCodec, pCodecCtx.get(), *log); + check_properties(pCodec, pCodecCtx.get()); pCodecCtx->time_base = AVRational{ 1, pCodecCtx->sample_rate }; if (pCodecCtx->codec_id == AV_CODEC_ID_AAC) { @@ -474,7 +468,7 @@ namespace dlib av_dict opt = args_.args_codec.codec_options; const int ret = avcodec_open2(pCodecCtx.get(), pCodec, opt.get()); if (ret < 0) - return fail(*log, "avcodec_open2() failed : ", get_av_error(ret)); + return fail("avcodec_open2() failed : ", get_av_error(ret)); if (pCodec->type == AVMEDIA_TYPE_AUDIO) { @@ -561,7 +555,7 @@ namespace dlib } else { open_ = false; state = ENCODE_ERROR; - (*log) << LERROR << "avcodec_send_frame() failed : " << get_av_error(ret); + logger_dlib_wrapper() << LERROR << "avcodec_send_frame() failed : " << get_av_error(ret); } }; @@ -581,7 +575,7 @@ namespace dlib { open_ = false; state = ENCODE_ERROR; - (*log) << LERROR << "avcodec_receive_packet() failed : " << get_av_error(ret); + logger_dlib_wrapper() << LERROR << "avcodec_receive_packet() failed : " << get_av_error(ret); } else { @@ -656,11 +650,10 @@ namespace dlib using namespace details; st = {}; - st.log = std::make_shared("ffmpeg::muxer"); st.args_ = a; if (!st.args_.enable_audio && !st.args_.enable_image) - return fail(*st.log, "You need to set at least one of `enable_audio` or `enable_image`"); + return fail("You need to set at least one of `enable_audio` or `enable_image`"); { st.connecting_time = system_clock::now(); @@ -672,7 +665,7 @@ namespace dlib int ret = avformat_alloc_output_context2(&pFormatCtx, nullptr, format_name, filename); if (ret < 0) - return fail(*st.log, "avformat_alloc_output_context2() failed : ", get_av_error(ret)); + return fail("avformat_alloc_output_context2() failed : ", get_av_error(ret)); st.pFormatCtx.reset(pFormatCtx); } @@ -703,8 +696,7 @@ namespace dlib const auto handle_packet = [ pFormatCtx = st.pFormatCtx.get(), - stream_id = stream_counter, - log = st.log + stream_id = stream_counter ] ( AVCodecContext* pCodecCtx, @@ -716,7 +708,7 @@ namespace dlib pkt->stream_index = stream_id; int ret = av_interleaved_write_frame(pFormatCtx, pkt); if (ret < 0) - (*log) << LERROR << "av_interleaved_write_frame() failed : " << get_av_error(ret); + logger_dlib_wrapper() << LERROR << "av_interleaved_write_frame() failed : " << get_av_error(ret); return ret == 0; }; @@ -729,18 +721,18 @@ namespace dlib supported.codec_name == args.args_codec.codec_name; }) == end(supported_codecs)) { - (*st.log) << LERROR - << "Codec " << avcodec_get_name(args.args_codec.codec) << " or " << args.args_codec.codec_name - << " cannot be stored in this file"; - (*st.log) << LINFO - << "List of supported codecs for muxer " << st.pFormatCtx->oformat->name << " in this installation of ffmpeg:"; + logger_dlib_wrapper() << LERROR + << "Codec " << avcodec_get_name(args.args_codec.codec) << " or " << args.args_codec.codec_name + << " cannot be stored in this file"; + logger_dlib_wrapper() << LINFO + << "List of supported codecs for muxer " << st.pFormatCtx->oformat->name << " in this installation of ffmpeg:"; for (const auto& supported : supported_codecs) - (*st.log) << LINFO << " " << supported.codec_name; + logger_dlib_wrapper() << LINFO << " " << supported.codec_name; return false; } // Codec is supported by muxer, so create encoder - enc = encoder(args, handle_packet, st.log); + enc = encoder(args, handle_packet); if (!enc.is_open()) return false; @@ -748,7 +740,7 @@ namespace dlib AVStream* stream = avformat_new_stream(st.pFormatCtx.get(), enc.pCodecCtx->codec); if (!stream) - return fail(*st.log, "avformat_new_stream() failed"); + return fail("avformat_new_stream() failed"); stream->id = stream_counter; stream->time_base = enc.pCodecCtx->time_base; @@ -757,7 +749,7 @@ namespace dlib int ret = avcodec_parameters_from_context(stream->codecpar, enc.pCodecCtx.get()); if (ret < 0) - return fail(*st.log, "avcodec_parameters_from_context() failed : ", get_av_error(ret)); + return fail("avcodec_parameters_from_context() failed : ", get_av_error(ret)); return true; }; @@ -786,7 +778,7 @@ namespace dlib int ret = avio_open2(&st.pFormatCtx->pb, st.args_.filepath.c_str(), AVIO_FLAG_WRITE, &st.pFormatCtx->interrupt_callback, opt.get()); if (ret < 0) - return fail(*st.log, "avio_open2() failed : ", get_av_error(ret)); + return fail("avio_open2() failed : ", get_av_error(ret)); } av_dict opt = st.args_.format_options; @@ -794,7 +786,7 @@ namespace dlib int ret = avformat_write_header(st.pFormatCtx.get(), opt.get()); if (ret < 0) - return fail(*st.log, "avformat_write_header() failed : ", get_av_error(ret)); + return fail("avformat_write_header() failed : ", get_av_error(ret)); st.connected_time = system_clock::now(); @@ -833,7 +825,7 @@ namespace dlib if (f.is_image()) { if (!st.encoder_image.is_open()) - return fail(*st.log, "frame is an image type but image encoder is not initialized"); + return fail("frame is an image type but image encoder is not initialized"); return st.encoder_image.push(std::move(f)); } @@ -841,7 +833,7 @@ namespace dlib else if (f.is_audio()) { if (!st.encoder_audio.is_open()) - return fail(*st.log, "frame is of audio type but audio encoder is not initialized"); + return fail("frame is of audio type but audio encoder is not initialized"); return st.encoder_audio.push(std::move(f)); } @@ -860,7 +852,7 @@ namespace dlib const int ret = av_write_trailer(st.pFormatCtx.get()); if (ret < 0) - (*st.log) << LERROR << "av_write_trailer() failed : " << details::get_av_error(ret); + logger_dlib_wrapper() << LERROR << "av_write_trailer() failed : " << details::get_av_error(ret); if ((st.pFormatCtx->oformat->flags & AVFMT_NOFILE) == 0) avio_closep(&st.pFormatCtx->pb); diff --git a/dlib/media/ffmpeg_utils.h b/dlib/media/ffmpeg_utils.h index 2d8f543a1..b58c0f1c4 100644 --- a/dlib/media/ffmpeg_utils.h +++ b/dlib/media/ffmpeg_utils.h @@ -35,6 +35,7 @@ extern "C" { #include #include #include +#include #include #include #include @@ -53,28 +54,7 @@ namespace dlib // --------------------------------------------------------------------------------------------------- - class register_ffmpeg - { - /*! - WHAT THIS OBJECT REPRESENTS - The point of this class is to statically register ffmpeg globally - and ensure its done only ONCE. - In C++17 you can have inline variables and this can be done much easier. - In C++14 and below you need something like this class. - !*/ - - public: - - static bool get() - { - static const bool v = register_library(); - return v; - } - - private: - - static bool register_library(); - }; + void register_ffmpeg(); // --------------------------------------------------------------------------------------------------- @@ -236,6 +216,11 @@ namespace dlib std::string get_audio_fmt_str(AVSampleFormat fmt); std::string get_channel_layout_str(uint64_t layout); +// --------------------------------------------------------------------------------------------------- + + dlib::logger& logger_ffmpeg(); + dlib::logger& logger_dlib_wrapper(); + // --------------------------------------------------------------------------------------------------- namespace details @@ -421,9 +406,9 @@ namespace dlib namespace details { template - inline bool fail(logger& out, Args&&... args) + inline bool fail(Args&&... args) { - auto ret = out << LERROR; + auto ret = logger_dlib_wrapper() << LERROR; #ifdef __cpp_fold_expressions ((ret << args),...); #else @@ -684,22 +669,84 @@ namespace dlib namespace details { + inline dlib::logger& logger_ffmpeg_private() + { + static dlib::logger GLOBAL("ffmpeg.internal"); + return GLOBAL; + } + } + + inline dlib::logger& logger_ffmpeg() + { + details::register_ffmpeg(); + return details::logger_ffmpeg_private(); + } + + inline dlib::logger& logger_dlib_wrapper() + { + static dlib::logger GLOBAL("ffmpeg.dlib"); + return GLOBAL; + } // --------------------------------------------------------------------------------------------------- - inline bool register_ffmpeg::register_library() + namespace details + { + +// --------------------------------------------------------------------------------------------------- + + inline void register_ffmpeg() { - avdevice_register_all(); + static const bool REGISTERED = [] + { + avdevice_register_all(); #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100) - // See https://github.com/FFmpeg/FFmpeg/blob/70d25268c21cbee5f08304da95be1f647c630c15/doc/APIchanges#L91 - avcodec_register_all(); + // See https://github.com/FFmpeg/FFmpeg/blob/70d25268c21cbee5f08304da95be1f647c630c15/doc/APIchanges#L91 + avcodec_register_all(); #endif #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100) - // See https://github.com/FFmpeg/FFmpeg/blob/70d25268c21cbee5f08304da95be1f647c630c15/doc/APIchanges#L86 - av_register_all(); + // See https://github.com/FFmpeg/FFmpeg/blob/70d25268c21cbee5f08304da95be1f647c630c15/doc/APIchanges#L86 + av_register_all(); #endif - return true; - } + + av_log_set_callback([](void* ptr, int level, const char *fmt, va_list vl) + { + auto& logger = details::logger_ffmpeg_private(); + + char line[256] = {0}; + static int print_prefix = 1; + + // Not sure if copying to vl2 is required by internal ffmpeg functions do this... + va_list vl2; + va_copy(vl2, vl); + int size = av_log_format_line2(ptr, level, fmt, vl2, &line[0], sizeof(line), &print_prefix); + va_end(vl2); + + // Remove all '\n' since dlib's logger already adds one + size = std::min(size, sizeof(line) - 1); + line[size] = '\0'; + for (int i = size - 1 ; i >= 0 ; --i) + if (line[i] == '\n') + line[i] = ' '; + + switch(level) + { + case AV_LOG_PANIC: + case AV_LOG_FATAL: logger << LFATAL << line; break; + case AV_LOG_ERROR: logger << LERROR << line; break; + case AV_LOG_WARNING: logger << LWARN << line; break; + case AV_LOG_INFO: + case AV_LOG_VERBOSE: logger << LINFO << line; break; + case AV_LOG_DEBUG: logger << LDEBUG << line; break; + case AV_LOG_TRACE: logger << LTRACE << line; break; + default: break; + } + }); + + return true; + }(); + (void)REGISTERED; + } // --------------------------------------------------------------------------------------------------- @@ -1192,17 +1239,17 @@ namespace dlib { const static auto protocols = [] { - const bool init = details::register_ffmpeg::get(); // Don't let this get optimized away + details::register_ffmpeg(); std::vector protocols; void* opaque = nullptr; const char* name = 0; - while (init && (name = avio_enum_protocols(&opaque, 0))) + while ((name = avio_enum_protocols(&opaque, 0))) protocols.emplace_back(name); opaque = nullptr; name = 0; - while (init && (name = avio_enum_protocols(&opaque, 1))) + while ((name = avio_enum_protocols(&opaque, 1))) protocols.emplace_back(name); return protocols; @@ -1217,16 +1264,16 @@ namespace dlib { const static auto demuxers = [] { - const bool init = details::register_ffmpeg::get(); // Don't let this get optimized away + details::register_ffmpeg(); std::vector demuxers; const AVInputFormat* demuxer = nullptr; #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100) // See https://github.com/FFmpeg/FFmpeg/blob/70d25268c21cbee5f08304da95be1f647c630c15/doc/APIchanges#L86 - while (init && (demuxer = av_iformat_next(demuxer))) + while ((demuxer = av_iformat_next(demuxer))) #else void* opaque = nullptr; - while (init && (demuxer = av_demuxer_iterate(&opaque))) + while ((demuxer = av_demuxer_iterate(&opaque))) #endif demuxers.push_back(demuxer->name); @@ -1257,17 +1304,17 @@ namespace dlib { const static auto ret = [] { - const bool init = details::register_ffmpeg::get(); // Don't let this get optimized away + details::register_ffmpeg(); std::vector all_details; const AVOutputFormat* muxer = nullptr; #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100) // See https://github.com/FFmpeg/FFmpeg/blob/70d25268c21cbee5f08304da95be1f647c630c15/doc/APIchanges#L86 - while (init && (muxer = av_oformat_next(muxer))) + while ((muxer = av_oformat_next(muxer))) #else void* opaque = nullptr; - while (init && (muxer = av_muxer_iterate(&opaque))) + while ((muxer = av_muxer_iterate(&opaque))) #endif { muxer_details details; @@ -1288,17 +1335,17 @@ namespace dlib { const static auto ret = [] { - const bool init = details::register_ffmpeg::get(); // Don't let this get optimized away + details::register_ffmpeg(); std::vector details; #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100) // See https://github.com/FFmpeg/FFmpeg/blob/70d25268c21cbee5f08304da95be1f647c630c15/doc/APIchanges#L91 AVCodec* codec = nullptr; - while (init && (codec = av_codec_next(codec))) + while ((codec = av_codec_next(codec))) #else const AVCodec* codec = nullptr; void* opaque = nullptr; - while (init && (codec = av_codec_iterate(&opaque))) + while ((codec = av_codec_iterate(&opaque))) #endif { codec_details detail; @@ -1339,7 +1386,7 @@ namespace dlib { const static auto ret = [] { - const bool init = details::register_ffmpeg::get(); // Don't let this get optimized away + details::register_ffmpeg(); std::vector devices; #if LIBAVDEVICE_VERSION_INT < AV_VERSION_INT(59, 0, 100) @@ -1350,7 +1397,7 @@ namespace dlib AVInputFormatPtr device{nullptr}; - while (init && (device = av_input_audio_device_next(device))) + while ((device = av_input_audio_device_next(device))) { device_details details; details.device_type = device->name; @@ -1360,7 +1407,7 @@ namespace dlib device = nullptr; - while (init && (device = av_input_video_device_next(device))) + while ((device = av_input_video_device_next(device))) { device_details details; details.device_type = device->name; @@ -1410,7 +1457,7 @@ namespace dlib { const static auto ret = [] { - const bool init = details::register_ffmpeg::get(); // Don't let this get optimized away + details::register_ffmpeg(); std::vector devices; #if LIBAVDEVICE_VERSION_INT < AV_VERSION_INT(59, 0, 100) @@ -1421,7 +1468,7 @@ namespace dlib AVOutputFormatPtr device{nullptr}; - while (init && (device = av_output_audio_device_next(device))) + while ((device = av_output_audio_device_next(device))) { device_details details; details.device_type = std::string(device->name); @@ -1431,7 +1478,7 @@ namespace dlib device = nullptr; - while (init && (device = av_output_video_device_next(device))) + while ((device = av_output_video_device_next(device))) { device_details details; details.device_type = std::string(device->name); diff --git a/examples/ffmpeg_video_demuxing_ex.cpp b/examples/ffmpeg_video_demuxing_ex.cpp index 9be2410b1..5fa5f733f 100644 --- a/examples/ffmpeg_video_demuxing_ex.cpp +++ b/examples/ffmpeg_video_demuxing_ex.cpp @@ -9,21 +9,24 @@ */ #include +#include #include #include #include using namespace std; using namespace dlib; +using namespace std::chrono; int main(const int argc, const char** argv) try { command_line_parser parser; - parser.add_option("i", "input video", 1); + parser.add_option("i", "input video", 1); + parser.add_option("verbose", "enable all internal ffmpeg logging"); parser.set_group_name("Help Options"); - parser.add_option("h", "alias of --help"); - parser.add_option("help", "display this message and exit"); + parser.add_option("h", "alias of --help"); + parser.add_option("help", "display this message and exit"); parser.parse(argc, argv); const char* one_time_opts[] = {"i"}; @@ -35,6 +38,12 @@ try return 0; } + if (parser.option("verbose")) + { + ffmpeg::logger_dlib_wrapper().set_level(LALL); + ffmpeg::logger_ffmpeg().set_level(LALL); + } + const std::string filepath = get_option(parser, "i", ""); image_window win; @@ -129,8 +138,10 @@ try array2d img; size_t audio_samples{0}; + const auto start = high_resolution_clock::now(); while (cap.read(frame)) { + convert(frame, img); if (frame.is_image() && frame.pixfmt() == AV_PIX_FMT_RGB24) { convert(frame, img); @@ -143,6 +154,9 @@ try printf("\r\tDecoding %zu samples", audio_samples); fflush(stdout); } } + const auto stop = high_resolution_clock::now(); + printf("Ran in %f s\n", duration_cast(stop - start).count() * 1e-6); + printf("\n");