FFMPEG源码之过滤器

功能介绍

FFmpeg的过滤器是用于对音视频流进行处理和转换的模块。它可以对输入流进行各种操作,如调整大小、调节亮度、对比度、裁剪、旋转等。

操作步骤

实现对某个视频的放大操作的详细步骤如下:

  1. 配置和初始化FFmpeg:在开始编写代码之前,需要配置和初始化FFmpeg库。这包括引入头文件、注册相关的编解码器和过滤器等。
  2. 打开输入文件:使用avformat_open_input函数打开要处理的视频文件,并获取音视频流信息。
  3. 配置输入流上下文和输出流上下文:使用avcodec_parameters_to_context函数从输入流中获取编解码器参数,并将其配置给相应的解码器上下文。创建输出流上下文,指定输出文件格式及其编码参数。
  4. 配置过滤器图:使用avfilter_graph_alloc函数创建过滤器图。
  5. 添加输入过滤器:使用avfilter_graph_create_filter函数为过滤器图添加输入过滤器。输入过滤器的参数是输入流上下文中的解码器上下文。
  6. 添加输出过滤器:使用avfilter_graph_create_filter函数为过滤器图添加输出过滤器。输出过滤器的参数是输出流上下文中的编码器上下文。
  7. 创建过滤器链:使用avfilter_graph_parse2函数将过滤器图中的过滤器链接起来,形成过滤器链。
  8. 配置过滤器链:使用avfilter_graph_config函数对过滤器链进行配置,包括设置过滤器参数和连接输入输出。
  9. 创建帧对象:使用av_frame_alloc函数创建待处理的帧对象。
    从输入文件中读取帧:使用av_read_frame函数从输入文件中循环读取帧数据,直到所有帧读取完毕。
  10. 处理帧数据:在帧数据处理循环中,将读取到的帧数据传入过滤器链进行处理。
  11. 将处理后的帧数据写入输出文件:使用av_frame_get_best_effort_timestamp函数获取帧的时间戳,然后使用av_interleaved_write_frame函数向输出文件写入帧数据。
  12. 释放资源:循环结束后,释放所有资源,包括释放帧对象、关闭输入文件、关闭输出文件等。

源码实现

下面是使用FFmpeg过滤器功能实现将input.mp4文件中的视频宽度扩大两倍并水平翻转的代码流程

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[]) {
    const char *filters = "scale=w=2*iw:h=ih, hflip";
    const char *video_file = "input.mp4";

    // 注册所有的过滤器
    avfilter_register_all();

    // 用于存储视频文件的格式上下文
    AVFormatContext *format_ctx = NULL;

    // 打开视频文件并获取格式上下文
    if (avformat_open_input(&format_ctx, video_file, NULL, NULL) != 0) {
        fprintf(stderr, "Unable to open video file.");
        return -1;
    }

    // 检索流信息
    if (avformat_find_stream_info(format_ctx, NULL) < 0) {
        fprintf(stderr, "Unable to retrieve stream information.");
        avformat_close_input(&format_ctx);
        return -1;
    }

    // 查找视频流索引
    int video_stream_index = av_find_best_stream(format_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (video_stream_index == AVERROR_STREAM_NOT_FOUND) {
        fprintf(stderr, "Unable to find video stream.");
        avformat_close_input(&format_ctx);
        return -1;
    }

    // 获取视频流解码器上下文
    AVCodecContext *codec_ctx = format_ctx->streams[video_stream_index]->codec;

    // 查找视频流解码器
    AVCodec *codec = avcodec_find_decoder(codec_ctx->codec_id);
    if (codec == NULL) {
        fprintf(stderr, "Unable to find decoder.");
        avformat_close_input(&format_ctx);
        return -1;
    }


    // 打开视频流解码器
    if (avcodec_open2(codec_ctx, codec, NULL) != 0) {
        fprintf(stderr, "Unable to open decoder.");
        avformat_close_input(&format_ctx);
        return -1;
    }

    // 创建过滤器图
    AVFilterGraph *filter_graph = avfilter_graph_alloc();
    if (!filter_graph) {
        fprintf(stderr, "Unable to create filter graph.\n");
        avcodec_close(codec_ctx);
        avformat_close_input(&format_ctx);
        return -1;
    }

    // 创建输入过滤器
    AVFilter *buffer_src = avfilter_get_by_name("buffer");
    AVFilterContext *buffer_src_ctx;
    if (avfilter_graph_create_filter(&buffer_src_ctx, buffer_src, "in", NULL, NULL, filter_graph) < 0) {
        fprintf(stderr, "Unable to create buffer source filter.\n");
        avfilter_graph_free(&filter_graph);
        avcodec_close(codec_ctx);
        avformat_close_input(&format_ctx);
        return -1;
    }

    // 设置输入过滤器参数
    if (av_opt_set_bin(buffer_src_ctx, "video_size", (uint8_t*)&codec_ctx->width, sizeof(codec_ctx->width), AV_OPT_SEARCH_CHILDREN) < 0) {
        fprintf(stderr, "Unable to set video size.\n");
        avfilter_graph_free(&filter_graph);
        avcodec_close(codec_ctx);
        avformat_close_input(&format_ctx);
        return -1;
    }

    // 创建输出过滤器
    AVFilterInOut *outputs = avfilter_inout_alloc();
    AVFilterInOut *inputs = avfilter_inout_alloc();
    AVFilter *buffer_sink = avfilter_get_by_name("buffersink");
    AVFilterContext *buffer_sink_ctx;
    if (avfilter_graph_create_filter(&buffer_sink_ctx, buffer_sink, "out", NULL, NULL, filter_graph) < 0) {
        fprintf(stderr, "Unable to create buffer sink filter.\n");
        avfilter_graph_free(&filter_graph);
        avfilter_inout_free(&outputs);
        avfilter_inout_free(&inputs);
        avcodec_close(codec_ctx);
        avformat_close_input(&format_ctx);
        return -1;
    }

    // 创建输入过滤器参数
    enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };

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

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

    // 连接输入输出过滤器
    if (avfilter_graph_parse_ptr(filter_graph, filters, &inputs, &outputs, NULL) < 0) {
        fprintf(stderr, "Unable to parse filter graph.\n");
        avfilter_graph_free(&filter_graph);
        avfilter_inout_free(&outputs);
        avfilter_inout_free(&inputs);
        avcodec_close(codec_ctx);
        avformat_close_input(&format_ctx);
        return -1;
    }

    // 配置过滤器图
    if (avfilter_graph_config(filter_graph, NULL) < 0) {
        fprintf(stderr, "Unable to configure filter graph.\n");
        avfilter_graph_free(&filter_graph);
        avfilter_inout_free(&outputs);
        avfilter_inout_free(&inputs);
        avcodec_close(codec_ctx);
        avformat_close_input(&format_ctx);
        return -1;
    }

    // 创建输入帧和输出帧
    AVFrame *frame = av_frame_alloc();
    AVFrame *out_frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Unable to allocate frame.\n");
        avfilter_graph_free(&filter_graph);
        avfilter_inout_free(&outputs);
        avfilter_inout_free(&inputs);
        avcodec_close(codec_ctx);
        avformat_close_input(&format_ctx);
        return -1;
    }

    // 打开输出文件
    FILE *video_dst_file = fopen("output.mp4", "wb");
    if (!video_dst_file) {
        fprintf(stderr, "Unable to open output file.\n");
        avfilter_graph_free(&filter_graph);
        avfilter_inout_free(&outputs);
        avfilter_inout_free(&inputs);
        av_frame_free(&frame);
        avcodec_close(codec_ctx);
        avformat_close_input(&format_ctx);
        return -1;
    }

    // 初始化空的AVPacket结构体,用于存储解码之后的数据
    AVPacket packet = { 0 };

    // 读取每个视频包
    while (av_read_frame(format_ctx, &packet) >= 0) {
        // 检查是否是视频流
        if (packet.stream_index == video_stream_index) {
            // 将视频包发送到解码器进行解码
            if (avcodec_send_packet(codec_ctx, &packet) != 0) {
                fprintf(stderr, "Error sending packet to decoder.\n");
                break;
            }

            // 解码每个解码帧
            while (avcodec_receive_frame(codec_ctx, frame) == 0) {
                // 将解码帧发送到过滤器图进行处理
                if (av_buffersrc_add_frame_flags(buffer_src_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF) != 0) {
                    fprintf(stderr, "Error adding frame to filter graph.\n");
                    break;
                }

                // 获取过滤后的帧
                while (av_buffersink_get_frame(buffer_sink_ctx, out_frame) == 0) {
                    // 直接将帧数据写入输出文件
                    fwrite(out_frame->data[0], 1, out_frame->linesize[0] * out_frame->height, video_dst_file);
                    fwrite(out_frame->data[1], 1, out_frame->linesize[1] * out_frame->height / 2, video_dst_file);
                    fwrite(out_frame->data[2], 1, out_frame->linesize[2] * out_frame->height / 2, video_dst_file);

                    // 释放帧对象
                    av_frame_unref(out_frame);
                }
            }
            // 释放帧对象
            av_frame_unref(frame);
        }

        // 释放数据包对象
        av_packet_unref(&packet);
    }

    // 关闭输出文件
    fclose(video_dst_file);

    // 释放资源
    avfilter_graph_free(&filter_graph);
    avfilter_inout_free(&outputs);
    avfilter_inout_free(&inputs);
    av_frame_free(&frame);
    avcodec_free_context(&codec_ctx);
    avformat_close_input(&format_ctx);

    return 0;
}


