10-2_使用ffmpeg实现叠加

使用ffmpeg实现OSD叠加

1.使用命令实现OSD叠加

ffmpeg -i input.h264 -vf "drawbox=x=50:y=50:w=200:h=100:color=red@0.5:t=max, drawbox=x=300:y=200:w=100:h=100:color=blue@1.0:t=5, drawtext=text='2024-10-01':x=W-tw-10:y=10:fontcolor=white:fontsize=50" -c:v libx264 -f h264 output.h264
  • drawbox滤镜用于绘制色块和方框。
  • drawtext:滤镜用于在视频上添加文本。
    • text='2024-10-01'指定要显示的固定日期。
    • x=W-tw-10将文本放置在视频宽度减去文本宽度再减去10像素的位置,即右上角。
    • y=10将文本放置在距离顶部10像素的位置。
    • fontcolor参数指定文本颜色为白色。
    • fontsize参数指定文本字体大小为50。

执行后会在码流中叠加对应色块、方框,以及使用字库叠加文字至码流中。

2.使用程序实现OSD叠加

主函数流程如下所示:

  1. 初始化并解析输入参数。
  2. 初始化 FFmpeg 库。
  3. 打开输入文件并初始化解码器。
  4. 初始化输出文件上下文和视频编码器。
  5. 初始化滤镜图。
  6. 读取、解码、处理和编码视频帧。
  7. 刷新编码器并写入所有剩余帧。
  8. 写入文件尾部并清理所有资源。

2.1 滤镜图函数流程

滤镜图在视频处理过程中用于对视频帧进行各种滤镜操作,比如调整帧率、添加文本或图形等。我们将结合 init_filter_graph 函数和 main 函数中的相关部分来详细说明。

  1. 定义滤镜描述

init_filter_graph 函数中,我们首先定义了一个滤镜描述字符串 filter_descr,它包含了一系列滤镜操作:

const char *filter_descr = "fps=fps=25,"
                           "drawbox=x=50:y=50:w=200:h=100:color=red@0.5:t=max,"
                           "drawbox=x=300:y=200:w=100:h=100:color=blue@1.0:t=5,"
                           "drawtext=text='2024-10-01':x=W-tw-10:y=10:fontcolor=white:fontsize=50";

这个字符串定义了以下滤镜操作:

  • fps=fps=25:将帧率设置为 25 fps。
  • drawbox:在指定位置绘制红色和蓝色矩形。
  • drawtext:在视频帧的右上角绘制白色文本“2024-10-01”。
  1. 获取输入/输出滤镜

接下来,我们获取缓冲区源和接收器滤镜:

const AVFilter *buffersrc  = avfilter_get_by_name("buffer");
const AVFilter *buffersink = avfilter_get_by_name("buffersink");
  • buffer 滤镜用于接收解码后的帧。
  • buffersink 滤镜用于输出处理后的帧。
  1. 手动设置视频参数

我们手动构建一个包含视频参数的字符串 args

char args[512];
snprintf(args, sizeof(args),
         "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
         dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
         dec_ctx->time_base.num, dec_ctx->time_base.den,
         dec_ctx->sample_aspect_ratio.num, dec_ctx->sample_aspect_ratio.den);

这个字符串包含了视频的宽度、高度、像素格式、时间基和像素纵横比。

  1. 创建缓冲区源和接收器

使用 avfilter_graph_create_filter 创建缓冲区源和接收器:

if (avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, nullptr, filter_graph) < 0) {
    std::cerr << "Cannot create buffer source\n";
    return -1;
}

if (avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", nullptr, nullptr, filter_graph) < 0) {
    std::cerr << "Cannot create buffer sink\n";
    return -1;
}
  • buffersrc_ctx 是缓冲区源滤镜的上下文。
  • buffersink_ctx 是缓冲区接收器滤镜的上下文。
  1. 设置滤镜图的端点

