百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

深入剖析ffplay.c(14) 深入剖析案例,促进以案为鉴

csdh11 2024-12-23 09:26 17 浏览

#if CONFIG_AVFILTER
static int configure_filtergraph(AVFilterGraph *graph, const char *filtergraph,
                                 AVFilterContext *source_ctx, AVFilterContext *sink_ctx)
{
    int ret, i;
    int nb_filters = graph->nb_filters;
    AVFilterInOut *outputs = NULL, *inputs = NULL;

    if (filtergraph) {
        outputs = avfilter_inout_alloc();
        inputs  = avfilter_inout_alloc();
        if (!outputs || !inputs) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }

        outputs->name       = av_strdup("in");
        outputs->filter_ctx = source_ctx;
        outputs->pad_idx    = 0;
        outputs->next       = NULL;

        inputs->name        = av_strdup("out");
        inputs->filter_ctx  = sink_ctx;
        inputs->pad_idx     = 0;
        inputs->next        = NULL;

        if ((ret = avfilter_graph_parse_ptr(graph, filtergraph, &inputs, &outputs, NULL)) < 0)
            goto fail;
    } else {
        if ((ret = avfilter_link(source_ctx, 0, sink_ctx, 0)) < 0)
            goto fail;
    }

    /* Reorder the filters to ensure that inputs of the custom filters are merged first */
    for (i = 0; i < graph->nb_filters - nb_filters; i++)
        FFSWAP(AVFilterContext*, graph->filters[i], graph->filters[i + nb_filters]);

    ret = avfilter_graph_config(graph, NULL);
fail:
    avfilter_inout_free(&outputs);
    avfilter_inout_free(&inputs);
    return ret;
}

以下是对 configure_filtergraph 函数的详细解释:

函数整体功能概述

configure_filtergraph 函数主要用于配置视频滤镜图(AVFilterGraph),根据是否提供滤镜图描述字符串(filtergraph),来决定是通过解析该字符串构建复杂的滤镜链路,还是简单地直接链接源滤镜上下文(source_ctx)和目标滤镜上下文(sink_ctx),之后还会对滤镜图中的滤镜顺序进行调整以确保特定顺序要求,最后完成整个滤镜图的配置工作,并且释放相关的临时资源,常用于视频处理流程中应用滤镜效果的场景,对实现多样化的视频滤镜处理起着关键作用。

函数具体流程分析

  1. 变量初始化与内存分配(有滤镜图描述字符串情况)
int ret, i;
int nb_filters = graph->nb_filters;
AVFilterInOut *outputs = NULL, *inputs = NULL;

