简体   繁体   English

如何在 C/C++ 中使用 FFmpeg API 叠加过滤器

[英]How to use FFmpeg API overlay filter in C / C++

I have a C++ project which creates 7/24 WebTV like RTMP stream and allows operations like changing current content on runtime, seeking content, looping through a playlist which is constructed by a json array, also supports changing whole playlist on runtime.我有一个 C++ 项目,它创建像 RTMP 流一样的 7/24 WebTV,并允许在运行时更改当前内容、查找内容、循环播放由 json 数组构建的播放列表等操作,还支持在运行时更改整个播放列表。

Currently i am reading H264 and AAC encoded packets from mp4 files then sending them to destination RTMP server after adjusting their PTS & DTS values without any encoding or decoding.目前我正在从 mp4 文件中读取 H264 和 AAC 编码的数据包,然后在调整它们的 PTS 和 DTS 值后将它们发送到目标 RTMP 服务器,无需任何编码或解码。

But i want to apply overlay images to raw frames using FFmpeg "overlay" filter after decoding H264 packets.但我想在解码 H264 数据包后使用 FFmpeg“覆盖”过滤器将覆盖图像应用于原始帧。 I looked at sample which came with FFmpeg examples ;我查看了 FFmpeg 示例附带的示例;

#define _XOPEN_SOURCE 600 /* for usleep */
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include <libavutil/opt.h>

const char *filter_descr = "scale=78:24,transpose=cclock";
/* other way:
   scale=78:24 [scl]; [scl] transpose=cclock // assumes "[in]" and "[out]" to be input output pads respectively
 */

static AVFormatContext *fmt_ctx;
static AVCodecContext *dec_ctx;
AVFilterContext *buffersink_ctx;
AVFilterContext *buffersrc_ctx;
AVFilterGraph *filter_graph;
static int video_stream_index = -1;
static int64_t last_pts = AV_NOPTS_VALUE;

static int open_input_file(const char *filename)
{
    int ret;
    AVCodec *dec;

    if ((ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot open input file\n");
        return ret;
    }

    if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");
        return ret;
    }

    /* select the video stream */
    ret = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &dec, 0);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot find a video stream in the input file\n");
        return ret;
    }
    video_stream_index = ret;

    /* create decoding context */
    dec_ctx = avcodec_alloc_context3(dec);
    if (!dec_ctx)
        return AVERROR(ENOMEM);
    avcodec_parameters_to_context(dec_ctx, fmt_ctx->streams[video_stream_index]->codecpar);

    /* init the video decoder */
    if ((ret = avcodec_open2(dec_ctx, dec, NULL)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot open video decoder\n");
        return ret;
    }

    return 0;
}

static int init_filters(const char *filters_descr)
{
    char args[512];
    int ret = 0;
    const AVFilter *buffersrc  = avfilter_get_by_name("buffer");
    const AVFilter *buffersink = avfilter_get_by_name("buffersink");
    AVFilterInOut *outputs = avfilter_inout_alloc();
    AVFilterInOut *inputs  = avfilter_inout_alloc();
    AVRational time_base = fmt_ctx->streams[video_stream_index]->time_base;
    enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE };

    filter_graph = avfilter_graph_alloc();
    if (!outputs || !inputs || !filter_graph) {
        ret = AVERROR(ENOMEM);
        goto end;
    }

    /* buffer video source: the decoded frames from the decoder will be inserted here. */
    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,
            time_base.num, time_base.den,
            dec_ctx->sample_aspect_ratio.num, dec_ctx->sample_aspect_ratio.den);

    ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
                                       args, NULL, filter_graph);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n");
        goto end;
    }

    /* buffer video sink: to terminate the filter chain. */
    ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
                                       NULL, NULL, filter_graph);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n");
        goto end;
    }

    ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts,
                              AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot set output pixel format\n");
        goto end;
    }

    /*
     * Set the endpoints for the filter graph. The filter_graph will
     * be linked to the graph described by filters_descr.
     */

    /*
     * The buffer source output must be connected to the input pad of
     * the first filter described by filters_descr; since the first
     * filter input label is not specified, it is set to "in" by
     * default.
     */
    outputs->name       = av_strdup("in");
    outputs->filter_ctx = buffersrc_ctx;
    outputs->pad_idx    = 0;
    outputs->next       = NULL;

    /*
     * The buffer sink input must be connected to the output pad of
     * the last filter described by filters_descr; since the last
     * filter output label is not specified, it is set to "out" by
     * default.
     */
    inputs->name       = av_strdup("out");
    inputs->filter_ctx = buffersink_ctx;
    inputs->pad_idx    = 0;
    inputs->next       = NULL;

    if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr,
                                    &inputs, &outputs, NULL)) < 0)
        goto end;

    if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)
        goto end;