我们设置滤镜图的输入和输出端点:

outputs->name = av_strdup("in");
outputs->filter_ctx = buffersrc_ctx;
outputs->pad_idx = 0;
outputs->next = nullptr;

inputs->name = av_strdup("out");
inputs->filter_ctx = buffersink_ctx;
inputs->pad_idx = 0;
inputs->next = nullptr;
  • outputs 表示滤镜图的输入端点。
  • inputs 表示滤镜图的输出端点。
  1. 解析并配置滤镜图

使用 avfilter_graph_parse_ptr 解析滤镜图,并使用 avfilter_graph_config 配置滤镜图:

if (avfilter_graph_parse_ptr(filter_graph, filter_descr, &inputs, &outputs, nullptr) < 0) {
    std::cerr << "Error parsing filter graph\n";
    return -1;
}

if (avfilter_graph_config(filter_graph, nullptr) < 0) {
    std::cerr << "Error configuring filter graph\n";
    return -1;
}
  • avfilter_graph_parse_ptr 解析滤镜描述字符串并构建滤镜图。
  • avfilter_graph_config 配置滤镜图,使其准备好处理帧。

main 函数中,滤镜图的使用主要体现在处理视频帧的过程中:

  1. 读取和解码视频帧
while (av_read_frame(fmt_ctx, &packet) >= 0) {
    if (packet.stream_index == 0) {
        if (avcodec_send_packet(dec_ctx, &packet) < 0) {
            std::cerr << "Error sending packet for decoding\n";
            break;
        }
        while (avcodec_receive_frame(dec_ctx, frame) == 0) {
            if (frame->pts == AV_NOPTS_VALUE) {
                frame->pts = frame_count++;
            }
            // 将解码后的帧推送到滤镜图
            if (av_buffersrc_add_frame(buffersrc_ctx, frame) < 0) {
                std::cerr << "Error feeding frame to filter graph\n";
                break;
            }
  • 读取输入文件中的帧并解码。
  • 将解码后的帧推送到滤镜图的缓冲区源滤镜。
  1. 从滤镜图中获取处理后的帧
            while (av_buffersink_get_frame(buffersink_ctx, filt_frame) >= 0) {
                // 编码过滤后的帧
                av_init_packet(&enc_pkt);
                enc_pkt.data = nullptr;
                enc_pkt.size = 0;
                filt_frame->pts = frame_count++;
                if (avcodec_send_frame(enc_ctx, filt_frame) >= 0) {
                    while (avcodec_receive_packet(enc_ctx, &enc_pkt) >= 0) {
                        enc_pkt.stream_index = 0;
                        av_packet_rescale_ts(&enc_pkt, enc_ctx->time_base, out_fmt_ctx->streams[0]->time_base);
                        av_interleaved_write_frame(out_fmt_ctx, &enc_pkt);
                        av_packet_unref(&enc_pkt);
                    }
                }
                av_frame_unref(filt_frame);
            }
            av_frame_unref(frame);
        }
    }
    av_packet_unref(&packet);
}
  • 从滤镜图的缓冲区接收器滤镜中获取处理后的帧。
  • 对处理后的帧进行编码并写入输出文件。

滤镜图使用步骤:初始化滤镜图、设置滤镜描述、创建缓冲区源和接收器、解析和配置滤镜图。

3.程序源码

extern "C" {
    #include <libavformat/avformat.h>
    #include <libavcodec/avcodec.h>
    #include <libavfilter/avfilter.h>
    #include <libavfilter/buffersink.h>
    #include <libavfilter/buffersrc.h>
    #include <libswscale/swscale.h>
    #include <libavutil/opt.h>
}

#include <iostream>
#include <string>