if (filtergraph) {
    outputs = avfilter_inout_alloc();
    inputs  = avfilter_inout_alloc();
    if (!outputs ||!inputs) {
        ret = AVERROR(ENOMEM);
        goto fail;
    }

    outputs->name       = av_strdup("in");
    outputs->filter_ctx = source_ctx;
    outputs->pad_idx    = 0;
    outputs->next       = NULL;

    inputs->name        = av_strdup("out");
    inputs->filter_ctx  = sink_ctx;
    inputs->pad_idx     = 0;
    inputs->next        = NULL;

首先定义了一些整型变量 ret(用于记录函数执行过程中的返回结果)和 i(用于循环计数),以及获取当前滤镜图(graph)中已有的滤镜数量 nb_filters,并声明了两个 AVFilterInOut 类型的指针 outputs 和 inputs,初始化为 NULL,用于后续构建滤镜链路相关的输入输出信息。

接着通过条件判断 filtergraph 是否有值(即是否提供了滤镜图描述字符串),如果有值,则进行以下操作:

  • 调用 avfilter_inout_alloc 函数分别为 outputs 和 inputs 分配内存空间,用于存储滤镜图输入输出相关的结构体信息,如果分配内存失败(即 avfilter_inout_alloc 函数返回的指针为 NULL),则将 ret 设置为 AVERROR(ENOMEM)(表示内存不足的错误码),然后通过 goto 语句跳转到 fail 标签处进行后续的资源释放和错误返回操作。
  • 在成功分配内存后,对 outputs 结构体进行初始化赋值:将其 name 成员变量通过 av_strdup 函数复制字符串 "in" 来赋值(av_strdup 函数会在内存中开辟新空间并复制传入的字符串,确保字符串的独立性和正确存储),表示输出端的名称;将 filter_ctx 成员变量赋值为 source_ctx,即将其与源滤镜上下文关联起来;将 pad_idx 成员变量设置为 0,表示使用的是第一个输入输出端口(具体取决于滤镜的端口定义和使用规则);最后将 next 成员变量设置为 NULL,表示这是输出链路的末尾节点(如果有多个输出链路节点,可以通过 next 指针串联起来,这里暂时只有一个)。
  • 同样地,对 inputs 结构体进行类似的初始化赋值操作:将 name 成员变量赋值为 "out",表示输入端的名称;将 filter_ctx 成员变量赋值为 sink_ctx,与目标滤镜上下文建立关联;将 pad_idx 成员变量设置为 0;将 next 成员变量设置为 NULL,完成输入端相关信息的设置,至此为后续解析滤镜图描述字符串构建好了基本的输入输出结构体信息。
  1. 滤镜图解析(有滤镜图描述字符串情况)
if ((ret = avfilter_graph_parse_ptr(graph, filtergraph, &inputs, &outputs, NULL)) < 0)
    goto fail;

在完成 outputs 和 inputs 结构体的初始化后,如果提供了滤镜图描述字符串,就调用 avfilter_graph_parse_ptr 函数来解析该字符串,传入滤镜图结构体指针 graph、滤镜图描述字符串 filtergraph、刚刚初始化好的输入结构体指针 &inputs 和输出结构体指针 &outputs,以及 NULL(最后一个参数可能用于传递一些额外的解析相关的辅助信息,这里不需要所以传入 NULL)。这个函数会根据描述字符串中的滤镜配置信息(例如滤镜名称、滤镜之间的连接关系等)在滤镜图中创建相应的滤镜节点,并按照描述建立它们之间的链路连接,如果解析过程出现错误(例如语法错误、找不到指定的滤镜等情况),函数返回值小于 0,此时将返回的错误码赋值给 ret,然后通过 goto 语句跳转到 fail 标签处进行资源释放和错误返回操作。

  1. 滤镜顺序调整(通用情况)
for (i = 0; i < graph->nb_filters - nb_filters; i++)
    FFSWAP(AVFilterContext*, graph->filters[i], graph->filters[i + nb_filters]);

无论是否提供了滤镜图描述字符串,都会执行这段滤镜顺序调整的代码。通过一个循环,循环次数为滤镜图中新增滤镜的数量(通过 graph->nb_filters - nb_filters 计算得到,即当前滤镜图中总的滤镜数量减去初始已有的滤镜数量,也就是新添加的滤镜个数),在每次循环中使用 FFSWAP 宏(通常用于交换两个同类型变量的值,这里用于交换滤镜图中滤镜上下文结构体指针 AVFilterContext* 类型的变量)来交换 graph->filters[i] 和 graph->filters[i + nb_filters] 的值,以此实现对滤镜顺序的调整,目的是确保自定义滤镜(可能是新添加的滤镜,具体取决于程序中的定义和使用逻辑)的输入能够先被合并处理,保证滤镜处理的顺序符合特定的要求,为后续滤镜图的正确配置和执行奠定基础。

  1. 滤镜图最终配置(通用情况)
ret = avfilter_graph_config(graph, NULL);

调用 avfilter_graph_config 函数对整个滤镜图(graph)进行最终的配置操作,传入滤镜图结构体指针 graph 和 NULL(最后一个参数可能用于传递一些额外的配置相关的辅助信息,这里不需要所以传入 NULL)。这个函数会进行一系列复杂的内部操作,比如初始化滤镜内部的资源、检查滤镜之间的连接是否合法、分配必要的内存等,确保滤镜图处于一个可正确执行的状态,如果配置过程中出现错误,函数返回值小于 0,这个返回值会被赋值给 ret,后续在 fail 标签处统一进行错误处理和返回操作。

  1. 资源释放与错误返回(通用情况)
fail:
avfilter_inout_free(&outputs);
avfilter_inout_free(&inputs);
return ret;

在 fail 标签处,无论之前是在内存分配、滤镜图解析还是滤镜图配置等哪个环节出现错误,都会执行以下操作:首先调用 avfilter_inout_free 函数分别释放之前为 outputs 和 inputs 结构体分配的内存空间(避免内存泄漏问题,确保资源能正确回收),然后将 ret 的值返回给函数调用者,这个返回值可能是表示成功(大于等于 0)或者各种错误情况(小于 0,不同的错误码对应不同的错误原因,如前面提到的内存不足错误码 AVERROR(ENOMEM) 等),以便调用者根据返回值知晓滤镜图配置的结果并进行相应的后续处理。

注意事项

  • 内存分配与释放的正确性:函数中多次涉及内存分配(如 avfilter_inout_alloc 函数分配 AVFilterInOut 结构体内存)和释放(如 avfilter_inout_free 函数释放相关内存)操作,这些操作的正确性至关重要。如果内存分配失败没有正确处理(例如没有及时返回错误码告知调用者)或者内存释放出现遗漏、重复释放等错误情况,会导致内存泄漏或者程序崩溃等严重问题,影响整个程序的稳定性和可靠性。
  • 滤镜图解析的准确性:在使用 avfilter_graph_parse_ptr 函数解析滤镜图描述字符串时,要求描述字符串必须符合 AVFilter 相关的语法规则和格式要求(例如滤镜名称书写正确、滤镜之间的连接表示清晰合理等),如果描述字符串存在语法错误、非法的滤镜名称或者不合理的连接关系等情况,会导致解析失败,无法正确构建滤镜图,进而影响后续的滤镜处理功能实现。同时,该函数内部的实现也需要保证能准确解析各种合法的描述字符串,不能出现解析错误或者遗漏信息等问题,否则同样会使滤镜图配置出现异常。
  • 滤镜顺序调整的合理性:滤镜顺序调整的逻辑(通过 FFSWAP 宏交换滤镜顺序)需要与整个滤镜图的设计以及滤镜处理的业务逻辑相匹配。如果调整顺序不合理(例如没有按照实际的滤镜依赖关系、处理顺序要求进行调整),可能导致滤镜处理结果不符合预期,比如某些滤镜在还未获取到正确的输入数据时就提前执行,或者滤镜之间的数据传递出现混乱等情况,影响最终的视频滤镜效果呈现。
  • 滤镜图配置的完整性与稳定性:avfilter_graph_config 函数进行的滤镜图最终配置操作涉及多个方面的复杂处理,其内部需要保证能全面、准确地完成配置工作,确保滤镜图中各个滤镜都能正确初始化资源、建立合法的连接并且处于可稳定执行的状态。如果配置过程出现遗漏或者错误(例如某些滤镜内部资源初始化失败、滤镜之间连接检查不严格等情况),会导致滤镜图在后续执行过程中出现各种问题,如程序崩溃、滤镜效果无法正确实现等,影响视频滤镜处理的正常进行。
  • 多线程环境(如果涉及):在多线程环境下调用 configure_filtergraph 函数时,要考虑线程安全性问题。由于函数涉及对共享的滤镜图结构体(graph)以及相关的输入输出结构体(outputs、inputs)等进行操作(如修改滤镜顺序、配置滤镜图等操作会改变滤镜图的状态,分配和释放内存涉及共享的内存资源管理),多个线程同时执行这些操作可能会引发数据不一致的情况。例如,一个线程正在解析滤镜图并修改滤镜图的相关结构,另一个线程同时进行滤镜顺序调整或者滤镜图配置操作,就会导致滤镜图的状态混乱,不符合预期的配置结果,影响视频滤镜功能的正确实现。所以在多线程场景下,通常需要采用合适的线程同步机制(如互斥锁等)来保证每次只有一个线程能够调用该函数进行滤镜图配置操作,确保操作的稳定性和结果的一致性。


static int configure_video_filters(AVFilterGraph *graph, VideoState *is, const char *vfilters, AVFrame *frame)
{
    enum AVPixelFormat pix_fmts[FF_ARRAY_ELEMS(sdl_texture_format_map)];
    char sws_flags_str[512] = "";
    char buffersrc_args[256];
    int ret;
    AVFilterContext *filt_src = NULL, *filt_out = NULL, *last_filter = NULL;
    AVCodecParameters *codecpar = is->video_st->codecpar;
    AVRational fr = av_guess_frame_rate(is->ic, is->video_st, NULL);
    AVDictionaryEntry *e = NULL;
    int nb_pix_fmts = 0;
    int i, j;

    for (i = 0; i < renderer_info.num_texture_formats; i++) {
        for (j = 0; j < FF_ARRAY_ELEMS(sdl_texture_format_map) - 1; j++) {
            if (renderer_info.texture_formats[i] == sdl_texture_format_map[j].texture_fmt) {
                pix_fmts[nb_pix_fmts++] = sdl_texture_format_map[j].format;
                break;
            }
        }
    }
    pix_fmts[nb_pix_fmts] = AV_PIX_FMT_NONE;

    while ((e = av_dict_get(sws_dict, "", e, AV_DICT_IGNORE_SUFFIX))) {
        if (!strcmp(e->key, "sws_flags")) {
            av_strlcatf(sws_flags_str, sizeof(sws_flags_str), "%s=%s:", "flags", e->value);
        } else
            av_strlcatf(sws_flags_str, sizeof(sws_flags_str), "%s=%s:", e->key, e->value);
    }
    if (strlen(sws_flags_str))
        sws_flags_str[strlen(sws_flags_str)-1] = '\0';

    graph->scale_sws_opts = av_strdup(sws_flags_str);

    snprintf(buffersrc_args, sizeof(buffersrc_args),
             "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
             frame->width, frame->height, frame->format,
             is->video_st->time_base.num, is->video_st->time_base.den,
             codecpar->sample_aspect_ratio.num, FFMAX(codecpar->sample_aspect_ratio.den, 1));
    if (fr.num && fr.den)
        av_strlcatf(buffersrc_args, sizeof(buffersrc_args), ":frame_rate=%d/%d", fr.num, fr.den);

    if ((ret = avfilter_graph_create_filter(&filt_src,
                                            avfilter_get_by_name("buffer"),
                                            "ffplay_buffer", buffersrc_args, NULL,
                                            graph)) < 0)
        goto fail;

    ret = avfilter_graph_create_filter(&filt_out,
                                       avfilter_get_by_name("buffersink"),
                                       "ffplay_buffersink", NULL, NULL, graph);
    if (ret < 0)
        goto fail;

    if ((ret = av_opt_set_int_list(filt_out, "pix_fmts", pix_fmts,  AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN)) < 0)
        goto fail;

    last_filter = filt_out;

/* Note: this macro adds a filter before the lastly added filter, so the
 * processing order of the filters is in reverse */
#define INSERT_FILT(name, arg) do {                                          \
    AVFilterContext *filt_ctx;                                               \
                                                                             \
    ret = avfilter_graph_create_filter(&filt_ctx,                            \
                                       avfilter_get_by_name(name),           \
                                       "ffplay_" name, arg, NULL, graph);    \
    if (ret < 0)                                                             \
        goto fail;                                                           \
                                                                             \
    ret = avfilter_link(filt_ctx, 0, last_filter, 0);                        \
    if (ret < 0)                                                             \
        goto fail;                                                           \
                                                                             \
    last_filter = filt_ctx;                                                  \
} while (0)

    if (autorotate) {
        double theta  = get_rotation(is->video_st);

        if (fabs(theta - 90) < 1.0) {
            INSERT_FILT("transpose", "clock");
        } else if (fabs(theta - 180) < 1.0) {
            INSERT_FILT("hflip", NULL);
            INSERT_FILT("vflip", NULL);
        } else if (fabs(theta - 270) < 1.0) {
            INSERT_FILT("transpose", "cclock");
        } else if (fabs(theta) > 1.0) {
            char rotate_buf[64];
            snprintf(rotate_buf, sizeof(rotate_buf), "%f*PI/180", theta);
            INSERT_FILT("rotate", rotate_buf);
        }
    }

    if ((ret = configure_filtergraph(graph, vfilters, filt_src, last_filter)) < 0)
        goto fail;

    is->in_video_filter  = filt_src;
    is->out_video_filter = filt_out;

fail:
    return ret;
}