end:
    avfilter_inout_free(&inputs);
    avfilter_inout_free(&outputs);

    return ret;
}

static void display_frame(const AVFrame *frame, AVRational time_base)
{
    int x, y;
    uint8_t *p0, *p;
    int64_t delay;

    if (frame->pts != AV_NOPTS_VALUE) {
        if (last_pts != AV_NOPTS_VALUE) {
            /* sleep roughly the right amount of time;
             * usleep is in microseconds, just like AV_TIME_BASE. */
            delay = av_rescale_q(frame->pts - last_pts,
                                 time_base, AV_TIME_BASE_Q);
            if (delay > 0 && delay < 1000000)
                usleep(delay);
        }
        last_pts = frame->pts;
    }

    /* Trivial ASCII grayscale display. */
    p0 = frame->data[0];
    puts("\033c");
    for (y = 0; y < frame->height; y++) {
        p = p0;
        for (x = 0; x < frame->width; x++)
            putchar(" .-+#"[*(p++) / 52]);
        putchar('\n');
        p0 += frame->linesize[0];
    }
    fflush(stdout);
}

int main(int argc, char **argv)
{
    int ret;
    AVPacket packet;
    AVFrame *frame;
    AVFrame *filt_frame;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s file\n", argv[0]);
        exit(1);
    }

    frame = av_frame_alloc();
    filt_frame = av_frame_alloc();
    if (!frame || !filt_frame) {
        perror("Could not allocate frame");
        exit(1);
    }

    if ((ret = open_input_file(argv[1])) < 0)
        goto end;
    if ((ret = init_filters(filter_descr)) < 0)
        goto end;

    /* read all packets */
    while (1) {
        if ((ret = av_read_frame(fmt_ctx, &packet)) < 0)
            break;

        if (packet.stream_index == video_stream_index) {
            ret = avcodec_send_packet(dec_ctx, &packet);
            if (ret < 0) {
                av_log(NULL, AV_LOG_ERROR, "Error while sending a packet to the decoder\n");
                break;
            }

            while (ret >= 0) {
                ret = avcodec_receive_frame(dec_ctx, frame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    break;
                } else if (ret < 0) {
                    av_log(NULL, AV_LOG_ERROR, "Error while receiving a frame from the decoder\n");
                    goto end;
                }

                frame->pts = frame->best_effort_timestamp;

                /* push the decoded frame into the filtergraph */
                if (av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0) {
                    av_log(NULL, AV_LOG_ERROR, "Error while feeding the filtergraph\n");
                    break;
                }

                /* pull filtered frames from the filtergraph */
                while (1) {
                    ret = av_buffersink_get_frame(buffersink_ctx, filt_frame);
                    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                        break;
                    if (ret < 0)
                        goto end;
                    display_frame(filt_frame, buffersink_ctx->inputs[0]->time_base);
                    av_frame_unref(filt_frame);
                }
                av_frame_unref(frame);
            }
        }
        av_packet_unref(&packet);
    }
end:
    avfilter_graph_free(&filter_graph);
    avcodec_free_context(&dec_ctx);
    avformat_close_input(&fmt_ctx);
    av_frame_free(&frame);
    av_frame_free(&filt_frame);

    if (ret < 0 && ret != AVERROR_EOF) {
        fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
        exit(1);
    }

    exit(0);
}

That sample uses these filters ;该示例使用这些过滤器;

"scale=78:24,transpose=cclock" “比例=78:24,转置=cclock”

I compiled and run it with a sample video file but it just outputs fancy characters to console, the code block given below is responsible for this ;我用一个示例视频文件编译并运行它,但它只是向控制台输出花哨的字符,下面给出的代码块对此负责;

   /* Trivial ASCII grayscale display. */
    p0 = frame->data[0];
    puts("\033c");
    for (y = 0; y < frame->height; y++) {
        p = p0;
        for (x = 0; x < frame->width; x++)
            putchar(" .-+#"[*(p++) / 52]);
        putchar('\n');
        p0 += frame->linesize[0];
    }
    fflush(stdout);

I have no issues with Encoding & Decoding, i just don't know how to apply "overlay" filter.我对编码和解码没有问题,我只是不知道如何应用“叠加”过滤器。 Are there any tutorials out there demonstrate how to use "overlay" filter?是否有任何教程演示如何使用“叠加”过滤器?

Just like in the example, except you use "overlay" .就像在示例中一样,除了您使用"overlay"

 snprintf(args, sizeof(args), args here...);
 avfilter_graph_create_filter(sink, avfilter_get_by_name("overlay"), nullptr, nullptr, arg, graph);

