剖析 FFmpeg:从基本功能到过滤器,实现音视频处理的灵活性

发布于:2025-05-09 ⋅ 阅读:(15) ⋅ 点赞:(0)

1.解复用

解复用就是把容器中的媒体流分离出来,方便我们对媒体流处理。


step1对媒体文件上下文初始化

AVFormatContext *ifmt_ctx = NULL; 

int ret = avformat_open_input(&ifmt_ctx, in_filename, NULL, NULL);//头部信息


ret = avformat_find_stream_info(ifmt_ctx, NULL);//

av_dump_format(ifmt_ctx, 0, in_filename, 0);

解析
int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);

  • AVFormatContext **ps:这是一个指向 AVFormatContext 指针的指针。AVFormatContext 属于 FFmpeg 里的核心结构体,它对输入或输出流的格式信息进行存储,像文件头、元数据、流信息等。调用该函数时,需传入一个指向 - - AVFormatContext 指针的指针,函数会为 AVFormatContext 分配内存,并且把其地址存储在这个指针里。
  • const char *url:这是一个字符串,代表输入流的 URL 或文件名。它可以是本地文件的路径,也能是网络流的 URL,比如 http://example.com/stream.mp4。
  • ff_const59 AVInputFormat *fmt:这是一个指向 AVInputFormat 结构体的指针,其作用是指定输入流的格式。若传入 NULL,FFmpeg 会自动对输入流的格式进行探测。
  • AVDictionary **options:这是一个指向 AVDictionary 指针的指针,用于传递额外的选项。AVDictionary 是一个键值对的集合,能够用来设置一些特定的参数,像编解码器选项、网络选项等。如果不需要传递额外选项,可以传入 NULL。

实现功能:读取输入流的文件头和元数据,将这些信息存储在AVFormatContext 中。
ret = avformat_find_stream_info(ifmt_ctx, NULL);
第二个参数与上面第四个一样
实现功能:

  • 填充流信息到 AVFormatContext:将探测到的每个流的详细信息填充到 AVFormatContext 中的 streams 数组里的对应 AVStream 结构体中。后续进行解码、处理等操作时,可以从这些结构体中获取所需的信息。
  • 在调用 avformat_open_input 后,AVFormatContext 中仅包含了部分基本的格式信息。该函数会读取一定数量的数据包,从中分析出每个流(如音频流、视频流、字幕流等)的详细信息,如编解码器类型、帧率、采样率、分辨率等。

step2 提取音频流-aac

audio_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
AVPacket pkt;

av_init_packet(&pkt);

av_read_frame(ifmt_ctx, &pkt) >=0 

fwrite( pkt.data, 1, pkt.size, aac_fd);

int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type, int wanted_stream_nb, int related_stream, AVCodec **decoder_ret, int flags);

  • AVFormatContext *ic:即代码里的 ifmt_ctx,它是一个指向 AVFormatContext 结构体的指针。AVFormatContext 是 FFmpeg 中用于存储输入或输出流格式信息的核心结构体,其中包含了多个流的信息,像音频流、视频流、字幕流等。
  • enum AVMediaType type:也就是代码中的 AVMEDIA_TYPE_AUDIO,此参数用来指定要查找的流的类型。AVMediaType 是一个枚举类型,常见的值有 AVMEDIA_TYPE_AUDIO(音频流)、AVMEDIA_TYPE_VIDEO(视频流)、AVMEDIA_TYPE_SUBTITLE(字幕流)等。
  • int wanted_stream_nb:在代码里为 -1,该参数指定你期望查找的流的编号。若为 -1,函数会自动查找最优的流;若指定了一个具体的编号,函数会尝试查找该编号对应的流。
  • int related_stream:代码中同样是 -1,该参数用于指定一个相关的流编号。例如,在查找音频流时,可以指定一个相关的视频流编号,函数会优先查找与该视频流相关的音频流。若为 -1,则不考虑相关性。
  • AVCodec **decoder_ret:代码中为 NULL,这是一个指向 AVCodec 指针的指针。若不为 NULL,函数会将找到的流所对应的最优解码器的指针存储在该指针指向的位置。若为 NULL,则不返回解码器信息。
  • int flags:代码中是 0,该参数是一个标志位,用于指定查找时的一些额外选项。通常设为 0 即可

av_read_frame

AVFormatContext *s:指向 AVFormatContext 结构体的指针,此结构体包含了输入流的格式信息。
AVPacket *pkt:指向 AVPacket 结构体的指针,函数会把读取到的数据包存储在这个结构体里。

功能:av_read_frame 会从输入流里读取一个数据包,这个数据包可能是音频数据包,也可能是视频数据包。读取到的数据包包含了原始的编码数据


step 3 提取视频流 h264

videoindex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
AVPacket pkt;
pkt = av_packet_alloc();
av_init_packet(pkt);

//涉及到两种 h264 两种模式 这里只讲最简单的ts流
ret = av_read_frame(ifmt_ctx, pkt);

size_t size = fwrite(pkt->data, 1, pkt->size, outfp);

2 解码

硬件播放要求:播放器的硬件设备,如显示器、扬声器等,只能处理原始的音视频信号。显示器需要接收由像素点组成的图像数据,按照一定的时序和格式进行显示;扬声器需要接收模拟音频信号,通过振动发出声音。而编码后的音视频数据是经过特定算法处理后的二进制数据,无法直接被硬件设备识别和处理。

解码还原数据:因此,播放器需要通过解码操作,将编码后的音视频数据还原为原始的图像帧和音频样本,然后将这些数据转换为适合硬件设备处理的信号格式,最终实现音视频的播放**。例如,对于视频数据,解码后得到的是一帧一帧的图像,播放器将这些图像按照一定的帧率依次显示在屏幕上,形成动态的视频画面;对于音频数据,解码后得到的是音频样本,播放器将这些样本转换为模拟音频信号,通过扬声器播放出来。

2.1 音频解码

针对于aac协议
音频解码本质上 将aac数据还原为pcm数据


step1 初始化解码器


AVPacket *pkt = NULL;
AVFrame *decoded_frame = NULL;

const AVCodec *codec;
AVCodecContext *codec_ctx= NULL;
AVCodecParserContext *parser = NULL;

enum AVCodecID audio_codec_id = AV_CODEC_ID_AAC;
codec = avcodec_find_decoder(audio_codec_id);
parser = av_parser_init(codec->id);
codec_ctx = avcodec_alloc_context3(codec);
avcodec_open2(codec_ctx, codec, NULL);
ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,
                               data, data_size,
                               AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
decoded_frame = av_frame_alloc();