如果有多个过滤器可以通过遍历过滤器源码数组来创建和连接多个过滤器。每次循环迭代,我们创建一个新的过滤器,并与前一个过滤器进行连接。最后一个过滤器将连接到输出过滤器。请注意,在每次迭代中,我们使用前一个过滤器的输出过滤器上下文作为当前过滤器的输入。

const char *filters[] = {
    "scale=w=2*iw:h=ih",
    "hflip",
    "rotate=30*PI/180"
};

// ...

// 创建输入输出过滤器链表
AVFilterInOut *outputs = avfilter_inout_alloc();
AVFilterInOut *inputs = avfilter_inout_alloc();

// 创建输入过滤器
AVFilterContext *buffer_src_ctx = NULL;
if (avfilter_graph_create_filter(&buffer_src_ctx, buffer_src, "in", NULL, NULL, filter_graph) < 0) {
    // 错误处理
}

// 设置输入过滤器参数
if (av_opt_set_bin(buffer_src_ctx, "video_size", (uint8_t*)&codec_ctx->width, sizeof(codec_ctx->width), AV_OPT_SEARCH_CHILDREN) < 0) {
    // 错误处理
}

// 循环遍历过滤器源码数组,并创建过滤器
for (int i = 0; i < num_filters; i++) {
    // 创建输出过滤器
    AVFilterContext *filter_ctx = NULL;
    if (avfilter_graph_create_filter(&filter_ctx, filter, "filter_name", NULL, NULL, filter_graph) < 0) {
        // 错误处理
    }

    // 连接过滤器
    if (avfilter_link(buffer_src_ctx, 0, filter_ctx, 0) < 0) {
        // 错误处理
    }

    buffer_src_ctx = filter_ctx; // 更新buffer_src_ctx为当前过滤器的输出过滤器上下文
}

// 创建输出过滤器
AVFilterContext *buffer_sink_ctx = NULL;
if (avfilter_graph_create_filter(&buffer_sink_ctx, buffer_sink, "out", NULL, NULL, filter_graph) < 0) {
    // 错误处理
}

// 连接输出过滤器
if (avfilter_link(buffer_src_ctx, 0, buffer_sink_ctx, 0) < 0) {
    // 错误处理
}

// 配置过滤器图
if (avfilter_graph_config(filter_graph, NULL) < 0) {
    // 错误处理
}

// ...

// 在解码和过滤循环中处理帧

// ...

// 释放资源
avfilter_graph_free(&filter_graph);
avfilter_inout_free(&outputs);
avfilter_inout_free(&inputs);

// ...

你可能感兴趣的:(FFMPEG,ffmpeg)