// 打开输入文件并使用用户指定的编解码器信息
int open_input_file(AVFormatContext **fmt_ctx, const char *filename, AVCodecContext **dec_ctx, int width, int height, AVPixelFormat pix_fmt, AVRational time_base) {
	// 查找 H.264 解码器
    AVCodec *decoder = avcodec_find_decoder(AV_CODEC_ID_H264);
    if (!decoder) {
        std::cerr << "Codec not found\n";
        return -1;
    }
	// 分配解码器上下文
    AVCodecContext *dec_ctx_local = avcodec_alloc_context3(decoder);
    if (!dec_ctx_local) {
        std::cerr << "Could not allocate video codec context\n";
        return -1;
    }

    // 手动指定编解码器参数
    dec_ctx_local->width = width;
    dec_ctx_local->height = height;
    dec_ctx_local->pix_fmt = pix_fmt;
    dec_ctx_local->time_base = time_base;
	// 打开编解码器
    if (avcodec_open2(dec_ctx_local, decoder, nullptr) < 0) {
        std::cerr << "Could not open codec\n";
        return -1;
    }

    *dec_ctx = dec_ctx_local;

    // 打开输入文件格式上下文
    if (avformat_open_input(fmt_ctx, filename, nullptr, nullptr) < 0) {
        std::cerr << "Error opening input file\n";
        return -1;
    }

    return 0;
}

// 初始化滤镜图以进行处理
int init_filter_graph(AVFilterGraph **graph, AVFilterContext **src_ctx, AVFilterContext **sink_ctx, AVCodecContext *dec_ctx) {
	// 分配滤镜图
    AVFilterGraph *filter_graph = avfilter_graph_alloc();
    AVFilterContext *buffersrc_ctx = nullptr;
    AVFilterContext *buffersink_ctx = nullptr;
	// 定义滤镜描述
    const char *filter_descr = "fps=fps=25,"
	    		       "drawbox=x=50:y=50:w=200:h=100:color=red@0.5:t=max,"
                               "drawbox=x=300:y=200:w=100:h=100:color=blue@1.0:t=5,"
                               "drawtext=text='2024-10-01':x=W-tw-10:y=10:fontcolor=white:fontsize=50";

    // 输入/输出滤镜
    const AVFilter *buffersrc  = avfilter_get_by_name("buffer");
    const AVFilter *buffersink = avfilter_get_by_name("buffersink");

    AVFilterInOut *inputs  = avfilter_inout_alloc();
    AVFilterInOut *outputs = avfilter_inout_alloc();

    // 手动指定用户输入的视频参数
    char args[512];
    snprintf(args, sizeof(args),
             "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
             dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
             dec_ctx->time_base.num, dec_ctx->time_base.den,
             dec_ctx->sample_aspect_ratio.num, dec_ctx->sample_aspect_ratio.den);

    std::cout << "Filter graph parameters: " << args << std::endl;

 	// 创建缓冲区源和接收器
    if (avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, nullptr, filter_graph) < 0) {
        std::cerr << "Cannot create buffer source\n";
        return -1;
    }

    if (avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", nullptr, nullptr, filter_graph) < 0) {
        std::cerr << "Cannot create buffer sink\n";
        return -1;
    }

    // 设置滤镜图的端点
    outputs->name = av_strdup("in");
    outputs->filter_ctx = buffersrc_ctx;
    outputs->pad_idx = 0;
    outputs->next = nullptr;

    inputs->name = av_strdup("out");
    inputs->filter_ctx = buffersink_ctx;
    inputs->pad_idx = 0;
    inputs->next = nullptr;

    // 解析并配置滤镜图
    if (avfilter_graph_parse_ptr(filter_graph, filter_descr, &inputs, &outputs, nullptr) < 0) {
        std::cerr << "Error parsing filter graph\n";
        return -1;
    }

    if (avfilter_graph_config(filter_graph, nullptr) < 0) {
        std::cerr << "Error configuring filter graph\n";
        return -1;
    }

    *graph = filter_graph;
    *src_ctx = buffersrc_ctx;
    *sink_ctx = buffersink_ctx;

    avfilter_inout_free(&inputs);
    avfilter_inout_free(&outputs);

    return 0;
}

