avfilter/vf_zoompan: rewrite so it doesn't cache all output frames
authorPaul B Mahol <onemda@gmail.com>
Sun, 24 Jan 2016 10:38:15 +0000 (11:38 +0100)
committerPaul B Mahol <onemda@gmail.com>
Sun, 24 Jan 2016 16:49:23 +0000 (17:49 +0100)
Signed-off-by: Paul B Mahol <onemda@gmail.com>
libavfilter/vf_zoompan.c

index c65ce56..0dc5f37 100644 (file)
@@ -18,6 +18,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
+#include "libavutil/avassert.h"
 #include "libavutil/eval.h"
 #include "libavutil/opt.h"
 #include "libavutil/pixdesc.h"
@@ -85,6 +86,12 @@ typedef struct ZPcontext {
     int prev_nb_frames;
     struct SwsContext *sws;
     int64_t frame_count;
+    const AVPixFmtDescriptor *desc;
+    AVFrame *in;
+    double var_values[VARS_NB];
+    int nb_frames;
+    int current_frame;
+    int finished;
 } ZPContext;
 
 #define OFFSET(x) offsetof(ZPContext, x)
@@ -116,141 +123,184 @@ static int config_output(AVFilterLink *outlink)
 
     outlink->w = s->w;
     outlink->h = s->h;
+    s->desc = av_pix_fmt_desc_get(outlink->format);
 
     return 0;
 }
 
