当前位置: 首页 > article >正文

使用线程局部存储解决ffmpeg中多实例调用下自定义日志回调问题

1 问题描述

最近在封装一个库,调用方传入一个URL及对应的回调后就开始执行ffmpeg拉流+硬解码+硬件格式转换,并将得到的数据帧通过回调传递给调用方;除了数据帧回调外,还有日志回调用来传递一些调试信息。

因为该封装库可能被一个进程调用多次(如存在多个小窗口同时播放多个视频),只要在日志回调中包含调用方唯一标识即可进行区分,对于我们自己的日志输出可以这样做,但是发现ffmpeg内部的一些日志如av_dump_format打印却不行,因为没有额外的信息来传递调用方标识,最后发现可以使用线程局部存储来解决这个问题,在此进行记录。

2 问题解决

为了获得av_dump_format的日志输出,我们首先使用av_log_set_callback来对ffmpeg里面的日志进行接管,但是av_dump_format里面打印日志时无法传递任何标识。

这里我们发现av_dump_format是在process函数中调用的,他们一定属于同一个线程,所以可以通过定义一个全局的线程局部变量,并在process函数开始设置为当前调用的上下文,之后就可以在av_dump_format的日志回调中使用这个线程局部变量来获取当前调用的上下文,这样就实现了区分多实例的目的。

其中线程局部变量使用c++11的thread_local来实现,相关代码如下。

static thread_local DecData *thread_local_data = NULL; /*这样可以区分多个实例的使用*/

// 接收ffmpeg内部的日志,当前仅仅显示小于info级别的日志
static void ffmpeg_log_callback(void *ptr, int level, const char *fmt, va_list vl) {
    if (level > AV_LOG_INFO) {
        return;
    }

    int log_size = vsnprintf(NULL, 0, fmt, vl) + 1;
    char * buf = (char*)malloc(log_size);
    vsnprintf(buf, log_size, fmt, vl);
    
    if (thread_local_data) {
        thread_local_data->config.log_cb(buf);
    }
    free(buf);
}

int process(DecData*  data) {
    // ffmpeg
    AVFormatContext *fmt_ctx = NULL;
    int video_stream_index = 0;
    int audio_stream_index = 0;
    AVStream* video_stream = NULL;
    AVStream* audio_stream = NULL;
    AVDictionary *opt = NULL;
    int ret = 0;
    AVPacket *pkt = NULL;
    AVStream* out_video_stream = NULL;
    AVStream* out_audio_stream = NULL;
    AVBSFContext * bst_ctx = NULL;
    AVPacket *bst_pkt = NULL;
    const char *name;

    ConfigInfo *config = &data->config;
    
    // 自定义ffmpeg日志回调函数
    thread_local_data = data;
    av_log_set_callback(ffmpeg_log_callback);

    // 如果是rtsp或rtsps则使用rtsp over tcp
    if (strncmp(config->pull_url, "rtsp://", 7) == 0 || strncmp(config->pull_url, "rtsps://", 8) == 0) {
        av_dict_set(&opt, "rtsp_transport", "tcp", 0);
    }
    // 如果是网络流,则设置i/o超时时间
    av_dict_set(&opt, "timeout", "5000000", 0);

    // 打开输入文件
    ret = avformat_open_input(&fmt_ctx, config->pull_url, NULL, &opt);
    if (ret != 0) {
        logging(data, "avformat_open_input error %d\n", ret);
        goto end;
    }
    av_dict_free(&opt);
    fmt_ctx->opaque = data;

    // 获取输入文件信息
    ret = avformat_find_stream_info(fmt_ctx, NULL);
    if (ret != 0) {
        logging(data, "avformat_find_stream_info error %d\n", ret);
        goto end;
    }

    // 打印输入文件信息
    av_dump_format(fmt_ctx, 0, config->pull_url, 0);
    ………
}


http://www.kler.cn/a/404819.html

相关文章:

  • 基于docker进行任意项目灵活发布
  • 「甲子光年」对话黄翔:从电子签回望中国SaaS“黄金十年”
  • 算法.图论-习题全集(Updating)
  • nginx代理解决跨域问题CORS错误
  • 《操作系统》实验内容 实验二 编程实现进程(线程)同步和互斥(Python 与 PyQt5 实现)
  • Android智能座舱,视频播放场景,通过多指滑屏退回桌面,闪屏问题的另一种解法
  • 力扣 LeetCode 110. 平衡二叉树(Day8:二叉树)
  • 在windows电脑上安装docker服务
  • 大模型试用-t5-base
  • 深度学习的分布式训练与集合通信(一)
  • 调试QRNet遇到的问题
  • 基于Windows系统用C++做一个点名工具
  • 算法学习笔记(六):二叉树一创建、插入、删除、BFS
  • 测试工程师如何在面试中脱颖而出
  • 【软件架构】软件的十二种架构简介
  • 操作系统安全入门:渗透测试基础与实践
  • 存算分离的过去、现在和未来
  • 【Oracle篇】SQL性能优化实战案例(从15秒优化到0.08秒)(第七篇,总共七篇)
  • 前端反向代理的配置和實現
  • 深入解析MySQL中的事务处理
  • 从0开始linux(28)——使用vscode远程链接linux云服务器
  • 【Redis】服务器异常重启,导致redis启动失败
  • Redis 6.2 源码导读
  • Java 实现:根据字符串生成正则表达式的方法详解
  • Rust 力扣 - 70. 爬楼梯
  • 网络编程 day4~day5.1——多点通信,域套接字