AVCodecParserContext 是 FFmpeg 库中的一个重要结构体,主要用于解析编码数据(如视频或音频的压缩数据包),将其拆分成一个个独立的编码单元(如视频中的帧、音频中的音频帧),以便后续进行解码处理.

  • AVCodecParserContext *s
    含义:指向 AVCodecParserContext 结构体的指针,该结构体是在调用 av_parser_init 函数时初始化得到的。它用于存储解析器的上下文信息,例如当前解析的状态、已经解析的数据等。
    作用:函数会根据这个解析器上下文的状态来继续解析输入的编码数据。
  • AVCodecContext *avctx
    含义:指向 AVCodecContext 结构体的指针,该结构体包含了编解码器的上下文信息,如编解码器的类型、帧率、分辨率等。
    作用:解析过程中可能需要这些编解码器的上下文信息来正确地解析数据。例如,在解析视频数据时,需要知道视频的分辨率和帧率等信息来判断一个视频帧的边界。
  • uint8_t **poutbuf
    含义:指向指针的指针,用于存储解析后输出的完整编码单元(如视频帧、音频帧)的数据地址。
    作用:函数在解析过程中,如果发现一个完整的编码单元,会将该编码单元的数据地址存储在 *poutbuf 中。
    int *poutbuf_size
    含义:指向整数的指针,用于存储解析后输出的完整编码单元的数据大小。
    作用:函数会将解析出的完整编码单元的数据大小存储在 *poutbuf_size 中。
    const uint8_t *buf
    含义:指向输入的编码数据缓冲区的指针,该缓冲区包含了需要解析的编码数据。
    作用:函数会从这个缓冲区中读取数据进行解析。
  • int buf_size
    含义:输入的编码数据缓冲区的大小,即 buf 所指向的缓冲区中数据的字节数。
    作用:函数会根据这个大小来确定需要解析的数据范围。
  • int64_t pts
    含义:表示输入数据的展示时间戳(Presentation Time Stamp),用于指示该数据应该在何时展示。通常在不知道具体的时间戳时,可以使用 AV_NOPTS_VALUE 来表示。
    作用:解析器可以根据这个时间戳来处理数据的展示顺序,同时也可以将其传递给后续的解码和播放环节。
  • int64_t dts
    含义:表示输入数据的解码时间戳(Decoding Time Stamp),用于指示该数据应该在何时进行解码。同样,在不知道具体的时间戳时,可以使用 AV_NOPTS_VALUE 来表示。
    作用:在一些复杂的编码格式中,解码顺序和展示顺序可能不同,解析器可以根据这个时间戳来安排数据的解码顺序。
  • int64_t pos
    含义:表示输入数据在原始流中的位置,通常用于标记数据的来源和位置信息。
    作用:在一些需要定位和调试的场景中,这个参数可以帮助开发者确定数据的具体来源。

功能:
av_parser_parse2 函数会尝试从 input_data 中解析出一个完整的帧,如果解析成功,pkt->data 和 pkt->size 会被设置为该视频帧的数据和大小,可以将其传递给解码器进行解码

ret = avcodec_send_packet(dec_ctx, pkt); 发送帧
ret = avcodec_receive_frame(dec_ctx, frame); 接受帧

0:表示成功从解码器接收到一个完整的解码帧。此时,frame 指针所指向的 AVFrame 结构体中就包含了解码后的有效数据,可以对这些数据进行后续处理,例如显示视频帧、播放音频帧等。
AVERROR(EAGAIN):意味着当前解码器还没有准备好输出一个完整的解码帧。这可能是因为之前发送的数据包还在解码过程中,或者解码器需要更多的输入数据包才能解码出一个完整的帧。在这种情况下,你可以继续调用 avcodec_send_packet 向解码器发送更多的编码数据包,然后再次尝试调用 avcodec_receive_frame。
AVERROR_EOF:表明解码器已经完成了所有输入数据的解码,不会再输出更多的帧。通常在调用 avcodec_send_packet 时发送了一个空的数据包(表示输入结束)后,解码器处理完剩余数据,就会返回这个错误码。


step 2 解码

static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,
                   FILE *outfile)
{
    int i, ch;
    int ret, data_size;
    /* send the packet with the compressed data to the decoder */
    ret = avcodec_send_packet(dec_ctx, pkt);
    if(ret == AVERROR(EAGAIN))
    {
        fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
    }
    else if (ret < 0)
    {
        fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",
                av_get_err(ret), pkt->size);
//        exit(1);
        return;
    }

    /* read all the output frames (infile general there may be any number of them */
    while (ret >= 0)
    {
        // 对于frame, avcodec_receive_frame内部每次都先调用
        ret = avcodec_receive_frame(dec_ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0)
        {
            fprintf(stderr, "Error during decoding\n");
            exit(1);
        }
        data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);
        if (data_size < 0)
        {
            /* This should not occur, checking just for paranoia */
            fprintf(stderr, "Failed to calculate data size\n");
            exit(1);
        }
        static int s_print_format = 0;
        if(s_print_format == 0)
        {
            s_print_format = 1;
            print_sample_format(frame);
        }
 
        for (i = 0; i < frame->nb_samples; i++)
        {
            for (ch = 0; ch < dec_ctx->channels; ch++)  // 交错的方式写入, 大部分float的格式输出
                fwrite(frame->data[ch] + data_size*i, 1, data_size, outfile);
        }
    }
}

核心为

 ret = avcodec_send_packet(dec_ctx, pkt);
 ret = avcodec_receive_frame(dec_ctx, frame);
 for (i = 0; i < frame->nb_samples; i++)
    {
        for (ch = 0; ch < dec_ctx->channels; ch++)  // 交错的方式写入, 大部分float的格式输出
              fwrite(frame->data[ch] + data_size*i, 1, data_size, outfile);
              }

int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

0:表示成功从解码器接收到一个完整的解码帧。此时,frame 指针所指向的 AVFrame 结构体中就包含了解码后的有效数据,可以对这些数据进行后续处理,例如显示视频帧、播放音频帧等。
AVERROR(EAGAIN):意味着当前解码器还没有准备好输出一个完整的解码帧。这可能是因为之前发送的数据包还在解码过程中,或者解码器需要更多的输入数据包才能解码出一个完整的帧。在这种情况下,你可以继续调用 avcodec_send_packet 向解码器发送更多的编码数据包,然后再次尝试调用 avcodec_receive_frame。
AVERROR_EOF:表明解码器已经完成了所有输入数据的解码,不会再输出更多的帧。通常在调用 avcodec_send_packet 时发送了一个空的数据包(表示输入结束)后,解码器处理完剩余数据,就会返回这个错误码。

一个音频帧 包含许多样本点 ,一个样本点包含几个声道 这些是关于音频的知识点


2.2 视频解码


step1 初始化
前面和音频解码一模一样,就是解码器不一样

video_codec_id = AV_CODEC_ID_H264;

step2 解码
前面也是一模一样

 for(int j=0; j<frame->height; j++)
            fwrite(frame->data[0] + j * frame->linesize[0], 1, frame->width, outfile);
        for(int j=0; j<frame->height/2; j++)
            fwrite(frame->data[1] + j * frame->linesize[1], 1, frame->width/2, outfile);
        for(int j=0; j<frame->height/2; j++)
            fwrite(frame->data[2] + j * frame->linesize[2], 1, frame->width/2, outfile);