以下是对 configure_video_filters 函数的详细解释:

函数整体功能概述

configure_video_filters 函数主要用于配置视频滤镜相关的操作,包括确定可用的像素格式、构建滤镜图相关的输入输出滤镜上下文、根据不同条件(如画面旋转角度等)添加特定的视频滤镜、解析并应用用户提供的滤镜图描述字符串等一系列操作,最终完成视频滤镜链路的搭建和配置,并将相关的滤镜上下文指针保存到 VideoState 结构体中,为后续视频帧经过滤镜处理提供完整的配置基础,常用于视频播放或处理过程中应用各种视频滤镜效果、画面旋转等功能实现的场景。

函数具体流程分析

  1. 像素格式相关初始化
enum AVPixelFormat pix_fmts[FF_ARRAY_ELEMS(sdl_texture_format_map)];
char sws_flags_str[512] = "";
char buffersrc_args[256];
int ret;
AVFilterContext *filt_src = NULL, *filt_out = NULL, *last_filter = NULL;
AVCodecParameters *codecpar = is->video_st->codecpar;
AVRational fr = av_guess_frame_rate(is->ic, is->video_st, NULL);
AVDictionaryEntry *e = NULL;
int nb_pix_fmts = 0;
int i, j;

for (i = 0; i < renderer_info.num_texture_formats; i++) {
    for (j = 0; j < FF_ARRAY_ELEMS(sdl_texture_format_map) - 1; j++) {
        if (renderer_info.texture_formats[i] == sdl_texture_format_map[j].texture_fmt) {
            pix_fmts[nb_pix_fmts++] = sdl_texture_format_map[j].format;
            break;
        }
    }
}
pix_fmts[nb_pix_fmts] = AV_PIX_FMT_NONE;

首先定义了一系列用于后续操作的变量,包括存储像素格式的数组 pix_fmts、用于存储 sws 标志字符串的数组 sws_flags_str、用于构造 buffer 源滤镜参数的字符串数组 buffersrc_args,以及多个用于记录返回结果、滤镜上下文指针等的变量。

接着通过两层嵌套循环来确定可用的像素格式,外层循环遍历 renderer_info 结构体中的纹理格式数量(renderer_info.num_texture_formats),内层循环遍历 sdl_texture_format_map 数组(减去 1 是为了避免越界访问,因为可能存在特定的结尾标识等情况),当发现 renderer_info 中的纹理格式与 sdl_texture_format_map 数组中某个元素的纹理格式(texture_fmt)相等时,就将对应的像素格式(sdl_texture_format_map[j].format)添加到 pix_fmts 数组中,并将可用像素格式的数量 nb_pix_fmts 加 1。最后在 pix_fmts 数组末尾添加 AV_PIX_FMT_NONE 作为结束标识,方便后续基于这个数组进行像素格式相关的操作(例如设置滤镜接受的像素格式列表等)。

  1. 构建 sws 标志字符串
while ((e = av_dict_get(sws_dict, "", e, AV_DICT_IGNORE_SUFFIX))) {
    if (!strcmp(e->key, "sws_flags")) {
        av_strlcatf(sws_flags_str, sizeof(sws_flags_str), "%s=%s:", "flags", e->value);
    } else
        av_strlcatf(sws_flags_str, sizeof(sws_flags_str), "%s=%s:", e->key, e->value);
}
if (strlen(sws_flags_str))
    sws_flags_str[strlen(sws_flags_str)-1] = '\0';

graph->scale_sws_opts = av_strdup(sws_flags_str);

通过一个循环遍历 sws_dict 字典(可能存储了与 sws 相关的各种配置参数信息,以键值对形式存在),每次循环获取下一个字典条目(通过 av_dict_get 函数,并传入相应参数来实现按特定规则遍历,忽略后缀相同的重复键等情况)。如果获取到的字典条目的键(e->key)是 "sws_flags",就使用 av_strlcatf 函数将 "flags" 以及对应的值(e->value)以特定格式("flags=%s:")追加到 sws_flags_str 字符串数组中;如果键不是 "sws_flags",同样以键值对的格式("%s=%s:")追加到字符串中。

循环结束后,如果 sws_flags_str 字符串的长度不为 0,说明有添加的内容,就将字符串的最后一个字符(多余的 :)替换为 '\0',使其成为一个格式正确的字符串,然后通过 av_strdup 函数复制这个字符串,并赋值给 graph 结构体的 scale_sws_opts 成员变量,用于后续可能涉及 sws 相关的缩放等滤镜操作的配置选项。

  1. 构造 buffer 源滤镜参数字符串