-static int filter_frame(AVFilterLink *inlink, AVFrame *in)
+static int output_single_frame(AVFilterContext *ctx, AVFrame *in, double *var_values, int i,
+                               double *zoom, double *dx, double *dy)
 {
-    AVFilterContext *ctx = inlink->dst;
-    AVFilterLink *outlink = ctx->outputs[0];
     ZPContext *s = ctx->priv;
-    double var_values[VARS_NB], nb_frames, zoom, dx, dy;
-    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(in->format);
-    AVFrame *out = NULL;
-    int i, k, x, y, w, h, ret = 0;
-
-    var_values[VAR_IN_W]  = var_values[VAR_IW] = in->width;
-    var_values[VAR_IN_H]  = var_values[VAR_IH] = in->height;
-    var_values[VAR_OUT_W] = var_values[VAR_OW] = s->w;
-    var_values[VAR_OUT_H] = var_values[VAR_OH] = s->h;
-    var_values[VAR_IN]    = inlink->frame_count + 1;
-    var_values[VAR_ON]    = outlink->frame_count + 1;
-    var_values[VAR_PX]    = s->x;
-    var_values[VAR_PY]    = s->y;
-    var_values[VAR_X]     = 0;
-    var_values[VAR_Y]     = 0;
-    var_values[VAR_PZOOM] = s->prev_zoom;
-    var_values[VAR_ZOOM]  = 1;
-    var_values[VAR_PDURATION] = s->prev_nb_frames;
-    var_values[VAR_A]     = (double) in->width / in->height;
-    var_values[VAR_SAR]   = inlink->sample_aspect_ratio.num ?
-        (double) inlink->sample_aspect_ratio.num / inlink->sample_aspect_ratio.den : 1;
-    var_values[VAR_DAR]   = var_values[VAR_A] * var_values[VAR_SAR];
-    var_values[VAR_HSUB]  = 1 << desc->log2_chroma_w;
-    var_values[VAR_VSUB]  = 1 << desc->log2_chroma_h;
+    AVFilterLink *outlink = ctx->outputs[0];
+    int64_t pts = av_rescale_q(in->pts, ctx->inputs[0]->time_base,
+                               outlink->time_base) + s->frame_count;
+    int k, x, y, w, h, ret = 0;
+    uint8_t *input[4];
+    int px[4], py[4];
+    AVFrame *out;
+
+    var_values[VAR_TIME] = pts * av_q2d(outlink->time_base);
+    var_values[VAR_FRAME] = i;
+    var_values[VAR_ON] = outlink->frame_count + 1;
+    if ((ret = av_expr_parse_and_eval(zoom, s->zoom_expr_str,
+                                      var_names, var_values,
+                                      NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0)
+        return ret;
 
-    if ((ret = av_expr_parse_and_eval(&nb_frames, s->duration_expr_str,
+    *zoom = av_clipd(*zoom, 1, 10);
+    var_values[VAR_ZOOM] = *zoom;
+    w = in->width * (1.0 / *zoom);
+    h = in->height * (1.0 / *zoom);
+
+    if ((ret = av_expr_parse_and_eval(dx, s->x_expr_str,
                                       var_names, var_values,
                                       NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0)
-        goto fail;
-
-    var_values[VAR_DURATION] = nb_frames;
-    for (i = 0; i < nb_frames; i++) {
-        int px[4];
-        int py[4];
-        uint8_t *input[4];
-        int64_t pts = av_rescale_q(in->pts, inlink->time_base,
-                                   outlink->time_base) + s->frame_count;
-
-        var_values[VAR_TIME] = pts * av_q2d(outlink->time_base);
-        var_values[VAR_FRAME] = i;
-        var_values[VAR_ON] = outlink->frame_count + 1;
-        if ((ret = av_expr_parse_and_eval(&zoom, s->zoom_expr_str,
-                                          var_names, var_values,
-                                          NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0)
-            goto fail;
+        return ret;
+    x = *dx = av_clipd(*dx, 0, FFMAX(in->width - w, 0));
+    var_values[VAR_X] = *dx;
+    x &= ~((1 << s->desc->log2_chroma_w) - 1);
 
-        zoom = av_clipd(zoom, 1, 10);
-        var_values[VAR_ZOOM] = zoom;
-        w = in->width * (1.0 / zoom);
-        h = in->height * (1.0 / zoom);
+    if ((ret = av_expr_parse_and_eval(dy, s->y_expr_str,
+                                      var_names, var_values,
+                                      NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0)
+        return ret;
+    y = *dy = av_clipd(*dy, 0, FFMAX(in->height - h, 0));
+    var_values[VAR_Y] = *dy;
+    y &= ~((1 << s->desc->log2_chroma_h) - 1);
+
+    out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
+    if (!out) {
+        ret = AVERROR(ENOMEM);
+        return ret;
+    }
 
-        if ((ret = av_expr_parse_and_eval(&dx, s->x_expr_str,
-                                          var_names, var_values,
-                                          NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0)
-            goto fail;
-        x = dx = av_clipd(dx, 0, FFMAX(in->width - w, 0));
-        var_values[VAR_X] = dx;
-        x &= ~((1 << desc->log2_chroma_w) - 1);
+    px[1] = px[2] = FF_CEIL_RSHIFT(x, s->desc->log2_chroma_w);
+    px[0] = px[3] = x;
 
-        if ((ret = av_expr_parse_and_eval(&dy, s->y_expr_str,
-                                          var_names, var_values,
-                                          NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0)
-            goto fail;
-        y = dy = av_clipd(dy, 0, FFMAX(in->height - h, 0));
-        var_values[VAR_Y] = dy;
-        y &= ~((1 << desc->log2_chroma_h) - 1);
+    py[1] = py[2] = FF_CEIL_RSHIFT(y, s->desc->log2_chroma_h);
+    py[0] = py[3] = y;
 
-        out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
-        if (!out) {
-            ret = AVERROR(ENOMEM);
-            goto fail;
-        }
+    s->sws = sws_alloc_context();
+    if (!s->sws) {
+        ret = AVERROR(ENOMEM);
+        return ret;
+    }
 
-        px[1] = px[2] = FF_CEIL_RSHIFT(x, desc->log2_chroma_w);
-        px[0] = px[3] = x;
+    for (k = 0; in->data[k]; k++)
+        input[k] = in->data[k] + py[k] * in->linesize[k] + px[k];
 
-        py[1] = py[2] = FF_CEIL_RSHIFT(y, desc->log2_chroma_h);
-        py[0] = py[3] = y;
+    av_opt_set_int(s->sws, "srcw", w, 0);
+    av_opt_set_int(s->sws, "srch", h, 0);
+    av_opt_set_int(s->sws, "src_format", in->format, 0);
+    av_opt_set_int(s->sws, "dstw", outlink->w, 0);
+    av_opt_set_int(s->sws, "dsth", outlink->h, 0);
+    av_opt_set_int(s->sws, "dst_format", outlink->format, 0);
+    av_opt_set_int(s->sws, "sws_flags", SWS_BICUBIC, 0);
 
-        s->sws = sws_alloc_context();
-        if (!s->sws) {
-            ret = AVERROR(ENOMEM);
-            goto fail;
-        }
+    if ((ret = sws_init_context(s->sws, NULL, NULL)) < 0)
+        return ret;
 
-        for (k = 0; in->data[k]; k++)
-            input[k] = in->data[k] + py[k] * in->linesize[k] + px[k];
+    sws_scale(s->sws, (const uint8_t *const *)&input, in->linesize, 0, h, out->data, out->linesize);
 
-        av_opt_set_int(s->sws, "srcw", w, 0);
-        av_opt_set_int(s->sws, "srch", h, 0);
-        av_opt_set_int(s->sws, "src_format", in->format, 0);
-        av_opt_set_int(s->sws, "dstw", outlink->w, 0);
-        av_opt_set_int(s->sws, "dsth", outlink->h, 0);
-        av_opt_set_int(s->sws, "dst_format", outlink->format, 0);
-        av_opt_set_int(s->sws, "sws_flags", SWS_BICUBIC, 0);
+    out->pts = pts;
+    s->frame_count++;
 
-        if ((ret = sws_init_context(s->sws, NULL, NULL)) < 0)
-            goto fail;
+    ret = ff_filter_frame(outlink, out);
+    sws_freeContext(s->sws);
+    s->sws = NULL;
+    s->current_frame++;
+    return ret;
+}
 
-        sws_scale(s->sws, (const uint8_t *const *)&input, in->linesize, 0, h, out->data, out->linesize);
+static int filter_frame(AVFilterLink *inlink, AVFrame *in)
+{
+    AVFilterContext *ctx = inlink->dst;
+    AVFilterLink *outlink = ctx->outputs[0];
+    ZPContext *s = ctx->priv;
+    double nb_frames;
+    int ret;
 
-        out->pts = pts;
-        s->frame_count++;
+    if (s->in) {
+        av_frame_free(&in);
+        return 0;
+    }
 
-        ret = ff_filter_frame(outlink, out);
-        out = NULL;
-        if (ret < 0)
-            break;
+    s->finished = 0;
+    s->var_values[VAR_IN_W]  = s->var_values[VAR_IW] = in->width;
+    s->var_values[VAR_IN_H]  = s->var_values[VAR_IH] = in->height;
+    s->var_values[VAR_OUT_W] = s->var_values[VAR_OW] = s->w;
+    s->var_values[VAR_OUT_H] = s->var_values[VAR_OH] = s->h;
+    s->var_values[VAR_IN]    = inlink->frame_count + 1;
+    s->var_values[VAR_ON]    = outlink->frame_count + 1;
+    s->var_values[VAR_PX]    = s->x;
+    s->var_values[VAR_PY]    = s->y;
+    s->var_values[VAR_X]     = 0;
+    s->var_values[VAR_Y]     = 0;
+    s->var_values[VAR_PZOOM] = s->prev_zoom;
+    s->var_values[VAR_ZOOM]  = 1;
+    s->var_values[VAR_PDURATION] = s->prev_nb_frames;
+    s->var_values[VAR_A]     = (double) in->width / in->height;
+    s->var_values[VAR_SAR]   = inlink->sample_aspect_ratio.num ?
+        (double) inlink->sample_aspect_ratio.num / inlink->sample_aspect_ratio.den : 1;
+    s->var_values[VAR_DAR]   = s->var_values[VAR_A] * s->var_values[VAR_SAR];
+    s->var_values[VAR_HSUB]  = 1 << s->desc->log2_chroma_w;
+    s->var_values[VAR_VSUB]  = 1 << s->desc->log2_chroma_h;
+
+    if ((ret = av_expr_parse_and_eval(&nb_frames, s->duration_expr_str,
+                                      var_names, s->var_values,
+                                      NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0) {
+        av_frame_free(&in);
+        return ret;
+    }
+
+    s->var_values[VAR_DURATION] = s->nb_frames = nb_frames;
+    s->in = in;
+
+    return 0;
+}
+
+static int request_frame(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    ZPContext *s = ctx->priv;
+    AVFrame *in = s->in;
+    double zoom, dx, dy;
+    int ret;
 
-        sws_freeContext(s->sws);
-        s->sws = NULL;
+    if (in) {
+        ret = output_single_frame(ctx, in, s->var_values, s->current_frame,
+                                  &zoom, &dx, &dy);
+        if (ret < 0)
+            goto fail;
     }
 
-    s->x = dx;
-    s->y = dy;
-    s->prev_zoom = zoom;
-    s->prev_nb_frames = nb_frames;
+    if (s->current_frame >= s->nb_frames) {
+        s->x = dx;
+        s->y = dy;
+        s->prev_zoom = zoom;
+        s->prev_nb_frames = s->nb_frames;
+        s->nb_frames = 0;
+        s->current_frame = 0;
+        av_frame_free(&s->in);
+        ret = 0;
+        s->finished = 1;
+        ret = ff_request_frame(ctx->inputs[0]);
+    }
 
 fail:
     sws_freeContext(s->sws);
     s->sws = NULL;
-    av_frame_free(&out);
-    av_frame_free(&in);
+
     return ret;
 }
 
+static int poll_frame(AVFilterLink *link)
+{
+    ZPContext *s = link->src->priv;
+    return s->nb_frames - s->current_frame;
+}
+
 static int query_formats(AVFilterContext *ctx)
 {
     static const enum AVPixelFormat pix_fmts[] = {
@@ -295,6 +345,8 @@ static const AVFilterPad outputs[] = {
         .name          = "default",
         .type          = AVMEDIA_TYPE_VIDEO,
         .config_props  = config_output,
+        .poll_frame    = poll_frame,
+        .request_frame = request_frame,
     },
     { NULL }
 };