一般我们用的就是 yuv420p 也就说 平面写入 这个涉及到视频知识 建议了解


3 修饰

3.1 avio

AVIO(AV Input/Output)是 FFmpeg 里用于处理输入输出操作的模块,对于播放器而言,AVIO 非常必要,下面从几个方面为你详细阐述:
支持多种输入源

  • 本地文件播放:播放器需要能够读取本地存储的音视频文件,如 MP4、AVI、MKV 等。AVIO 提供了访问本地文件系统的能力,借助 avio_open 等函数可以打开本地文件,然后读取文件中的数据。例如,当用户在播放器中选择一个本地的 MP4 文件进行播放时,AVIO 会负责从文件系统中读取文件内容,为后续的解码和播放操作提供数据支持。
  • 网络流播放:如今在线播放音视频内容越来越普遍,像在线视频网站、直播平台等。AVIO 支持多种网络协议,如 HTTP、RTMP、RTSP 等,能够从网络上获取音视频流。通过 avio_open2 函数可以打开网络 URL,建立网络连接并接收数据。例如,在播放网络直播流时,AVIO 会持续从服务器接收音视频数据,保证播放的流畅性。

数据缓冲与管理

  • 缓冲机制:音视频数据的读取和处理需要一定的时间,为了避免播放过程中出现卡顿,播放器通常需要使用缓冲机制。AVIO 提供了数据缓冲功能,它会将读取到的数据暂时存储在缓冲区中,解码器可以从缓冲区中获取数据进行解码。这样可以平衡数据读取和处理的速度差异,提高播放的稳定性。
  • 数据管理:AVIO 还负责管理数据的读取位置和字节顺序等。在读取文件或网络流时,它会记录当前的读取位置,确保数据的正确读取。同时,对于不同字节顺序的数据,AVIO 会进行相应的转换,保证播放器能够正确处理数据。

统一的输入输出接口

  • 简化开发:AVIO 为播放器提供了统一的输入输出接口,无论输入源是本地文件还是网络流,播放器都可以使用相同的接口进行数据读取操作。这样可以简化播放器的开发过程,减少代码的复杂度。开发者只需要关注数据的解码和播放逻辑,而不需要关心具体的输入源和数据读取方式。
  • 可扩展性:由于 AVIO 提供了统一的接口,当需要支持新的输入源或协议时,只需要在 AVIO 模块中添加相应的实现即可,而不需要对播放器的其他部分进行大规模修改。这提高了播放器的可扩展性,使得播放器能够适应不断变化的需求。

其实本质上是将avformat_open_input 函数内部会调用 AVIO 的相关函数来打开本地文件,为后续的播放操作做好准备。

 uint8_t *io_buffer = av_malloc(BUF_SIZE);
    AVIOContext *avio_ctx = avio_alloc_context(io_buffer, BUF_SIZE, 0, (void *)in_file,    \
                                               read_packet, NULL, NULL);
    AVFormatContext *format_ctx = avformat_alloc_context();
    format_ctx->pb = avio_ctx;
    int ret = avformat_open_input(&format_ctx, NULL, NULL, NULL);

AVIOContext *avio_ctx = avio_alloc_context(io_buffer, BUF_SIZE, 0, (void *)in_file, read_packet, NULL, NULL);

io_buffer:之前分配的缓冲区。
BUF_SIZE:缓冲区的大小。
0:代表这是一个只读的 I/O 上下文。若为 1,则表示可写。
(void *)in_file:传递给回调函数的用户数据,一般是文件指针或者其他自定义的数据结构。
read_packet:自定义的读取数据包的回调函数,当需要从输入源读取数据时,会调用此函数。
NULL:这里是写数据包的回调函数,由于是只读上下文,所以设为 NULL。
NULL:这里是查找位置的回调函数,若不需要支持查找操作,可设为 NULL。

3.2 重采样

适配输出设备

  • 采样率差异:不同的音频输出设备支持的采样率有所不同。例如,CD 音频的标准采样率是 44.1kHz,而一些高清音频设备可能支持 96kHz 甚至 192kHz 的-- 采样率。当播放器播放的音频文件采样率与输出设备不匹配时,就需要进行重采样。比如,播放器播放一个 48kHz 采样率的音频文件,但输出设备只支持 44.1kHz,这时通过重采样将音频转换为 44.1kHz,才能让输出设备正常播放音频。
  • 声道布局不同:音频文件的声道布局和输出设备也可能存在差异。常见的声道布局有单声道、立体声、5.1 声道、7.1 声道等。若播放器播放的是 5.1 声道音频,而输出设备是立体声扬声器,就需要通过重采样将 5.1 声道音频转换为立体声,以适配输出设备。

兼容多种音频源

  • 不同格式音频:在实际应用中,播放器要支持多种音频格式,像 MP3、AAC、WAV 等,这些格式的音频可能采用不同的采样率和声道布局。播放器需要对这些不同格式的音频进行重采样,使其能够统一输出到音频设备。例如,一个 MP3 文件的采样率是 44.1kHz 立体声,另一个 AAC 文件的采样率是 48kHz 5.1 声道,播放器在播放时需要将它们重采样到相同的采样率和声道布局,方便后续处理和输出。
  • 多音频流混合:有些播放器具备同时播放多个音频流的功能,如音视频同步播放或者多语言音频切换。这些音频流的采样参数可能各不相同,为了实现音频的同步和混合,需要对它们进行重采样,让它们的采样率、声道数等参数保持一致。

优化音质

  • 提升采样率:在某些情况下,通过提高音频的采样率可以提升音质。例如,将低采样率的音频重采样到高采样率,能让音频包含更多的细节和更宽的频率范围。不过,这种提升效果也会受到原始音频质量的限制。
    抗锯齿处理:重采样过程中可以进行抗锯齿处理,减少音频信号在采样过程中产生的混叠噪声,从而提高音频的清晰度和纯净度。

确保音频同步

  • 音视频同步:在播放视频时,音频和视频的同步至关重要。由于视频和音频的编码、解码过程可能存在差异,导致它们的时间戳不一致。通过重采样可以对音频的播放速度进行微调,确保音频和视频能够同步播放,避免出现音画不同步的问题。
  • 多声道同步:对于多声道音频,各个声道之间的同步也很重要。重采样可以保证各个声道的音频数据在时间上保持一致,让听众能够感受到正确的音频空间感和立体感。

step1 初始化

struct SwrContext *swr_ctx;
swr_ctx = swr_alloc();


 // 设置重采样参数
    /* set options */
    // 输入参数
    av_opt_set_int(swr_ctx, "in_channel_layout",    src_ch_layout, 0);
    av_opt_set_int(swr_ctx, "in_sample_rate",       src_rate, 0);
    av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", src_sample_fmt, 0);
    // 输出参数
    av_opt_set_int(swr_ctx, "out_channel_layout",    dst_ch_layout, 0);
    av_opt_set_int(swr_ctx, "out_sample_rate",       dst_rate, 0);
    av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0);
    
 if ((ret = swr_init(swr_ctx)) < 0) {
        fprintf(stderr, "Failed to initialize the resampling context\n");
        goto end;
    }
    
