static int filter_frame(AVFilterLink *inlink, AVFrame *buf)
{
    AVFilterContext *ctx = inlink->dst;
    AVFilterLink *outlink = ctx->outputs[0];
    BlendContext *b = ctx->priv;

    int ret = 0;
    int is_bottom = (inlink == ctx->inputs[BOTTOM]);
    struct FFBufQueue *queue =
        (is_bottom ? &b->queue_bottom : &b->queue_top);
    ff_bufqueue_add(ctx, queue, buf);

    while (1) {
        AVFrame *top_buf, *bottom_buf, *out_buf;

        if (!ff_bufqueue_peek(&b->queue_top, 0) ||
            !ff_bufqueue_peek(&b->queue_bottom, 0)) break;

        top_buf = ff_bufqueue_get(&b->queue_top);
        bottom_buf = ff_bufqueue_get(&b->queue_bottom);

        out_buf = ff_get_video_buffer(outlink, outlink->w, outlink->h);
        if (!out_buf) {
            return AVERROR(ENOMEM);
        }
        av_frame_copy_props(out_buf, top_buf);

        b->frame_requested = 0;
        blend_frame(ctx, top_buf, bottom_buf, out_buf);
        ret = ff_filter_frame(outlink, out_buf);
        av_frame_free(&top_buf);
        av_frame_free(&bottom_buf);
    }
    return ret;
}