【问题标题】:c++ how to use lambdas to set ffmpeg's av_log_set_callback() to a member function?c++ 如何使用 lambdas 将 ffmpeg 的 av_log_set_callback() 设置为成员函数?
【发布时间】:2018-02-13 00:28:00
【问题描述】:

我一直在研究如何使用 lambdas 作为回调,但我的语法似乎并不正确。我正在通过 Visual Studio 2013 Community 在 C++ 中工作,并且我很确定这种类型的 lambda 使用是受支持的。我正在开发一个基于 FFMPEG libav 库的视频库。

基本问题是提供给 FFMPEG 的 libav 库客户端的日志记录回调。它有这样的调用结构:

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

当日志回调是静态成员函数时,这对 C 或 C++ 都很好,但我想安装一个类成员函数,根据 C++ 调用的性质,它需要 this 参数方法函数。

我目前的样子:(不相关的部分已删除)

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? 

我要修复的语法在最后 4 行。正确的形式是什么?

【问题讨论】:

  • 我不确定如何使用全局变量或 TLS 存储来实现。我看不出如何使用全局变量来完成;但在 TLS 的情况下,是否会创建一个仅处理此回调的单独类?然后 my_lib 的构造函数在这个 callback_redirecting_class 上执行“new”,它使用 TLS 保存/隐藏指向创建 callback_redirecting_class 实例的类的“this”指针?

标签: c++ lambda ffmpeg callback


【解决方案1】:

FFMPEG 是一个 C 风格的库。您不能使用 capturing lambda 或 std::function,它们需要 C 样式的函数指针。因此,在这种情况下,您必须坚持使用独立函数或静态类方法来处理您的 av_log_set_callback() 回调。

av_log_set_callback() 不允许您将用户定义的值传递给回调,一次只能激活 1 个回调。由于您想在回调中使用类的 this 指针,因此一次只能使用类的 1 个实例,并且必须使用全局变量将 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);
}

或者,使用单例模式来确保只存在 1 个实例:

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);
}

附带说明,由于您使用的是 C++11 或更高版本,您可能会考虑使用 std::function 进行客户端回调,然后客户端可以使用“任何 Callable 目标 -- 函数,lambda expressionbind expression 或其他函数对象,以及指向成员函数的指针和指向数据成员的指针”,而不必来回传递单独的 void*

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);
}

【讨论】:

  • 似乎应该有某种方法可以使用 C++11 或 std::bind 从成员函数创建具有所需调用/返回签名的函数。那是我在这里尝试做的: namespace ph = std::placeholders;自动回调 = std::bind( &my_libav::RedirectLoggingOutputs, this, ph::_1, ph::_2, ph::_3, ph::_4 );
  • "似乎应该有某种方法可以使用 C++11 或 std::bind 从成员函数创建具有所需调用/返回签名的函数。 " - NO, because C is involved。您正在处理的 C 回调仅适用于 pointer-to-function。在 C++ 中,获得一个的唯一方法是 1) 独立函数,2) 静态类方法,或 3) 使用内联汇编 (NOT std::bind()) 调用的手工制作 代理函数非静态类方法。最后一个是可能的,但它是 C++ 无法为您完成的高级技术。
  • 似乎是 FFMPEG 作者的主要设计疏忽,让客户端回调无法识别调用者或在回调中包含非全局客户端数据。
猜你喜欢
  • 1970-01-01
  • 2014-11-27
  • 2021-10-26
  • 2015-03-19
  • 1970-01-01
  • 2015-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多