//给输入源分配内存空间
   ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize, src_nb_channels,
                                             src_nb_samples, src_sample_fmt, 0);
//
 max_dst_nb_samples = dst_nb_samples =
            av_rescale_rnd(src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);

    /* buffer is going to be directly written to a rawaudio file, no alignment */
    dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout);
    // 分配输出缓存内存
     ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize, dst_nb_channels,
                                             dst_nb_samples, dst_sample_fmt, 0);
ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, (const uint8_t **)src_data, src_nb_samples);

struct SwrContext *swr_ctx 中的 SwrContext 是 FFmpeg 库里用于音频重采样的上下文结构体,swr_ctx 则是指向该结构体的指针。借助这个结构体和相关函数,能够实现音频在采样率、声道布局、样本格式等方面的转换

int av_opt_set_int(void *obj, const char *name, int64_t val, int search_flags);

  • void *obj:指向要设置选项的对象的指针。在音频重采样场景中,通常是指向 SwrContext 结构体的指针,该结构体用于存储音频重采样的上下文信息;在其他场景下,也可能是指向编解码器上下文(AVCodecContext)等其他对象的指针。
  • const char *name:要设置的选项的名称,是一个字符串。例如,在设置音频重采样的参数时,可能会使用 “in_sample_rate” 表示输入音频的采样率,“out_channel_layout” 表示输出音频的声道布局等。
  • int64_t val:要设置的选项的值,是一个 64 位整数。具体的值根据选项的不同而不同,比如设置采样率时,该值就是具体的采样率数值(如 44100、48000 等);设置声道布局时,该值是对应的声道布局常量(如 AV_CH_LAYOUT_MONO、AV_CH_LAYOUT_STEREO 等)。
  • int search_flags:搜索标志,用于指定查找选项的方式。一般设置为 0 即可,表示使用默认的查找方式。

av_samples_alloc_array_and_samples 函数:这是 FFmpeg 中用于分配音频样本数据缓冲区的函数

&src_data:指向一个 uint8_t ** 类型的指针,用于存储分配后的音频数据缓冲区的指针数组。对于多声道音频,src_data 中的每个元素对应一个声道的数据缓冲区。
&src_linesize:指向一个 int 类型的指针,用于存储每行数据的大小。在音频处理中,每行数据通常对应一个声道的一个样本。
src_nb_channels:前面计算得到的输入音频的声道数量。
src_nb_samples:输入音频的样本数量。
src_sample_fmt:输入音频的样本格式,例如 AV_SAMPLE_FMT_S16 表示 16 位有符号整数格式。
0:表示是否进行内存对齐的标志,这里设置为 0 表示不进行特殊的内存对齐。

av_rescale_rnd 函数:这是 FFmpeg 提供的用于进行整数缩放和四舍五入的函数,其原型为 int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd);。

参数解释:
src_nb_samples:输入音频的样本数量。
dst_rate:输出音频的采样率。
src_rate:输入音频的采样率。
AV_ROUND_UP:四舍五入模式,表示向上取整。

功能:该函数的作用是根据输入音频的样本数量、输入采样率和输出采样率,计算出重采样后输出音频的样本数量。在重采样过程中,由于采样率发生了变化,样本数量也会相应改变。通过 av_rescale_rnd 函数可以准确计算出输出样本的数量,保证音频时长在重采样前后基本一致。

结果存储:计算得到的输出样本数量同时赋值给 max_dst_nb_samples 和 dst_nb_samples,max_dst_nb_samples 通常用于后续分配足够大的输出缓冲区,以确保能容纳所有重采样后的样

int swr_convert(struct SwrContext *s, uint8_t **out, int out_count, const uint8_t **in, int in_count);

参数 类型 描述
s struct SwrContext * 指向 SwrContext 结构体的指针。SwrContext 是 FFmpeg 中用于音频重采样的上下文结构体,它包含了重采样所需的各种配置信息,如输入输出的声道布局、采样率、样本格式等。在调用 swr_convert 之前,需要对 SwrContext 进行正确的初始化和参数设置。
out uint8_t ** 指向输出音频数据缓冲区的指针数组。对于多声道音频,out 数组中的每个元素对应一个声道的数据缓冲区。重采样后的音频数据将被存储在这些缓冲区中。
out_count int 输出缓冲区能够容纳的最大样本数量。这个参数决定了 swr_convert 函数最多可以输出多少个样本。需要注意的是,实际输出的样本数量可能会小于 out_count,具体取决于输入样本的数量和重采样的处理情况。
in const uint8_t ** 指向输入音频数据缓冲区的指针数组。同样,对于多声道音频,in 数组中的每个元素对应一个声道的数据缓冲区。输入的音频数据将从这些缓冲区中读取。如果设置为 NULL,则表示没有新的输入数据,swr_convert 会继续处理之前缓存的输入数据。
in_count int 输入缓冲区中的样本数量。如果 inNULL,则 in_count 应设置为 0。

4 过滤器

首先会对输入的音视频数据进行解码,将压缩的音视频数据转换为原始的音视频帧。然后,这些解码后的帧会被送入过滤器链中进行各种处理,如视频的缩放、裁剪、叠加,音频的音量调整、声道转换等操作。过滤器可以对帧的内容进行修改、变换,以满足用户的各种需求。处理完成后,再将处理后的帧进行编码,输出为新的音视频文件或流。

播放器使用过滤器主要有以下原因:一是提升播放质量,能改善音视频的画质和音质,如降噪、调整色彩等;二是实现格式转换与兼容,支持多种音视频格式,还能在不同格式间转换以适应设备;三是拓展功能,可添加字幕、特效,进行音频混音等;四是满足个性化需求,用户能根据自己的喜好对音视频进行定制化处理。

4.1 过滤器基本知识

滤镜图包含滤镜链 ->包含过滤器

AVFilterGraph
功能 : AVFilterGraph 是 FFmpeg 中用于处理音视频流的核心组件,它能将多个过滤器组合起来,对音视频数据进行复杂的处理
重要参数

struct AVFilterGraph
{
AVFilterContext **filters;//数组
unsigned nb_filters;
} 
  1. AVFilterContext **filters
    类型:指向 AVFilterContext 指针的指针,也就是一个 AVFilterContext 指针数组。
    功能:这个数组用于存储过滤器图中所有的过滤器上下文实例。每个 AVFilterContext 代表一个具体的过滤器,包含了该过滤器的状态信息和配置参数。通过这个数组,可以方便地对图中的所有过滤器进行遍历和管理。
    示例:假设我们创建了一个包含三个过滤器的过滤器图,分别是 scale(用于视频缩放)、crop(用于视频裁剪)和 overlay(用于视频叠加),那么 filters 数组可能会依次存储这三个过滤器对应的 AVFilterContext 指针。
  2. unsigned nb_filters
    类型:无符号整数。
    功能:表示 filters 数组中实际存储的 AVFilterContext 实例的数量,即过滤器图中当前包含的过滤器的个数。通过 nb_filters 可以准确知道过滤器图的规模,在遍历 filters 数组时也可以作为循环的终止条件。
    示例:如果 nb_filters 的值为 3,说明 filters 数组中存储了 3 个 AVFilterContext 指针,即当前过滤器图中有 3 个过滤器。

AVFilter
功能:AVFilter 是 FFmpeg 中用于表示一个抽象过滤器的结构体,它定义了过滤器的基本属性和功能
重要成员

  1. const char *name
    功能概述:该成员是一个指向常量字符数组的指针,用于存储过滤器的名称。过滤器名称是过滤器的唯一标识符,在 FFmpeg 的过滤器系统中具有重要作用,无论是用户在命令行中指定过滤器,还是代码中通过名称查找过滤器,都依赖于这个名称。
    示例说明:在你给出的注释中提到 overlay,这就是一个具体的过滤器名称。overlay 过滤器的功能是将一个视频叠加到另一个视频之上,常用于添加水印、画中画效果等场景。通过这个名称,用户可以在 FFmpeg 命令中使用该过滤器,例如 ffmpeg -i background.mp4 -i overlay.mp4 -filter_complex “overlay=10:10” output.mp4,其中 “overlay=10:10” 就指定了使用 overlay 过滤器,并设置叠加的位置为 (10, 10)。
  2. const AVFilterPad *inputs
    功能概述:该成员是一个指向常量 AVFilterPad 结构体数组的指针,用于描述过滤器的输入端口。输入端口定义了过滤器可以接收的输入数据的类型和数量,是过滤器与外部进行数据交互的入口。不同的过滤器可能有不同数量和类型的输入端口,通过这些端口,过滤器可以接收音视频数据进行处理。
    示例说明:以 overlay 过滤器为例,它有两个输入端口,一个用于接收背景视频,另一个用于接收要叠加的视频。inputs 数组会包含两个 AVFilterPad 结构体,分别描述这两个输入端口的属性,如支持的媒体类型(视频或音频)、端口名称等。这样,在构建过滤器图时,就可以将相应的视频流连接到这些输入端口,为过滤器提供输入数据。
  3. const AVFilterPad *outputs
    功能概述:该成员是一个指向常量 AVFilterPad 结构体数组的指针,用于描述过滤器的输出端口。输出端口定义了过滤器处理后输出数据的类型和数量,是过滤器将处理结果传递给其他过滤器或输出到外部的出口。与输入端口类似,不同的过滤器可能有不同数量和类型的输出端口。
    示例说明:还是以 overlay 过滤器为例,它只有一个输出端口,用于输出叠加后的视频。outputs 数组会包含一个 AVFilterPad 结构体,描述该输出端口的属性,如输出的媒体类型、端口名称等。在过滤器图中,这个输出端口可以连接到其他过滤器的输入端口,以便进一步处理叠加后的视频,或者直接作为最终的输出。

AVFilterContext
功能: FFmpeg 中表示一个具体过滤器实例的结构体,它将抽象的 AVFilter 实例化,包含了过滤器运行所需的状态和配置信息
重要成员:

struct AVFilterContext
{
const AVFilter *filter;//
char *name;
AVFilterPad *input_pads;
AVFilterLink **inputs;//输入连接
unsigned nb_inputs
AVFilterPad *output_pads;//输出连接
AVFilterLink **outputs;
unsigned nb_outputs;
struct AVFilterGraph *graph; // 从属于哪个AVFilterGraph
}
  1. const AVFilter *filter
    功能:指向 AVFilter 结构体的指针,用于指定该过滤器上下文所对应的抽象过滤器。AVFilter 定义了过滤器的通用属性和行为,而 AVFilterContext 则是其具体的运行实例。通过这个指针,AVFilterContext 能够调用 AVFilter 中定义的各种操作函数,实现具体的音视频处理功能。
    示例:若 filter 指向 scale 过滤器,那么该 AVFilterContext 就会按照 scale 过滤器的逻辑对输入的视频进行缩放处理。
  2. char *name
    功能:存储该过滤器上下文的名称,这是一个用户自定义的字符串,用于在调试、日志记录或过滤器图的管理中标识该过滤器实例。有了这个名称,在复杂的过滤器图中可以更方便地识别和定位特定的过滤器。
    示例:在一个包含多个 scale 过滤器的过滤器图中,可以将其中一个 scale 过滤器上下文命名为 “scale_main”,另一个命名为 “scale_preview”,这样就能清晰地区分它们。
  3. AVFilterPad *input_pads
    功能:指向 AVFilterPad 结构体数组的指针,描述了该过滤器的输入端口。AVFilterPad 定义了每个输入端口的属性,如支持的媒体类型(音频或视频)、端口名称等。通过这些输入端口,过滤器可以接收外部传入的音视频数据。
    示例:对于 overlay 过滤器,其 input_pads 数组可能包含两个 AVFilterPad,分别对应背景视频和要叠加的视频的输入端口。
  4. AVFilterLink **inputs
    功能:指向 AVFilterLink 指针数组的指针,每个 AVFilterLink 代表一个输入连接,用于连接该过滤器与其他过滤器的输出。通过这些连接,音视频数据可以从上游过滤器流入当前过滤器。
    示例:如果当前过滤器是 crop 过滤器,其 inputs 数组中的某个 AVFilterLink 可能连接到 scale 过滤器的输出,这样 scale 过滤器处理后的视频数据就可以流入 crop 过滤器进行裁剪处理。
  5. unsigned nb_inputs
    功能:表示 inputs 数组中实际存在的输入连接的数量,即该过滤器当前有多少个有效的输入。通过这个成员,可以准确知道过滤器接收数据的来源数量。
    示例:若 nb_inputs 的值为 2,说明该过滤器有两个输入连接,可能同时接收两个不同的音视频流进行处理。
  6. AVFilterPad *output_pads
    功能:指向 AVFilterPad 结构体数组的指针,描述了该过滤器的输出端口。与输入端口类似,AVFilterPad 定义了每个输出端口的属性,过滤器处理后的音视频数据将通过这些输出端口传递给其他过滤器或输出到外部。
    示例:scale 过滤器的 output_pads 数组通常只有一个 AVFilterPad,用于输出缩放后的视频数据。
  7. AVFilterLink **outputs
    功能:指向 AVFilterLink 指针数组的指针,每个 AVFilterLink 代表一个输出连接,用于连接该过滤器与其他过滤器的输入。通过这些连接,当前过滤器处理后的音视频数据可以流向其他过滤器进行进一步处理。
    示例:如果 scale 过滤器的 outputs 数组中的某个 AVFilterLink 连接到 overlay 过滤器的输入,那么 scale 过滤器缩放后的视频数据就会流入 overlay 过滤器进行叠加处理。
  8. unsigned nb_outputs
    功能:表示 outputs 数组中实际存在的输出连接的数量,即该过滤器当前有多少个有效的输出。通过这个成员,可以了解过滤器处理后的数据将流向多少个下游过滤器。
    示例:若 nb_outputs 的值为 1,说明该过滤器处理后的音视频数据只会流向一个下游过滤器。
  9. struct AVFilterGraph *graph
    功能:指向 AVFilterGraph 结构体的指针,用于指定该过滤器上下文所属的过滤器图。AVFilterGraph 是一个包含多个过滤器的集合,通过这个指针,AVFilterContext 可以与同一过滤器图中的其他过滤器进行交互,并且可以利用过滤器图提供的资源管理和调度功能。
    示例:在一个复杂的音视频处理流程中,多个过滤器可能会组成一个过滤器图,每个 AVFilterContext 都通过 graph 指针与这个过滤器图关联,共同完成音视频的处理任务。