// 初始化视频编码器(libx264)
int init_encoder(AVCodecContext **enc_ctx, AVFormatContext *fmt_ctx, AVCodecContext *dec_ctx, const char *filename) {
    // 查找 H.264 编码器
    AVCodec *encoder = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!encoder) {
        std::cerr << "Codec not found\n";
        return -1;
    }
	// 分配编码器上下文
    AVCodecContext *enc_ctx_local = avcodec_alloc_context3(encoder);
    if (!enc_ctx_local) {
        std::cerr << "Could not allocate video codec context\n";
        return -1;
    }
	// 设置编码器参数
    enc_ctx_local->bit_rate = 2000000;
    enc_ctx_local->width = dec_ctx->width;
    enc_ctx_local->height = dec_ctx->height;
    //enc_ctx_local->time_base =  dec_ctx->time_base;  // Ensure time_base matches input
    //enc_ctx_local->framerate =  dec_ctx->framerate;  // Set framerate from input
    enc_ctx_local->framerate = (AVRational){25, 1};
    enc_ctx_local->time_base = (AVRational){1, 25};  // Time base should match the framerate
    enc_ctx_local->gop_size = 10;
    enc_ctx_local->max_b_frames = 1;
    enc_ctx_local->pix_fmt = AV_PIX_FMT_YUV420P;

    // 打开编码器
    if (avcodec_open2(enc_ctx_local, encoder, nullptr) < 0) {
        std::cerr << "Could not open codec\n";
        return -1;
    }

    *enc_ctx = enc_ctx_local;

    // 打开输出文件并设置输出流的时间基
    AVStream *out_stream = avformat_new_stream(fmt_ctx, nullptr);
    if (!out_stream) {
        std::cerr << "Error allocating output stream\n";
        return -1;
    }

    out_stream->time_base = enc_ctx_local->time_base;  // 设置输出流的时间基
    if (avcodec_parameters_from_context(out_stream->codecpar, enc_ctx_local) < 0) {
        std::cerr << "Failed to copy encoder parameters to output stream\n";
        return -1;
    }

    if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
        if (avio_open(&fmt_ctx->pb, filename, AVIO_FLAG_WRITE) < 0) {
            std::cerr << "Could not open output file\n";
            return -1;
        }
    }

    if (avformat_write_header(fmt_ctx, nullptr) < 0) {
        std::cerr << "Error writing header\n";
        return -1;
    }

    return 0;
}