snprintf(buffersrc_args, sizeof(buffersrc_args),
         "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
         frame->width, frame->height, frame->format,
         is->video_st->time_base.num, is->video_st->time_base.den,
         codecpar->sample_aspect_ratio.num, FFMAX(codecpar->sample_aspect_ratio.den, 1));
if (fr.num && fr.den)
    av_strlcatf(buffersrc_args, sizeof(buffersrc_args), ":frame_rate=%d/%d", fr.num, fr.den);

使用 snprintf 函数按照特定格式构造 buffer 源滤镜(在后续创建滤镜时会用到)的参数字符串 buffersrc_args,将视频帧的宽度(frame->width)、高度(frame->height)、像素格式(frame->format)、视频流的时间基(is->video_st->time_base 的分子和分母分别填入对应位置)以及像素纵横比(codecpar->sample_aspect_ratio 的分子和分母,分母通过 FFMAX 函数确保不为 0,取其与 1 中的较大值)等信息填充到字符串中。

如果成功猜测到了视频的帧率(fr.num 和 fr.den 都有值,fr 是通过 av_guess_frame_rate 函数根据视频相关信息猜测得到的帧率),则使用 av_strlcatf 函数将帧率信息(以 ":frame_rate=%d/%d" 的格式)追加到 buffersrc_args 字符串中,使得这个参数字符串包含了完整的 buffer 源滤镜所需的各种视频相关参数信息。

  1. 创建 buffer 源滤镜和 buffersink 滤镜上下文
if ((ret = avfilter_graph_create_filter(&filt_src,
                                        avfilter_get_by_name("buffer"),
                                        "ffplay_buffer", buffersrc_args, NULL,
                                        graph)) < 0)
    goto fail;

ret = avfilter_graph_create_filter(&filt_out,
                                   avfilter_get_by_name("buffersink"),
                                   "ffplay_buffersink", NULL, NULL, graph);
if (ret < 0)
    goto fail;

分别调用 avfilter_graph_create_filter 函数来创建 buffer 源滤镜(filt_src)和 buffersink 滤镜(filt_out)的上下文。创建 buffer 源滤镜时,传入要创建的滤镜上下文指针 &filt_src、通过 avfilter_get_by_name 函数获取的 "buffer" 滤镜名称对应的滤镜结构体指针、自定义的滤镜名称 "ffplay_buffer"、前面构造好的参数字符串 buffersrc_args 以及 NULL(最后一个参数可能用于传递额外的创建相关的辅助信息,这里不需要所以传入 NULL)和滤镜图结构体指针 graph。如果创建过程出现错误(返回值小于 0),则通过 goto 语句跳转到 fail 标签处进行错误处理。

同样地,创建 buffersink 滤镜上下文时,传入相应的参数,只是其参数字符串传入 NULL(因为 buffersink 滤镜可能不需要额外的类似 buffer 源滤镜那样的明确参数),若创建出现错误也跳转到 fail 标签处。

  1. 设置 buffersink 滤镜接受的像素格式列表
if ((ret = av_opt_set_int_list(filt_out, "pix_fmts", pix_fmts,  AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN)) < 0)
    goto fail;

调用 av_opt_set_int_list 函数为 buffersink 滤镜(filt_out)设置可接受的像素格式列表,传入滤镜上下文指针 filt_out、参数名 "pix_fmts"、前面确定好的像素格式数组 pix_fmts、结束标识 AV_PIX_FMT_NONE(用于表示像素格式列表的结束)以及 AV_OPT_SEARCH_CHILDREN(表示搜索子选项等相关的标志位,具体取决于函数内部的逻辑需求)。如果设置过程出现错误(返回值小于 0),则跳转到 fail 标签处进行错误处理,确保 buffersink 滤镜能正确接受后续处理中可能出现的符合要求的像素格式的视频帧。

  1. 添加特定视频滤镜(根据画面旋转等条件)
last_filter = filt_out;
#define INSERT_FILT(name, arg) do {                                          \
    AVFilterContext *filt_ctx;                                               \
                                                                             \
    ret = avfilter_graph_create_filter(&filt_ctx,                            \
                                       avfilter_get_by_name(name),           \
                                       "ffplay_" name, arg, NULL, graph);    \
    if (ret < 0)                                                             \
        goto fail;                                                           \
                                                                             \
    ret = avfilter_link(filt_ctx, 0, last_filter, 0);                        \
    if (ret < 0)                                                             \
        goto fail;                                                           \
                                                                             \
    last_filter = filt_ctx;                                                  \
} while (0)

if (autorotate) {
    double theta  = get_rotation(is->video_st);

    if (fabs(theta - 90) < 1.0) {
        INSERT_FILT("transpose", "clock");
    } else if (fabs(theta - 180) < 1.0) {
        INSERT_FILT("hflip", NULL);
        INSERT_FILT("vflip", NULL);
    } else if (fabs(theta - 270) < 1.0) {
        INSERT_FILT("transpose", "cclock");
    } else if (fabs(theta) > 1.0) {
        char rotate_buf[64];
        snprintf(rotate_buf, sizeof(rotate_buf), "%f*PI/180", theta);
        INSERT_FILT("rotate", rotate_buf);
    }
}

首先将 last_filter 指针指向 buffersink 滤镜上下文(filt_out),为后续添加滤镜并建立链路做准备。然后定义了一个宏 INSERT_FILT,这个宏的作用是创建一个指定名称(name)的滤镜上下文(filt_ctx),通过 avfilter_graph_create_filter 函数来创建滤镜并传入相应参数(滤镜名称、自定义滤镜名、可能的参数 arg 以及滤镜图指针等),若创建失败则跳转到 fail 标签处。接着使用 avfilter_link 函数将新创建的滤镜与上一个添加的滤镜(last_filter)进行链接(连接它们的输入输出端口,这里都是连接第一个端口,索引为 0),若链接失败同样跳转到 fail 标签处,最后将 last_filter 指针更新为新创建的滤镜上下文,以便后续继续添加滤镜时能正确建立链路。

在 autorotate 条件为真(可能表示是否自动根据视频画面的旋转角度进行相应处理的标志位)时,通过 get_rotation 函数获取视频画面的旋转角度 theta(单位应该是角度制),然后根据不同的角度范围添加不同的旋转相关的滤镜:

  • 如果角度 theta 与 90 度的差值绝对值小于 1.0 度,就使用 INSERT_FILT 宏添加一个 "transpose" 滤镜,并传入 "clock" 参数,表示顺时针旋转 90 度的转置滤镜操作。
  • 如果角度与 180 度的差值绝对值小于 1.0 度,就依次添加 "hflip"(水平翻转)和 "vflip"(垂直翻转)滤镜,实现相当于旋转 180 度的效果。
  • 如果角度与 270 度的差值绝对值小于 1.0 度,添加 "transpose" 滤镜并传入 "cclock" 参数,即逆时针旋转 90 度的转置滤镜操作。
  • 如果角度的绝对值大于 1.0 度,说明有其他非标准的旋转角度,先通过 snprintf 函数将角度值转换为弧度制(以 "%f*PI/180" 的格式存储在 rotate_buf 字符串数组中),然后使用 INSERT_FILT 宏添加 "rotate" 滤镜,并传入这个角度对应的弧度制字符串参数,实现按照指定角度旋转视频画面的效果。
  1. 配置滤镜图并保存滤镜上下文指针