AVFilterLink
功能:定义两个filters之间的联接
结构成员:

struct AVFilterLink
{
AVFilterContext *src;
AVFilterPad *srcpad;
AVFilterContext *dst;
AVFilterPad *dstpad;
struct AVFilterGraph *graph;
}

AVFilterPad
-定义filter的输⼊/输出接⼝
重要成员

struct AVFilterPad
{
const char *name;
AVFrame *(*get_video_buffer)(AVFilterLink *link, int w, int h);
AVFrame *(*get_audio_buffer)(AVFilterLink *link, int nb_samples);
int (*filter_frame)(AVFilterLink *link, AVFrame *frame);
AVFilterPad-定义filter的输⼊/输出接⼝22
int (*request_frame)(AVFilterLink *link);
}

AVFilterPad 结构体在 FFmpeg 里定义了过滤器的输入或输出接口,其成员规定了过滤器与外部交流数据的方式以及对数据进行处理的行为。下面详细解读该结构体的各个成员:

  1. const char *name
    功能:此成员是一个指向常量字符串的指针,代表输入或输出端口的名称。端口名称可用于在调试、日志记录以及过滤器图的构建过程中,对特定的输入或输出端口加以标识。
    示例:对于 overlay 过滤器,其输入端口可能分别命名为 “main” 和 “overlay”,以此表明哪个端口接收主视频流,哪个端口接收要叠加的视频流。
  2. AVFrame *(*get_video_buffer)(AVFilterLink *link, int w, int h);
    功能:这是一个函数指针,指向用于分配视频缓冲区的函数。当过滤器需要处理视频帧时,会调用此函数来获取一块合适的内存区域,用于存储视频数据。函数的参数包括一个 AVFilterLink 指针,它表示当前的连接,以及视频帧的宽度 w 和高度 h。
    示例:若一个 scale 过滤器要对输入的视频进行缩放处理,在处理过程中就会调用该函数来分配一个新的视频缓冲区,以存储缩放后的视频帧。
  3. AVFrame *(*get_audio_buffer)(AVFilterLink *link, int nb_samples);
    功能:同样是一个函数指针,不过指向的是用于分配音频缓冲区的函数。当过滤器需要处理音频帧时,会调用此函数来获取一块合适的内存区域,用于存储音频数据。函数的参数包含一个 AVFilterLink 指针以及音频帧中的样本数量 nb_samples。
    示例:在音频混音过滤器中,当需要处理多个音频流时,会调用该函数为混音后的音频数据分配缓冲区。
  4. int (*filter_frame)(AVFilterLink *link, AVFrame *frame);
    功能:这是一个函数指针,指向实际处理音视频帧的函数。当有新的音视频帧到达过滤器的输入端口时,会调用此函数对该帧进行处理。函数接收一个 AVFilterLink 指针和一个 AVFrame 指针作为参数,处理完成后返回一个整数值,表示处理结果(如成功或失败)。
    示例:在 crop 过滤器中,filter_frame 函数会对输入的视频帧进行裁剪操作,并将裁剪后的视频帧传递给下一个过滤器。
  5. int (*request_frame)(AVFilterLink *link);
    功能:这是一个函数指针,指向用于请求新帧的函数。当过滤器需要从上游过滤器获取新的音视频帧时,会调用此函数。函数接收一个 AVFilterLink 指针作为参数,返回一个整数值,表示请求结果(如成功、失败或需要更多数据)。
    示例:在一个视频播放流程中,当解码器将所有已解码的视频帧都传递给下游过滤器后,下游过滤器可能会调用 request_frame 函数,请求解码器继续解码并提供新的视频帧。

AVFilterInOut
过滤器链输⼊/输出的链接列表

常见的过滤器
以下是FFmpeg中常见的过滤器及其语法表格:

