使用线程局部存储解决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来实现,相关代码如下。

cpp 复制代码
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);
    .........
}
相关推荐
问道飞鱼2 小时前
【工具介绍】Ffmpeg工具介绍与简单使用
ffmpeg·视频工具
l***77526 小时前
从MySQL5.7平滑升级到MySQL8.0的最佳实践分享
ffmpeg
ZouZou老师12 小时前
FFmpeg性能优化经典案例
性能优化·ffmpeg
aqi0015 小时前
FFmpeg开发笔记(九十)采用FFmpeg套壳的音视频转码百宝箱FFBox
ffmpeg·音视频·直播·流媒体
齐齐大魔王17 小时前
FFmpeg
ffmpeg
你好音视频18 小时前
FFmpeg RTSP拉流流程深度解析
ffmpeg
IFTICing1 天前
【环境配置】ffmpeg下载、安装、配置(Windows环境)
windows·ffmpeg
haiy20111 天前
FFmpeg 编译
ffmpeg
aqi001 天前
FFmpeg开发笔记(八十九)基于FFmpeg的直播视频录制工具StreamCap
ffmpeg·音视频·直播·流媒体
八月的雨季 最後的冰吻2 天前
FFmepg--28- 滤镜处理 YUV 视频帧:实现上下镜像效果
ffmpeg·音视频