FFmpeg : logging (#2774)

* 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 <pf@me>
Co-authored-by: Adrià Arrufat <1671644+arrufat@users.noreply.github.com>
This commit is contained in:
pfeatherstone 2023-04-19 13:32:19 +01:00 committed by GitHub
parent 5f7026ab52
commit 53c4be9026
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 200 additions and 138 deletions

View File

@ -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

View File

@ -75,8 +75,7 @@ namespace dlib
decoder_extractor(
const args& a,
av_ptr<AVCodecContext> pCodecCtx_,
const AVCodec* codec,
std::shared_ptr<logger> log_
const AVCodec* codec
);
bool is_open() const noexcept;
@ -108,7 +107,6 @@ namespace dlib
resizer resizer_image;
resampler resizer_audio;
std::queue<frame> frame_queue;
std::shared_ptr<logger> log;
};
}
@ -154,7 +152,6 @@ namespace dlib
details::av_ptr<AVCodecParserContext> parser;
details::av_ptr<AVPacket> packet;
details::decoder_extractor extractor;
std::shared_ptr<logger> log;
};
// ---------------------------------------------------------------------------------------------------
@ -236,7 +233,6 @@ namespace dlib
int stream_id_video{-1};
int stream_id_audio{-1};
std::queue<frame> frame_queue;
std::shared_ptr<logger> log;
} st;
};
@ -262,9 +258,8 @@ namespace dlib
inline decoder_extractor::decoder_extractor(
const args& a,
av_ptr<AVCodecContext> pCodecCtx_,
const AVCodec* codec,
std::shared_ptr<logger> 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<logger>("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<logger>("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<AVCodecContext> 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;

View File

@ -91,8 +91,7 @@ namespace dlib
encoder(
const args& a,
std::function<bool(AVCodecContext*,AVPacket*)> sink,
std::shared_ptr<logger> log_
std::function<bool(AVCodecContext*,AVPacket*)> sink
);
bool open();
@ -106,7 +105,6 @@ namespace dlib
details::resampler resizer_audio;
details::audio_fifo fifo;
std::function<bool(AVCodecContext*,AVPacket*)> sink;
std::shared_ptr<logger> 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<logger> 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<bool(std::size_t, const char*)> sink
) : encoder(a, [sink](AVCodecContext*, AVPacket* pkt) {
return sink(pkt->size, (const char*)pkt->data);
}, std::make_shared<logger>("ffmpeg::encoder"))
})
{
}
inline encoder::encoder(
const args& a,
std::function<bool(AVCodecContext*,AVPacket*)> sink_,
std::shared_ptr<logger> log_
std::function<bool(AVCodecContext*,AVPacket*)> 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<logger>("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);

View File

@ -35,6 +35,7 @@ extern "C" {
#include <libavutil/channel_layout.h>
#include <libavutil/audio_fifo.h>
#include <libavutil/imgutils.h>
#include <libavutil/log.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <libavformat/avformat.h>
@ -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<class... Args>
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,21 +669,83 @@ 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<int>(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<std::string> 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<std::string> 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<muxer_details> 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<codec_details> 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<device_details> 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<device_details> 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);

View File

@ -9,21 +9,24 @@
*/
#include <cstdio>
#include <chrono>
#include <dlib/media.h>
#include <dlib/gui_widgets.h>
#include <dlib/cmd_line_parser.h>
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<rgb_pixel> 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<microseconds>(stop - start).count() * 1e-6);
printf("\n");