if ((ret = configure_filtergraph(graph, vfilters, filt_src, last_filter)) < 0)
    goto fail;

is->in_video_filter  = filt_src;
is->out_video_filter = filt_out;

调用 configure_filtergraph 函数(前面已介绍过其功能,用于解析滤镜图描述字符串并进行滤镜图的配置操作,包括调整滤镜顺序、完成最终配置等),传入滤镜图结构体指针 graph、用户提供的滤镜图描述字符串 vfilters、buffer 源滤镜上下文 filt_src 以及最后添加的滤镜上下文 last_filter,若配置过程出现错误(返回值小于 0),则跳转到 fail 标签处进行错误处理。


在成功配置滤镜图后,将 buffer 源滤镜上下文指针 filt_src 赋值给 VideoState 结构体(is)的 in_video_filter 成员变量,将 buffersink 滤镜上下文指针 filt_out 赋值给 is 的 out_video_filter 成员变量,这样后续在处理视频帧时就能方便地找到对应的输入输出滤镜链路,使视频帧能够顺利通过配置好的滤镜图进行相应的滤镜处理。

  1. 错误处理与返回结果
fail:
return ret;

在 fail 标签处,直接将 ret 的值返回给函数调用者,ret 的值可能表示成功(大于等于 0)或者各种错误情况(小于 0,不同的错误码对应不同的错误原因,如滤镜创建失败、滤镜图配置失败等相关的错误码),以便调用者根据返回值知晓视频滤镜配置的结果并进行相应的后续处理。

