c++ how to use lambdas to set ffmpeg's av_log_set_callback() to a member function?

I've been researching how to use lambdas as callbacks, but I do not seem to be getting my syntax correct. I am working in C++ via Visual Studio 2013 Community, & I am pretty sure this type of lambda use is supported. I am working on a video library which is based on the FFMPEG libav libraries.

The basic issue is the logging callback provided to library clients of FFMPEG's libav. It has this calling structure:

void logging_callback( void *ptr, int level, const char *fmt, va_list vargs  );

This is fine for C or fine for C++ when the logging callback is a static member function, but I want to install a class member function, which needs the this parameter per the C++ nature of calling method functions.

What I have so far looks like: (unrelated portions removed)

class my_libav
{ 
   // logging messages callback for use by library client, any log messages generated 
   // by the library will be sent thru this to the lib client's logic:
   typedef void(*STREAM_LOGGING_CALLBACK_CB) (std::string msg, void* p_object);

   // my lib's logger has a void* param so clients can redirect to member functs easier
   void SetStreamLoggingCallback(STREAM_LOGGING_CALLBACK_CB p_stream_logger, void* p_object);

   // member function to be installed as FFMPEG's av_log_set_callback():
   void RedirectLoggingOutputs( void *ptr, int level, const char *fmt, va_list vargs );

   STREAM_LOGGING_CALLBACK_CB  mp_stream_logging_callback; // client's callback
   void*                       mp_stream_logging_object;   // client's data

};

void my_libav::SetStreamLoggingCallback(STREAM_LOGGING_CALLBACK_CB p_stream_logger, void* p_object)
{
  mp_stream_logging_callback = p_stream_logger;
  mp_stream_logging_object = p_object;
}

void my_libav::RedirectLoggingOutputs(void *ptr, int level, const char *fmt, va_list vargs )
{
  // logic that resolves the message and sends through client's callback
  // installed int mp_stream_logging_callback
}