过滤器名称 功能 语法示例
scale 对视频进行缩放处理 scale=w:h
例如:scale=640:480 ,将视频缩放到宽640像素,高480像素;
scale=iw/2:ih/2 ,将视频宽高缩小为原来的一半,iwih 分别表示输入视频的宽和高。
crop 裁剪视频画面 crop=w:h:x:y
例如:crop=320:240:100:50 ,从视频中裁剪出宽320像素、高240像素的区域,起始坐标为 (x=100, y=50)
overlay 将一个视频叠加到另一个视频上 overlay=x:y
例如:overlay=10:20 ,将第二个输入视频叠加到第一个输入视频上,叠加位置为 (x=10, y=20)
fps 改变视频的帧率 fps=fps
例如:fps=25 ,将视频帧率设置为25帧每秒。
eq 调整视频的亮度、对比度、饱和度等参数 eq=contrast:brightness:saturation:gamma:gamma_r:gamma_g:gamma_b:gamma_weight
例如:eq=1.2:0.1:1.5 ,将对比度设置为1.2,亮度设置为0.1,饱和度设置为1.5。
hflip 水平翻转视频画面 hflip
无需额外参数,直接使用 hflip 即可将视频水平翻转。
vflip 垂直翻转视频画面 vflip
无需额外参数,直接使用 vflip 即可将视频垂直翻转。
volume 调整音频的音量 volume=volume
例如:volume=0.5 ,将音频音量降低为原来的一半;volume=2dB ,将音频音量提高2分贝。
pan 调整音频声道布局 pan=out_channel_layout:gain_matrix
例如:`pan=stereo
afade 实现音频淡入淡出效果 afade=type:start_time:duration
例如:afade=t=in:st=0:d=5 ,从音频开始处进行5秒的淡入效果;afade=t=out:st=10:d=3 ,从第10秒开始进行3秒的淡出效果。

这些只是FFmpeg众多过滤器中的一部分,每个过滤器还可能有更多的参数和用法,可以通过 ffmpeg -h filter=filter_name 命令查看具体过滤器的详细信息。

4.2 简单过滤器

这⾥需要重点提的是两个特别的filter,⼀个是buffer,⼀个是buffersink,
滤波器buffer代表filter graph中的源头,原始数据就往这个filter节点输⼊的;
⽽滤波器buffersink代表filter graph中的输出节点,处理完成的数据从这个filter节点输出

在这里插入图片描述


step1初始化

avfilter_register_all();

AVFilterGraph* filter_graph = avfilter_graph_alloc();

sprintf(args,
        "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
        in_width, in_height, AV_PIX_FMT_YUV420P,
        1, 25, 1, 1);
AVFilter* bufferSrc = avfilter_get_by_name("buffer");   // AVFilterGraph的输入源 avfilter_get_by_name是 FFmpeg 提供的一个函数,其作用是根据滤镜的名称来查找并返回对应的AVFilter对象。
AVFilterContext* bufferSrc_ctx;//AVFilterContext结构体代表了一个滤镜的实例,每个滤镜在实际使用时都需要创建一个对应的
ret = avfilter_graph_create_filter(&bufferSrc_ctx, bufferSrc, "in", args, NULL, filter_graph);//初始化

AVBufferSinkParams *bufferSink_params;
    AVFilterContext* bufferSink_ctx;
    AVFilter* bufferSink = avfilter_get_by_name("buffersink");
    //AV_PIX_FMT_YUV420P 作为支持的像素格式,AV_PIX_FMT_NONE 作为数组的结束标志。这意味着该 buffersink 滤镜只会接收 YUV420P 格式的视频帧。
    enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };
    bufferSink_params = av_buffersink_params_alloc();
    bufferSink_params->pixel_fmts = pix_fmts;
    ret = avfilter_graph_create_filter(&bufferSink_ctx, bufferSink, "out", NULL,
                                       bufferSink_params, filter_graph);

int avfilter_graph_create_filter(AVFilterContext **filt_ctx, const AVFilter *filt, const char *name, const char *args, void *opaque, AVFilterGraph *graph_ctx);

AVFilterContext **filt_ctx
类型:指向 AVFilterContext 指针的指针。
功能:该参数用于存储创建好的过滤器上下文的指针。函数执行成功后,会将新创建的过滤器上下文的地址赋给 *filt_ctx。通过这个指针,后续就可以对该过滤器上下文进行操作,比如设置参数、连接其他过滤器等。
示例:调用函数前声明一个 AVFilterContext 指针变量 bufferSrc_ctx,然后将 &bufferSrc_ctx 作为参数传入函数,函数成功执行后,bufferSrc_ctx 就指向了新创建的过滤器上下文。
const AVFilter *filt
类型:指向 AVFilter 结构体的常量指针。
功能:指定要创建的过滤器的类型。AVFilter 结构体定义了过滤器的基本属性和操作函数,这个参数告诉函数需要创建哪种类型的过滤器实例。
示例:如果要创建一个 buffer 过滤器(常用于将数据输入到过滤器图中),可以先通过 avfilter_get_by_name(“buffer”) 获取 buffer 过滤器的 AVFilter 指针,然后将其作为该参数传入函数。
const char *name
类型:指向常量字符数组的指针。
功能:为新创建的过滤器上下文指定一个名称。这个名称主要用于调试、日志记录和在过滤器图中标识该过滤器实例。名称可以自定义,方便在复杂的过滤器图中区分不同的过滤器。
示例:可以将名称设置为 “in”,表示这个过滤器是输入过滤器,用于标识它在过滤器图中的作用。
const char *args
类型:指向常量字符数组的指针。
功能:用于传递初始化过滤器所需的参数。这些参数以字符串形式表示,不同的过滤器有不同的参数格式和含义。函数会根据这些参数对过滤器进行初始化设置。
示例:对于 buffer 过滤器,args 可能包含输入视频的宽度、高度、像素格式等信息,例如 “video_size=640x480:pix_fmt=rgb24:time_base=1/25”。
void *opaque
类型:通用指针。
功能:该参数通常用于传递一些额外的上下文信息,不过在大多数情况下可以设置为 NULL。某些过滤器可能会使用这个指针来访问自定义的数据或回调函数,但这取决于具体的过滤器实现。
示例:如果没有额外的上下文信息需要传递,就将其设置为 NULL。
AVFilterGraph *graph_ctx
类型:指向 AVFilterGraph 结构体的指针。
功能:指定新创建的过滤器上下文所属的过滤器图。过滤器图用于管理和组织多个过滤器上下文,将过滤器添加到过滤器图中后,它们可以相互连接,形成一个完整的音视频处理流程。
示例:先创建一个 AVFilterGraph 实例 filter_graph,然后将其作为该参数传入函数,这样新创建的过滤器上下文就会被添加到这个过滤器图中

在 FFmpeg 里,bufferSink_params 是为 buffersink 过滤器配置相关参数用的,它对应的类型是 AVBufferSinkParams 结构体

在处理视频时,像素格式是关键信息,不同的视频源或者处理步骤可能会产生不同像素格式的视频帧,像 AV_PIX_FMT_YUV420P、AV_PIX_FMT_RGB24 等。借助 bufferSink_params 能够明确 buffersink 过滤器所支持的像素格式。
要是处理的是音频数据,bufferSink_params 还能用于配置音频相关的参数,例如采样率、声道布局、样本格式等。通过指定这些参数,可以让 buffersink 过滤器接收特定格式的音频数据。


step2 滤镜图

 AVFilter *cropFilter = avfilter_get_by_name("crop");
    AVFilterContext *cropFilter_ctx;
    ret = avfilter_graph_create_filter(&cropFilter_ctx, cropFilter, "crop",
                                       "out_w=iw:out_h=ih/2:x=0:y=0", NULL, filter_graph);
    if (ret < 0) {
        printf("Fail to create crop filter\n");
        return -1;
    }

    // vflip filter
    //flip 滤镜使用起来非常方便,只需指定滤镜名称就能完成垂直翻转任务。
    AVFilter *vflipFilter = avfilter_get_by_name("vflip");
    AVFilterContext *vflipFilter_ctx;
    ret = avfilter_graph_create_filter(&vflipFilter_ctx, vflipFilter, "vflip", NULL, NULL, filter_graph);
    if (ret < 0) {
        printf("Fail to create vflip filter\n");
        return -1;
    }

    // overlay filter
    //overlay 滤镜用于将一个视频叠加到另一个视频之上
    //main_w:表示主视频的宽度。
//    overlay_w:表示叠加视频的宽度。
//    main_h:表示主视频的高度。
//    overlay_h:表示叠加视频的高度。
    AVFilter *overlayFilter = avfilter_get_by_name("overlay");
    AVFilterContext *overlayFilter_ctx;
    ret = avfilter_graph_create_filter(&overlayFilter_ctx, overlayFilter, "overlay",
                                       "y=0:H/2", NULL, filter_graph);
    if (ret < 0) {
        printf("Fail to create overlay filter\n");
        return -1;
    }

分别初始化过滤器


step3 配置滤镜图

 // src filter to split filter
//    // 将 sourceFilter1_ctx 的输出垫连接到 overlayFilter_ctx 的第一个输入垫
//    int ret1 = avfilter_link(sourceFilter1_ctx, 0, overlayFilter_ctx, 0);
//    // 将 sourceFilter2_ctx 的输出垫连接到 overlayFilter_ctx 的第二个输入垫
//    int ret2 = avfilter_link(sourceFilter2_ctx, 0, overlayFilter_ctx, 1);
    ret = avfilter_link(bufferSrc_ctx, 0, splitFilter_ctx, 0);
    if (ret != 0) {
        printf("Fail to link src filter and split filter\n");
        return -1;
    }
    // split filter's first pad to overlay filter's main pad
    ret = avfilter_link(splitFilter_ctx, 0, overlayFilter_ctx, 0);
    if (ret != 0) {
        printf("Fail to link split filter and overlay filter main pad\n");
        return -1;
    }
    // split filter's second pad to crop filter
    ret = avfilter_link(splitFilter_ctx, 1, cropFilter_ctx, 0);
    if (ret != 0) {
        printf("Fail to link split filter's second pad and crop filter\n");
        return -1;
    }
    // crop filter to vflip filter
    ret = avfilter_link(cropFilter_ctx, 0, vflipFilter_ctx, 0);
    if (ret != 0) {
        printf("Fail to link crop filter and vflip filter\n");
        return -1;
    }
    // vflip filter to overlay filter's second pad
    ret = avfilter_link(vflipFilter_ctx, 0, overlayFilter_ctx, 1);
    if (ret != 0) {
        printf("Fail to link vflip filter and overlay filter's second pad\n");
        return -1;
    }
    // overlay filter to sink filter
    ret = avfilter_link(overlayFilter_ctx, 0, bufferSink_ctx, 0);
    if (ret != 0) {
        printf("Fail to link overlay filter and sink filter\n");
        return -1;
    }
     ret = avfilter_link(overlayFilter_ctx, 0, bufferSink_ctx, 0);
ret = avfilter_graph_config(filter_graph, NULL);

avfilter_graph_config 检测滤镜图是不是配置完整


step4 处理帧

   if (av_buffersrc_add_frame(bufferSrc_ctx, frame_in) < 0) {
        printf("Error while add frame.\n");
        break;
    }
    // filter内部自己处理
    /* pull filtered pictures from the filtergraph */
    ret = av_buffersink_get_frame(bufferSink_ctx, frame_out);
    if (ret < 0)
        break;

4.3 复杂滤镜图

int init_filters(const AVFrame* main_frame, const AVFrame* logo_frame, int x, int y)
{
    int ret = 0;
    //用于表示滤镜图的输入和输出
    AVFilterInOut *inputs = NULL;
    AVFilterInOut *outputs = NULL;
    char filter_args[1024] = { 0 };

    filter_graph = avfilter_graph_alloc();
    if (!filter_graph) {
        printf("Error: allocate filter graph failed\n");
        return -1;
    }

    snprintf(filter_args, sizeof(filter_args),
             "buffer=video_size=%dx%d:pix_fmt=%d:time_base=1/25:pixel_aspect=%d/%d[main];" // Parsed_buffer_0
             "buffer=video_size=%dx%d:pix_fmt=%d:time_base=1/25:pixel_aspect=%d/%d[logo];" // Parsed_bufer_1
             "[main][logo]overlay=%d:%d[result];" // Parsed_overlay_2
             "[result]buffersink", // Parsed_buffer_sink_3
             main_frame->width, main_frame->height, main_frame->format, main_frame->sample_aspect_ratio.num, main_frame->sample_aspect_ratio.den,
             logo_frame->width, logo_frame->height, logo_frame->format, logo_frame->sample_aspect_ratio.num, logo_frame->sample_aspect_ratio.den,
             x, y);

    ret = avfilter_graph_parse2(filter_graph, filter_args, &inputs, &outputs);//描述滤镜图
    if (ret < 0) {
        printf("Cannot parse graph\n");
        return ret;
    }

    ret = avfilter_graph_config(filter_graph, NULL);
    if (ret < 0) {
        printf("Cannot configure graph\n");
        return ret;
    }

    // Get AVFilterContext from AVFilterGraph parsing from string
    mainsrc_ctx = avfilter_graph_get_filter(filter_graph, "Parsed_buffer_0");
    logosrc_ctx = avfilter_graph_get_filter(filter_graph, "Parsed_buffer_1");
    resultsink_ctx = avfilter_graph_get_filter(filter_graph, "Parsed_buffersink_3");

    return 0;
}

filter的语法
当滤波过程复杂到⼀定程度时,即需要多个滤波器进⾏复杂的连接来实现整个滤波过程,这时候对于
调⽤者来说,继续采⽤上述⽅法来构建滤波图就显得不够效率。对于复杂的滤波过程,ffmpeg提供了⼀个更为⽅便的滤波过程创建⽅式。
这种复杂的滤波器过程创建⽅式要求⽤户以字符串的⽅式描述各个滤波器之间的关系
关于语法解析
[variable]这个是一个输入或输出标签,一般语法就是 输入 filter=命令参数 输出
但是buffer没有输入,buffer_sink没有输出
int avfilter_graph_parse2(AVFilterGraph *graph, const char *filters, AVFilterInOut **inputs, AVFilterInOut **outputs);

AVFilterGraph *graph
类型:指向 AVFilterGraph 结构体的指针。
功能:表示要将解析后的过滤器添加到的目标过滤器图。AVFilterGraph 是 FFmpeg 中用于管理和组织多个过滤器上下文的结构体,所有解析得到的过滤器都会被添加到这个图中,以便后续进行音视频处理。
const char *filters
类型:指向常量字符数组的指针。
功能:该参数是一个包含过滤器图描述的字符串。字符串中描述了过滤器的名称、参数以及它们之间的连接关系。例如 “scale=640:480,crop=320:240💯50” 表示先使用 scale 过滤器将视频缩放到 640x480,再使用 crop 过滤器从缩放后的视频中裁剪出 320x240 的区域,起始坐标为 (100, 50)。
AVFilterInOut **inputs
类型:指向 AVFilterInOut 指针的指针。
功能:用于返回过滤器图的输入列表。AVFilterInOut 结构体表示过滤器图的输入或输出点,包含名称和对应的过滤器上下文等信息。函数执行成功后,*inputs 会指向一个链表,链表中的每个节点表示一个输入点。如果不需要输入信息,可以将其设置为 NULL。
AVFilterInOut **outputs
类型:指向 AVFilterInOut 指针的指针。
功能:用于返回过滤器图的输出列表。与 inputs 类似,函数执行成功后,*outputs 会指向一个链表,链表中的每个节点表示一个输出点。如果不需要输出信息,可以将其设置为 NULL。


网站公告

今日签到

点亮在社区的每一天
去签到