int main(int argc, char *argv[]) {
    if (argc < 7) {
        std::cerr << "Usage: " << argv[0] << " input_file output_file width height pix_fmt time_base\n";
        std::cerr << "Example: " << argv[0] << " input.h264 output.h264 1920 1080 0 25\n";
        return -1;
    }
	// 解析命令行参数
    const char *input_file = argv[1];
    const char *output_file = argv[2];
    int width = std::stoi(argv[3]);
    int height = std::stoi(argv[4]);
    AVPixelFormat pix_fmt = static_cast<AVPixelFormat>(std::stoi(argv[5])); // Example: 0 for AV_PIX_FMT_YUV420P
    AVRational time_base = {1, std::stoi(argv[6])};  // Example: 1/25 for 25fps

    // 初始化 FFmpeg 并注册所有编解码器和格式
    av_register_all();
    avfilter_register_all();

    // 1.  打开输入文件并手动指定编解码器信息
    AVFormatContext *fmt_ctx = nullptr;
    AVCodecContext *dec_ctx = nullptr;
    if (open_input_file(&fmt_ctx, input_file, &dec_ctx, width, height, pix_fmt, time_base) < 0) {
        std::cerr << "Error opening input file\n";
        return -1;
    }

    // 初始化输出上下文以编码文件
    AVFormatContext *out_fmt_ctx = nullptr;
    avformat_alloc_output_context2(&out_fmt_ctx, nullptr, nullptr, output_file);
    if (!out_fmt_ctx) {
        std::cerr << "Could not create output context\n";
        return -1;
    }

    // 3. 初始化视频编码器
    AVCodecContext *enc_ctx = nullptr;
    if (init_encoder(&enc_ctx, out_fmt_ctx, dec_ctx, output_file) < 0) {
        std::cerr << "Error initializing encoder\n";
        return -1;
    }

    // 4.  初始化滤镜图
    AVFilterGraph *filter_graph = nullptr;
    AVFilterContext *buffersrc_ctx = nullptr;
    AVFilterContext *buffersink_ctx = nullptr;

    if (init_filter_graph(&filter_graph, &buffersrc_ctx, &buffersink_ctx, dec_ctx) < 0) {
        std::cerr << "Error initializing filter graph\n";
        return -1;
    }

    // 5. 开始处理视频帧
    AVPacket packet;
    AVFrame *frame = av_frame_alloc();
    AVFrame *filt_frame = av_frame_alloc();
    AVPacket enc_pkt;
    int64_t frame_count = 0;
    while (av_read_frame(fmt_ctx, &packet) >= 0) {
        if (packet.stream_index == 0) {   // 假设流 0 是视频流
            if (avcodec_send_packet(dec_ctx, &packet) < 0) {
                std::cerr << "Error sending packet for decoding\n";
                break;
            }

            while (avcodec_receive_frame(dec_ctx, frame) == 0) {
		if (frame->pts == AV_NOPTS_VALUE) {
                	frame->pts = frame_count++;
            	}
                // 将解码后的帧推送到滤镜图
                if (av_buffersrc_add_frame(buffersrc_ctx, frame) < 0) {
                    std::cerr << "Error feeding frame to filter graph\n";
                    break;
                }

                // 从滤镜图中拉取过滤后的帧
                while (av_buffersink_get_frame(buffersink_ctx, filt_frame) >= 0) {
                    // 编码过滤后的帧
                    av_init_packet(&enc_pkt);
                    enc_pkt.data = nullptr;
                    enc_pkt.size = 0;
		    filt_frame->pts = frame_count++;
                    if (avcodec_send_frame(enc_ctx, filt_frame) >= 0) {
                        while (avcodec_receive_packet(enc_ctx, &enc_pkt) >= 0) {
                            enc_pkt.stream_index = 0;
                            av_packet_rescale_ts(&enc_pkt, enc_ctx->time_base, out_fmt_ctx->streams[0]->time_base);
                            av_interleaved_write_frame(out_fmt_ctx, &enc_pkt);  // 写入编码后的帧
                            av_packet_unref(&enc_pkt);
                        }
                    }
                    av_frame_unref(filt_frame);
                }
                av_frame_unref(frame);
            }
        }
        av_packet_unref(&packet);
    }

    // 刷新编码器
    avcodec_send_frame(enc_ctx, nullptr);
    while (avcodec_receive_packet(enc_ctx, &enc_pkt) >= 0) {
        av_packet_rescale_ts(&enc_pkt, enc_ctx->time_base, out_fmt_ctx->streams[0]->time_base);
        av_interleaved_write_frame(out_fmt_ctx, &enc_pkt);
        av_packet_unref(&enc_pkt);
    }

    // Write file trailer
    av_write_trailer(out_fmt_ctx);

    // 写入文件尾部
    av_frame_free(&frame);
    av_frame_free(&filt_frame);
    avfilter_graph_free(&filter_graph);
    avcodec_free_context(&enc_ctx);
    avcodec_free_context(&dec_ctx);
    avformat_close_input(&fmt_ctx);
    if (!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE))
        avio_closep(&out_fmt_ctx->pb);
    avformat_free_context(out_fmt_ctx);

    return 0;
}