my_libav::my_libav()
{
  // this is the old static member function I deleted:
  // av_log_set_callback( &my_libav::logging_callback );

  // I tried using std::bind & placeholders, but this syntax is wrong too:
  // namespace ph = std::placeholders;
  // auto callback = std::bind( &my_libav::RedirectLoggingOutputs, this, ph::_1, ph::_2, ph::_3, ph::_4 );
  // 
  // av_log_set_callback( callback ); // <- 'callback' is not the correct type

  // this is my try at a lambda. I know that the *this* cannot be 
  // in the capture, but I don't know the right syntax to work around 
  // this issue:
  std::function<void(void *ptr, int level, const char *fmt, va_list vargs )> 
        f2 = [this](void *ptr, int level, const char *fmt, va_list vargs ){
                        RedirectLoggingOutputs( ptr, level, fmt, vargs ); };

    av_log_set_callback( f2 ); // <- 'f2' is not the correct type, how to fix? 

The syntax I am trying to fix is in the last 4 lines. What is the correct form?

1 answer

  • answered 2018-02-13 02:13 Remy Lebeau

    FFMPEG is a C style library. You can't use a capturing lambda, or a std::function, where a C-style function pointer is expected. So, in this situation, you will have to stick with using a standalone function, or a static class method, for your av_log_set_callback() callback.

    av_log_set_callback() does not allow you to pass a user-defined value to the callback, and you can only have 1 callback active at a time. Since you want to use your class's this pointer with your callback, you will be able to use only 1 instance of your class at a time, and you will have to use a global variable to pass your this pointer to your callback.

    Try something like this:

    class my_libav
    { 
    public:
        typedef void (*STREAM_LOGGING_CALLBACK_CB)(std::string msg, void* p_object);
    
        my_libav();
        my_libav(const my_libav &) = delete;
        ~my_libav();
        my_libav& operator=(const my_libav &) = delete;
    
        void SetStreamLoggingCallback(STREAM_LOGGING_CALLBACK_CB p_stream_logger, void* p_object);
    
    private:
        STREAM_LOGGING_CALLBACK_CB  mp_stream_logging_callback = nullptr;
        void*                       mp_stream_logging_object = nullptr;
    
        static void RedirectLoggingOutputs( void *ptr, int level, const char *fmt, va_list vargs );
    };
    

    static my_libav *g_mylibav = nullptr;
    
    my_libav::my_libav()
    {
        if (g_mylibav) throw std::runtime_error("Only 1 instance of my_libav is allowed at a time!");
        g_mylibav = this;
        av_log_set_callback( &my_libav::RedirectLoggingOutputs );
    }
    
    my_libav::~my_libav()
    {
        av_log_set_callback( nullptr );
        g_mylibav = nullptr;
    }
    
    void my_libav::SetStreamLoggingCallback(STREAM_LOGGING_CALLBACK_CB p_stream_logger, void* p_object)
    {
        mp_stream_logging_callback = p_stream_logger;
        mp_stream_logging_object = p_object;
    }
    
    void my_libav::RedirectLoggingOutputs(void *ptr, int level, const char *fmt, va_list vargs )
    {
        std::string msg;
        ...
        if (g_mylibav->mp_stream_logging_callback)
            g_mylibav->mp_stream_logging_callback(msg, g_mylibav->mp_stream_logging_object);
    }
    

    Or, use a singleton pattern to ensure only 1 instance exists:

    class my_libav
    { 
    public:
        typedef void (*STREAM_LOGGING_CALLBACK_CB)(std::string msg, void* p_object);
    
        ~my_libav();    
    
        static my_libav& getInstance();
    
        void SetStreamLoggingCallback(STREAM_LOGGING_CALLBACK_CB p_stream_logger, void* p_object);
    
    private:
        STREAM_LOGGING_CALLBACK_CB  mp_stream_logging_callback = nullptr;
        void*                       mp_stream_logging_object = nullptr;
    
        my_libav();
        my_libav(const my_libav &) = delete;
        my_libav& operator=(const my_libav &) = delete;
    
        static void RedirectLoggingOutputs( void *ptr, int level, const char *fmt, va_list vargs );
    };
    

    static my_libav *g_mylibav = nullptr;
    
    my_libav::my_libav()
    {
        g_mylibav = this;
        av_log_set_callback( &my_libav::RedirectLoggingOutputs );
    }
    
    my_libav::~my_libav()
    {
        av_log_set_callback( nullptr );
        g_mylibav = nullptr;
    }
    
    my_libav& my_libav::getInstance()
    {
        static my_libav inst;
        return inst;
    }
    
    void my_libav::SetStreamLoggingCallback(STREAM_LOGGING_CALLBACK_CB p_stream_logger, void* p_object)
    {
        mp_stream_logging_callback = p_stream_logger;
        mp_stream_logging_object = p_object;
    }
    
    void my_libav::RedirectLoggingOutputs(void *ptr, int level, const char *fmt, va_list vargs )
    {
        std::string msg;
        ...
        if (g_mylibav->mp_stream_logging_callback)
            g_mylibav->mp_stream_logging_callback(msg, g_mylibav->mp_stream_logging_object);
    }
    

    On a side note, since you are using C++11 or later anyway, you might consider using std::function for your client callback, then a client can use "any Callable target -- function, lambda expression, bind expression, or other function object, as well as a pointer to member function and pointer to data member", and not have to pass around a separate void* back and forth:

    class my_libav
    { 
    public:
        typedef std::function<void(std::string)> STREAM_LOGGING_CALLBACK_CB;
    
        ...
    
        void SetStreamLoggingCallback(STREAM_LOGGING_CALLBACK_CB p_stream_logger);
    
    private:
        STREAM_LOGGING_CALLBACK_CB  mp_stream_logging_callback;
    
        ...
    
        static void RedirectLoggingOutputs( void *ptr, int level, const char *fmt, va_list vargs );
    };
    

    void my_libav::SetStreamLoggingCallback(STREAM_LOGGING_CALLBACK_CB p_stream_logger)
    {
        mp_stream_logging_callback = p_stream_logger;
    }
    
    void my_libav::RedirectLoggingOutputs(void *ptr, int level, const char *fmt, va_list vargs )
    {
        std::string msg;
        ...
        if (g_mylibav->mp_stream_logging_callback)
            g_mylibav->mp_stream_logging_callback(msg);
    }