注意事项

  • 像素格式处理的准确性:在确定可用像素格式的过程中(通过两层嵌套循环查找匹配的纹理格式对应的像素格式),依赖于 renderer_info 和 sdl_texture_format_map 等相关结构体中数据的准确性和完整性。如果这些结构体中的纹理格式等信息存在错误、不完整或者与实际的视频处理环境不匹配的情况,会导致确定的可用像素格式不准确,进而影响后续滤镜对视频帧像素格式的处理,可能出现无法正确处理视频帧或者显示异常等问题。
  • 滤镜相关函数调用的正确性:函数中多次调用了 avfilter_graph_create_filter、avfilter_link、av_opt_set_int_list 以及 configure_filtergraph 等与滤镜操作相关的函数,这些函数的正确执行至关重要。例如,创建滤镜上下文时,如果滤镜名称传递错误、参数格式不符合要求等情况会导致滤镜创建失败;链接滤镜时,若端口连接不符合滤镜的输入输出规则会导致链接失败;设置滤镜选项或配置滤镜图时,任何参数错误、语法错误等都会使操作无法正常完成,影响整个视频滤镜链路的搭建和功能实现。
  • 画面旋转等条件判断的合理性:在根据画面旋转角度添加相应滤镜的逻辑中,角度判断的阈值(如 fabs(theta - 90) < 1.0 等条件中的 1.0)以及不同角度对应的滤镜添加方式需要合理设置并符合实际的视频画面旋转需求。如果阈值设置过大或过小,可能导致添加的滤镜不符合画面实际的旋转情况,影响视频画面的正确显示效果;同时,不同角度与滤镜的对应关系要准确,否则会造成画面旋转方向、角度等出现错误,无法达到预期的视觉效果。
  • 多线程环境(如果涉及):在多线程环境下调用 configure_video_filters 函数时,要考虑线程安全性问题。由于函数涉及对共享的滤镜图结构体(graph)、VideoState 结构体(is)中的相关成员变量(如与视频流、滤镜上下文相关的变量)以及各种滤镜相关的结构体(如 AVFilterContext 等)进行操作(如创建滤镜、配置滤镜


static int configure_audio_filters(VideoState *is, const char *afilters, int force_output_format)
{
    static const enum AVSampleFormat sample_fmts[] = { AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_NONE };
    int sample_rates[2] = { 0, -1 };
    int64_t channel_layouts[2] = { 0, -1 };
    int channels[2] = { 0, -1 };
    AVFilterContext *filt_asrc = NULL, *filt_asink = NULL;
    char aresample_swr_opts[512] = "";
    AVDictionaryEntry *e = NULL;
    char asrc_args[256];
    int ret;

    avfilter_graph_free(&is->agraph);
    if (!(is->agraph = avfilter_graph_alloc()))
        return AVERROR(ENOMEM);
    is->agraph->nb_threads = filter_nbthreads;

    while ((e = av_dict_get(swr_opts, "", e, AV_DICT_IGNORE_SUFFIX)))
        av_strlcatf(aresample_swr_opts, sizeof(aresample_swr_opts), "%s=%s:", e->key, e->value);
    if (strlen(aresample_swr_opts))
        aresample_swr_opts[strlen(aresample_swr_opts)-1] = '\0';
    av_opt_set(is->agraph, "aresample_swr_opts", aresample_swr_opts, 0);

    ret = snprintf(asrc_args, sizeof(asrc_args),
                   "sample_rate=%d:sample_fmt=%s:channels=%d:time_base=%d/%d",
                   is->audio_filter_src.freq, av_get_sample_fmt_name(is->audio_filter_src.fmt),
                   is->audio_filter_src.channels,
                   1, is->audio_filter_src.freq);
    if (is->audio_filter_src.channel_layout)
        snprintf(asrc_args + ret, sizeof(asrc_args) - ret,
                 ":channel_layout=0x%"PRIx64,  is->audio_filter_src.channel_layout);

    ret = avfilter_graph_create_filter(&filt_asrc,
                                       avfilter_get_by_name("abuffer"), "ffplay_abuffer",
                                       asrc_args, NULL, is->agraph);
    if (ret < 0)
        goto end;


    ret = avfilter_graph_create_filter(&filt_asink,
                                       avfilter_get_by_name("abuffersink"), "ffplay_abuffersink",
                                       NULL, NULL, is->agraph);
    if (ret < 0)
        goto end;

    if ((ret = av_opt_set_int_list(filt_asink, "sample_fmts", sample_fmts,  AV_SAMPLE_FMT_NONE, AV_OPT_SEARCH_CHILDREN)) < 0)
        goto end;
    if ((ret = av_opt_set_int(filt_asink, "all_channel_counts", 1, AV_OPT_SEARCH_CHILDREN)) < 0)
        goto end;

    if (force_output_format) {
        channel_layouts[0] = is->audio_tgt.channel_layout;
        channels       [0] = is->audio_tgt.channels;
        sample_rates   [0] = is->audio_tgt.freq;
        if ((ret = av_opt_set_int(filt_asink, "all_channel_counts", 0, AV_OPT_SEARCH_CHILDREN)) < 0)
            goto end;
        if ((ret = av_opt_set_int_list(filt_asink, "channel_layouts", channel_layouts,  -1, AV_OPT_SEARCH_CHILDREN)) < 0)
            goto end;
        if ((ret = av_opt_set_int_list(filt_asink, "channel_counts" , channels       ,  -1, AV_OPT_SEARCH_CHILDREN)) < 0)
            goto end;
        if ((ret = av_opt_set_int_list(filt_asink, "sample_rates"   , sample_rates   ,  -1, AV_OPT_SEARCH_CHILDREN)) < 0)
            goto end;
    }


    if ((ret = configure_filtergraph(is->agraph, afilters, filt_asrc, filt_asink)) < 0)
        goto end;

    is->in_audio_filter  = filt_asrc;
    is->out_audio_filter = filt_asink;

end:
    if (ret < 0)
        avfilter_graph_free(&is->agraph);
    return ret;
}
#endif  /* CONFIG_AVFILTER */

以下是对 configure_audio_filters 函数的详细解释:

函数整体功能概述

configure_audio_filters 函数主要用于配置音频滤镜相关的操作,涵盖了释放旧的音频滤镜图(若存在)、分配新的音频滤镜图资源、构建滤镜图相关的输入输出滤镜上下文、设置滤镜的各种参数(如采样格式、采样率、声道布局等)、根据条件强制设置输出格式以及解析并应用用户提供的音频滤镜图描述字符串等一系列任务,最终完成音频滤镜链路的搭建和配置,并将相关的滤镜上下文指针保存到 VideoState 结构体中,为后续音频数据经过滤镜处理提供完整的配置基础,常用于音频播放或处理过程中应用各种音频滤镜效果、格式转换等功能实现的场景。

函数具体流程分析

  1. 变量初始化与音频滤镜图资源管理
static const enum AVSampleFormat sample_fmts[] = { AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_NONE };
int sample_rates[2] = { 0, -1 };
int64_t channel_layouts[2] = { 0, -1 };
int channels[2] = { 0, -1 };
AVFilterContext *filt_asrc = NULL, *filt_asink = NULL;
char aresample_swr_opts[512] = "";
AVDictionaryEntry *e = NULL;
char asrc_args[256];
int ret;

avfilter_graph_free(&is->agraph);
if (!(is->agraph = avfilter_graph_alloc()))
    return AVERROR(ENOMEM);
is->agraph->nb_threads = filter_nbthreads;

首先定义了一系列用于后续音频滤镜配置操作的变量,包括固定的采样格式数组 sample_fmts(其中包含了 AV_SAMPLE_FMT_S16 以及结束标识 AV_SAMPLE_FMT_NONE),用于存储采样率、声道布局、声道数的数组,两个音频滤镜上下文指针 filt_asrc 和 filt_asink,用于构建 aresample 相关选项字符串的数组 aresample_swr_opts,以及用于遍历字典的指针 e、构造源滤镜参数字符串的 asrc_args 和用于记录函数执行结果的 ret。

接着调用 avfilter_graph_free 函数释放之前 VideoState 结构体(is)中已有的音频滤镜图资源(若存在的话),避免内存泄漏等问题。然后通过 avfilter_graph_alloc 函数尝试分配新的音频滤镜图结构体资源,并将其赋值给 is->agraph,如果分配失败(返回的指针为 NULL),则直接返回 AVERROR(ENOMEM)(表示内存不足的错误码),告知调用者无法完成音频滤镜图的配置操作。在成功分配滤镜图资源后,将滤镜图中的线程数量设置为 filter_nbthreads(这个变量可能在其他地方定义,用于指定滤镜图处理过程中使用的线程数量)。

  1. 构建 aresample 相关选项字符串
while ((e = av_dict_get(swr_opts, "", e, AV_DICT_IGNORE_SUFFIX)))
    av_strlcatf(aresample_swr_opts, sizeof(aresample_swr_opts), "%s=%s:", e->key, e->value);
if (strlen(aresample_swr_opts))
    aresample_swr_opts[strlen(aresample_swr_opts)-1] = '\0';
av_opt_set(is->agraph, "aresample_swr_opts", aresample_swr_opts, 0);

通过一个循环遍历 swr_opts 字典(可能存储了与 aresample 相关的各种配置参数信息,以键值对形式存在),每次循环获取下一个字典条目(通过 av_dict_get 函数,并传入相应参数来实现按特定规则遍历,忽略后缀相同的重复键等情况)。对于获取到的每个字典条目,使用 av_strlcatf 函数将键和值以 "%s=%s:" 的格式追加到 aresample_swr_opts 字符串数组中,构建出包含所有相关配置选项的字符串。

循环结束后,如果 aresample_swr_opts 字符串的长度不为 0,说明有添加的内容,就将字符串的最后一个字符(多余的 :)替换为 '\0',使其成为一个格式正确的字符串,然后通过 av_opt_set 函数将这个字符串设置为音频滤镜图(is->agraph)的 "aresample_swr_opts" 属性值,用于后续可能涉及 aresample 相关的音频采样等滤镜操作的配置选项。

  1. 构造 abuffer 源滤镜参数字符串
ret = snprintf(asrc_args, sizeof(asrc_args),
               "sample_rate=%d:sample_fmt=%s:channels=%d:time_base=%d/%d",
               is->audio_filter_src.freq, av_get_sample_fmt_name(is->audio_filter_src.fmt),
               is->audio_filter_src.channels,
               1, is->audio_filter_src.freq);
if (is->audio_filter_src.channel_layout)
    snprintf(asrc_args + ret, sizeof(asrc_args) - ret,
             ":channel_layout=0x%"PRIx64,  is->audio_filter_src.channel_layout);

使用 snprintf 函数按照特定格式构造 abuffer 源滤镜(在后续创建滤镜时会用到)的参数字符串 asrc_args,将音频源的采样率(is->audio_filter_src.freq)、采样格式名称(通过 av_get_sample_fmt_name 函数获取 is->audio_filter_src.fmt 对应的名称)、声道数(is->audio_filter_src.channels)以及时间基(分子为 1,分母为采样率 is->audio_filter_src.freq)等信息填充到字符串中。

如果音频源存在声道布局(is->audio_filter_src.channel_layout 不为 0),则使用 snprintf 函数继续将声道布局信息(以 ":channel_layout=0x%"PRIx64 的格式,PRIx64 用于正确格式化 64 位无符号整数类型的声道布局值)追加到 asrc_args 字符串中,使得这个参数字符串包含了完整的 abuffer 源滤镜所需的各种音频相关参数信息。

  1. 创建 abuffer 源滤镜和 abuffersink 滤镜上下文
ret = avfilter_graph_create_filter(&filt_asrc,
                                   avfilter_get_by_name("abuffer"), "ffplay_abuffer",
                                   asrc_args, NULL, is->agraph);
if (ret < 0)
    goto end;


ret = avfilter_graph_create_filter(&filt_asink,
                                   avfilter_get_by_name("abuffersink"), "ffplay_abuffersink",
                                   NULL, NULL, is->agraph);
if (ret < 0)
    goto end;

分别调用 avfilter_graph_create_filter 函数来创建 abuffer 源滤镜(filt_asrc)和 abuffersink 滤镜(filt_asink)的上下文。创建 abuffer 源滤镜时,传入要创建的滤镜上下文指针 &filt_asrc、通过 avfilter_get_by_name 函数获取的 "abuffer" 滤镜名称对应的滤镜结构体指针、自定义的滤镜名称 "ffplay_abuffer"、前面构造好的参数字符串 asrc_args 以及 NULL(最后一个参数可能用于传递额外的创建相关的辅助信息,这里不需要所以传入 NULL)和音频滤镜图结构体指针 is->agraph。如果创建过程出现错误(返回值小于 0),则通过 goto 语句跳转到 end 标签处进行错误处理。

同样地,创建 abuffersink 滤镜上下文时,传入相应的参数,只是其参数字符串传入 NULL(因为 abuffersink 滤镜可能不需要额外的类似 abuffer 源滤镜那样的明确参数),若创建出现错误也跳转到 end 标签处。

  1. 设置 abuffersink 滤镜相关参数
if ((ret = av_opt_set_int_list(filt_asink, "sample_fmts", sample_fmts,  AV_SAMPLE_FMT_NONE, AV_OPT_SEARCH_CHILDREN)) < 0)
    goto end;
if ((ret = av_opt_set_int(filt_asink, "all_channel_counts", 1, AV_OPT_SEARCH_CHILDREN)) < 0)
    goto end;

首先调用 av_opt_set_int_list 函数为 abuffersink 滤镜(filt_asink)设置可接受的采样格式列表,传入滤镜上下文指针 filt_asink、参数名 "sample_fmts"、前面定义好的采样格式数组 sample_fmts(包含 AV_SAMPLE_FMT_S16 和结束标识 AV_SAMPLE_FMT_NONE)、结束标识 AV_SAMPLE_FMT_NONE(用于表示采样格式列表的结束)以及 AV_OPT_SEARCH_CHILDREN(表示搜索子选项等相关的标志位,具体取决于函数内部的逻辑需求)。如果设置过程出现错误(返回值小于 0),则跳转到 end 标签处进行错误处理,确保 abuffersink 滤镜能正确接受后续处理中可能出现的符合要求的采样格式的音频数据。

接着调用 av_opt_set_int 函数将 abuffersink 滤镜的 "all_channel_counts" 属性设置为 1(这个属性的具体含义可能与是否接受所有声道数量相关,设置为 1 可能表示接受多种声道数量情况,具体取决于 AVFilter 相关的定义和使用逻辑),若设置出现错误同样跳转到 end 标签处。

  1. 根据条件强制设置输出格式(force_output_format 为真时)
if (force_output_format) {
    channel_layouts[0] = is->audio_tgt.channel_layout;
    channels       [0] = is->audio_tgt.channels;
    sample_rates   [0] = is->audio_tgt.freq;
    if ((ret = av_opt_set_int(filt_asink, "all_channel_counts", 0, AV_OPT_SEARCH_CHILDREN)) < 0)
        goto end;
    if ((ret = av_opt_set_int_list(filt_asink, "channel_layouts", channel_layouts,  -1, AV_OPT_SEARCH_CHILDREN)) < 0)
        goto end;
    if ((ret = av_opt_set_int_list(filt_asink, "channel_counts", channels      ,  -1, AV_OPT_SEARCH_CHILDREN)) < 0)
        goto end;
    if ((ret = av_opt_set_int_list(filt_asink, "sample_rates"  , sample_rates  ,  -1, AV_OPT_SEARCH_CHILDREN)) < 0)
        goto end;
}

当 force_output_format 条件为真(可能表示是否强制按照特定的目标输出格式来配置滤镜,具体取决于程序中的逻辑需求)时,进行以下操作:

  • 将 channel_layouts、channels 和 sample_rates 数组的第一个元素分别设置为目标音频的声道布局(is->audio_tgt.channel_layout)、声道数(is->audio_tgt.channels)和采样率(is->audio_tgt.freq),明确了要强制设置的输出音频格式相关的关键参数。
  • 接着依次调用 av_opt_set_int 和 av_opt_set_int_list 等函数来为 abuffersink 滤镜设置相关属性:调用 av_opt_set_int 函数将 "all_channel_counts" 属性设置为 0(与之前设置为 1 相对,可能表示限制只接受特定的声道数量情况,具体含义取决于滤镜相关定义),若设置失败则跳转到 end 标签处。调用 av_opt_set_int_list 函数分别设置 "channel_layouts"、"channel_counts" 和 "sample_rates" 属性,传入对应的参数数组(channel_layouts、channels 和 sample_rates)以及相应的结束标识(-1 等,用于表示列表结束,具体取决于函数要求)和搜索子选项标志位(AV_OPT_SEARCH_CHILDREN),若在设置这些属性过程中出现错误,都跳转到 end 标签处进行错误处理,通过这些设置操作确保 abuffersink 滤镜能按照强制要求的输出格式来处理音频数据。
  1. 配置滤镜图并保存滤镜上下文指针
if ((ret = configure_filtergraph(is->agraph, afilters, filt_asrc, filt_asink)) < 0)
    goto end;

is->in_audio_filter  = filt_asrc;
is->out_audio_filter = filt_asink;

调用 configure_filtergraph 函数(前面已介绍过其功能,用于解析滤镜图描述字符串并进行滤镜图的配置操作,包括调整滤镜顺序、完成最终配置等),传入音频滤镜图结构体指针 is->agraph、用户提供的音频滤镜图描述字符串 afilters、abuffer 源滤镜上下文 filt_asrc 以及 abuffersink 滤镜上下文 filt_asink,若配置过程出现错误(返回值小于 0),则跳转到 end 标签处进行错误处理。

在成功配置滤镜图后,将 abuffer 源滤镜上下文指针 filt_asrc 赋值给 VideoState 结构体(is)的 in_audio_filter 成员变量,将 abuffersink 滤镜上下文指针 filt_asink 赋值给 is 的 out_audio_filter 成员变量,这样后续在处理音频数据时就能方便地找到对应的输入输出滤镜链路,使音频数据能够顺利通过配置好的滤镜图进行相应的滤镜处理。

  1. 错误处理与返回结果
end:
if (ret < 0)
    avfilter_graph_free(&is->agraph);
return ret;

在 end 标签处,如果 ret 的值小于 0(表示在前面的音频滤镜图配置过程中出现了错误,例如滤镜创建失败、滤镜参数设置错误、滤镜图配置失败等情况),则调用 avfilter_graph_free 函数释放之前分配的音频滤镜图资源(避免内存泄漏问题),然后将 ret 的值返回给函数调用者,这个返回值就是表示错误情况的错误码(不同的错误码对应不同的错误原因),以便调用者根据返回值知晓音频滤镜配置的结果并进行相应的后续处理;如果 ret 的值大于等于 0,则直接返回 ret,表示音频滤镜配置成功。

注意事项

  • 滤镜图资源管理的正确性:在释放旧的音频滤镜图(通过 avfilter_graph_free 函数)和分配新的滤镜图资源(通过 avfilter_graph_alloc 函数)过程中,要确保操作的正确性和完整性。如果释放操作遗漏了相关资源或者分配资源失败没有正确处理(例如没有及时返回错误码告知调用者),会导致内存泄漏或者后续无法进行滤镜图配置等严重问题,影响整个程序的稳定性和音频滤镜功能的实现。
  • 滤镜相关函数调用的正确性:函数中多次调用了 avfilter_graph_create_filter、av_opt_set_int_list、av_opt_set_int 以及 configure_filtergraph 等与滤镜操作相关的函数,这些函数的正确执行至关重要。例如,创建滤镜上下文时,如果滤镜名称传递错误、参数格式不符合要求等情况会导致滤镜创建失败;设置滤镜选项时,任何参数错误、不符合滤镜属性要求等都会使操作无法正常完成,影响整个音频滤镜链路的搭建和功能实现;配置滤镜图时,若描述字符串存在语法错误或者不符合滤镜图构建规则等问题,也会导致配置失败,进而影响音频滤镜处理流程。
  • 参数设置的合理性与一致性:在设置音频滤镜的各种参数(如采样格式、采样率、声道布局等)过程中,传入的参数值要合理且与音频数据的实际情况以及程序中其他部分的逻辑相匹配。例如,采样格式数组 sample_fmts 的定义要包含实际支持且合理的采样格式;声道布局、声道数和采样率等参数的设置要符合音频源和目标输出的要求,否则可能导致音频滤镜处理出现错误,比如音频数据无法正确通过滤镜进行格式转换、声道调整等操作,影响最终的音频播放效果。
  • 多线程环境(如果涉及):在多线程环境下调用 configure_audio_filters 函数时,要考虑线程安全性问题。由于函数涉及对共享的音频滤镜图结构体(is->agraph)、VideoState 结构体(is)中的相关成员变量(如与音频源、目标音频格式、滤镜上下文相关的变量)以及各种滤镜相关的结构体(如 AVFilterContext 等)进行操作(如创建滤镜、设置滤镜参数、配置滤镜图等操作会改变滤镜图的状态,分配和释放音频滤镜图资源涉及共享的内存资源管理),多个线程同时执行这些操作可能会引发数据不一致的情况。例如,一个线程正在创建滤镜并设置参数,另一个线程同时修改了音频源的相关参数或者滤镜图的配置信息,就会导致滤镜创建失败、参数设置错误或者滤镜图配置不符合预期,影响音频滤镜功能的正确实现。所以在多线程场景下,通常需要采用合适的线程同步机制(如互斥锁等)来保证每次只有一个线程能够调用该函数进行音频滤镜

相关推荐

用Python轻松修改Word文件的作者和时间,打造自己的专属效率工具

你是否曾经遇到过需要批量修改Word文件的作者、创建时间或修改时间的情况?手动操作不仅费时费力,还容易出错。可以用Python编写一个小工具,轻松解决这个问题!无论你是编程新手还是有一定经验的...

插件开发js代码划分(js插件编写)

在开发Chrome插件时,将JavaScript代码拆分成多个模块而非集中放置,主要基于性能优化、可维护性提升和浏览器插件特性适配等多方面的考量。以下是具体原因及区别分析:一、拆分的核心原因...

5分钟掌握Python中的标准输入、标准输出、标准错误

读取用户输入从标准输入获取输入:user_input=input("Impartyourwisdom:")print(f"Youshared:{user_input}")...

高大上的解答:在 &#39;packages.pyi&#39; 中找不到引用 &#39;urllib3&#39;

DeepSeek的一句代码:...

Flask 入门教程(flask快速入门)

目录什么是Flask?环境配置与安装第一个Flask应用:HelloWorld路由与视图函数模板与Jinja2表单处理与用户输入...

每日一库之 Go 语言开发者的神器—Gotx

点击上方蓝色“Go语言中文网”关注我们,领全套Go资料,每天学习Go语言简介Gotx是一个Go语言(Golang)的解释器和运行环境,只有单个可执行文件,绿色、跨平台,无需安装任何Go语言环境就可...

MySQL性能调优工具包制作(mysql性能调整)

一、最终工具包内容mysql_tuning_toolkit/├──scripts/#核心脚本│├──sysbench-pro.sh#...

掌握TensorFlow核心用法:从安装到实战的完整指南

一、为什么TensorFlow值得学习?作为全球使用最广泛的开源机器学习框架,TensorFlow已累计获得超过17万GitHub星标,支撑着Google搜索、Waymo自动驾驶、NASA卫星图像分析...

如何把PY 打包成EXE安装文件(pypy 打包exe)

将Python脚本打包成EXE文件通常使用第三方工具实现,以下是详细步骤和注意事项:...

Pygame Zero 详细使用教程(python zerorpc)

PygameZero是一个基于Pygame的简化游戏开发框架,特别适合初学者和快速原型开发。它隐藏了许多底层的复杂性,使得开发者可以更专注于游戏逻辑的实现。本文将通过分析提供的代码,详细介绍如...

Stable diffusion AI画图辅助脚本 Script 的使用(二)

本篇为脚本使用介绍的第二部分,主要介绍Promptmatrix提示词矩阵以及UltimateSDUpscale终极SD放大这两个脚本,同时也简单介绍一下如何编写自己的脚本。1、Promp...

一文明白Python 的import如何工作

Pythonimport系统的基础知识Python的import系统是该语言设计的关键部分,允许模块化编程和代码的轻松重用。了解这个系统对任何Python程序员都很重要,因为它决定了代码的结构...

Highlight.js - 前端的代码语法高亮库

千辛万苦写了篇技术分享,贴了一堆代码,兴高采烈地发到了自己的博客网站上。结果却发现代码全是白底黑字,字体还难看得很,你瞬间就没了兴致。能不能让网页也能像IDE那样,做带语法高亮的炫酷显示呢?来看一...

xbox xsx/s ps2模拟器 战神12,北欧女神2 配置教程

xsxxss下载PS2独立模拟器,Retroarch全能模拟器地址。...

RetroArch 着色器、金手指怎么用? 重返复古游戏萤幕滤镜效果

自从上次分享RetroArch模拟器的一些技巧后,许多模拟器新用户对老旧游戏机感到好奇,为什么游戏画面看起来会有很多马赛克。这主要是因为当年的游戏开发商是针对当时的屏幕进行设计的,所以在现在的高分辨率...