Then you need TWO create two source pads.然后你需要两个创建两个源垫。 ie IE

avfilter_graph_create_filter(sourceX, avfilter_get_by_name("buffer"), nullptr, args, nullptr, m_graph);

and one sink pad.和一个水槽垫。 Then feed one source with the video frame and other second with the image to overlay然后用视频帧馈送一个源,另一个用图像馈送第二个源以叠加

Following code fragments will be helpful..以下代码片段将有所帮助..

    char args[512];
    int ret = 0;
    const AVFilter *bufferSrc  = avfilter_get_by_name("buffer");
    const AVFilter *bufferOvr  = avfilter_get_by_name("buffer");
    const AVFilter *bufferSink = avfilter_get_by_name("buffersink");
    const AVFilter *ovrFilter  = avfilter_get_by_name("overlay");
    const AVFilter *colorFilter  = avfilter_get_by_name("colorchannelmixer");
    enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };

    fFilterGraph = avfilter_graph_alloc();
    if (!fFilterGraph) {
        ret = AVERROR(ENOMEM);
        goto end;
    }

    /* buffer video source: the decoded frames from the decoder will be inserted here. */
    snprintf(args, sizeof(args),
         "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
         decCtx->width, decCtx->height, decCtx->pix_fmt,
         fTimeBase.num, fTimeBase.den,
         decCtx->sample_aspect_ratio.num, decCtx->sample_aspect_ratio.den);
    ret = avfilter_graph_create_filter(&fBufSrc0Ctx, bufferSrc, "in0",
                       args, NULL, fFilterGraph);
    if (ret < 0)
        goto end;

    /* buffer video overlay source: the overlayed frame from the file will be inserted here. */
    snprintf(args, sizeof(args),
         "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
         ovrCtx->width, ovrCtx->height, ovrCtx->pix_fmt,
         fTimeBase.num, fTimeBase.den,
         ovrCtx->sample_aspect_ratio.num, ovrCtx->sample_aspect_ratio.den);
    ret = avfilter_graph_create_filter(&fBufSrc1Ctx, bufferOvr, "in1",
                       args, NULL, fFilterGraph);
    if (ret < 0)
        goto end;

    /* color filter */
    snprintf(args, sizeof(args), "aa=%f", (float)fWatermarkOpacity / 10.0);
    ret = avfilter_graph_create_filter(&fColorFilterCtx, colorFilter, "colorFilter",
                       args, NULL, fFilterGraph);
    if (ret < 0)
        goto end;

    /* overlay filter */
    switch (fWatermarkPos) {
    case 0:
        /* Top left */
        snprintf(args, sizeof(args), "x=%d:y=%d:repeatlast=1",
             fWatermarkOffset, fWatermarkOffset);
        break;
    case 1:
        /* Top right */
        snprintf(args, sizeof(args), "x=W-w-%d:y=%d:repeatlast=1",
             fWatermarkOffset, fWatermarkOffset);
        break;
    case 3:
        /* Bottom left */
        snprintf(args, sizeof(args), "x=%d:y=H-h-%d:repeatlast=1",
             fWatermarkOffset, fWatermarkOffset);
        break;
    case 4:
        /* Bottom right */
        snprintf(args, sizeof(args), "x=W-w-%d:y=H-h-%d:repeatlast=1",
             fWatermarkOffset, fWatermarkOffset);
        break;

    case 2:
    default:
        /* Center */
        snprintf(args, sizeof(args), "x=(W-w)/2:y=(H-h)/2:repeatlast=1");
        break;
    }
    ret = avfilter_graph_create_filter(&fOvrFilterCtx, ovrFilter, "overlay",
                       args, NULL, fFilterGraph);
    if (ret < 0)
        goto end;

    /* buffer sink - destination of the final video */
    ret = avfilter_graph_create_filter(&fBufSinkCtx, bufferSink, "out",
                       NULL, NULL, fFilterGraph);
    if (ret < 0)
        goto end;

    ret = av_opt_set_int_list(fBufSinkCtx, "pix_fmts", pix_fmts,
                  AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
    if (ret < 0)
        goto end;

    /*
     * Link all filters..
     */
    avfilter_link(fBufSrc0Ctx, 0, fOvrFilterCtx, 0);
    avfilter_link(fBufSrc1Ctx, 0, fColorFilterCtx, 0);
    avfilter_link(fColorFilterCtx, 0, fOvrFilterCtx, 1);
    avfilter_link(fOvrFilterCtx, 0, fBufSinkCtx, 0);
    if ((ret = avfilter_graph_config(fFilterGraph, NULL)) < 0)
        goto end;

end:

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM